واجهة 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ب للقواعد بالتفصيل.
- سجّل الدخول كمسؤول حساب.
- افتح الحساب ← التكاملات (
/account/integrations). - أنشئ تكاملًا: أعطه اسمًا وامنحه أقل مجموعة صلاحيات يحتاجها فعلًا.
- انسخ المفتاح الظاهر في النافذة التي تظهر مرة واحدة، واحفظه في مدير الأسرار على خادمك.
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 |
قراءة حالة الرسالة |
الصلاحيات خارج هذه القائمة لا يمكن منحها أبدًا — فهرس الصلاحيات قائمة سماح صريحة، وليس انعكاسًا لصلاحيات البوابة الداخلية.
صيغة المفتاح
qwk_<publicId>.<secret>أرسله مع كل طلب:
Authorization: Bearer qwk_REPLACE_ME.REPLACE_MEالجزء publicId يُستخدم للبحث عن بيانات الاعتماد، والجزء secret يُتحقق منه في زمن ثابت
مقابل قيمة hash مخزّنة. النص الصريح للسر لا يُخزَّن ولا يُسجَّل ولا يمكن استرجاعه بعد إنشائه.
3. تدوير المفتاح وإلغاؤه
يمكن أن يكون هناك مفتاحان نشطان على نفس التكامل في وقت واحد، وهذا بالضبط ما يجعل التدوير بدون انقطاع ممكنًا:
- من صفحة التكامل، أصدر مفتاحًا ثانيًا (تدوير). الآن المفتاحان يعملان معًا.
- انشر المفتاح الجديد في مخزن الأسرار عندك وحوّل خدمتك إليه.
- نفّذ طلب قراءة تجريبيًا بالمفتاح الجديد للتأكد أنه يعمل.
- ألغِ المفتاح القديم.
الإلغاء فوري ويسري على الطلب التالي مباشرة — المفتاح الملغى يفشل في المصادقة في الحال، ولا توجد فترة سماح. ومحاولة الاحتفاظ بمفتاح ثالث نشط في نفس الوقت مرفوضة؛ ألغِ أحد المفتاحين الحاليين أولًا.
4. البداية السريعة: من البوابة إلى أول رسالة
export QWEIRA_API_KEY='qwk_REPLACE_ME.REPLACE_ME'
export QWEIRA_BASE_URL='https://api.qweira.com'الخطوة 1 — اعرض الأجهزة واختر جهازًا قيمة channel عنده whatsapp أو telegram وقيمة
status عنده connected (راجع القسم 5 إن احتجت إنشاء جهاز وتفويضه أولًا):
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 فريد:
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:
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
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
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".
أرسل الإجراء المطلوب بالضبط، ولا شيء غيره:
POST /api/public/v1/devices/{deviceId}/authorization
Content-Type: application/json
{ "action": "submit_verification_code", "code": "12345" }وإذا كانت الاستجابة ما زالت تتطلب التحقق بخطوتين، تصبح actionRequired هي
submit_two_factor_password؛ فأرسل:
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.
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}/reconnectPOST /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:
{
"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
عندها أكثر من بضع دقائق، حتى لا يمكن إعادة تشغيل تسليم مُلتقط لاحقًا.
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):
{
"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.
والاستجابة كائن صفحة:
{
"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
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)
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)
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)
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 |
— | حالة رسالة أرسلتها أنت |