جاري فحص الخدمة مباشرة…

المرجع الكامل لـ WhatsApp API — للبشر وللآلة

بوابة واحدة، ومسارا تكامل: اربط رقم هاتف مباشرة، أو ابنِ منتجك كاملاً فوق المنصة — تطبيقات مستأجَرة، باقات إعادة بيع، جلسات وحصص لكل عميل. وواجهة نماذج AI لها توثيقها الخاص على /docs-ai. كل مسار في هذه الصفحة منشور أيضاً كمواصفة OpenAPI 3 على /openapi.json.

180 عملية موثّقةواتساب API · REST v1 · JSON فقط
مواصفة OpenAPI 3 آليةGET /openapi.json — للأدوات والذكاء الاصطناعي
79 رمز خطأ ثابتاًتُتحقق منها الاختبارات آلياً

1. نظرة عامة

هذه الصفحة هي المرجع الكامل لواجهة بوابة واتساب REST — نفس الواجهة التي تعمل خلف لوحة الويب وتطبيقات الجوال وتكاملات طرف ثالث. كُتبت لجمهورين معاً: البشر يقرؤون النص، والآلة تقرأ GET /openapi.json (مواصفة OpenAPI 3 يفرض اختبارات البرمجة مطابقتها للشيفرة).

البوابة متعددة المستأجرين: لكل تطبيق مستأجر مفتاح مطوّر خاص به، وباقات إعادة بيعه، وعملاؤه وجلساته — معزولة تماماً عن أي تطبيق آخر. تطبيق «الدفتر المحاسبي» هو أحد التكاملات المرجعية المبنية على نفس هذه الواجهة تماماً، وليس له أي امتياز.

الخمس بيانات الاعتمادية التي ستواجهها

البيانالصيغةأين يُستخدم
جلسة المستخدم (JWT)Authorization: Bearer …حساب اللوحة: الملف، الفوترة، مفاتيح API، تطبيقات المطوّر
مفتاح العميلX-API-Key: wsk_live_…مفتاح لكل مستخدم نهائي: جلسات، إرسال، حصة، webhooks
مفتاح التطبيقX-App-Key: wsapp_live_…مفتاح لكل تطبيق مستأجر: إدارة عملائك والإرسال نيابة عنهم
مفتاح AIsk_…بوابة AI ‏(POST /api/v1/chat و chat/completions): عبر X-API-Key أو Bearer — فوترة بالتوكنات
مفتاح التسجيلX-Bootstrap-Key: …التسجيل الأولي فقط (على الخادم دائماً، ولا يُشحن داخل تطبيق)

المفردات المشتركة

المصطلحالمعنى
الجلسةرقم واتساب مرتبط واحد: ربط QR أو كود إقران، وحالته qr ← connecting ← connected.
الاشتراكربط باقة فعّال (مستخدم أو عميل ← باقة): يحمل الحصة وحد الأجهزة والفترة.
الحصةالرسائل المتبقية في الفترة الحالية؛ يتوقف الإرسال بـ QUOTA_EXCEUSTED عند النفاد.
التطبيق المستأجرمنتجك المبني على البوابة: مفتاحه وباقاته وعملاؤه.
العميلمستخدم نهائي مسجَّل تحت تطبيقك، يملك مفتاح wsk وربما جلسات.
كيف تقرأ هذه الصفحة: أسطر المسارات وأمثلة الكود لغتها محايدة — بدّل لغة الواجهة من الشريط العلوي في أي صفحة. وكل ما له معنى مُترجم، بما فيه كتالوج الأخطاء المكوَّن من 79 صفاً.
لوحة الإدارة الآن على /zico — والرابط القديم /admin يوجّه إليها.

2. مسارا التكامل

كل تكامل يبدأ باختيار أحد المسارين. يشتركان في نفس الجلسات والإرسال ونموذج الأخطاء — والفرق في من يتعرف، ومن يربط الرقم، ومن يدفع ثمن الحصة.

