1. نظرة عامة
هذه الصفحة هي المرجع الكامل لواجهة بوابة واتساب REST — نفس الواجهة التي تعمل خلف لوحة الويب وتطبيقات الجوال وتكاملات طرف ثالث. كُتبت لجمهورين معاً: البشر يقرؤون النص، والآلة تقرأ GET /openapi.json (مواصفة OpenAPI 3 يفرض اختبارات البرمجة مطابقتها للشيفرة).
البوابة متعددة المستأجرين: لكل تطبيق مستأجر مفتاح مطوّر خاص به، وباقات إعادة بيعه، وعملاؤه وجلساته — معزولة تماماً عن أي تطبيق آخر. تطبيق «الدفتر المحاسبي» هو أحد التكاملات المرجعية المبنية على نفس هذه الواجهة تماماً، وليس له أي امتياز.
الخمس بيانات الاعتمادية التي ستواجهها
| البيان | الصيغة | أين يُستخدم |
|---|---|---|
| جلسة المستخدم (JWT) | Authorization: Bearer … | حساب اللوحة: الملف، الفوترة، مفاتيح API، تطبيقات المطوّر |
| مفتاح العميل | X-API-Key: wsk_live_… | مفتاح لكل مستخدم نهائي: جلسات، إرسال، حصة، webhooks |
| مفتاح التطبيق | X-App-Key: wsapp_live_… | مفتاح لكل تطبيق مستأجر: إدارة عملائك والإرسال نيابة عنهم |
| مفتاح AI | sk_… | بوابة AI (POST /api/v1/chat و chat/completions): عبر X-API-Key أو Bearer — فوترة بالتوكنات |
| مفتاح التسجيل | X-Bootstrap-Key: … | التسجيل الأولي فقط (على الخادم دائماً، ولا يُشحن داخل تطبيق) |
المفردات المشتركة
| المصطلح | المعنى |
|---|---|
| الجلسة | رقم واتساب مرتبط واحد: ربط QR أو كود إقران، وحالته qr ← connecting ← connected. |
| الاشتراك | ربط باقة فعّال (مستخدم أو عميل ← باقة): يحمل الحصة وحد الأجهزة والفترة. |
| الحصة | الرسائل المتبقية في الفترة الحالية؛ يتوقف الإرسال بـ QUOTA_EXCEUSTED عند النفاد. |
| التطبيق المستأجر | منتجك المبني على البوابة: مفتاحه وباقاته وعملاؤه. |
| العميل | مستخدم نهائي مسجَّل تحت تطبيقك، يملك مفتاح wsk وربما جلسات. |
/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 خطوات)
- أنشئ حساباً:
POST /api/v1/auth/registerثم أكّد برمز OTP القادم على واتساب. - فعّل باقة:
POST /api/v1/subscription/subscribe— الباقات المجانية تتفعّل فوراً، والمدفوعة تُرجع PAYMENT_REQUIRED حتى يتأكد الدفع. - أنشئ مفتاحاً:
POST /api/v1/api-keys←wsk_live_…يُعرض مرة واحدة. - اربط الرقم:
POST /api/v1/whatsapp/sessionsثمconnect(QR) أوconnect-phone(كود الإقران). - أرسل:
POST /api/v1/messages/sendبمفتاحwsk.
المسار ب — ابنِ منتجك فوق البوابة (6 خطوات)
- أنشئ التطبيق من لوحة العميل ← تطبيقاتي؛ يصدر مفتاح
X-App-Keyمرة واحدة. - عرّف باقات إعادة البيع:
POST /api/v1/apps/:id/plans(الحدود والمدة وسعرك وتخفيضك). - سجّل عملاءك:
POST /api/v1/app/clients← لكل عميل مفتاحwsk(أو وثّقه برمز OTP أولاً — انظر القسم 8). - اربط رقم كل عميل من داخل تطبيقك (QR أو كود إقران) — الجلسة تخص العميل وحده.
- احسب من مستخدميك: منتجات Google Play تطابق معرّفات باقاتك حرفاً بحرف؛
POST /api/v1/client/subscribeتفعّل بعد الشراء. - أرسل نيابة عن عملائك:
POST /api/v1/app/messages/sendبـsession_idأوclient_id.
3. البدء السريع
الرابط الأساسي: https://masarroute.com (وحين التطوير المحلي http://localhost:3011). كل طلب ورد بـ JSON وUTF-8، والإصدار في المسار: /api/v1.
curl https://masarroute.com/health
# Public: active platform plans with prices and discounts — no key needed curl https://masarroute.com/api/v1/plans
# 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 https://masarroute.com/openapi.json -o openapi.json
4. المصادقة
| الترويسة | القيمة | المستخدم |
|---|---|---|
Authorization | Bearer JWT | مستخدم اللوحة (مسارات الإدارة تتطلب صلاحية المدير) |
X-API-Key | wsk_live_… | واجهة العميل: جلسات، إرسال، حصة، webhooks |
X-App-Key | wsapp_live_… | التطبيق المستأجر (ويُقبل أيضاً كـ Bearer) |
Authorization / X-API-Key | sk_… | بوابة AI (POST /api/v1/chat و chat/completions) — فوترة بالتوكنات وتدوير لكل تطبيق |
X-Bootstrap-Key | server secret | التسجيل الأولي فقط — لا يُشحن أبداً داخل تطبيق |
تُحفظ المفاتيح مُجزّأة (SHA-256 + pepper) ولا يمكن استرجاعها — تُعرض مرة واحدة عند الإنشاء، وإلا فالمخرج هو التدوير أو الإبطال.
التسجيل (رمز OTP على واتساب)
{
"name": "Zakaria",
"country_code": "967",
"phone": "772935854",
"password": "Secret#2026",
"password_confirm": "Secret#2026",
"email": "[email protected]" // optional
}{ "phone": "772935854", "code": "123456" }الدخول
ثلاث طرق، كلها تعيد JWT. حقل identifier يقبل رقماً أو بريداً، وجميع صيغ الرقم مقبولة:
772935854 · 967772935854 · 00967772935854 · +967772935854
{
"identifier": "772935854", // phone or email (case-insensitive)
"password": "••••••••"
}// 1) request-otp { "identifier": "772935854" } // existing accounts only
// 2) verify-otp { "phone": "772935854", "code": "123456" }الدخول الاجتماعي: افتح رابط الدخول في المتصفح (يحوّل 302 للمزود بحالة لمرة واحدة)، وافق، فيعيد الرجوع 302 إلى /login حاملاً JWT في الفراغمنت (لا يُسجَّل). البريد الموثّق لدى المزود يربط الحساب المطابق، وغير الموثّق ينشئ حساباً بلا بريد. الموقوفون وعملاء التطبيقات مرفوضون. يتطلب مفاتيح GOOGLE_/GITHUB_ في .env، ورابط الرجوع أدناه مسجلاً في لوحة كل مزود.
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).
// 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. باقات المنصة والتسعير
يعيد باقات المنصة النشطة مع سعرها بعد التخفيض. حقول إضافية على كل باقة:
original_price— السعر قبل التخفيض.price_monthly/price_yearly— السعر بعد التخفيض.has_discountوdiscount_label— وجود التخفيض ونصه للعرض.devices_unlimited—trueعندما يكون حد الأجهزة ٠ (مفتوح).
التسعير التلقائي للباقة المخصصة
السعر = (عدد الرسائل ÷ ١٠٠٠) × سعر الألف حسب الشريحة + (الأجهزة − ١) × ٢. سعر الألف ينخفض كلما زاد الحجم، والجهاز الأول مشمول.
{
"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 -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 مرة واحدة ويُخزَّن مُجزّأاً، وفقدانه يعني توليده من جديد (وموت المفتاح القديم فوراً).
يتحقق من صلاحية المفتاح ويعيد اسم التطبيق ومعرّفه وعدد العملاء — استعمله فحصاً صحياً لتكاملك.
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. تسجيل عملاء تطبيقك
{
"phone": "500000000",
"country_code": "966",
"name": "Acme Store",
"app_plan_id": "APPLICATION_PLAN_ID" // optional at signup
}رقم محلي مع رمز الدولة، أو رقم دولي كامل. ينشئ الطلب المستخدم والاشتراك في معاملة واحدة، ويعيد مفتاح api_key مرة واحدة جاهزاً للاستخدام فوراً:
{
"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: «هذه الباقة ليست من باقات تطبيقك».
قائمة العملاء والباقات
يعيد plans (كتالوج إعادة البيع الخاص بك) وbase_plans (باقات المنصة، لمرجعك أنت — عملاؤك لا يشترونها). أنشئ باقاتك من لوحة العميل ← تطبيقاتي.
إسناد أو تجديد أو تغيير باقة العميل
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) قبل إنشاء أي حساب.من يتحمّل كلفة رسائل عملائك
القاعدة واحدة في كل مكان (الفحص والخصم معاً):
- إن كان لعميلك باقة خاصة به ⇒ تُحتسب منها.
- وإلا ⇒ تُحتسب من باقة صاحب التطبيق: رصيد واحد تشتريه أنت لتطبيقك كله.
لهذا يبقى رصيدك في لوحتك يَنقص مع كل رسالة يرسلها عملاؤك، ويرفض محرك الإرسال عند نفاده بـ QUOTA_EXCEUSTED. وحذف عميل لا يصفّر رصيدك — الاشتراك صف مستقلّ.
POST /app/clients/:id/subscribe، فتسري عليه القاعدة 1 ويبقى رصيدك أنت ساكناً — وهذا مقصود حين يبيع كل عميل باقة على حدة.9. الشراء عبر Google Play
كتالوج عام لباقات تطبيقك
يقرأ تطبيقك باقاته دون أي مصادقة:
{
"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.
- اعرض
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_… (لا بمفتاح التطبيق):
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":"..."}'// 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). - عند التجديد تُضاف المدة إلى نهاية الفترة الحالية إن كان الاشتراك بنفس الباقة سارياً.
- الحدود المطبَّقة بعد التفعيل هي حدود باقة التطبيق (لا الباقة الأساسية).
Purchase.PurchasedState.PURCHASED.10. جلسات وربط الأرقام
الجلسة تربط رقم واتساب واحد بالبوابة. دورة حياتها qr ← connecting ← connected، ويبقى jid فارغاً حتى يكتمل الربط. الواجهات الثلاث تعرض الأفعال نفسها — اختر البادئة المطابقة لبيان دخولك.
| الهدف | مفتاح التطبيق | JWT (المالك) | مفتاح wsk |
|---|---|---|---|
| قائمة الجلسات | GET /app/clients/:id/sessions | GET /whatsapp/sessions | GET /client/sessions |
| إنشاء | POST /app/clients/:id/sessions | POST /whatsapp/sessions | POST /client/sessions |
| بدء الربط (QR) | POST /app/sessions/:sid/connect | POST /whatsapp/sessions/:sid/connect | POST /client/sessions/:id/connect |
| كود الإقران | …/connect-phone | …/connect-phone | …/connect-phone |
| قراءة الباركود | GET /app/sessions/:sid/qr | GET /whatsapp/sessions/:sid/qr | GET /client/sessions/:id/qr |
| الحالة | GET /app/sessions/:sid/status | استقصِ نهاية qr — الرد يحمل الحالة | |
| فصل / حذف | …/disconnect · DELETE …/:sid | …/disconnect · DELETE …/:sid | …/disconnect · DELETE …/:id |
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"}'قد يعيد الطلب رمز QR مباشرة أو رمزاً لاحقاً. استعمل الحقلين qr وqr_image. الصورة qr_image هي data URI مولدة داخل الخادم ولا تُرسل إلى أي موقع خارجي.
الربط بكود الهاتف بدلاً من QR
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.
- لا تستدعِ
connectقبلconnect-phone. استدعاءconnectيبني عميل QR مستقلاً ويفصل العميل الذي بُني في طلب الهاتف، فتنتهي الجلسة. - الرقم بصيغة دولية بدون
+(966500000000) — البوابة ترفضINVALID_PHONEللصيغة المحلية أو الرمز المضاف. - جلسة واحدة للعميل الواحد عند
device_limit: 1. أعد استخدام الجلسة المحفوظة عبرGET /clients/:id/sessionsبدل إنشاء جديد، وإلا بلغت حد الأجهزة. - لا تجعل
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 حدّث الصورة. الرمز يتغير تلقائياً قبل انتهائه.
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 إلى خدمات خارجية ولا تسجله في سجلات التطبيق.التدفق الكامل للربط برقم الهاتف (من الجوال)
المسار المُختبَر في تطبيق عميل حقيقي، بالترتيب. ينطبق على أي تطبيق يربط عملاءه بالبوابة:
// 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/send | X-API-Key |
| تطبيقك نيابة عن عميل | POST /api/v1/app/messages/send | X-App-Key |
| تطبيق المستخدم (جوال بلا خادم) | POST /api/v1/client/messages/send | X-API-Key |
| جلسة اللوحة (JWT) | POST /api/v1/whatsapp/send | Bearer |
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، وعندها تستخدم البوابة أول جلسة متصلة للعميل. تمر الرسالة بمحرك الحماية والحصة قبل الإرسال.
إرسال صورة أو مستند
multipart/form-data — الملف يُرفع مباشرة من التطبيق إلى البوابة، ثم إلى واتساب، ولا يُخزَّن على القرص.
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 أعد الإرسال من ملفك المحلي — الخادم لا يحتفظ بنسخة.الإرسال الجماعي
كشف حساب مئة عميل = مئة طلب من الموبايل، وكل واحد يفقد الاتصال احتمالياً. الدفعة تجعلها طلباً واحداً قابلاً لإعادة المحاولة.
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 عنصر. الفشل الجزئي مقصود: رسالة فشلت لرقم لا تلغي البقية.
{
"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 -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"}'| HTTP | code | المعنى |
|---|---|---|
| 200 | — | نُفِّذ. الرد يحمل Idempotent-Replay: false |
| 200 | — | مكرر: أُعيدت نفس النتيجة المخزَّنة مع Idempotent-Replay: true |
| 409 | IDEMPOTENCY_KEY_REUSED | استخدمتَ نفس المفتاح بمحتوى مختلف — ولّد مفتاحاً جديداً |
| 409 | REQUEST_IN_FLIGHT | الطلب نفسه قيد المعالجة — انتظر ثوانٍ |
الطلبات الفاشلة لا تُخزَّن، فإعادة المحاولة ممكنة بحرية. مدة التخزين 24 ساعة، والمفتاح العالق يُستردّ تلقائياً بعد دقيقتين.
حالة التسليم بلا webhooks
webhooks تحتاج رابطاً عاماً، أي خادماً — وهو عكس نموذج تطبيق بلا خادم. هذه النقطتان تعطيانك نفس المعلومة بالاستقصاء من داخل التطبيق.
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"]}'{
"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.
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، سجّل رابطاً واستقبل إشعاراً عند كل تغيير حالة.
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 مرة واحدة فقط.
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"
}X-Gateway-Signature — وإلا قبلتَ أي طلب من أي مصدر.إدارة الاشتراكات:
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 | الأرقام الجديدة لا تبدأ بحجم كامل |
حدود المعدل
| النطاق | الحد | code | Retry-After |
|---|---|---|---|
| رموز الدخول والاستعادة لكل رقم | 3 أكواد / 10 دقائق · 5 محاولات لكل كود | RATE_LIMITED | 60s |
| OTP الواجهة العامة | نفس الحدود بنافذة أضيق | RATE_LIMITED | 600s |
| التسجيل الأولي لكل IP | 5 / ساعة، و200 عالمياً | RATE_LIMITED_GLOBAL | 3600s |
| الدفعات العامة للواجهة | تحكم دفعي لكل مسار | RATE_LIMITED | 60s |
| حصة الرسائل في الباقة الفعّالة | حسب الباقة والفترة | QUOTA_EXCEUSTED | set |
retry_after.staged فتستطيع تجربة التدفق كاملاً دون المخاطرة برقم حقيقي.14. رموز الأخطاء 79
كل فشل تعيده البوابة يحمل ظرفاً واحداً: رمزاً آلياً ثابتاً، ورسالة إنسانية، وrequest_id تستدعيه عند التواصل مع الدعم. الحالة HTTP وحدها ليست ما تتفرّع عليه — تفرّع على الرمز.
{
"code": "RATE_LIMITED",
"message": "too many requests",
"error": "تجاوزت حد الطلبات",
"request_id": "b31f9c2e-…",
"retry_after": 60
}request_id يعود أيضاً في ترويسة X-Request-Id، وكل 429 يحمل Retry-After. هذا الجدول موثّق آلياً: كل رمز هنا موجود في شيفرة البوابة، وكل رمز ترسله البوابة موجود هنا — نسيان أحدهما يُسقط الاختبارات.
مدخلات غير صالحة
الطلب مرفوض قبل أي معالجة. صحّح البيانات وأعد.
| Code | HTTP | المعنى | ماذا يفعل التطبيق |
|---|---|---|---|
BAD_REQUEST | 400 | طلب غير صالح (عام) | خطأ في بناء الطلب |
NO_ACTIVE_PLAN | 402 | لا يوجد اشتراك فعّال للحساب | أكمل التوثيق أو الاشتراك — لا تعدّل شيفرتك |
PAYMENT_REQUIRED | 402 | باقة مدفوعة تُفعّل فقط بعد تأكيد الدفع | توقّع الشراء عبر Google Play ثم /client/subscribe |
BATCH_TOO_LARGE | 400 | الدفعة أكبر من 100 عنصر | قسّمها على دفعات |
CAPTION_TOO_LONG | 400 | الوصف يتجاوز 1024 حرفاً | قصّر الوصف |
CLIENT_NOT_USABLE | 400 | العميل غير صالح | تحقق من بيانات العميل |
EMPTY_BATCH | 400 | قائمة الدفعة فارغة | أرسل عنصراً واحداً على الأقل |
EMPTY_FILE | 400 | الملف فارغ | تأكد أن الملف ليس 0 بايت |
FILE_READ_FAILED | 400 | تعذر قراءة الملف المرفوع | أعد الرفع |
FREE_PLAN_IS_LIFETIME | 400 | الباقة المجانية دائمة فقط | استخدم billing_cycle=daily |
INVALID_CODE_FORMAT | 400 | صيغة الكود غير صحيحة | أرسل 4-10 خانات |
INVALID_ID | 400 | معرّف غير صالح | تحقق من المعرّف |
INVALID_INPUT | 400 | قيمة مدخل غير صحيحة | صحّح الإدخال |
INVALID_PHONE | 400 | رقم الهاتف غير صالح | استخدم الصيغة الدولية |
INVALID_REQUEST | 400 | طلب غير صالح | صحّح بناء الطلب |
INVALID_SESSION_ID | 400 | معرّف الجلسة غير صالح | أنشئ جلسة جديدة |
INVALID_WEBHOOK_URL | 400 | رابط webhook غير صالح | http أو https فقط |
MISSING_FIELD | 400 | حقل مطلوب مفقود | أرسل الحقل الناقص |
MISSING_FILE | 400 | لم يُرسل ملف | تأكد أن الحقل اسمه file |
MISSING_ID | 400 | معرّف الرسالة مفقود | أرسل wa_message_id |
MISSING_PREFIXES | 400 | لم تحدد مفاتيح للإبطال | أرسل prefixes أو all |
MISSING_SESSION_ID | 400 | session_id مفقود | أنشئ جلسة أولاً |
MISSING_TO | 400 | المستلم (to) مفقود | أرسل رقم المستلم |
RESERVED_NUMBER | 400 | الرقم محجوز (رقم البوابة) | استخدم رقمك الشخصي |
SESSION_LIMIT_REACHED | 400 | بلغت حد الأجهزة | رقِّ باقتك أو احذف جهازاً |
UNKNOWN_EVENT | 400 | اسم حدث غير معروف | استخدم قيمة من القائمة |
VALIDATION_FAILED | 400 | فشل التحقق من المدخلات | اعرض المتطلبات |
WOULD_SELF_REVOKE | 400 | إبطال كل المفاتيح | استخدم rotate بدلها |
FILE_TOO_LARGE | 413 | حجم الملف أكبر من الحد | صورة 5MB · مستند 16MB |
UNSUPPORTED_DOCUMENT_TYPE | 415 | نوع مستند غير مدعوم | PDF أو Word أو Excel |
UNSUPPORTED_IMAGE_TYPE | 415 | نوع صورة غير مدعوم | JPG أو PNG أو WEBP |
المصادقة والاشتراك والحصة
هنا يقرّر التطبيق سلوكه: «سجّل الدخول» / «اشترِ باقة» / «انتظر» / «نفدت باقتك».
| Code | HTTP | المعنى | ماذا يفعل التطبيق |
|---|---|---|---|
CODE_EXPIRED | 401 | انتهت صلاحية الكود (10 دقائق) | اطلب كوداً جديداً |
INVALID_BOOTSTRAP_KEY | 401 | مفتاح التسجيل خاطئ | خطأ في بناء التطبيق |
INVALID_CODE | 401 | كود التحقق خاطئ | اطلب كوداً جديداً |
NO_ACTIVE_KEY | 401 | لا يوجد مفتاح نشط | استخدم rotate |
TOO_MANY_ATTEMPTS | 401 | 5 محاولات خاطئة | اطلب كوداً جديداً |
UNAUTHORIZED | 401 | المفتاح مفقود أو منتهي | اطلب مفتاحاً جديداً |
NO_ACTIVE_PLAN | 402 | لم يوثّق رقمه بعد | وجّهه لتوثيق الرقم |
PAYMENT_REQUIRED | 402 | باقة مدفوعة بلا دفع مؤكد | الدفع عبر Play فقط |
SUBSCRIPTION_INACTIVE | 402 | الاشتراك منتهي | وجّهه للشراء |
ACCOUNT_BLOCKED | 403 | الحساب موقوف | وجّه المستخدم للدعم |
FORBIDDEN | 403 | لا صلاحية | خطأ منطقي في التطبيق |
INVALID_APP_KEY | 403 | مفتاح التطبيق غير صالح أو موقوف | تحقّق من مفتاحك — لا تُعطّل المستخدم |
APP_CLIENT_PLANS_ONLY | 403 | باقات المنصة ليست لعملاء التطبيقات | اشترِ من كتالوج تطبيقك عبر Google Play |
QUOTA_EXCEUSTED | 429 | نفدت حصة الرسائل | اعرض «نفدت باقتك» وزِد |
RATE_LIMITED | 429 | تجاوز حد الطلبات | انتظر Retry-After |
RECIPIENT_HOURLY_LIMIT | 400 | رسائل كثيرة لنفس المستلم في ساعة | أخبر المستخدم، وحدّث السجل لاحقاً |
RECIPIENT_DAILY_LIMIT | 400 | رسائل كثيرة لنفس المستلم في يوم | أخبر المستخدم، وحدّث السجل لاحقاً |
DUPLICATE_MESSAGE | 400 | نفس النص لنفس الرقم خلال 10 دقائق | لا تكرر — حماية من الإرسال المزدوج |
MEDIA_SEND_REJECTED | 400 | محرك الحماية رفض المرفق | اقرأ error — هو سبب الرفض بالعربية |
RATE_LIMITED_GLOBAL | 429 | تجاوز حد التسجيل العام | وجّه للدعم |
BOOTSTRAP_DISABLED | 503 | التسجيل معطّل | استخدم مسار OTP |
OTP_DELIVERY_FAILED | 503 | الواتساب الرئيسي غير متصل | وجّه للدعم |
OAUTH_NOT_CONFIGURED | 503 | تسجيل Google/GitHub غير مفعّل | أضف مفاتيح المزود في .env |
SERVICE_UNAVAILABLE | 503 | الخدمة غير متاحة | أعد المحاولة بعد قليل |
لا يوجد
المورد غير موجود — حدّث الحالة قبل إعادة المحاولة.
| Code | HTTP | المعنى | ماذا يفعل التطبيق |
|---|---|---|---|
MESSAGE_NOT_FOUND | 404 | الرسالة غير موجودة | تحقق من المعرّف |
NOT_FOUND | 404 | المورد غير موجود | حدّث الحالة |
SESSION_NOT_FOUND | 404 | الجلسة غير موجودة | أنشئ جلسة |
خدمة AI Models API
أخطاء بوابة AI (POST /api/v1/chat وPOST /api/v1/chat/completions بمفاتيح sk_): الباقة، الحصة، النموذج، وفشل المزود الخارجي.
| Code | HTTP | المعنى | ماذا يفعل التطبيق |
|---|---|---|---|
NO_AI_PLAN | 402 | لا يوجد اشتراك فعّال لخدمة AI | اشترك في باقة AI ثم أعد الطلب |
AI_QUOTA_EXHAUSTED | 429 | انتهت حصة التوكنات أو الطلبات في باقة AI | جدّد الباقة أو طوّرها |
MODEL_NOT_FOUND | 404 | النموذج غير موجود أو معطّل | اسحب قائمة النماذج من /api/v1/chat/models |
PROVIDER_ERROR | 502 | المزود الخارجي رفض الطلب | اقرأ الرسالة وأعد المحاولة بمدخلات أخرى |
PROVIDER_RATE_LIMITED | 429 | المزود الخارجي يطبّق حدّاً للطلبات | انتظر ثم أعد بتدرّج |
PROVIDER_UNREACHABLE | 502 | تعذّر الوصول إلى المزود الخارجي | أعد المحاولة بتدرّج — مفتاحك ليس سبب الفشل |
تعارض
لا تكرّر الطلب؛ إما أنه نُفّذ أو يحتاج تغيّراً.
| Code | HTTP | المعنى | ماذا يفعل التطبيق |
|---|---|---|---|
ALREADY_EXISTS | 409 | موجود مسبقاً | لا تكرر |
CONFLICT | 409 | تعارض في الحالة | أعد تحميل الحالة |
IDEMPOTENCY_KEY_REUSED | 409 | نفس المفتاح بمحتوى مختلف | ولّد مفتاحاً جديداً |
PHONE_ALREADY_REGISTERED | 409 | الرقم مسجّل | استخدم الاسترجاع لا التسجيل |
REQUEST_IN_FLIGHT | 409 | الطلب نفسه قيد المعالجة | انتظر ثوانٍ |
خطأ خادم
سجّل request_id وأعد المحاولة بتدرّج.
| Code | HTTP | المعنى | ماذا يفعل التطبيق |
|---|---|---|---|
IDEMPOTENCY_ERROR | 500 | خطأ في نظام منع التكرار | أعد المحاولة |
INTERNAL_ERROR | 500 | خطأ داخلي في الخادم | سجّل request_id وأعد |
KEY_ISSUE_FAILED | 500 | تعذر إصدار المفتاح | أعد المحاولة |
OTP_SAVE_FAILED | 500 | تعذر حفظ الكود | أعد المحاولة |
PHONE_PAIRING_FAILED | 500 | فشل الربط بكود الهاتف | أعد المحاولة |
REGISTER_FAILED | 500 | تعذر إنشاء الحساب | أعد المحاولة |
REVOKE_FAILED | 500 | فشل الإبطال | أعد المحاولة |
WEBHOOK_CREATE_FAILED | 500 | تعذر إنشاء الاشتراك | أعد المحاولة |
WHATSAPP_CONNECT_FAILED | 500 | فشل ربط واتساب | أعد المحاولة |
MEDIA_SEND_FAILED | 502 | فشل رفع المرفق على مستوى الشبكة | أعد الإرسال — الرسالة في سجلك |
SESSION_DISCONNECTED | 502 | الجلسة غير متصلة | أعد ربط الجلسة |
SESSION_ERROR | 502 | خطأ عام في الجلسة | أعد ربط الجلسة |
15. واجهة العميل — /api/v1/client
مصفوفة لتطبيقات الهاتف التي لا تملك خادماً: كل مستخدم يدير جلسته وحصته بنفسه بمفتاح wsk خاص به. لا يحتاج التطبيق إلى X-App-Key ولا إلى أي خادم وسيط — الهاتف يتحدث مع البوابة مباشرة.
/client/register بـ X-Bootstrap-Key موجود للتهيئة الأولية فقط، لا يمنح حصة قبل توثيق الرقم، ولا يُشحن أبداً في حزمة التطبيق. المسار الإنتاجي هو زوج OTP أدناه.المصادقة: تسجيل واسترجاع في نقطة واحدة
لا يوجد أي مفتاح داخل حزمة التطبيق. البرهان على ملكية الرقم هو استقبال كود على واتساب — وهذا ما يجعل التسجيل الوهمي بلا قيمة، ويعالج فقدان المفتاح عند إعادة التثبيت.
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 | الرقم موجود | «مرحباً بعودتك» — أو يُدخل الكود مباشرة |
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" تكون المفاتيح القديمة مبطلة — وهو بالضبط ما تحتاجه بعد إعادة التثبيت.
| code | HTTP | المعنى |
|---|---|---|
INVALID_CODE | 401 | الكود خاطئ |
CODE_EXPIRED | 401 | انتهت صلاحيته (10 دقائق) |
TOO_MANY_ATTEMPTS | 401 | 5 محاولات خاطئة — اطلب كوداً جديداً |
RESERVED_NUMBER | 400 | هذا هو رقم البوابة نفسه |
RATE_LIMITED | 429 | 3 أكواد لكل رقم / 10 دقائق |
ACCOUNT_BLOCKED | 403 | الحساب موقوف |
OTP_DELIVERY_FAILED | 503 | الواتساب الرئيسي غير متصل |
إدارة المفاتيح — مخرج التسرّب
| الهدف | Endpoint |
|---|---|
| قائمة مفاتيحي | GET /api/v1/client/keys |
| تدوير (يُبطل القديم) | POST /api/v1/client/keys/rotate |
| إبطال محدد أو الكل | POST /api/v1/client/keys/revoke |
الاستجابة لا تكشف إلا البادئة (key_prefix) — لا تُعاد المفاتيح كاملة ولا الـ hash.
curl -X POST "https://masarroute.com/api/v1/client/keys/rotate" \ -H "X-API-Key: wsk_live_..."
يعيد مفتاحاً جديداً ويُبطل القديم فوراً. العملية محميّة من الإبطال الذاتي: لا يمكنك إبطال المفتاح المستخدم في هذا الطلب، ولا كل مفاتيحك دفعة واحدة وإلا لا يبقى وسيلة للدخول.
عزل المستأجرين
كل wsk يرى جلساته ورسائله وحصته وحده. طلب جلسة أو رسالة لا يملكها يُرجع 404 — لا نكشف وجودها. والجلسة الرئيسية (مرسل OTP) لا تظهر أبداً في هذه الواجهة.
16. فهرس المسارات الكامل 178
كل عملية تُسجّل فعلاً في البوابة، مرتّبة حسب المجال. الصفوف لغتها محايدة عن قصد: الطريقة والمسار وبيان الدخول المطلوب. أما أشكال الطلبات والمعاملات ومخططات البيانات ففي المواصفة الآلية أدناه.
curl https://masarroute.com/openapi.json | jq ".paths | keys | length"
العمليات الأساسية 6
| Method | Path | المصادقة |
|---|---|---|
| GET | /api/v1/client/safety/logs | API key |
| GET | /api/v1/safety/logs | JWT |
| GET | /countries | عام |
| GET | /geo | عام |
| GET | /health | عام |
| GET | /openapi.json | عام |
صفحات HTML 7
| Method | Path | المصادقة |
|---|---|---|
| GET | / | عام |
| GET | /admin | عام |
| GET | /dashboard | عام |
| GET | /docs | عام |
| GET | /login | عام |
| GET | /register | عام |
| GET | /zico | عام |
المصادقة والمفاتيح 23
| Method | Path | المصادقة |
|---|---|---|
| GET | /api/v1/api-keys | JWT |
| POST | /api/v1/api-keys | JWT |
| DELETE | /api/v1/api-keys/{id} | API key |
| POST | /api/v1/api-keys/{id}/rotate | JWT |
| POST | /api/v1/auth/admin-login | عام |
| POST | /api/v1/auth/change-password | JWT |
| POST | /api/v1/auth/confirm-phone-change | JWT |
| POST | /api/v1/auth/forgot-password | عام |
| GET | /api/v1/auth/me | JWT |
| 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-change | JWT |
| POST | /api/v1/auth/request-reset | عام |
| POST | /api/v1/auth/update-profile | JWT |
| 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/keys | API key |
| POST | /api/v1/client/keys/revoke | API key |
| POST | /api/v1/client/keys/rotate | API key |
| POST | /api/v1/client/register | Bootstrap |
الباقات والتسعير 8
| Method | Path | المصادقة |
|---|---|---|
| GET | /api/v1/app/plans | App key |
| GET | /api/v1/app/plans/public | عام |
| GET | /api/v1/apps/{appID}/plans | JWT |
| POST | /api/v1/apps/{appID}/plans | JWT |
| 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
| Method | Path | المصادقة |
|---|---|---|
| POST | /api/v1/client/subscribe | API key |
| GET | /api/v1/client/usage | API key |
| GET | /api/v1/payment-methods | عام |
| GET | /api/v1/subscription | JWT |
| POST | /api/v1/subscription/subscribe | JWT |
| GET | /api/v1/usage | JWT |
الجلسات والربط 28
| Method | Path | المصادقة |
|---|---|---|
| GET | /api/v1/app/clients/{id}/sessions | App key |
| POST | /api/v1/app/clients/{id}/sessions | App key |
| DELETE | /api/v1/app/sessions/{sid} | App key |
| POST | /api/v1/app/sessions/{sid}/connect | App key |
| POST | /api/v1/app/sessions/{sid}/connect-phone | App key |
| POST | /api/v1/app/sessions/{sid}/disconnect | App key |
| GET | /api/v1/app/sessions/{sid}/status | App key |
| GET | /api/v1/apps/{appID}/clients/{id}/sessions | JWT |
| POST | /api/v1/apps/{appID}/clients/{id}/sessions | JWT |
| DELETE | /api/v1/apps/{appID}/sessions/{sid} | JWT |
| POST | /api/v1/apps/{appID}/sessions/{sid}/connect | JWT |
| POST | /api/v1/apps/{appID}/sessions/{sid}/connect-phone | JWT |
| POST | /api/v1/apps/{appID}/sessions/{sid}/disconnect | JWT |
| GET | /api/v1/apps/{appID}/sessions/{sid}/status | JWT |
| GET | /api/v1/client/sessions | API key |
| POST | /api/v1/client/sessions | API key |
| DELETE | /api/v1/client/sessions/{id} | API key |
| POST | /api/v1/client/sessions/{id}/connect | API key |
| POST | /api/v1/client/sessions/{id}/connect-phone | API key |
| POST | /api/v1/client/sessions/{id}/disconnect | API key |
| GET | /api/v1/client/sessions/{id}/qr | API key |
| GET | /api/v1/whatsapp/sessions | JWT |
| POST | /api/v1/whatsapp/sessions | JWT |
| DELETE | /api/v1/whatsapp/sessions/{id} | JWT |
| POST | /api/v1/whatsapp/sessions/{id}/connect | JWT |
| POST | /api/v1/whatsapp/sessions/{id}/connect-phone | JWT |
| POST | /api/v1/whatsapp/sessions/{id}/disconnect | JWT |
| GET | /api/v1/whatsapp/sessions/{id}/qr | JWT |
الرسائل 13
| Method | Path | المصادقة |
|---|---|---|
| GET | /api/v1/app/messages | App key |
| POST | /api/v1/app/messages/send | App key |
| GET | /api/v1/apps/{appID}/messages | JWT |
| GET | /api/v1/client/messages | API key |
| POST | /api/v1/client/messages/send | API key |
| POST | /api/v1/client/messages/send-batch | API key |
| POST | /api/v1/client/messages/send-media | API key |
| POST | /api/v1/client/messages/status | API key |
| GET | /api/v1/client/messages/status/{id} | API key |
| DELETE | /api/v1/client/messages/{id} | API key |
| GET | /api/v1/messages | JWT |
| POST | /api/v1/messages/send | API key |
| POST | /api/v1/whatsapp/send | JWT |
إشعارات التسليم 4
| Method | Path | المصادقة |
|---|---|---|
| GET | /api/v1/client/webhooks | API key |
| POST | /api/v1/client/webhooks | API key |
| DELETE | /api/v1/client/webhooks/{id} | API key |
| POST | /api/v1/client/webhooks/{id}/toggle | API key |
واجهة التطبيق 7
| Method | Path | المصادقة |
|---|---|---|
| GET | /api/v1/app/me | App key |
| GET | /api/v1/apps | JWT |
| POST | /api/v1/apps | JWT |
| DELETE | /api/v1/apps/{appID} | JWT |
| GET | /api/v1/apps/{appID}/accounting | JWT |
| POST | /api/v1/apps/{appID}/regenerate | JWT |
| POST | /api/v1/apps/{appID}/toggle | JWT |
عملاء التطبيق 8
| Method | Path | المصادقة |
|---|---|---|
| GET | /api/v1/app/clients | App key |
| POST | /api/v1/app/clients | App key |
| DELETE | /api/v1/app/clients/{id} | App key |
| POST | /api/v1/app/clients/{id}/subscribe | App key |
| GET | /api/v1/apps/{appID}/clients | JWT |
| POST | /api/v1/apps/{appID}/clients | JWT |
| DELETE | /api/v1/apps/{appID}/clients/{id} | JWT |
| POST | /api/v1/apps/{appID}/clients/{id}/subscribe | JWT |
الإدارة 67
| Method | Path | المصادقة |
|---|---|---|
| GET | /api/v1/admin/admins | JWT إداري |
| GET | /api/v1/admin/apps | JWT إداري |
| POST | /api/v1/admin/apps | JWT إداري |
| DELETE | /api/v1/admin/apps/{appID} | JWT إداري |
| GET | /api/v1/admin/apps/{appID}/accounting | JWT إداري |
| GET | /api/v1/admin/apps/{appID}/clients | JWT إداري |
| POST | /api/v1/admin/apps/{appID}/clients | JWT إداري |
| DELETE | /api/v1/admin/apps/{appID}/clients/{id} | JWT إداري |
| GET | /api/v1/admin/apps/{appID}/clients/{id}/sessions | JWT إداري |
| POST | /api/v1/admin/apps/{appID}/clients/{id}/sessions | JWT إداري |
| POST | /api/v1/admin/apps/{appID}/clients/{id}/subscribe | JWT إداري |
| GET | /api/v1/admin/apps/{appID}/messages | JWT إداري |
| GET | /api/v1/admin/apps/{appID}/plans | JWT إداري |
| POST | /api/v1/admin/apps/{appID}/plans | JWT إداري |
| DELETE | /api/v1/admin/apps/{appID}/plans/{planID} | JWT إداري |
| PUT | /api/v1/admin/apps/{appID}/plans/{planID} | JWT إداري |
| POST | /api/v1/admin/apps/{appID}/regenerate | JWT إداري |
| DELETE | /api/v1/admin/apps/{appID}/sessions/{sid} | JWT إداري |
| POST | /api/v1/admin/apps/{appID}/sessions/{sid}/connect | JWT إداري |
| POST | /api/v1/admin/apps/{appID}/sessions/{sid}/connect-phone | JWT إداري |
| POST | /api/v1/admin/apps/{appID}/sessions/{sid}/disconnect | JWT إداري |
| GET | /api/v1/admin/apps/{appID}/sessions/{sid}/status | JWT إداري |
| POST | /api/v1/admin/apps/{appID}/toggle | JWT إداري |
| GET | /api/v1/admin/discounts | JWT إداري |
| POST | /api/v1/admin/discounts | JWT إداري |
| DELETE | /api/v1/admin/discounts/{id} | JWT إداري |
| PUT | /api/v1/admin/discounts/{id} | JWT إداري |
| GET | /api/v1/admin/main-session | JWT إداري |
| POST | /api/v1/admin/main-session/connect | JWT إداري |
| POST | /api/v1/admin/main-session/connect-phone | JWT إداري |
| POST | /api/v1/admin/main-session/disconnect | JWT إداري |
| GET | /api/v1/admin/main-session/qr | JWT إداري |
| POST | /api/v1/admin/password | JWT إداري |
| GET | /api/v1/admin/payment-methods | JWT إداري |
| POST | /api/v1/admin/payment-methods | JWT إداري |
| DELETE | /api/v1/admin/payment-methods/{id} | JWT إداري |
| PUT | /api/v1/admin/payment-methods/{id} | JWT إداري |
| POST | /api/v1/admin/payment-methods/{id}/toggle | JWT إداري |
| GET | /api/v1/admin/plans | JWT إداري |
| POST | /api/v1/admin/plans | JWT إداري |
| POST | /api/v1/admin/plans/quote | JWT إداري |
| GET | /api/v1/admin/plans/quote-tiers | JWT إداري |
| DELETE | /api/v1/admin/plans/{id} | JWT إداري |
| PUT | /api/v1/admin/plans/{id} | JWT إداري |
| GET | /api/v1/admin/reports | JWT إداري |
| GET | /api/v1/admin/safety/logs | JWT إداري |
| GET | /api/v1/admin/sessions | JWT إداري |
| DELETE | /api/v1/admin/sessions/{id} | JWT إداري |
| POST | /api/v1/admin/sessions/{id}/pause | JWT إداري |
| POST | /api/v1/admin/sessions/{id}/resume | JWT إداري |
| GET | /api/v1/admin/settings | JWT إداري |
| PUT | /api/v1/admin/settings | JWT إداري |
| GET | /api/v1/admin/stats | JWT إداري |
| GET | /api/v1/admin/subscriptions | JWT إداري |
| POST | /api/v1/admin/subscriptions/activate | JWT إداري |
| POST | /api/v1/admin/subscriptions/purge-orphans | JWT إداري |
| POST | /api/v1/admin/subscriptions/{id}/block | JWT إداري |
| POST | /api/v1/admin/subscriptions/{id}/renew | JWT إداري |
| POST | /api/v1/admin/subscriptions/{id}/unblock | JWT إداري |
| GET | /api/v1/admin/users | JWT إداري |
| POST | /api/v1/admin/users | JWT إداري |
| DELETE | /api/v1/admin/users/{id} | JWT إداري |
| PUT | /api/v1/admin/users/{id} | JWT إداري |
| POST | /api/v1/admin/users/{id}/block | JWT إداري |
| POST | /api/v1/admin/users/{id}/grant-credit | JWT إداري |
| POST | /api/v1/admin/users/{id}/unblock | JWT إداري |
| GET | /api/v1/settings/public | عام |
أخرى 1
| Method | Path | المصادقة |
|---|---|---|
| GET | /api/v1/geo | عام |
17. واجهة نماذج الذكاء الاصطناعي — توثيق مستقل
للجانب AI في المنصة بيت خاص: توثيق مستقل بفهرس 30 عملية وأمثلة جاهزة للنسخ بلغات PHP وLaravel وNode وPython؛ وصفحة أسعار مستقلة بباقات رصيد حيّة وأسعار النماذج لكل مليون توكن؛ وصفحة أسئلة شائعة. ولم يعد شيء عن AI هنا — هذا المرجع خالص لواتساب.
/openapi.json تغطي المنصة كاملة بما فيها AI.18. للآلة والذكاء الاصطناعي
هذا القسم للأدوات والوكلاء والمولّدات. البوابة تنشر مصدر حقيقة واحد بثلاثة أوجه متزامنة: هذه الصفحة للبشر، وGET /openapi.json للآلة، وحزمة اختبارات تسقط البناء إن اختلفت الثلاثة.
كيف يبدأ الوكيل
- احصل على
/openapi.jsonأولاً — فيه الـ 214 عملية كاملة، وبيان الدخول لكل عملية (مصفوفة فارغة = عام)، ومخططات الوسائط، و enum لكل رموز الأخطاء الـ 79. - أرسل بيانات الدخول المُعلنة على العملية تحديداً — أي مخطط خاطئ يُرفض قبل تشغيل أي معالج.
- تفرّع على
body.codeلا على الحالة HTTP. وفي 429 التزم بـ Retry-After (وأيضاًretry_after) قبل إعادة المحاولة. - أرسل
Idempotency-Keyمع أي إرسال قد تعيد المحاولة فيه — فيصبح الإعادة بلا خطر تضاعف. - اذكر
request_idعند طلب المساعدة من البشر — إنه مفتاح فهرس سجلات الخادم.
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/api/v1؛ أرقام الهواتف بصيغة دولية بلا + مقدّم؛ الطوابع الزمنية بصيغة RFC 3339؛ والمعرّفات UUID. أي تغيير كاسر يظهر تحت إصدار مسار جديد فقط.19. قواعد الأمان
- خزّن المفاتيح في Secret Manager أو متغير بيئة على الخادم — لا في المستودع أبداً.
- لا ترسل المفتاح إلى المتصفح أو حزم تطبيقات الهاتف؛ البيانات الوحيدة في العميل هي مفتاح المستخدم
wskالمُنشأ وقت التشغيل. - راقب
last_used_atواستبدل المفتاح فور أي تسريب مشتبه به (تدوير للعميل، توليد للتطبيق). - طبّق idempotency بمعرّف طلب خاص بك حتى لا تتكرر الرسالة أبداً.
- ضع حدوداً خاصة بإرسالاتك ولا تُعد الإرسال عشوائياً بعد المهلة — استقصِ الحالة بدلاً من ذلك.
- استخدم HTTPS في كل بيئة الإنتاج، وأبقِ التسجيل العام مطفأً إن لم تكن بحاجته.
- تحقّق من توقيعات webhooks (HMAC-SHA256) قبل ثقة أي محتوى — انظر القسم 12.
-
X-Bootstrap-Keyبنية خادمية فقط؛ تسريبه داخل تطبيق يعني تسريب مسار التسجيل للعالم كله. - الدخول للإدارة على
/zicoخلف JWT وflag المدير — ولا تعرض رموز الإدارة لشيفرة غير المدراء.
request_id ورمز الخطأ — ولا يطلب مفتاحاً أبداً. ومن يطلب مفتاحاً بأي قناة فهو مهاجم.