للمطوّرين

واجهة Qweira للتكامل

واجهة من خادم إلى خادم لإدارة الأجهزة والقوالب وإرسال الرسائل عبر واتساب وتيليجرام.

واجهة Qweira العامة للتكامل — دليل المطوّر

واجهة برمجية (API) من خادم إلى خادم لإدارة أجهزة الحساب، والقوالب النصية القابلة لإعادة الاستخدام، وإرسال الرسائل بنصها النهائي، وقراءة حالة الرسالة. هذا الدليل يوثّق السلوك المُنفَّذ فعليًا في /api/public/v1/*. وعند أي اختلاف بين هذا الدليل ومسوّدة تصميم OpenAPI، فالكلمة الأخيرة لهذا الدليل (وللواجهة العاملة).

1. ما هي هذه الواجهة

واجهة Qweira العامة للتكامل تتيح لخدمة خلفية عندك (CRM أو ERP أو أداة داخلية) أن تدير أجهزة المراسلة الخاصة بحسابها، وقوالبها النصية، وإرسال الرسائل الصادرة، عبر HTTPS عادي ومفتاح bearer.

وهي من خادم إلى خادم فقط:

  • لا تضع مفتاح التكامل أبدًا في جافاسكريبت المتصفح، أو داخل تطبيق موبايل، أو في مستودع عام، أو في رابط أو query string، أو في السجلات.
  • لا تستدعِ الواجهة مباشرة من عميل يتحكم فيه المستخدم النهائي. مرّرها عبر الخادم الخاص بك.
  • Qweira تنقل النص النهائي الذي ترسله أنت. ويبقى تطبيقك مسؤولًا عن توليد أي رمز لمرة واحدة وعن انتهاء صلاحيته وإعادة إرساله والتحقق منه — Qweira لا تولّد رموز OTP ولا تتحقق منها.

كل عملية تقع تحت /api/public/v1/. وهناك 14 عملية عامة بالضبط: 7 للأجهزة، و5 للقوالب، و2 للرسائل. راجع القسم 14 للقائمة الكاملة.

2. الحصول على مفتاح

واجهة التكامل تتطلب باقة مدفوعة — Basic أو أعلى. الحسابات على باقة Starter المجانية لا يمكنها إنشاء مفاتيح تكامل، والمفتاح يتوقف عن العمل فور توقف اشتراك حسابه. راجع القسم 2ب للقواعد بالتفصيل.

  1. سجّل الدخول كمسؤول حساب.
  2. افتح الحساب ← التكاملات (/account/integrations).
  3. أنشئ تكاملًا: أعطه اسمًا وامنحه أقل مجموعة صلاحيات يحتاجها فعلًا.
  4. انسخ المفتاح الظاهر في النافذة التي تظهر مرة واحدة، واحفظه في مدير الأسرار على خادمك.

Qweira لا تستطيع عرض المفتاح مرة أخرى. وإذا ضاع، فدوّره (القسم 3).

2ب. متطلبات الباقة

الوصول إلى الواجهة مرتبط باشتراك الحساب، ويُفحص مع كل طلب لا عند إنشاء المفتاح فقط. المفتاح الذي صدر على باقة مدفوعة يتوقف عن المصادقة في اللحظة التي يُخفَّض فيها الحساب أو يتوقف اشتراكه.

حالة الاشتراك الوصول للواجهة
Basic أو Growth أو Pro أو Enterprise — Active نعم
أي باقة مدفوعة — Grace (جارٍ إعادة محاولة الدفع) نعم
أي باقة مدفوعة — Trial نعم
أي باقة مدفوعة — PastDue أو Suspended أو Expired لا
Starter (المجانية) لا

الرفض يعود بـ 403 مع الكود PLAN_UPGRADE_REQUIRED. وهذا الخطأ لا يُعاد معه المحاولة: لا بد من ترقية الحساب أو تجديد اشتراكه. تعامل معه في خدمتك كإنذار إعدادات، لا كخطأ مؤقت تتراجع أمامه تدريجيًا.

الصلاحيات (Scopes)

كل تكامل يُمنح واحدة أو أكثر من ست صلاحيات بالضبط. ويُرفض الطلب ما لم يكن التكامل المُصادَق عليه يملك الصلاحية التي تتطلبها العملية المستدعاة تحديدًا:

Scope ماذا يمنح
devices:view عرض الأجهزة وقراءتها
devices:manage إنشاء الأجهزة وتفويضها وإعادة وصلها وفصلها وحذفها
templates:view عرض القوالب وقراءتها
templates:manage إنشاء القوالب وتحديثها وحذفها
messages:send إرسال الرسائل الصادرة
messages:read قراءة حالة الرسالة

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

صيغة المفتاح

text
qwk_<publicId>.<secret>

أرسله مع كل طلب:

text
Authorization: Bearer qwk_REPLACE_ME.REPLACE_ME

الجزء publicId يُستخدم للبحث عن بيانات الاعتماد، والجزء secret يُتحقق منه في زمن ثابت مقابل قيمة hash مخزّنة. النص الصريح للسر لا يُخزَّن ولا يُسجَّل ولا يمكن استرجاعه بعد إنشائه.

3. تدوير المفتاح وإلغاؤه

يمكن أن يكون هناك مفتاحان نشطان على نفس التكامل في وقت واحد، وهذا بالضبط ما يجعل التدوير بدون انقطاع ممكنًا:

  1. من صفحة التكامل، أصدر مفتاحًا ثانيًا (تدوير). الآن المفتاحان يعملان معًا.
  2. انشر المفتاح الجديد في مخزن الأسرار عندك وحوّل خدمتك إليه.
  3. نفّذ طلب قراءة تجريبيًا بالمفتاح الجديد للتأكد أنه يعمل.
  4. ألغِ المفتاح القديم.

الإلغاء فوري ويسري على الطلب التالي مباشرة — المفتاح الملغى يفشل في المصادقة في الحال، ولا توجد فترة سماح. ومحاولة الاحتفاظ بمفتاح ثالث نشط في نفس الوقت مرفوضة؛ ألغِ أحد المفتاحين الحاليين أولًا.

4. البداية السريعة: من البوابة إلى أول رسالة

bash
export QWEIRA_API_KEY='qwk_REPLACE_ME.REPLACE_ME'
export QWEIRA_BASE_URL='https://api.qweira.com'

الخطوة 1 — اعرض الأجهزة واختر جهازًا قيمة channel عنده whatsapp أو telegram وقيمة status عنده connected (راجع القسم 5 إن احتجت إنشاء جهاز وتفويضه أولًا):

bash
curl --fail-with-body \
  --request GET \
  --url "$QWEIRA_BASE_URL/api/public/v1/devices?limit=50" \
  --header "Authorization: Bearer $QWEIRA_API_KEY" \
  --header "Accept: application/json"

الخطوة 2 — أرسل النص النهائي إلى ذلك الجهاز، مع Idempotency-Key فريد:

bash
curl --fail-with-body \
  --request POST \
  --url "$QWEIRA_BASE_URL/api/public/v1/messages" \
  --header "Authorization: Bearer $QWEIRA_API_KEY" \
  --header "Content-Type: application/json" \
  --header "Idempotency-Key: crm-login-01J3AB9JYHMEJGYG8GXJ7D4F82" \
  --data '{
    "deviceId": "dev_01j3ab5cr3jsm8gk9zfrf6r21t",
    "to": "+201001234567",
    "text": "Your login code is 483921"
  }'

استجابة 202 تعني أن الرسالة قُبلت بشكل دائم للتسليم غير المتزامن — لا تعني أن المستلم استلمها.

الخطوة 3 — اقرأ الحالة بالمعرّف الذي عاد في جسم استجابة 202:

bash
curl --fail-with-body \
  --request GET \
  --url "$QWEIRA_BASE_URL/api/public/v1/messages/81781948-15bd-47af-a276-638caf255c21" \
  --header "Authorization: Bearer $QWEIRA_API_KEY" \
  --header "Accept: application/json"

5. الأجهزة

الجهاز هو اتصال قناة مملوك للحساب. وهناك قيمتان فقط للقناة العامة: whatsapp وtelegram. وللجهاز أربع حالات دورة حياة عامة فقط:

status المعنى
authorization_required أُنشئ ولم يُفوَّض بعد؛ الحقل actionRequired يخبرك بالخطوة التالية
connecting تم إرسال التفويض، وجارٍ إنشاء الاتصال
connected جاهز للإرسال
disconnected لم يعد متصلًا؛ استدعِ إعادة الوصل

وأربعة إجراءات عامة قد يحتاج العميل لتنفيذها، تظهر في actionRequired:

actionRequired المعنى
scan_qr اعرض authorization (حمولة رمز QR) على مالك الحساب
submit_verification_code أرسل الرمز الذي استلمه المالك، عبر نقطة نهاية التفويض
submit_two_factor_password أرسل كلمة مرور التحقق بخطوتين الخاصة بمالك الحساب
replace_credentials بيانات اعتماد الجهاز المخزّنة لم تعد صالحة ويجب استبدالها

الحقل authorization نص مبهم قصير العمر (يظهر فقط مباشرة بعد توليده، مثلًا بعد POST /devices مباشرة). لا تحفظه ولا تسجّله. والحقل phoneNumber الذي تعيده الواجهة مُقنَّع إلى آخر أربعة أرقام (مثل ****4567).

مسار WhatsApp

http
POST /api/public/v1/devices
Idempotency-Key: <unique-value>
Content-Type: application/json

{
  "channel": "whatsapp",
  "name": "OTP Sender",
  "phoneNumber": "+201001234567"
}

الاستجابة تحمل status: "authorization_required" وactionRequired: "scan_qr"، ويحمل authorization قيمة QR المطلوب عرضها. اعرضها على مالك الحساب واستعلم دوريًا عن GET /devices/{deviceId} حتى تصبح status هي connected. وإذا انتهت صلاحية رمز QR قبل مسحه، استدعِ POST /devices/{deviceId}/authorization بجسم فارغ لتحديثه.

مسار Telegram

http
POST /api/public/v1/devices
Idempotency-Key: <unique-value>
Content-Type: application/json

{
  "channel": "telegram",
  "name": "OTP Sender",
  "phoneNumber": "+201001234567",
  "apiId": "123456",
  "apiHash": "REPLACE_ME"
}

الحقول apiId/apiHash وphoneNumber حقول من المستوى الأعلى في طلب الإنشاء — وهي ليست متداخلة داخل كائن credentials. والاستجابة تحمل actionRequired: "submit_verification_code". أرسل الإجراء المطلوب بالضبط، ولا شيء غيره:

http
POST /api/public/v1/devices/{deviceId}/authorization
Content-Type: application/json

{ "action": "submit_verification_code", "code": "12345" }

وإذا كانت الاستجابة ما زالت تتطلب التحقق بخطوتين، تصبح actionRequired هي submit_two_factor_password؛ فأرسل:

http
POST /api/public/v1/devices/{deviceId}/authorization
Content-Type: application/json

{ "action": "submit_two_factor_password", "password": "REPLACE_ME" }

جسم طلب التفويض يتطلب دائمًا الحقل action (واحدة من قيم الإجراءات الأربع أعلاه)، بالإضافة إلى code عندما يكون الإجراء submit_verification_code، أو password عندما يكون submit_two_factor_password. ولا تُعِد إرسال رمز أو كلمة مرور تلقائيًا بعد انتهاء المهلة — اقرأ الحالة الحالية للجهاز أولًا، ثم أرسل فقط ما ينتظره فعلًا.

إعادة الوصل والفصل

كل من POST /devices/{deviceId}/reconnect وPOST /devices/{deviceId}/disconnect يتطلب Idempotency-Key ولا يأخذ جسمًا. وكلاهما يعيد الحالة الحالية أو التالية للجهاز. إعادة وصل جهاز غير متصل حاليًا تنقله نحو connecting، وفصل جهاز مفصول أصلًا عملية آمنة بلا أثر تعيد الحالة الحالية.

الحذف

DELETE /devices/{deviceId} يحذف الجهاز حذفًا منطقيًا وينفّذ تنظيفًا للقناة بأفضل جهد ممكن. والحذف المتكرر آمن ويعيد 204 دائمًا، حتى لو كان الجهاز محذوفًا من قبل أو لم يوجد أصلًا لهذا التكامل.

6. القوالب

نصوص مملوكة للحساب وقابلة لإعادة الاستخدام لتكوين الرسائل. القوالب هي اسم ونص فقط — لا استبدال متغيرات ولا معالجة ولا مرفقات في هذه الواجهة.

  • name يجب أن يكون فريدًا داخل الحساب، والمقارنة لا تفرّق بين الحروف الكبيرة والصغيرة.
  • text يُخزَّن ويُعاد كما هو حرفيًا؛ و{{placeholders}} لا تستبدلها Qweira. تطبيقك هو المسؤول عن تكوين النص النهائي قبل استدعاء POST /messages.
http
POST /api/public/v1/templates
Content-Type: application/json

{
  "name": "Login code",
  "text": "Your login code is {{code}}"
}

خمس عمليات تغطي دورة الحياة كاملة: GET /templates (عرض، مقسّم لصفحات)، وPOST /templates (إنشاء)، وGET /templates/{templateId} (قراءة واحد)، وPUT /templates/{templateId} (تحديث الاسم أو النص — والحقول التي لا تمثّلها هذه الواجهة تبقى كما هي دون مساس)، و DELETE /templates/{templateId} (حذف).

7. الرسائل

POST /messages يرسل رسالة واحدة بنصها النهائي إلى مستلم واحد عبر جهاز واحد متصل ومملوك للحساب. ويجب أن تكون to بصيغة E.164 (مثل +201001234567). استجابة 202 تعني أن الرسالة حُفظت بشكل دائم للتسليم غير المتزامن — وهي ليست إيصال تسليم، والتسليم مرة واحدة على الأقل، لذا ينبغي أن يحتمل تطبيقك تكرارًا نادرًا.

GET /messages/{messageId} يعيد الحالة المعيارية الحالية لرسالة أرسلها تكاملك أنت — ولا يعيد أبدًا رسالة تخص تكاملًا آخر أو حسابًا آخر.

الحالات

status المعنى
queued مقبولة ولم تُرسل بعد
sent سُلِّمت لجهة التسليم
delivered وصلت إلى جهاز المستلم
read قرأها المستلم (يعتمد على القناة، وغير مضمون في كل قناة)
failed فشل التسليم؛ وfailureCode يحمل سببًا ثابتًا وآمنًا

failureCode يظهر فقط عندما تكون status هي failed، ويكون null فيما عدا ذلك.

8. عدم تكرار العملية (Idempotency)

أربع عمليات تتطلب ترويسة Idempotency-Key:

  • POST /messages (إرسال)
  • POST /devices (إنشاء)
  • POST /devices/{deviceId}/reconnect
  • POST /devices/{deviceId}/disconnect

القواعد:

  • يجب أن يكون المفتاح من 16 إلى 128 حرف ASCII قابلًا للطباعة، وفريدًا لكل محاولة عملية منطقية في نظامك.
  • ولّد مفتاحًا واحدًا لكل إجراء تجاري وأعد استخدامه عند إعادة المحاولة بعد انتهاء مهلة أو خطأ شبكة — لا تولّد مفتاحًا جديدًا لإعادة المحاولة.
  • نفس المفتاح مع نفس جسم الطلب يعيد النتيجة الأصلية (نفس رمز الحالة، نفس المورد) لمدة 24 ساعة على الأقل.
  • نفس المفتاح مع جسم طلب مختلف يُرفض بـ 409 IDEMPOTENCY_KEY_REUSED.
  • إغفال مفتاح مطلوب يعيد 400 IDEMPOTENCY_KEY_REQUIRED.

غياب الترويسة في عملية تتطلبها إشارة لك بأن تصلح العميل، لا أن تعيد المحاولة بشكل أعمى.

8ب. Webhooks التسليم

حالة الرسالة متاحة بالاستعلام الدوري عن GET /messages/{id}، ومتاحة أيضًا بالدفع: اضبط webhook للتسليم وستقوم Qweira بإرسال كل تغيّر حالة للرسائل التي أرسلها ذلك التكامل.

ضبط نقطة النهاية

في البوابة: الحساب ← التكاملات ← Webhook على صف التكامل. ضع رابط HTTPS مطلقًا يمكن الوصول إليه من الإنترنت العام. عناوين loopback والعناوين الخاصة وlink-local وعناوين بيانات السحابة مرفوضة، والوجهة يُعاد فحصها مقابل DNS مباشرة قبل كل إرسال.

في أول مرة تضبط فيها الرابط، تولّد Qweira سر توقيع وتعرضه مرة واحدة. احفظه في مدير الأسرار عندك — فمثل مفتاح التكامل، لا يمكن عرضه مرة أخرى. وتغيير الرابط وحده يبقي السر الحالي؛ وعلّم على "إصدار سر توقيع جديد" لتدويره.

الحمولة

POST إلى نقطة النهاية عندك، بـ Content-Type: application/json:

json
{
  "id": "evt_9f2c1b8a4d6e4f0f9a7c2b3d5e6f7a8b",
  "type": "message.status",
  "createdAt": "2026-07-25T09:12:13.921Z",
  "data": {
    "messageId": "mId_3f7c1e9a-2b4d-4c6e-8a0f-1d2e3f4a5b6c",
    "status": "delivered",
    "failureCode": null
  }
}

الحقل status يستخدم نفس مفردات GET /messages/{id}: queued وsent وdelivered وread وfailed. وfailureCode لا يكون غير فارغ إلا عندما تكون status هي failed. وحدث webhook.test يحمل نفس المظروف مع كائن data كل حقوله فارغة.

ترويسات الطلب:

الترويسة المعنى
Qweira-Signature t=<unix seconds>,v1=<hex HMAC-SHA256>
Qweira-Event-Id نفس id في الجسم؛ ثابت عبر إعادات المحاولة
Qweira-Event-Type نفس type في الجسم

التحقق من التوقيع

احسب HMAC-SHA256(secret, "{t}.{raw request body}") وقارنه بـ v1 في زمن ثابت. تحقق مقابل بايتات الجسم الخام، قبل أي تحليل JSON أو إعادة تسلسل. وارفض الأحداث التي مضى على t عندها أكثر من بضع دقائق، حتى لا يمكن إعادة تشغيل تسليم مُلتقط لاحقًا.

python
import hashlib, hmac, time

def verify(raw_body: bytes, header: str, secret: str, tolerance: int = 300) -> bool:
    parts = dict(p.split("=", 1) for p in header.split(","))
    if abs(time.time() - int(parts["t"])) > tolerance:
        return False
    expected = hmac.new(
        secret.encode(), f'{parts["t"]}.'.encode() + raw_body, hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, parts["v1"])

سلوك التسليم

  • استجب بـ 2xx خلال 10 ثوانٍ. وأي شيء غير ذلك — بما في ذلك إعادة التوجيه، التي لا تُتبع أبدًا — يُحسب فشلًا.
  • المحاولات الفاشلة يُعاد إرسالها بتراجع تدريجي تقريبًا عند 30 ثانية، ودقيقتين، و10 دقائق، وساعة، و6 ساعات، ثم تُسقط.
  • التسليم مرة واحدة على الأقل والأحداث قد تصل بترتيب مختلف. أزل التكرار اعتمادًا على Qweira-Event-Id، وتعامل مع الحالة كمجموعة ملاحظات لا كتسلسل: لا تُرجِع أبدًا delivered إلى sent لمجرد وصول حدث متأخر.
  • أقرّ الاستلام أولًا، ثم عالج بعد ذلك. تنفيذ العمل قبل الرد هو سبب انتهاء المهل والتسليمات المكررة.
  • صحة التسليم (آخر نجاح، وآخر فشل وسببه) تظهر في صفحة التكاملات. استخدم إرسال حدث تجريبي بعد أي تغيير للتأكد أن نقطة النهاية تقبل تسليمًا حقيقيًا وتتحقق منه.

9. حدود المعدل وإعادة المحاولة

كل عملية تنتمي إلى واحدة من خمس فئات معدل مسماة، تُطبَّق لكل تكامل (مع الرجوع إلى تقسيم حسب عنوان IP عندما لا يكون الطلب مُصادقًا عليه)، باستخدام نافذة عدّ طلبات ثابتة:

الفئة تستخدمها
read كل عمليات GET للعرض والقراءة
write إنشاء وتحديث وحذف القوالب
message إرسال الرسائل
device إنشاء الجهاز وإعادة وصله وفصله وحذفه
key-management إنشاء وتدوير وإلغاء المفاتيح من البوابة

عمليات قراءة الأجهزة والرسائل تحمل أيضًا ترويستي X-RateLimit-Limit وX-RateLimit-Remaining في الاستجابة لتتمكن من التراجع استباقيًا.

عند 429 RATE_LIMITED، احترم ترويسة Retry-After (بالثواني) قبل إعادة المحاولة. وللأعطال المؤقتة عمومًا (429 و503 ومهلات الشبكة)، استخدم تراجعًا أُسيًّا محدودًا مع jitter، وأعد استخدام نفس Idempotency-Key في أي إعادة محاولة لعملية تتطلبه. ولا تُعِد محاولة أخطاء التحقق أو التصريح من فئة 4xx دون تغيير الطلب.

10. الأخطاء

كل استجابة خطأ هي application/problem+json (RFC 9457):

json
{
  "type": "https://developers.qweira.com/errors/not_found",
  "title": "Not found",
  "status": 404,
  "detail": "The requested device was not found.",
  "code": "NOT_FOUND",
  "correlationId": "01J3AB71R2YQT0QNC2RWT7Q40Q"
}

الحقل code معرّف ثابت وموثّق وقابل للقراءة آليًا — ابنِ معالجة أخطائك عليه، لا على نص title/detail الذي قد يتغيّر. والحقل correlationId يعرّف الطلب على جانب الخادم؛ اقتبسه حرفيًا في أي طلب دعم.

أكواد الأخطاء الثابتة

code الحالة المعنى
UNAUTHORIZED 401 مفتاح تكامل مفقود أو غير صالح الصيغة أو منتهي أو ملغى
SCOPE_REQUIRED 403 المفتاح المُصادق عليه لا يملك الصلاحية التي تتطلبها هذه العملية
PLAN_UPGRADE_REQUIRED 403 باقة الحساب لا تشمل واجهة التكامل، أو أن اشتراكه متوقف. راجع القسم 2ب
NOT_FOUND 404 المورد غير موجود داخل حساب تكاملك، أو يخص حسابًا آخر (الاستجابات غير كاشفة — لا يمكنك التمييز بين "غير موجود" و"ليس لك")
VALIDATION_FAILED 422 حقل أو أكثر في الطلب غير صالح؛ راجع كائن errors للرسائل لكل حقل
IDEMPOTENCY_KEY_REQUIRED 400 لم تُرسل ترويسة Idempotency-Key المطلوبة
IDEMPOTENCY_KEY_REUSED 409 أُعيد استخدام نفس Idempotency-Key مع جسم طلب مختلف
CONFLICT 409 مطالبة idempotency ما زالت قيد التنفيذ، أو انتقال دورة حياة غير صالح، أو اسم قالب مستخدم بالفعل في الحساب
RATE_LIMITED 429 تجاوز حد المعدل لفئة العملية؛ راجع Retry-After
FEATURE_DISABLED 503 واجهة التكامل العامة غير مفعّلة حاليًا في هذه البيئة
INTERNAL_ERROR 5xx خطأ خادم غير متوقع؛ أعد المحاولة بتراجع تدريجي

أكواد 422 الخاصة بعمليات بعينها وقد تصادفها أيضًا: DEVICE_NOT_CONNECTED (الجهاز الهدف لإرسال الرسالة غير متصل)، وUNSUPPORTED_CHANNEL (إنشاء جهاز بقناة غير whatsapp / telegram)، وDEVICE_OPERATION_FAILED (الجهاز ليس في حالة صالحة للتعديل المطلوب).

11. تقسيم النتائج

GET /devices وGET /templates مقسّمان لصفحات عبر معاملي استعلام:

  • cursor — نص مبهم تعيده الصفحة السابقة في nextCursor. أغفله لجلب الصفحة الأولى.
  • limit — حجم الصفحة، من 1 إلى 100، والافتراضي 25.

والاستجابة كائن صفحة:

json
{
  "items": [ ],
  "nextCursor": "75"
}

nextCursor يكون null عندما لا توجد صفحة تالية. تعامل مع المؤشر كقيمة مبهمة — لا تحلّله ولا تبنِه بنفسك؛ أعد دائمًا نفس ما أعادته الصفحة السابقة بالضبط.

12. حل المشكلات

الحالة الكود ماذا يعني ماذا تفعل
401 UNAUTHORIZED المفتاح مفقود أو غير صالح الصيغة أو منتهي أو ملغى تأكد أن ترويسة Authorization: Bearer <key> موجودة وصحيحة؛ ودوّر المفتاح إن كان ملغى. ولا تضع المفتاح أبدًا في تذكرة دعم.
403 SCOPE_REQUIRED المفتاح صالح لكنه لا يملك الصلاحية التي تحتاجها هذه العملية أضف الصلاحية الناقصة للتكامل من البوابة، أو استخدم مفتاحًا يملكها بالفعل.
403 PLAN_UPGRADE_REQUIRED الحساب على باقة Starter المجانية، أو اشتراكه المدفوع متأخر السداد أو موقوف أو منتهٍ رقِّ الحساب إلى Basic أو أعلى، أو سدّد الفاتورة المستحقة. لا يُعاد معه المحاولة — أطلق إنذارًا بدل التراجع التدريجي.
404 NOT_FOUND المورد غير موجود لحساب هذا التكامل تأكد أن المعرّف يخص هذا الحساب/التكامل. الواجهة لن تخبرك إن كان المورد موجودًا في مكان آخر.
409 CONFLICT / IDEMPOTENCY_KEY_REUSED اسم قالب مكرر، أو طلب idempotent قيد التنفيذ، أو انتقال دورة حياة غير صالح، أو مفتاح أُعيد استخدامه بجسم مختلف في حالة إعادة استخدام idempotency، ولّد مفتاحًا جديدًا لطلب جديد فعلًا؛ ولا تُعِد استخدام مفتاح عبر حمولات مختلفة. وفي تعارض أسماء القوالب، اختر اسمًا فريدًا.
422 VALIDATION_FAILED / DEVICE_NOT_CONNECTED / UNSUPPORTED_CHANNEL / DEVICE_OPERATION_FAILED شكل الطلب JSON صالح لكنه يفشل في التحقق من قواعد العمل اقرأ كائن errors (التحقق) أو detail (حالة الجهاز) وأصلح الطلب؛ هذه الأخطاء لا يُعاد معها المحاولة دون تغيير.
429 RATE_LIMITED طلبات أكثر من اللازم لفئة معدل هذه العملية انتظر عدد الثواني في Retry-After، ثم أعد المحاولة بتراجع تدريجي.

13. أمثلة برمجية

كل الأمثلة ترسل رسالة واحدة عبر جهاز متصل مع مفتاح idempotency.

curl

bash
curl --fail-with-body \
  --request POST \
  --url "https://api.qweira.com/api/public/v1/messages" \
  --header "Authorization: Bearer $QWEIRA_API_KEY" \
  --header "Content-Type: application/json" \
  --header "Idempotency-Key: $(uuidgen)" \
  --data '{
    "deviceId": "dev_01j3ab5cr3jsm8gk9zfrf6r21t",
    "to": "+201001234567",
    "text": "Your login code is 483921"
  }'

C# (HttpClient)

csharp
using System.Net.Http.Headers;
using System.Net.Http.Json;

using var client = new HttpClient { BaseAddress = new Uri("https://api.qweira.com") };
client.DefaultRequestHeaders.Authorization =
    new AuthenticationHeaderValue("Bearer", Environment.GetEnvironmentVariable("QWEIRA_API_KEY"));

using var request = new HttpRequestMessage(HttpMethod.Post, "/api/public/v1/messages")
{
    Content = JsonContent.Create(new
    {
        deviceId = "dev_01j3ab5cr3jsm8gk9zfrf6r21t",
        to = "+201001234567",
        text = "Your login code is 483921"
    })
};
request.Headers.Add("Idempotency-Key", Guid.NewGuid().ToString("N"));

using var response = await client.SendAsync(request);
response.EnsureSuccessStatusCode();
Console.WriteLine(await response.Content.ReadAsStringAsync());

Node.js (fetch)

javascript
const response = await fetch("https://api.qweira.com/api/public/v1/messages", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.QWEIRA_API_KEY}`,
    "Content-Type": "application/json",
    "Idempotency-Key": crypto.randomUUID(),
  },
  body: JSON.stringify({
    deviceId: "dev_01j3ab5cr3jsm8gk9zfrf6r21t",
    to: "+201001234567",
    text: "Your login code is 483921",
  }),
});

if (!response.ok) throw new Error(await response.text());
console.log(await response.json());

Python (requests)

python
import os
import uuid
import requests

response = requests.post(
    "https://api.qweira.com/api/public/v1/messages",
    headers={
        "Authorization": f"Bearer {os.environ['QWEIRA_API_KEY']}",
        "Idempotency-Key": str(uuid.uuid4()),
    },
    json={
        "deviceId": "dev_01j3ab5cr3jsm8gk9zfrf6r21t",
        "to": "+201001234567",
        "text": "Your login code is 483921",
    },
    timeout=10,
)
response.raise_for_status()
print(response.json())

14. مرجع العمليات

كل المسارات نسبية إلى /api/public/v1.

الأجهزة (7)

Method Path Scope Idempotency-Key ملاحظات
GET /devices devices:view عرض مقسّم لصفحات
POST /devices devices:manage مطلوب إنشاء وبدء التفويض
GET /devices/{deviceId} devices:view قراءة جهاز واحد
POST /devices/{deviceId}/authorization devices:manage متابعة التفويض
POST /devices/{deviceId}/reconnect devices:manage مطلوب إعادة الوصل
POST /devices/{deviceId}/disconnect devices:manage مطلوب الفصل
DELETE /devices/{deviceId} devices:manage حذف منطقي، آمن التكرار

القوالب (5)

Method Path Scope ملاحظات
GET /templates templates:view عرض مقسّم لصفحات
POST /templates templates:manage إنشاء
GET /templates/{templateId} templates:view قراءة واحد
PUT /templates/{templateId} templates:manage تحديث الاسم أو النص
DELETE /templates/{templateId} templates:manage حذف

الرسائل (2)

Method Path Scope Idempotency-Key ملاحظات
POST /messages messages:send مطلوب إرسال النص النهائي؛ يعيد 202
GET /messages/{messageId} messages:read حالة رسالة أرسلتها أنت