البُعدالمسار أ — الربط المباشرالمسار ب — بناء تطبيق
من أنتمطوّر أو مؤسسة ترسل من رقمك أنتصاحب منتج له مستخدمون نهائيون
بيان الدخولJWT + X-API-Key (wsk)X-App-Key (wsapp)
من يربط رقم واتسابأنتكل عميل من خلال تطبيقك
من يدفع الحصةاشتراكك في المنصةباقة العميل، وإلا باقة صاحب التطبيق
فوترة المستخدم النهائيلا تنطبقتسعيرك أنت + منتجات Google Play
المسارات النموذجية/api/v1/whatsapp/*, /api/v1/messages/send/api/v1/app/*, /api/v1/apps/*

المسار أ — اربط رقمك (5 خطوات)

  1. أنشئ حساباً: POST /api/v1/auth/register ثم أكّد برمز OTP القادم على واتساب.
  2. فعّل باقة: POST /api/v1/subscription/subscribe — الباقات المجانية تتفعّل فوراً، والمدفوعة تُرجع PAYMENT_REQUIRED حتى يتأكد الدفع.
  3. أنشئ مفتاحاً: POST /api/v1/api-keys ← wsk_live_… يُعرض مرة واحدة.
  4. اربط الرقم: POST /api/v1/whatsapp/sessions ثم connect (QR) أو connect-phone (كود الإقران).
  5. أرسل: POST /api/v1/messages/send بمفتاح wsk.

المسار ب — ابنِ منتجك فوق البوابة (6 خطوات)

  1. أنشئ التطبيق من لوحة العميل ← تطبيقاتي؛ يصدر مفتاح X-App-Key مرة واحدة.
  2. عرّف باقات إعادة البيع: POST /api/v1/apps/:id/plans (الحدود والمدة وسعرك وتخفيضك).
  3. سجّل عملاءك: POST /api/v1/app/clients ← لكل عميل مفتاح wsk (أو وثّقه برمز OTP أولاً — انظر القسم 8).
  4. اربط رقم كل عميل من داخل تطبيقك (QR أو كود إقران) — الجلسة تخص العميل وحده.
  5. احسب من مستخدميك: منتجات Google Play تطابق معرّفات باقاتك حرفاً بحرف؛ POST /api/v1/client/subscribe تفعّل بعد الشراء.
  6. أرسل نيابة عن عملائك: POST /api/v1/app/messages/send بـ session_id أو client_id.
باقات المنصة ليست لعملاء تطبيقك: أي محاولة اشتراك لعميل في باقة منصة تُرفض بـ APP_CLIENT_PLANS_ONLY (403). عملاؤك يشترون من كتالوجك أنت فقط.

3. البدء السريع

الرابط الأساسي: https://masarroute.com (وحين التطوير المحلي http://localhost:3011). كل طلب ورد بـ JSON وUTF-8، والإصدار في المسار: /api/v1.

GET/health
cURL
curl https://masarroute.com/health
GET/api/v1/plans
cURL
# Public: active platform plans with prices and discounts — no key needed
curl https://masarroute.com/api/v1/plans
POST/api/v1/messages/send
cURL
# Your first send — needs an active plan + a linked session + a wsk key
curl -X POST "https://masarroute.com/api/v1/messages/send" \
  -H "X-API-Key: wsk_live_..." \
  -H "Content-Type: application/json" \
  -d '{"to":"966500000000","text":"Hello from the gateway"}'
احصل على المواصفة الآلية بسطر واحد — فيها كل مسار وكل مخطط أمان وكل رموز الأخطاء:
cURL
curl https://masarroute.com/openapi.json -o openapi.json

4. المصادقة

الترويسةالقيمةالمستخدم
AuthorizationBearer JWTمستخدم اللوحة (مسارات الإدارة تتطلب صلاحية المدير)
X-API-Keywsk_live_…واجهة العميل: جلسات، إرسال، حصة، webhooks
X-App-Keywsapp_live_…التطبيق المستأجر (ويُقبل أيضاً كـ Bearer)
Authorization / X-API-Keysk_…بوابة AI ‏(POST /api/v1/chat و chat/completions) — فوترة بالتوكنات وتدوير لكل تطبيق
X-Bootstrap-Keyserver secretالتسجيل الأولي فقط — لا يُشحن أبداً داخل تطبيق

تُحفظ المفاتيح مُجزّأة (SHA-256 + pepper) ولا يمكن استرجاعها — تُعرض مرة واحدة عند الإنشاء، وإلا فالمخرج هو التدوير أو الإبطال.

التسجيل (رمز OTP على واتساب)

POST/api/v1/auth/register
JSON
{
  "name": "Zakaria",
  "country_code": "967",
  "phone": "772935854",
  "password": "Secret#2026",
  "password_confirm": "Secret#2026",
  "email": "[email protected]"     // optional
}
POST/api/v1/auth/verify-register
JSON
{ "phone": "772935854", "code": "123456" }

الدخول

ثلاث طرق، كلها تعيد JWT. حقل identifier يقبل رقماً أو بريداً، وجميع صيغ الرقم مقبولة:

772935854 · 967772935854 · 00967772935854 · +967772935854

POST/api/v1/auth/admin-login
JSON — password login
{
  "identifier": "772935854",     // phone or email (case-insensitive)
  "password": "••••••••"
}
POST/api/v1/auth/request-otp → POST /api/v1/auth/verify-otp
JSON — OTP login
// 1) request-otp { "identifier": "772935854" }   // existing accounts only
// 2) verify-otp  { "phone": "772935854", "code": "123456" }
GET/api/v1/auth/oauth/{google|github}/login → GET /api/v1/auth/oauth/{provider}/callback

الدخول الاجتماعي: افتح رابط الدخول في المتصفح (يحوّل 302 للمزود بحالة لمرة واحدة)، وافق، فيعيد الرجوع 302 إلى /login حاملاً JWT في الفراغمنت (لا يُسجَّل). البريد الموثّق لدى المزود يربط الحساب المطابق، وغير الموثّق ينشئ حساباً بلا بريد. الموقوفون وعملاء التطبيقات مرفوضون. يتطلب مفاتيح GOOGLE_/GITHUB_ في .env، ورابط الرجوع أدناه مسجلاً في لوحة كل مزود.

URLs
GET /api/v1/auth/oauth/google/login
GET /api/v1/auth/oauth/google/callback
GET /api/v1/auth/oauth/github/login
GET /api/v1/auth/oauth/github/callback

استعادة كلمة السر برمز OTP

رمز من ٦ أرقام يصل إلى واتساب إن كان المُدخل رقماً، وإلى البريد إن كان بريداً (يتطلب ضبط SMTP_HOST في .env).

POST/api/v1/auth/request-reset → POST /api/v1/auth/verify-reset
JSON
// request-reset
{ "identifier": "772935854" }

// verify-reset — invalidates every other reset code
{
  "identifier": "772935854",
  "code": "123456",
  "password": "NewPass#2026",
  "password_confirm": "NewPass#2026"
}
// verify-reset step 1 — code only, verifies without consuming
{ "identifier": "772935854", "code": "123456" }
// → { "verified": true, ... }
الحدود: ٣ رموز لكل مُدخل كل ١٠ دقائق، و٥ محاولات لكل رمز، ورمز واحد صالح فقط. تعيين كلمة سر جديدة يُبطل كل رموز الاستعادة الأخرى.

توثيق الرقم يحدث لحظة الربط

ربط جهاز واتساب (QR أو كود إقران) يثبت ملكية الرقم: يُوسم الحساب بـ phone_verified_at تلقائياً حين تبلغ الجلسة connected. لذلك يستطيع تطبيق الجوال حذف صفحة الـOTP — التوثيق أثر جانبي للربط. ويبقى مسار /client/auth/otp لاسترجاع المفتاح بعد إعادة التثبيت.

التسجيل بلا باقة مقصود: POST /app/clients يقبل الطلب بلا باقة إطلاقاً ("has_subscription": false)، واشتراك العميل يُنشأ لاحقاً حين يختار باقة.

5. باقات المنصة والتسعير

GET/api/v1/plans

يعيد باقات المنصة النشطة مع سعرها بعد التخفيض. حقول إضافية على كل باقة:

  • original_price — السعر قبل التخفيض.
  • price_monthly / price_yearly — السعر بعد التخفيض.
  • has_discount وdiscount_label — وجود التخفيض ونصه للعرض.
  • devices_unlimited — true عندما يكون حد الأجهزة ٠ (مفتوح).

التسعير التلقائي للباقة المخصصة

السعر = (عدد الرسائل ÷ ١٠٠٠) × سعر الألف حسب الشريحة + (الأجهزة − ١) × ٢. سعر الألف ينخفض كلما زاد الحجم، والجهاز الأول مشمول.

GET/api/v1/plans/quote?messages=500000&devices=25
JSON — response
{
  "messages": 500000, "devices": 25,
  "price": 146, "yearly_price": 1460,
  "price_per_thousand": 0.162,
  "tier": "500K",
  "plan_slug": "pro",          // nearest fixed plan
  "plan_name": "Professional"
}

شرائح التسعير الحالية (لكل ١٠٠٠ رسالة): أقل من ٥ آلاف ٠٫٣٠ · ٥ آلاف ٠٫٢٦ · ٢٥ ألف ٠٫٢٣ · ١٠٠ ألف ٠٫٢١ · ٢٥٠ ألف ٠٫١٨ · ٥٠٠ ألف ٠٫١٦٢ · مليون ٠٫١٤٥ · مليون ونصف ٠٫١١٥ · ٥ ملايين ٠٫٠٩

اشتراكك واستهلاكك

Endpointماذا يعيد
GET /api/v1/subscriptionباقتك الخاصة الفعّالة فقط — الحالة والفترة والمتبقي. وعميل التطبيق بلا باقة يتلقى 404/402 (بدون باقة) لا باقة المالك أبداً
POST /api/v1/subscription/subscribeتفعيل باقة مجانية {plan_slug, billing_cycle}؛ والمدفوعة تُرجع PAYMENT_REQUIRED حتى يتأكد الدفع
GET /api/v1/usageبطاقة الحصة: الحدود والمستهلك والمتبقي والفترة واسم الباقة. تعرض باقة المستدعي الخاصة فقط — و402 ‏NO_ACTIVE_PLAN عند غيابها (تمويل المالك للإرسال لا للعرض)

واجهات التسعير والتخفيض (للإدارة فقط)

Endpointالوظيفة
POST /api/v1/admin/plans/quoteتسعير فوري لباقة مخصصة (لوحة الإدارة)
GET /api/v1/admin/plans/quote-tiersشرائح التسعير وسعر الجهاز الإضافي
GET /api/v1/admin/discountsقائمة التخفيضات وحالتها
POST /api/v1/admin/discountsإنشاء تخفيض (نسبة % أو مبلغ)
PUT /api/v1/admin/discounts/:idتعديل أو تفعيل/إيقاف تخفيض
DELETE /api/v1/admin/discounts/:idحذف تخفيض

6. التخفيضات

التخفيضات تخص باقات المنصة فقط ولا تشمل باقات إعادة البيع ولا عملاء التطبيقات.

  • النطاق: باقة واحدة، عدة باقات، أو كل الباقات.
  • النوع: نسبة مئوية (١-٩٠) أو مبلغ ثابت.
  • تخفيض مخصّص لباقة يتقدّم على التخفيض الشامل.
  • يمكن تحديد تاريخ بداية ونهاية، والإيقاف الفوري من اللوحة.
cURL
curl -X POST "https://masarroute.com/api/v1/admin/discounts" \
  -H "Authorization: Bearer ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"plan_ids":["PLAN_UUID"],"kind":"percent","value":15,
       "note":"end-of-month promo"}'

يظهر التخفيض مباشرةً في بطاقات الأسعار في الصفحة الرئيسية وفي لوحة تحكم العميل: شارة الخصم، السعر القديم مشطوباً، والسعر الجديد.

الأسعار المخزّنة في الباقات تبقى كما هي؛ التخفيض طبقة عرض فقط، والسعر الفعلي هو ما يُحتسب عند التفعيل بعد الدفع.

7. تطبيقك وهويته

التطبيق هو مستؤجرك داخل البوابة. أنشئه من لوحة العميل ← تطبيقاتي؛ يُعرض مفتاح X-App-Key مرة واحدة ويُخزَّن مُجزّأاً، وفقدانه يعني توليده من جديد (وموت المفتاح القديم فوراً).

GET/api/v1/app/me

يتحقق من صلاحية المفتاح ويعيد اسم التطبيق ومعرّفه وعدد العملاء — استعمله فحصاً صحياً لتكاملك.

JavaScript / Node.js
const response = await fetch(`${BASE_URL}/api/v1/app/me`, {
  headers: { "X-App-Key": process.env.GATEWAY_APP_KEY }
});
const { app, clients } = await response.json();
يُقبل مفتاح التطبيق أيضاً كـ Authorization: Bearer wsapp_live_… بنطاق مطابق. ولا يملك أبداً سوى عملائك أنت: بيانات تطبيق آخر غير موجودة أمامه.

دورة حياة المفتاح

Endpointالأثر
POST /api/v1/apps/:id/regenerateمفتاح جديد، والمفتاح السابق يموت فوراً
POST /api/v1/apps/:id/toggleتفعيل/إيقاف التطبيق كاملاً دون حذفه
DELETE /api/v1/apps/:idحذف التطبيق (مع عملائه وجلساته)

باقات إعادة البيع والمحاسبة

Endpointالوظيفة
GET /api/v1/app/plansباقات إعادة بيعك + الباقات الأساسية للمنصة
GET /api/v1/apps/:id/plansنفس الكتالوج من واجهة المالك (JWT)
POST /api/v1/apps/:id/plansإنشاء باقة: معرّف، سعر، حدود، مدة، تخفيض
PUT /api/v1/apps/:id/plans/:planIdتعديل السعر أو التخفيض أو الحدود
DELETE /api/v1/apps/:id/plans/:planIdحذف باقة (واشتراكات قائمة تستمر)
GET /api/v1/apps/:id/accountingإيراد البيع مقابل تكلفة المنصة — هامشك لكل باقة

8. تسجيل عملاء تطبيقك

POST/api/v1/app/clients
JSON
{
  "phone": "500000000",
  "country_code": "966",
  "name": "Acme Store",
  "app_plan_id": "APPLICATION_PLAN_ID"   // optional at signup
}

رقم محلي مع رمز الدولة، أو رقم دولي كامل. ينشئ الطلب المستخدم والاشتراك في معاملة واحدة، ويعيد مفتاح api_key مرة واحدة جاهزاً للاستخدام فوراً:

JSON — response
{
  "client": { "id": "…", "phone": "+966500000000" },
  "subscription": { "status": "active", … },
  "api_key": "wsk_live_…",              // once only — store it
  "key_notice": "Store the key now — it will not be shown again",
  "phone_verified": false,               // becomes true at linking
  "message": "client registered under the app"
}

قاعدة صارمة: التطبيق يبيع باقاته هو فقط — طلب بباقة المنصة أو بباقة تطبيق آخر يُرفض بـ 400: «هذه الباقة ليست من باقات تطبيقك».

قائمة العملاء والباقات

GET/api/v1/app/clients
GET/api/v1/app/plans

يعيد plans (كتالوج إعادة البيع الخاص بك) وbase_plans (باقات المنصة، لمرجعك أنت — عملاؤك لا يشترونها). أنشئ باقاتك من لوحة العميل ← تطبيقاتي.

إسناد أو تجديد أو تغيير باقة العميل

POST/api/v1/app/clients/:clientId/subscribe
cURL
curl -X POST "https://masarroute.com/api/v1/app/clients/CLIENT_ID/subscribe" \
  -H "X-App-Key: wsapp_live_..." \
  -H "Content-Type: application/json" \
  -d '{"app_plan_id":"APPLICATION_PLAN_ID"}'

تجديد نفس الباقة يضيف المدة إلى نهاية الفترة الحالية. تغيير الباقة يبدأ فترة جديدة ويصفّر عدّاد الرسائل. عند انتهاء الحصة تتوقف الإرسال حتى التجديد أو ترقية الباقة.

حد الأجهزة: device_limit = 0 يعني أجهزة غير محدودة. والحد المطبَّق على العميل هو حد باقة تطبيقك إن وُجد، وإلا حد الباقة الأساسية.

طريقتان لتسجيل عميل تطبيقك — اختر واحدة

كلاهما يمنح المستخدم مفتاح wsk_live_…، والفرق في ما يثبت هويته وفي أين يجد رسالته إن فقد المفتاح.

الطريقةالبرهان على الرقممتى تختارها
أ. تسجيل تلقائي
POST /app/clients
لا شيء في البداية — ربط واتساب هو ما يوثّق الرقمتجربة بلا احتكاك: الاسم والرقم فقط، ثم ربطه بجلسة خاصة به
ب. كود من رقم المنصة
/client/auth/otp + /verify
كود يصلك على واتساب من الرقم الرئيسي للمنصة قبل أي تسجيلتريد إثبات الرقم قبل التسجيل، أو كان التسجيل التلقائي محظوراً في بلدك
في الطريقة ب، مرّر مفتاح تطبيقك في خطوة التحقق: حقل app_key هو ما يربط الحساب الجديد بتطبيقك — فيظهر في لوحتك ويُموَّل من رصيدك. ومفتاح خاطئ أو موقوف ⇒ INVALID_APP_KEY (403) قبل إنشاء أي حساب.

من يتحمّل كلفة رسائل عملائك

القاعدة واحدة في كل مكان (الفحص والخصم معاً):

  1. إن كان لعميلك باقة خاصة به ⇒ تُحتسب منها.
  2. وإلا ⇒ تُحتسب من باقة صاحب التطبيق: رصيد واحد تشتريه أنت لتطبيقك كله.

لهذا يبقى رصيدك في لوحتك يَنقص مع كل رسالة يرسلها عملاؤك، ويرفض محرك الإرسال عند نفاده بـ QUOTA_EXCEUSTED. وحذف عميل لا يصفّر رصيدك — الاشتراك صف مستقلّ.

إن أردت كل عميل يدفع من باقته هو (نموذج نقطة البيع): أعطِ كل عميل باقة عبر POST /app/clients/:id/subscribe، فتسري عليه القاعدة 1 ويبقى رصيدك أنت ساكناً — وهذا مقصود حين يبيع كل عميل باقة على حدة.

9. الشراء عبر Google Play

كتالوج عام لباقات تطبيقك

يقرأ تطبيقك باقاته دون أي مصادقة:

GET/api/v1/app/plans/public?app_id=APP_ID
JSON — response
{
  "plans": [
    {
      "slug": "whatsapp_1000",     // = product_id in Google Play
      "name": "Plan 1000",
      "description": "1000 messages / month",
      "message_limit": 1000,
      "duration_days": 30,
      "price": 10,
      "device_limit": 5,
      "is_active": true,
      "original_price": 15,          // present only when discounted
      "discount_percent": 33,
      "discount_label": "limited-time offer"
    }
  ]
}

slug هو نفسه معرّف المنتج في Google Play، والتطابق حرفي: ما ترسله في product_id بعد الشراء يجب أن يساويه تماماً.

التخفيضات على باقاتك

باقة المنصة ملك المنصة وحدها ولا تُعرض للمطورين. باقات تطبيقك تسعّرها أنت، وتضبط التخفيض بحقل واحد: price (السعر النهائي) وdiscount_percent (النسبة 0–90). لا يُطلب منك إدخال السعر مرتين.

  • السعر «قبل الخصم» original_price يُشتق في البوابة من النسبة والسعر النهائي، ويُستخدم للعرض فقط — يظهر مشطوباً في تطبيقك.
  • نسبة بلا سعر نهائي تُرفض: خصم على لا شيء ليس تخفيضاً.
  • إن أرسلت original_price صريحاً (من واجهة برمجية) يُحترم، من غيره تُرفض بـ 400 إن لم يطابق النسبة.
  • discount_label نص حر اختياري يظهر في تطبيقك (حتى 60 حرفاً).
  • نسبة 0 = لا تخفيض، ويُمسح السعر قبل الخصم حتى لا يبقى رقماً مكرراً في السجل.
  • التخفيض على سعر البيع فقط: لا يمس التكلفة التي تدفعها للمنصة ولا الاشتراكات القائمة، ويظهر هامشك في /accounting.
عرض التخفيض في تطبيقك (Flutter). الحقول الثلاثة في رد الكتالوج العامة تصل جاهزة، والباقي عرض:
  • اعرض price كسعر فعلي، وoriginal_price مشطوباً فوقه عندما discount_percent > 0.
  • الشرط في Dart: discountPercent > 0 && originalPrice > price + 0.001 — لا تعتمد على الحقل وحده فباقة بلا خصم قد تحمل 0.
  • وسم المطور discount_label اعرضه حرفياً؛ إن كان فارغاً اكتب النسبة بنفسك.
  • لا تحسب النسبة من price وoriginal_price — البوابة تُرسلها جاهزة، وحسابك قد يخالف النسبة التي حددها المطور.
final p = WhatsAppPlan.fromJson(item);
final discounted = p.discountPercent > 0 && p.originalPrice > p.price + 0.001;
// Render: struck old price + new price + percent badge

قواعد معرّف منتج Play

  • يبدأ بحرف لاتيني صغير، ثم أحرف صغيرة وأرقام و _ و . فقط (٣-٦٣ حرفاً).
  • بلا مسافات وبلا شرطات -.
  • صحيح: whatsapp_1000 · com.zico.ledger.whatsapp_1000
  • خاطئ: 300-m (شرطة) · 1000 (يبدأ برقم) · WhatsApp_1000 (حرف كبير)

من أين تجلبه: Play Console ← Monetization ← Products ← In-app products ← افتح المنتج وانسخ خانة Product ID كما هي، والصقها في حقل معرّف المنتج عند إنشاء باقة التطبيق من لوحة العميل. أي رمز غير مطابق يُرفض فوراً مع اقتراح تصحيح.

تفعيل الاشتراك بعد الشراء

بعد نجاح الشراء في Google Play، يرسل التطبيق طلباً بمفتاح العميل نفسه wsk_live_… (لا بمفتاح التطبيق):

POST/api/v1/client/subscribe
cURL
curl -X POST "https://masarroute.com/api/v1/client/subscribe" \
  -H "X-API-Key: wsk_live_..." \
  -H "Content-Type: application/json" \
  -d '{"product_id":"whatsapp_1000",
       "purchase_token":"...",
       "obfuscated_external_account_id":"..."}'
Android (Kotlin)
// 1) verify the purchase with BillingClient, then:
// 2) send product_id + purchase_token
val body = JSONObject()
    .put("product_id", productId)                 // same slug as in the gateway
    .put("purchase_token", purchase.token)         // from BillingClient
    .put("obfuscated_external_account_id", obfId)  // optional obfuscated id
fetch("$BASE/api/v1/client/subscribe", Request(url, body)) {
    val data = JSONObject(it.readText())
    if (data.has("subscription")) enableSending()
}
  • المنتج يجب أن يكون من باقات تطبيق العميل نفسه: لا يُفعَّل منتج تطبيق آخر ولا باقة منصة.
  • الحماية من التكرار: نفس purchase_token لا يُفعَّل مرتين (يُعاد نفس الاشتراك مع already_active: true).
  • عند التجديد تُضاف المدة إلى نهاية الفترة الحالية إن كان الاشتراك بنفس الباقة سارياً.
  • الحدود المطبَّقة بعد التفعيل هي حدود باقة التطبيق (لا الباقة الأساسية).
دفع Play بلا استدعاء هذا المسار = المستخدم دفع ولم يحصل على شيء؛ لذا اطلبه فوراً بعد Purchase.PurchasedState.PURCHASED.

10. جلسات وربط الأرقام

الجلسة تربط رقم واتساب واحد بالبوابة. دورة حياتها qr ← connecting ← connected، ويبقى jid فارغاً حتى يكتمل الربط. الواجهات الثلاث تعرض الأفعال نفسها — اختر البادئة المطابقة لبيان دخولك.

الهدفمفتاح التطبيقJWT (المالك)مفتاح wsk
قائمة الجلساتGET /app/clients/:id/sessionsGET /whatsapp/sessionsGET /client/sessions
إنشاءPOST /app/clients/:id/sessionsPOST /whatsapp/sessionsPOST /client/sessions
بدء الربط (QR)POST /app/sessions/:sid/connectPOST /whatsapp/sessions/:sid/connectPOST /client/sessions/:id/connect
كود الإقران…/connect-phone…/connect-phone…/connect-phone
قراءة الباركودGET /app/sessions/:sid/qrGET /whatsapp/sessions/:sid/qrGET /client/sessions/:id/qr
الحالةGET /app/sessions/:sid/statusاستقصِ نهاية qr — الرد يحمل الحالة
فصل / حذف…/disconnect · DELETE …/:sid…/disconnect · DELETE …/:sid…/disconnect · DELETE …/:id
POST/api/v1/app/clients/:clientId/sessions
cURL
curl -X POST "https://masarroute.com/api/v1/app/clients/CLIENT_ID/sessions" \
  -H "X-App-Key: wsapp_live_..." \
  -H "Content-Type: application/json" \
  -d '{"device_name":"Store sales"}'
POST/api/v1/app/sessions/:sessionId/connect

قد يعيد الطلب رمز QR مباشرة أو رمزاً لاحقاً. استعمل الحقلين qr وqr_image. الصورة qr_image هي data URI مولدة داخل الخادم ولا تُرسل إلى أي موقع خارجي.

الربط بكود الهاتف بدلاً من QR

POST/api/v1/app/sessions/:sessionId/connect-phone
cURL
curl -X POST "https://masarroute.com/api/v1/app/sessions/SESSION_ID/connect-phone" \
  -H "X-App-Key: wsapp_live_..." \
  -H "Content-Type: application/json" \
  -d '{"phone":"966500000000"}'

يعيد الطلب الحقل code أو connected:true، ثم يدخله المستخدم من واتساب ← الأجهزة المرتبطة ← ربط جهاز ← إدخال الكود. تابع status حتى يصبح connected.

الطريقة الصحيحة — لا تجمع المسارين.
  1. لا تستدعِ connect قبل connect-phone. استدعاء connect يبني عميل QR مستقلاً ويفصل العميل الذي بُني في طلب الهاتف، فتنتهي الجلسة.
  2. الرقم بصيغة دولية بدون + (966500000000) — البوابة ترفض INVALID_PHONE للصيغة المحلية أو الرمز المضاف.
  3. جلسة واحدة للعميل الواحد عند device_limit: 1. أعد استخدام الجلسة المحفوظة عبر GET /clients/:id/sessions بدل إنشاء جديد، وإلا بلغت حد الأجهزة.
  4. لا تجعل connect-phone ضمن حلقة استقصاء — الطلب نفسه ينشئ مصافحة ويصدر الكود. الاستقصاء على status فقط، كل 1.5 ثانية.
قراءة الرقم المرتبط. حقل jid يحمل الرقم بعد اكتمال الربط بصيغة 966500000000:[email protected] — قبل الربط يكون فارغاً. تجريد الجزء بعد @ ثم بعد : يعطي الرقم. لا تعرض رقماً إلا من جلسة بحالة connected.
الفصل دون فقدان الربط. POST /sessions/:id/disconnect يفصل المقبس ويُبقي الجهاز في تخزين WhatsApp، فتكفي connect واحدة للاتصال من جديد دون كود. أما DELETE /sessions/:id فيحذف الجهاز نهائياً ويستلزم ربطاً جديداً من الصفر.

راقب status وqr_generation وqr_error. عند تغيّر qr_generation حدّث الصورة. الرمز يتغير تلقائياً قبل انتهائه.

JavaScript — auto-refresh
let generation = -1;
const timer = setInterval(async () => {
  const status = await api(`/sessions/${sessionId}/status`);
  if (status.status === "connected") { clearInterval(timer); return; }
  if (status.qr_generation !== generation) {
    generation = status.qr_generation;
    document.querySelector("#qr").src = status.qr_image;
  }
}, 1500);
الـQR مادة ربط حساسة. لا ترسل qr إلى خدمات خارجية ولا تسجله في سجلات التطبيق.

التدفق الكامل للربط برقم الهاتف (من الجوال)

المسار المُختبَر في تطبيق عميل حقيقي، بالترتيب. ينطبق على أي تطبيق يربط عملاءه بالبوابة:

register → session → code → poll
// 1) register the client with no plan (api_key comes back once — store it)
const client = await api("/app/clients", {
  method: "POST",
  body: { phone: "966500000000", country_code: "966", name: "Acme" }
});
await saveKeyOnce(client.api_key);           // never print, never log

// 2) reuse the stored session, or create one (device_limit = 1)
let session = (await api(`/app/clients/${client.id}/sessions`)).sessions
  .find(s => s.status !== "connected");
if (!session) session = await api(`/app/clients/${client.id}/sessions`, { method: "POST" });

// 3) request the pairing code — never call connect before it
const { code } = await api(`/app/sessions/${session.id}/connect-phone`, {
  method: "POST",
  body: { phone: "967782174050" }            // international, no +
});
    showCode(code);                               // user: Linked devices → Link device

// 4) poll status only — never re-request the code
const timer = setInterval(async () => {
  const s = await api(`/app/sessions/${session.id}/status`);
  if (s.status === "connected") {             // link + phone verified
    clearInterval(timer);
    await showLinkedNumber(s.jid);            // 966500000000:[email protected]
  }
  if (s.qr_error) { clearInterval(timer); showError(s.qr_error); }
}, 1500);
لماذا هكذا؟ استدعاء connect قبل connect-phone يبني عميلاً مستقلاً ويفصل عميل الهاتف، فيموت المقبس خلال أجزاء من الثانية بعد إصدار الكود ويبقى التطبيق منتظراً كوداً لن يُقبل. أما connect-phone وحده فيبني مصافحته الخاصة ويعمل. القاعدة: عميل واحد لكل طريقة ربط، ولا تبنِ مساراً ثم تبنِ الآخر فوقه.

11. إرسال الرسائل

تصنع باسمEndpointالمصادقة
تكامل مباشر (رقمك أنت)POST /api/v1/messages/sendX-API-Key
تطبيقك نيابة عن عميلPOST /api/v1/app/messages/sendX-App-Key
تطبيق المستخدم (جوال بلا خادم)POST /api/v1/client/messages/sendX-API-Key
جلسة اللوحة (JWT)POST /api/v1/whatsapp/sendBearer
POST/api/v1/app/messages/send
cURL
curl -X POST "https://masarroute.com/api/v1/app/messages/send" \
  -H "X-App-Key: wsapp_live_..." \
  -H "Content-Type: application/json" \
  -d '{"session_id":"SESSION_ID","to":"966500000000","text":"Hello"}'

يمكن استخدام client_id بدل session_id، وعندها تستخدم البوابة أول جلسة متصلة للعميل. تمر الرسالة بمحرك الحماية والحصة قبل الإرسال.

GET/api/v1/app/messages?client_id=CLIENT_ID

إرسال صورة أو مستند

POST/api/v1/client/messages/send-media

‏multipart/form-data — الملف يُرفع مباشرة من التطبيق إلى البوابة، ثم إلى واتساب، ولا يُخزَّن على القرص.

cURL
curl -X POST "https://masarroute.com/api/v1/client/messages/send-media" \
  -H "X-API-Key: wsk_live_..." \
  -F "session_id=SESSION_UUID" \
  -F "to=967700000001" \
  -F "type=image" \
  -F "caption=Payment receipt" \
  -F "[email protected];type=image/png"
Fieldمطلوبالحد
fileنعمصورة 5MB · مستند 16MB
typeلاimage / document (افتراضي)
captionلا1024 حرف

الصور المسموحة: JPEG · PNG · WEBP. المستندات: PDF · Word · Excel · PowerPoint · TXT · CSV.

الملف المحسوب على نفس حصة الرسائل، ويمرّ بنفس حدود الحماية (الحصة اليومية، حد لكل مستلم، فترة الإحماء). إن فشل الرفع بـ MEDIA_SEND_FAILED أعد الإرسال من ملفك المحلي — الخادم لا يحتفظ بنسخة.

الإرسال الجماعي

كشف حساب مئة عميل = مئة طلب من الموبايل، وكل واحد يفقد الاتصال احتمالياً. الدفعة تجعلها طلباً واحداً قابلاً لإعادة المحاولة.

POST/api/v1/client/messages/send-batch
cURL
curl -X POST "https://masarroute.com/api/v1/client/messages/send-batch" \
  -H "X-API-Key: wsk_live_..." \
  -H "Idempotency-Key: batch-2026-09-27-morning" \
  -H "Content-Type: application/json" \
  -d '{"session_id":"SESSION_UUID","items":[
        {"to":"967700000001","text":"Invoice 1042"},
        {"to":"967700000002","text":"Invoice 1043"}
      ]}'

الحد الأقصى 100 عنصر. الفشل الجزئي مقصود: رسالة فشلت لرقم لا تلغي البقية.

JSON — response
{
  "code": "OK",
  "total": 3,
  "succeeded": 2,
  "failed": 1,
  "overall_success": false,
  "results": [
    { "to": "967700000001", "ok": true,  "wa_message_id": "STG7D303278FCB129C7" },
    { "to": "967700000002", "ok": true,  "wa_message_id": "STG44A1B0C9D2E7F35" },
    { "to": "", "ok": false, "code": "INVALID_ITEM",
      "error": "fields to and text are required for every item" }
  ]
}
الدفعة تحمل Idempotency-Key اختيارياً. بدونه، كل عنصر يحصل على مفتاح مشتق من الدفعة — فإعادة إرسال الدفعة نفسها لا تضاعف أي رسالة.

منع الإرسال المزدوج — Idempotency-Key

تطبيق بلا خادم يعيد الإرسال بعد انقطاع الشبكة. بدون حماية تصل الرسالة مرتين وتُحتسب مرتين — وهو أسرع طريق لإلغاء الرقم من واتساب.

أضف ترويسة Idempotency-Key بقيمة فريدة لكل عملية منطقية (استخدم معرّفاً تولّده أنت، لا معرّفاً عشوائياً جديداً كل محاولة):

cURL
curl -X POST "https://masarroute.com/api/v1/client/messages/send" \
  -H "X-API-Key: wsk_live_..." \
  -H "Idempotency-Key: invoice-1042-2026-09-27" \
  -H "Content-Type: application/json" \
  -d '{"session_id":"SESSION_UUID","to":"967700000001","text":"Invoice 1042"}'
HTTPcodeالمعنى
200—نُفِّذ. الرد يحمل Idempotent-Replay: false
200—مكرر: أُعيدت نفس النتيجة المخزَّنة مع Idempotent-Replay: true
409IDEMPOTENCY_KEY_REUSEDاستخدمتَ نفس المفتاح بمحتوى مختلف — ولّد مفتاحاً جديداً
409REQUEST_IN_FLIGHTالطلب نفسه قيد المعالجة — انتظر ثوانٍ

الطلبات الفاشلة لا تُخزَّن، فإعادة المحاولة ممكنة بحرية. مدة التخزين 24 ساعة، والمفتاح العالق يُستردّ تلقائياً بعد دقيقتين.

حالة التسليم بلا webhooks

‏webhooks تحتاج رابطاً عاماً، أي خادماً — وهو عكس نموذج تطبيق بلا خادم. هذه النقطتان تعطيانك نفس المعلومة بالاستقصاء من داخل التطبيق.

GET/api/v1/client/messages/status/:wa_message_id
POST/api/v1/client/messages/status
cURL — batch of 100
curl -X POST "https://masarroute.com/api/v1/client/messages/status" \
  -H "X-API-Key: wsk_live_..." \
  -H "Content-Type: application/json" \
  -d '{"ids":["W1","W2","W3"]}'
JSON — response
{
  "code": "OK",
  "requested": 3,
  "found": 2,
  "messages": [
    { "wa_message_id": "W1", "to": "967700000001", "type": "image",
      "status": "delivered", "created_at": "2026-09-27T05:10:00Z",
      "delivered_at": "2026-09-27T05:10:31Z" },
    { "wa_message_id": "W2", "to": "967700000002", "type": "text",
      "status": "read", "created_at": "2026-09-27T05:10:00Z",
      "delivered_at": "2026-09-27T05:10:28Z",
      "read_at": "2026-09-27T05:12:04Z" }
  ],
  "missing": ["W3"]
}

الحالات: sent · delivered · read · staged (وضع الاختبار) · blocked_safety مع error.

حاجز الحصة — يُطبَّق على أي طلب قبل كل شيء:
status → code
200  active subscription
401  UNAUTHORIZED           key missing or expired
402  NO_ACTIVE_PLAN         phone not verified yet
402  SUBSCRIPTION_INACTIVE  subscription ended or blocked
429  QUOTA_EXCEUSTED        quota exhausted (with Retry-After)

12. إشعارات التسليم (Webhooks)

بدل polling، سجّل رابطاً واستقبل إشعاراً عند كل تغيير حالة.

POST/api/v1/client/webhooks
cURL
curl -X POST "https://masarroute.com/api/v1/client/webhooks" \
  -H "X-API-Key: wsk_live_..." \
  -H "Content-Type: application/json" \
  -d '{"url":"https://my-app.example/hook","events":"sent,delivered,read"}'

الأحداث المتاحة: sent · delivered · read · failed — أو * للكل. الرد يعيد secret مرة واحدة فقط.

What arrives at your URL
POST /hook
X-Gateway-Event: delivered
X-Gateway-Delivery: 4f2a…            ← unique id per delivery attempt
X-Gateway-Signature: sha256=ab73…    ← HMAC-SHA256 of the body below

{
  "event": "delivered",
  "wa_message_id": "STG7D303278FCB129C7",
  "to": "967700000001",
  "session_id": "b0cd44b0-…",
  "user_id": "17647a33-…",
  "timestamp": "2026-09-27T05:14:22Z"
}
تحقّق دائماً من التوقيع:احسب HMAC-SHA256 للـ body بالسرّ الذي أعطاك إياه الخادم وقارنه بـ X-Gateway-Signature — وإلا قبلتَ أي طلب من أي مصدر.

إدارة الاشتراكات:

Endpoints
GET    /api/v1/client/webhooks              list subscriptions
POST   /api/v1/client/webhooks/:id/toggle   enable / disable
DELETE /api/v1/client/webhooks/:id          delete

البوابة تقبل http:// وhttps:// فقط، ولها مهلة 8 ثوانٍ. الرد برمز حالة ≥ 300 يُسجَّل في سجل الخادم.

13. محرك الحماية والحدود

كل رسالة تعبر محرك الحماية والحصة قبل أن تخرج. المحرك موجود لإبقاء الأرقام حيّة: واتساب يحظر الحسابات التي تتصرف كالروبوتات، لذا تفرض البوابة قواعد الحجم والتكرار والإحماء — مع رمز مستقل لكل رفض.

القاعدةcodeما تحميه
رسائل كثيرة لنفس المستلم في ساعةRECIPIENT_HOURLY_LIMITرقم المستلم يبلّغك كرسائل مزعجة
رسائل كثيرة لنفس المستلم في يومRECIPIENT_DAILY_LIMITنفسه، على مدى يوم
نفس النص لنفس الرقم خلال 10 دقائقDUPLICATE_MESSAGEالإرسال المزدوج من إعادة المحاولة — أسرع طريق للحظر
المحرك رفض المرفقMEDIA_SEND_REJECTEDحقل error يحمل السبب بالعربية
فترة الإحماء لم تكتمل لهذه الجلسة/المستلم (أول ٧٢ ساعة بحد يومي)WARMUP_LIMITالأرقام الجديدة لا تبدأ بحجم كامل

حدود المعدل

النطاقالحدcodeRetry-After
رموز الدخول والاستعادة لكل رقم3 أكواد / 10 دقائق · 5 محاولات لكل كودRATE_LIMITED60s
‏OTP الواجهة العامةنفس الحدود بنافذة أضيقRATE_LIMITED600s
التسجيل الأولي لكل IP5 / ساعة، و200 عالمياًRATE_LIMITED_GLOBAL3600s
الدفعات العامة للواجهةتحكم دفعي لكل مسارRATE_LIMITED60s
حصة الرسائل في الباقة الفعّالةحسب الباقة والفترةQUOTA_EXCEUSTEDset
التزم بـ Retry-After في كل 429 — هو المرجع، ويُعاد أيضاً في جسم الرد كـ retry_after.
في وضع الاختبار لا شيء يصل إلى واتساب: تستقر الرسائل بحالة staged فتستطيع تجربة التدفق كاملاً دون المخاطرة برقم حقيقي.

14. رموز الأخطاء 79

كل فشل تعيده البوابة يحمل ظرفاً واحداً: رمزاً آلياً ثابتاً، ورسالة إنسانية، وrequest_id تستدعيه عند التواصل مع الدعم. الحالة HTTP وحدها ليست ما تتفرّع عليه — تفرّع على الرمز.

JSON — error envelope
{
  "code": "RATE_LIMITED",
  "message": "too many requests",
  "error": "تجاوزت حد الطلبات",
  "request_id": "b31f9c2e-…",
  "retry_after": 60
}

‏request_id يعود أيضاً في ترويسة X-Request-Id، وكل 429 يحمل Retry-After. هذا الجدول موثّق آلياً: كل رمز هنا موجود في شيفرة البوابة، وكل رمز ترسله البوابة موجود هنا — نسيان أحدهما يُسقط الاختبارات.

مدخلات غير صالحة

الطلب مرفوض قبل أي معالجة. صحّح البيانات وأعد.

CodeHTTPالمعنىماذا يفعل التطبيق
BAD_REQUEST400طلب غير صالح (عام)خطأ في بناء الطلب
NO_ACTIVE_PLAN402لا يوجد اشتراك فعّال للحسابأكمل التوثيق أو الاشتراك — لا تعدّل شيفرتك
PAYMENT_REQUIRED402باقة مدفوعة تُفعّل فقط بعد تأكيد الدفعتوقّع الشراء عبر Google Play ثم /client/subscribe
BATCH_TOO_LARGE400الدفعة أكبر من 100 عنصرقسّمها على دفعات
CAPTION_TOO_LONG400الوصف يتجاوز 1024 حرفاًقصّر الوصف
CLIENT_NOT_USABLE400العميل غير صالحتحقق من بيانات العميل
EMPTY_BATCH400قائمة الدفعة فارغةأرسل عنصراً واحداً على الأقل
EMPTY_FILE400الملف فارغتأكد أن الملف ليس 0 بايت
FILE_READ_FAILED400تعذر قراءة الملف المرفوعأعد الرفع
FREE_PLAN_IS_LIFETIME400الباقة المجانية دائمة فقطاستخدم billing_cycle=daily
INVALID_CODE_FORMAT400صيغة الكود غير صحيحةأرسل 4-10 خانات
INVALID_ID400معرّف غير صالحتحقق من المعرّف
INVALID_INPUT400قيمة مدخل غير صحيحةصحّح الإدخال
INVALID_PHONE400رقم الهاتف غير صالحاستخدم الصيغة الدولية
INVALID_REQUEST400طلب غير صالحصحّح بناء الطلب
INVALID_SESSION_ID400معرّف الجلسة غير صالحأنشئ جلسة جديدة
INVALID_WEBHOOK_URL400رابط webhook غير صالحhttp أو https فقط
MISSING_FIELD400حقل مطلوب مفقودأرسل الحقل الناقص
MISSING_FILE400لم يُرسل ملفتأكد أن الحقل اسمه file
MISSING_ID400معرّف الرسالة مفقودأرسل wa_message_id
MISSING_PREFIXES400لم تحدد مفاتيح للإبطالأرسل prefixes أو all
MISSING_SESSION_ID400session_id مفقودأنشئ جلسة أولاً
MISSING_TO400المستلم (to) مفقودأرسل رقم المستلم
RESERVED_NUMBER400الرقم محجوز (رقم البوابة)استخدم رقمك الشخصي
SESSION_LIMIT_REACHED400بلغت حد الأجهزةرقِّ باقتك أو احذف جهازاً
UNKNOWN_EVENT400اسم حدث غير معروفاستخدم قيمة من القائمة
VALIDATION_FAILED400فشل التحقق من المدخلاتاعرض المتطلبات
WOULD_SELF_REVOKE400إبطال كل المفاتيحاستخدم rotate بدلها
FILE_TOO_LARGE413حجم الملف أكبر من الحدصورة 5MB · مستند 16MB
UNSUPPORTED_DOCUMENT_TYPE415نوع مستند غير مدعومPDF أو Word أو Excel
UNSUPPORTED_IMAGE_TYPE415نوع صورة غير مدعومJPG أو PNG أو WEBP

المصادقة والاشتراك والحصة

هنا يقرّر التطبيق سلوكه: «سجّل الدخول» / «اشترِ باقة» / «انتظر» / «نفدت باقتك».

CodeHTTPالمعنىماذا يفعل التطبيق
CODE_EXPIRED401انتهت صلاحية الكود (10 دقائق)اطلب كوداً جديداً
INVALID_BOOTSTRAP_KEY401مفتاح التسجيل خاطئخطأ في بناء التطبيق
INVALID_CODE401كود التحقق خاطئاطلب كوداً جديداً
NO_ACTIVE_KEY401لا يوجد مفتاح نشطاستخدم rotate
TOO_MANY_ATTEMPTS4015 محاولات خاطئةاطلب كوداً جديداً
UNAUTHORIZED401المفتاح مفقود أو منتهياطلب مفتاحاً جديداً
NO_ACTIVE_PLAN402لم يوثّق رقمه بعدوجّهه لتوثيق الرقم
PAYMENT_REQUIRED402باقة مدفوعة بلا دفع مؤكدالدفع عبر Play فقط
SUBSCRIPTION_INACTIVE402الاشتراك منتهيوجّهه للشراء
ACCOUNT_BLOCKED403الحساب موقوفوجّه المستخدم للدعم
FORBIDDEN403لا صلاحيةخطأ منطقي في التطبيق
INVALID_APP_KEY403مفتاح التطبيق غير صالح أو موقوفتحقّق من مفتاحك — لا تُعطّل المستخدم
APP_CLIENT_PLANS_ONLY403باقات المنصة ليست لعملاء التطبيقاتاشترِ من كتالوج تطبيقك عبر Google Play
QUOTA_EXCEUSTED429نفدت حصة الرسائلاعرض «نفدت باقتك» وزِد
RATE_LIMITED429تجاوز حد الطلباتانتظر Retry-After
RECIPIENT_HOURLY_LIMIT400رسائل كثيرة لنفس المستلم في ساعةأخبر المستخدم، وحدّث السجل لاحقاً
RECIPIENT_DAILY_LIMIT400رسائل كثيرة لنفس المستلم في يومأخبر المستخدم، وحدّث السجل لاحقاً
DUPLICATE_MESSAGE400نفس النص لنفس الرقم خلال 10 دقائقلا تكرر — حماية من الإرسال المزدوج
MEDIA_SEND_REJECTED400محرك الحماية رفض المرفقاقرأ error — هو سبب الرفض بالعربية
RATE_LIMITED_GLOBAL429تجاوز حد التسجيل العاموجّه للدعم
BOOTSTRAP_DISABLED503التسجيل معطّلاستخدم مسار OTP
OTP_DELIVERY_FAILED503الواتساب الرئيسي غير متصلوجّه للدعم
OAUTH_NOT_CONFIGURED503تسجيل Google/GitHub غير مفعّلأضف مفاتيح المزود في .env
SERVICE_UNAVAILABLE503الخدمة غير متاحةأعد المحاولة بعد قليل

لا يوجد

المورد غير موجود — حدّث الحالة قبل إعادة المحاولة.

CodeHTTPالمعنىماذا يفعل التطبيق
MESSAGE_NOT_FOUND404الرسالة غير موجودةتحقق من المعرّف
NOT_FOUND404المورد غير موجودحدّث الحالة
SESSION_NOT_FOUND404الجلسة غير موجودةأنشئ جلسة

خدمة AI Models API

أخطاء بوابة AI ‏(POST /api/v1/chat وPOST /api/v1/chat/completions بمفاتيح sk_): الباقة، الحصة، النموذج، وفشل المزود الخارجي.

CodeHTTPالمعنىماذا يفعل التطبيق
NO_AI_PLAN402لا يوجد اشتراك فعّال لخدمة AIاشترك في باقة AI ثم أعد الطلب
AI_QUOTA_EXHAUSTED429انتهت حصة التوكنات أو الطلبات في باقة AIجدّد الباقة أو طوّرها
MODEL_NOT_FOUND404النموذج غير موجود أو معطّلاسحب قائمة النماذج من /api/v1/chat/models
PROVIDER_ERROR502المزود الخارجي رفض الطلباقرأ الرسالة وأعد المحاولة بمدخلات أخرى
PROVIDER_RATE_LIMITED429المزود الخارجي يطبّق حدّاً للطلباتانتظر ثم أعد بتدرّج
PROVIDER_UNREACHABLE502تعذّر الوصول إلى المزود الخارجيأعد المحاولة بتدرّج — مفتاحك ليس سبب الفشل

تعارض

لا تكرّر الطلب؛ إما أنه نُفّذ أو يحتاج تغيّراً.

CodeHTTPالمعنىماذا يفعل التطبيق
ALREADY_EXISTS409موجود مسبقاًلا تكرر
CONFLICT409تعارض في الحالةأعد تحميل الحالة
IDEMPOTENCY_KEY_REUSED409نفس المفتاح بمحتوى مختلفولّد مفتاحاً جديداً
PHONE_ALREADY_REGISTERED409الرقم مسجّلاستخدم الاسترجاع لا التسجيل
REQUEST_IN_FLIGHT409الطلب نفسه قيد المعالجةانتظر ثوانٍ

خطأ خادم

سجّل request_id وأعد المحاولة بتدرّج.

CodeHTTPالمعنىماذا يفعل التطبيق
IDEMPOTENCY_ERROR500خطأ في نظام منع التكرارأعد المحاولة
INTERNAL_ERROR500خطأ داخلي في الخادمسجّل request_id وأعد
KEY_ISSUE_FAILED500تعذر إصدار المفتاحأعد المحاولة
OTP_SAVE_FAILED500تعذر حفظ الكودأعد المحاولة
PHONE_PAIRING_FAILED500فشل الربط بكود الهاتفأعد المحاولة
REGISTER_FAILED500تعذر إنشاء الحسابأعد المحاولة
REVOKE_FAILED500فشل الإبطالأعد المحاولة
WEBHOOK_CREATE_FAILED500تعذر إنشاء الاشتراكأعد المحاولة
WHATSAPP_CONNECT_FAILED500فشل ربط واتسابأعد المحاولة
MEDIA_SEND_FAILED502فشل رفع المرفق على مستوى الشبكةأعد الإرسال — الرسالة في سجلك
SESSION_DISCONNECTED502الجلسة غير متصلةأعد ربط الجلسة
SESSION_ERROR502خطأ عام في الجلسةأعد ربط الجلسة

15. واجهة العميل — /api/v1/client

مصفوفة لتطبيقات الهاتف التي لا تملك خادماً: كل مستخدم يدير جلسته وحصته بنفسه بمفتاح wsk خاص به. لا يحتاج التطبيق إلى X-App-Key ولا إلى أي خادم وسيط — الهاتف يتحدث مع البوابة مباشرة.

أول تشغيل: مسار /client/register بـ X-Bootstrap-Key موجود للتهيئة الأولية فقط، لا يمنح حصة قبل توثيق الرقم، ولا يُشحن أبداً في حزمة التطبيق. المسار الإنتاجي هو زوج OTP أدناه.

المصادقة: تسجيل واسترجاع في نقطة واحدة

لا يوجد أي مفتاح داخل حزمة التطبيق. البرهان على ملكية الرقم هو استقبال كود على واتساب — وهذا ما يجعل التسجيل الوهمي بلا قيمة، ويعالج فقدان المفتاح عند إعادة التثبيت.

1) request the code
curl -X POST "https://masarroute.com/api/v1/client/auth/otp" \
  -H "Content-Type: application/json" \
  -d '{"phone":"772935010","country_code":"967"}'

يردّ بـ action يحدّد ما يعرضه التطبيق:

actionالمعنىما يعرضه التطبيق
signupالرقم جديد«إنشاء حساب» ← يطلب الاسم
recoverالرقم موجود«مرحباً بعودتك» — أو يُدخل الكود مباشرة
2) verify — take the key
curl -X POST "https://masarroute.com/api/v1/client/auth/verify" \
  -H "Content-Type: application/json" \
  -d '{"phone":"772935010","country_code":"967","code":"482913",
       "name":"Zakaria",
       "app_key":"wsapp_live_..."}'

الرد يعيد api_key مرة واحدة فقط. وعند action: "recover" تكون المفاتيح القديمة مبطلة — وهو بالضبط ما تحتاجه بعد إعادة التثبيت.

codeHTTPالمعنى
INVALID_CODE401الكود خاطئ
CODE_EXPIRED401انتهت صلاحيته (10 دقائق)
TOO_MANY_ATTEMPTS4015 محاولات خاطئة — اطلب كوداً جديداً
RESERVED_NUMBER400هذا هو رقم البوابة نفسه
RATE_LIMITED4293 أكواد لكل رقم / 10 دقائق
ACCOUNT_BLOCKED403الحساب موقوف
OTP_DELIVERY_FAILED503الواتساب الرئيسي غير متصل
حدود OTP مربوطة بحساب مجاني: 3 أكواد لكل رقم / 10 دقائق و5 محاولات لكل كود. استنزافهما يمنع التخمين دون إزعاج المستخدم الحقيقي.

إدارة المفاتيح — مخرج التسرّب

الهدفEndpoint
قائمة مفاتيحيGET /api/v1/client/keys
تدوير (يُبطل القديم)POST /api/v1/client/keys/rotate
إبطال محدد أو الكلPOST /api/v1/client/keys/revoke

الاستجابة لا تكشف إلا البادئة (key_prefix) — لا تُعاد المفاتيح كاملة ولا الـ hash.

cURL — on leak
curl -X POST "https://masarroute.com/api/v1/client/keys/rotate" \
  -H "X-API-Key: wsk_live_..."

يعيد مفتاحاً جديداً ويُبطل القديم فوراً. العملية محميّة من الإبطال الذاتي: لا يمكنك إبطال المفتاح المستخدم في هذا الطلب، ولا كل مفاتيحك دفعة واحدة وإلا لا يبقى وسيلة للدخول.

عزل المستأجرين

كل wsk يرى جلساته ورسائله وحصته وحده. طلب جلسة أو رسالة لا يملكها يُرجع 404 — لا نكشف وجودها. والجلسة الرئيسية (مرسل OTP) لا تظهر أبداً في هذه الواجهة.

والباقي في أقسامه: الإرسال والوسائط والدفعات و Idempotency واستقصاء الحالة في القسم 11؛ و webhooks في 12؛ وربوط الجلسات وQR وكود الإقران في 10؛ ومن يدفع في القسم 8.

16. فهرس المسارات الكامل 178

كل عملية تُسجّل فعلاً في البوابة، مرتّبة حسب المجال. الصفوف لغتها محايدة عن قصد: الطريقة والمسار وبيان الدخول المطلوب. أما أشكال الطلبات والمعاملات ومخططات البيانات ففي المواصفة الآلية أدناه.

المفتاح: JWT = ترويسة Bearer من اللوحة · API key = X-API-Key ‏(wsk_live) · App key = X-App-Key ‏(wsapp_live) · Bootstrap = X-Bootstrap-Key · عام = بلا بيانات.
cURL
curl https://masarroute.com/openapi.json | jq ".paths | keys | length"

العمليات الأساسية 6

MethodPathالمصادقة
GET/api/v1/client/safety/logsAPI key
GET/api/v1/safety/logsJWT
GET/countriesعام
GET/geoعام
GET/healthعام
GET/openapi.jsonعام

صفحات HTML 7

MethodPathالمصادقة
GET/عام
GET/adminعام
GET/dashboardعام
GET/docsعام
GET/loginعام
GET/registerعام
GET/zicoعام

المصادقة والمفاتيح 23

MethodPathالمصادقة
GET/api/v1/api-keysJWT
POST/api/v1/api-keysJWT
DELETE/api/v1/api-keys/{id}API key
POST/api/v1/api-keys/{id}/rotateJWT
POST/api/v1/auth/admin-loginعام
POST/api/v1/auth/change-passwordJWT
POST/api/v1/auth/confirm-phone-changeJWT
POST/api/v1/auth/forgot-passwordعام
GET/api/v1/auth/meJWT
GET/api/v1/auth/oauth/{provider}/loginعام
GET/api/v1/auth/oauth/{provider}/callbackعام
POST/api/v1/auth/registerعام
POST/api/v1/auth/request-otpعام
POST/api/v1/auth/request-phone-changeJWT
POST/api/v1/auth/request-resetعام
POST/api/v1/auth/update-profileJWT
POST/api/v1/auth/verify-otpعام
POST/api/v1/auth/verify-registerعام
POST/api/v1/auth/verify-resetعام
POST/api/v1/client/auth/otpعام
POST/api/v1/client/auth/verifyعام
GET/api/v1/client/keysAPI key
POST/api/v1/client/keys/revokeAPI key
POST/api/v1/client/keys/rotateAPI key
POST/api/v1/client/registerBootstrap

الباقات والتسعير 8

MethodPathالمصادقة
GET/api/v1/app/plansApp key
GET/api/v1/app/plans/publicعام
GET/api/v1/apps/{appID}/plansJWT
POST/api/v1/apps/{appID}/plansJWT
DELETE/api/v1/apps/{appID}/plans/{planID}JWT
PUT/api/v1/apps/{appID}/plans/{planID}JWT
GET/api/v1/plansعام
GET/api/v1/plans/quoteعام

الاشتراك والحصة 6

MethodPathالمصادقة
POST/api/v1/client/subscribeAPI key
GET/api/v1/client/usageAPI key
GET/api/v1/payment-methodsعام
GET/api/v1/subscriptionJWT
POST/api/v1/subscription/subscribeJWT
GET/api/v1/usageJWT

الجلسات والربط 28

MethodPathالمصادقة
GET/api/v1/app/clients/{id}/sessionsApp key
POST/api/v1/app/clients/{id}/sessionsApp key
DELETE/api/v1/app/sessions/{sid}App key
POST/api/v1/app/sessions/{sid}/connectApp key
POST/api/v1/app/sessions/{sid}/connect-phoneApp key
POST/api/v1/app/sessions/{sid}/disconnectApp key
GET/api/v1/app/sessions/{sid}/statusApp key
GET/api/v1/apps/{appID}/clients/{id}/sessionsJWT
POST/api/v1/apps/{appID}/clients/{id}/sessionsJWT
DELETE/api/v1/apps/{appID}/sessions/{sid}JWT
POST/api/v1/apps/{appID}/sessions/{sid}/connectJWT
POST/api/v1/apps/{appID}/sessions/{sid}/connect-phoneJWT
POST/api/v1/apps/{appID}/sessions/{sid}/disconnectJWT
GET/api/v1/apps/{appID}/sessions/{sid}/statusJWT
GET/api/v1/client/sessionsAPI key
POST/api/v1/client/sessionsAPI key
DELETE/api/v1/client/sessions/{id}API key
POST/api/v1/client/sessions/{id}/connectAPI key
POST/api/v1/client/sessions/{id}/connect-phoneAPI key
POST/api/v1/client/sessions/{id}/disconnectAPI key
GET/api/v1/client/sessions/{id}/qrAPI key
GET/api/v1/whatsapp/sessionsJWT
POST/api/v1/whatsapp/sessionsJWT
DELETE/api/v1/whatsapp/sessions/{id}JWT
POST/api/v1/whatsapp/sessions/{id}/connectJWT
POST/api/v1/whatsapp/sessions/{id}/connect-phoneJWT
POST/api/v1/whatsapp/sessions/{id}/disconnectJWT
GET/api/v1/whatsapp/sessions/{id}/qrJWT

الرسائل 13

MethodPathالمصادقة
GET/api/v1/app/messagesApp key
POST/api/v1/app/messages/sendApp key
GET/api/v1/apps/{appID}/messagesJWT
GET/api/v1/client/messagesAPI key
POST/api/v1/client/messages/sendAPI key
POST/api/v1/client/messages/send-batchAPI key
POST/api/v1/client/messages/send-mediaAPI key
POST/api/v1/client/messages/statusAPI key
GET/api/v1/client/messages/status/{id}API key
DELETE/api/v1/client/messages/{id}API key
GET/api/v1/messagesJWT
POST/api/v1/messages/sendAPI key
POST/api/v1/whatsapp/sendJWT

إشعارات التسليم 4

MethodPathالمصادقة
GET/api/v1/client/webhooksAPI key
POST/api/v1/client/webhooksAPI key
DELETE/api/v1/client/webhooks/{id}API key
POST/api/v1/client/webhooks/{id}/toggleAPI key

واجهة التطبيق 7

MethodPathالمصادقة
GET/api/v1/app/meApp key
GET/api/v1/appsJWT
POST/api/v1/appsJWT
DELETE/api/v1/apps/{appID}JWT
GET/api/v1/apps/{appID}/accountingJWT
POST/api/v1/apps/{appID}/regenerateJWT
POST/api/v1/apps/{appID}/toggleJWT

عملاء التطبيق 8

MethodPathالمصادقة
GET/api/v1/app/clientsApp key
POST/api/v1/app/clientsApp key
DELETE/api/v1/app/clients/{id}App key
POST/api/v1/app/clients/{id}/subscribeApp key
GET/api/v1/apps/{appID}/clientsJWT
POST/api/v1/apps/{appID}/clientsJWT
DELETE/api/v1/apps/{appID}/clients/{id}JWT
POST/api/v1/apps/{appID}/clients/{id}/subscribeJWT

الإدارة 67

MethodPathالمصادقة
GET/api/v1/admin/adminsJWT إداري
GET/api/v1/admin/appsJWT إداري
POST/api/v1/admin/appsJWT إداري
DELETE/api/v1/admin/apps/{appID}JWT إداري
GET/api/v1/admin/apps/{appID}/accountingJWT إداري
GET/api/v1/admin/apps/{appID}/clientsJWT إداري
POST/api/v1/admin/apps/{appID}/clientsJWT إداري
DELETE/api/v1/admin/apps/{appID}/clients/{id}JWT إداري
GET/api/v1/admin/apps/{appID}/clients/{id}/sessionsJWT إداري
POST/api/v1/admin/apps/{appID}/clients/{id}/sessionsJWT إداري
POST/api/v1/admin/apps/{appID}/clients/{id}/subscribeJWT إداري
GET/api/v1/admin/apps/{appID}/messagesJWT إداري
GET/api/v1/admin/apps/{appID}/plansJWT إداري
POST/api/v1/admin/apps/{appID}/plansJWT إداري
DELETE/api/v1/admin/apps/{appID}/plans/{planID}JWT إداري
PUT/api/v1/admin/apps/{appID}/plans/{planID}JWT إداري
POST/api/v1/admin/apps/{appID}/regenerateJWT إداري
DELETE/api/v1/admin/apps/{appID}/sessions/{sid}JWT إداري
POST/api/v1/admin/apps/{appID}/sessions/{sid}/connectJWT إداري
POST/api/v1/admin/apps/{appID}/sessions/{sid}/connect-phoneJWT إداري
POST/api/v1/admin/apps/{appID}/sessions/{sid}/disconnectJWT إداري
GET/api/v1/admin/apps/{appID}/sessions/{sid}/statusJWT إداري
POST/api/v1/admin/apps/{appID}/toggleJWT إداري
GET/api/v1/admin/discountsJWT إداري
POST/api/v1/admin/discountsJWT إداري
DELETE/api/v1/admin/discounts/{id}JWT إداري
PUT/api/v1/admin/discounts/{id}JWT إداري
GET/api/v1/admin/main-sessionJWT إداري
POST/api/v1/admin/main-session/connectJWT إداري
POST/api/v1/admin/main-session/connect-phoneJWT إداري
POST/api/v1/admin/main-session/disconnectJWT إداري
GET/api/v1/admin/main-session/qrJWT إداري
POST/api/v1/admin/passwordJWT إداري
GET/api/v1/admin/payment-methodsJWT إداري
POST/api/v1/admin/payment-methodsJWT إداري
DELETE/api/v1/admin/payment-methods/{id}JWT إداري
PUT/api/v1/admin/payment-methods/{id}JWT إداري
POST/api/v1/admin/payment-methods/{id}/toggleJWT إداري
GET/api/v1/admin/plansJWT إداري
POST/api/v1/admin/plansJWT إداري
POST/api/v1/admin/plans/quoteJWT إداري
GET/api/v1/admin/plans/quote-tiersJWT إداري
DELETE/api/v1/admin/plans/{id}JWT إداري
PUT/api/v1/admin/plans/{id}JWT إداري
GET/api/v1/admin/reportsJWT إداري
GET/api/v1/admin/safety/logsJWT إداري
GET/api/v1/admin/sessionsJWT إداري
DELETE/api/v1/admin/sessions/{id}JWT إداري
POST/api/v1/admin/sessions/{id}/pauseJWT إداري
POST/api/v1/admin/sessions/{id}/resumeJWT إداري
GET/api/v1/admin/settingsJWT إداري
PUT/api/v1/admin/settingsJWT إداري
GET/api/v1/admin/statsJWT إداري
GET/api/v1/admin/subscriptionsJWT إداري
POST/api/v1/admin/subscriptions/activateJWT إداري
POST/api/v1/admin/subscriptions/purge-orphansJWT إداري
POST/api/v1/admin/subscriptions/{id}/blockJWT إداري
POST/api/v1/admin/subscriptions/{id}/renewJWT إداري
POST/api/v1/admin/subscriptions/{id}/unblockJWT إداري
GET/api/v1/admin/usersJWT إداري
POST/api/v1/admin/usersJWT إداري
DELETE/api/v1/admin/users/{id}JWT إداري
PUT/api/v1/admin/users/{id}JWT إداري
POST/api/v1/admin/users/{id}/blockJWT إداري
POST/api/v1/admin/users/{id}/grant-creditJWT إداري
POST/api/v1/admin/users/{id}/unblockJWT إداري
GET/api/v1/settings/publicعام

أخرى 1

MethodPathالمصادقة
GET/api/v1/geoعام

17. واجهة نماذج الذكاء الاصطناعي — توثيق مستقل

للجانب AI في المنصة بيت خاص: توثيق مستقل بفهرس 30 عملية وأمثلة جاهزة للنسخ بلغات PHP وLaravel وNode وPython؛ وصفحة أسعار مستقلة بباقات رصيد حيّة وأسعار النماذج لكل مليون توكن؛ وصفحة أسئلة شائعة. ولم يعد شيء عن AI هنا — هذا المرجع خالص لواتساب.

مرجع واتساب الخالص: 180 عملية مدرجة أعلاه، ومواصفة OpenAPI بـ214 عملية على /openapi.json تغطي المنصة كاملة بما فيها AI.

18. للآلة والذكاء الاصطناعي

هذا القسم للأدوات والوكلاء والمولّدات. البوابة تنشر مصدر حقيقة واحد بثلاثة أوجه متزامنة: هذه الصفحة للبشر، وGET /openapi.json للآلة، وحزمة اختبارات تسقط البناء إن اختلفت الثلاثة.

كيف يبدأ الوكيل

  1. احصل على /openapi.json أولاً — فيه الـ 214 عملية كاملة، وبيان الدخول لكل عملية (مصفوفة فارغة = عام)، ومخططات الوسائط، و enum لكل رموز الأخطاء الـ 79.
  2. أرسل بيانات الدخول المُعلنة على العملية تحديداً — أي مخطط خاطئ يُرفض قبل تشغيل أي معالج.
  3. تفرّع على body.code لا على الحالة HTTP. وفي 429 التزم بـ Retry-After (وأيضاً retry_after) قبل إعادة المحاولة.
  4. أرسل Idempotency-Key مع أي إرسال قد تعيد المحاولة فيه — فيصبح الإعادة بلا خطر تضاعف.
  5. اذكر request_id عند طلب المساعدة من البشر — إنه مفتاح فهرس سجلات الخادم.
JavaScript — machine entry point
const spec = await fetch("https://masarroute.com/openapi.json").then(r => r.json());
const send = spec.paths["/api/v1/messages/send"].post;
console.log(send.security);          // [{ apiKeyHeader: [] }]
console.log(spec.components.schemas.Error.properties.code.enum.length); // 79
الاتفاقيات: JSON فقط بترميز UTF-8؛ المسارات تحت /api/v1؛ أرقام الهواتف بصيغة دولية بلا + مقدّم؛ الطوابع الزمنية بصيغة RFC 3339؛ والمعرّفات UUID. أي تغيير كاسر يظهر تحت إصدار مسار جديد فقط.

19. قواعد الأمان

  1. خزّن المفاتيح في Secret Manager أو متغير بيئة على الخادم — لا في المستودع أبداً.
  2. لا ترسل المفتاح إلى المتصفح أو حزم تطبيقات الهاتف؛ البيانات الوحيدة في العميل هي مفتاح المستخدم wsk المُنشأ وقت التشغيل.
  3. راقب last_used_at واستبدل المفتاح فور أي تسريب مشتبه به (تدوير للعميل، توليد للتطبيق).
  4. طبّق idempotency بمعرّف طلب خاص بك حتى لا تتكرر الرسالة أبداً.
  5. ضع حدوداً خاصة بإرسالاتك ولا تُعد الإرسال عشوائياً بعد المهلة — استقصِ الحالة بدلاً من ذلك.
  6. استخدم HTTPS في كل بيئة الإنتاج، وأبقِ التسجيل العام مطفأً إن لم تكن بحاجته.
  7. تحقّق من توقيعات webhooks (HMAC-SHA256) قبل ثقة أي محتوى — انظر القسم 12.
  8. ‏X-Bootstrap-Key بنية خادمية فقط؛ تسريبه داخل تطبيق يعني تسريب مسار التسجيل للعالم كله.
  9. الدخول للإدارة على /zico خلف JWT وflag المدير — ولا تعرض رموز الإدارة لشيفرة غير المدراء.
الدعم يطلب request_id ورمز الخطأ — ولا يطلب مفتاحاً أبداً. ومن يطلب مفتاحاً بأي قناة فهو مهاجم.