الأخطاء والحدود
ما يحمله الرفض
كل رفض هو وصف مشكلة (problem) بصيغة JSON — توقّعه أنيس في الاستدعاءات التي توقَّع ردودها (راجع التحقق من الردود):
{
"type": "https://developers.anis.ly/errors/price-changed",
"title": "Price changed",
"status": 409,
"code": "price_changed",
"requestId": "01J9R2K8T4V6XQ0M3B7C5D9E1F"
}
- تفرّع بناءً على
code. ولا تتفرّع أبدًا بناءً علىtitleأوdetail: فهما مكتوبان للبشر، وقد تتغيّر صياغتهما، ويأتيان بالإنجليزية أيًّا كانت اللغة التي تطلبها (وقد لا يأتيdetailأصلًا). ولتعرض على مستخدميك رسالة بلغتهم، استعن بصفحة الرمز في رموز الأخطاء — فلكل صفحة نسخة عربية أيضًا. typeهو عنوان صفحة الرمز — لكل رمز صفحة في رموز الأخطاء، تشرح معناه، وهل اشتُري شيء بطلب الشراء، وما العمل.requestIdهو ما تذكره حين تسأل أنيس عن استدعاء ما. وهو موجود أيضًا في الترويسةX-Request-Idلكل ردّ.
أمران قد يحملهما الرفض
Retry-After— حين تعرف أنيس كم يجب الانتظار، بالثواني. التزم به.Idempotency-Replayed: true— في طلب الشراء: الردّ المسجَّل لطلب شراء مغلق بالفعل.
حالات رفض تُخفي القاعدة التي أخفقت
بعض حالات الرفض تعطي الردّ نفسه لأسباب مختلفة، عن قصد، حتى لا يُخبر الرفضُ أحدًا أبدًا بما يمكنه الالتفاف عليه:
| الرمز | يشمل |
|---|---|
invalid_credentials |
مفتاحًا غير معروف، أو مفتاحًا غير نشط، أو مفتاحًا مُلغى، أو توقيعًا خاطئًا، أو توقيعًا منتهي الصلاحية، أو استدعاءً إلى عنوان غير الذي أعطتك إياه أنيس |
malformed_signed_request |
ترويسة توقيع مفقودة أو لا يمكن قراءتها، أو أجزاءً موقّعة ليست ما يحتاجه المسار أو ليست بترتيبه، أو بصمة محتوى (Content-Digest) لا يمكن قراءتها أو لا تصف المحتوى المُرسَل |
insufficient_scope |
صلاحية ناقصة، أو استدعاءً من خارج الشبكات المسموح بها لك |
wallet_not_granted، resource_not_found |
شيئًا غير موجود، أو ليس لك |
validation_failed |
الاستدعاء يخالف قاعدة. قد يحمل التحقق من مستلم الشحن المباشر أخطاء الحقول في extensions.fields؛ ولا تعتمد على تفاصيل الحقول في حالات الرفض الأخرى. |
قائمة فحص الرفض 401
عندما تُرفض استدعاءاتك بالرمز invalid_credentials، افحص ما يلي، بهذا الترتيب:
- العنوان. تستدعي العنوان الذي أعطتك إياه أنيس، والمضيف (host) الذي توقّعه في
@authorityهو ذلك العنوان بأحرف صغيرة. والوسيط (proxy) الذي يغيّر المضيف يُفسد كل استدعاء. - معرّف المفتاح وملف المفتاح. قيمة
keyidهي معرّف المفتاح الذي حصلت عليه عند تسجيل المفتاح، وأنت توقّع بالنصف الخاص من المفتاح نفسه. - حالة المفتاح. اقرأ حالة تسجيل المفتاح: لا يوقّع إلا المفتاح
active. والمفتاح المُلغى أو المنتهي، أو المفتاح القديم بعد انتهاء فترة التداخل لمفتاحه البديل، يُرفض في كل استدعاء. - ساعتك. ساعة خادمك متزامنة (NTP)، وقيمة
createdهي وقت التوقيع، وعمر التوقيع 60 ثانية أو أقل — انظر الساعة. - التوقيع. بالصيغة ذات الـ 64 بايت، لا بصيغة DER أبدًا، وقيمة
algهيecdsa-p256-sha256. - الفحص الذاتي للتوقيع. شغّل افحص توقيعك واتبع ما يُجيب به.
وإذا فشل الفحص الذاتي أيضًا ولم يتغيّر شيء من جهتك، فقد يكون وصولك معلّقًا: اكتب إلى
support@anis.ly واذكر قيمة requestId لاستدعاء مرفوض.
الحدود
يمكن لموظفي أنيس وضع حدود على تطبيقك، للتطبيق كله أو لكل محفظة على حدة. كل حدّ يعدّ نوعًا واحدًا من الاستدعاءات ضمن نافذة زمنية:
| الحدّ | ما يعدّه |
|---|---|
| كل الاستدعاءات | كل استدعاء موقّع |
| طلبات الشراء | إنشاء طلب بطاقات أو شحن عداد أو الشحن المباشر أو استئنافها؛ وتستخدم كلها حدّ orders الحالي. |
| عمليات الكشف | كلتا عمليتي الكشف |
حين يُبلَغ الحدّ، يُرفض الاستدعاء بالرمز rate_limited، مع Retry-After حين يكون
وقت إعادة الضبط معروفًا. وتحمي أنيس نفسها أيضًا بحدود خاصة بها لا تعطي وقتًا لإعادة الضبط — تجد في الأقسام أدناه
أرقامها ومدة الانتظار. بعد انقضاء الانتظار، أرسل الاستدعاء مرة أخرى. أما طلب الشراء فأمره مختلف: استأنفه بمعرّف العملية نفسه — فقد تكون
نسخة سابقة منه (استدعاء انتهت مهلته، أو إعادة محاولة أجراها عميل HTTP لديك) مرّت فعلًا، ولذلك فإن حدّ معدّل الاستدعاءات لا
يثبت أن طلب الشراء لم يُقدَّم. راجع طلبات الشراء والاستعادة.
يُحتسب الاستدعاء ضمن الحدّ لحظة وصوله، قبل فحصه. فطلب الشراء المرفوض بسبب سعره أو رصيدك أو المخزون يكون قد استهلك أحد طلباتك في تلك النافذة، لذا تحقّق من الأسعار والرصيد قبل تقديم الطلب.
تبيّن صفحة المسارات والصلاحيات الحدود التي يُحتسب كل استدعاء ضمنها.
حدود الإنفاق أمر مختلف
owner_limit_exceeded (محجوز، ولا يُرسَل اليوم)
وdaily_limit_exceeded حدود على ما يجوز للحساب التجاري لدى أنيس إنفاقه، لا على
عدد مرات استدعائك. الانتظار لا يعيد ضبطها — لا تُعِد المحاولة في حلقة.
يغطي سقف إنفاق واحد للتطبيق إجماليات طلبات البطاقات وقيمة expectedTotal لشحن العدادات والشحن المباشر عبر محافظه. لا يوجد سقف مستقل للعدادات أو الشحن المباشر. يبدأ التطبيق الجديد بسقف افتراضي قدره 5,000 د.ل يوميًا لجميع محافظه معًا. تتبع النوافذ توقيت UTC؛ وتبدأ نافذة اليوم الواحد عند 00:00 UTC (02:00 بتوقيت ليبيا). الرفض المؤكد بالرمز spending_limit_exceeded يأتي مع HTTP 429؛ وتحدّد Retry-After الانتظار حتى إعادة الضبط. لا تكرر العملية المرفوضة في حلقة. بعد إعادة الضبط، يستخدم الشراء الجديد معرّف عملية جديدًا. أما فشل الوصول أو الاتصال فلا يحسم محاولة سابقة مجهولة النتيجة.
حدود تضعها أنيس لحماية نفسها
تحمي هذه الحدود جميع الشركاء. يعدّها كل خادم استقبال لدى أنيس على حدة، وتشغّل أنيس عدة خوادم منها، لذا فكل رقم في
الجدول الأول هامش آمن: ابقَ ضمنه ولن يرفضك ذلك الحدّ أبدًا؛ وإن تجاوزته فالرفض يتوقف على الخادم الذي يستقبل كل
استدعاء. ويكون رفضها بالرمز rate_limited دون Retry-After.
| الحدّ | الهامش الآمن |
|---|---|
| الاستدعاءات من عنوان مصدر واحد | 600 في الدقيقة |
| طلبات الشراء الجارية (أُرسلت ولم يصل ردّها بعد) | 16 لكل تطبيق |
| عمليات الكشف الجارية | 8 لكل تطبيق |
| كل الاستدعاءات الجارية الأخرى | 64 لكل تطبيق |
ويزول الرفض بسبب الاستدعاءات الجارية بمجرد انتهاء أحد استدعاءاتك من النوع نفسه.
وتُحتسب طلبات الشراء وعمليات الكشف أيضًا ضمن ميزانية واحدة مشتركة بين كل الخوادم: 1200 في الدقيقة لكل تطبيق، و1200
في الدقيقة لكل مفتاح. ويكون رفضها بالرمز rate_limited مع Retry-After.
ويجب أن يلتزم كل استدعاء أيضًا بالحدود التالية. والاستدعاء الذي يتجاوزها يُرفض بالرمز
validation_failed في كل مرة — فإرساله مرة أخرى لا يفيد:
| الحدّ | القيمة |
|---|---|
| محتوى طلب الشراء | 8,192 بايت (8 كيلوبايت) كحدّ أقصى، بترميز UTF-8 — ويُرفض Content-Type يذكر charset آخر بالحالة HTTP 422 والرمز validation_failed. أرسل Content-Length؛ ويُرفض المحتوى الأكبر بالحالة HTTP 422 والرمز validation_failed |
| المحتوى الآخر | 64 كيلوبايت كحدّ أقصى، يُرسل مع Content-Length — ويُرفض الترميز المجزّأ (chunked transfer encoding) في كل استدعاء |
| الترويسات كلها معًا | 32 كيلوبايت كحدّ أقصى |
| الطريقة (method) والمسار والاستعلام معًا | 2048 حرفًا كحدّ أقصى |
البطاقات في طلب شراء واحد (quantity) |
100 كحدّ أقصى |
الانتظار قبل الإرسال مرة أخرى
حين يحمل الرفض Retry-After، انتظر المدة التي يذكرها. وحين لا يحملها — rate_limited
من الحدود أعلاه، أو dependency_unavailable، أو
request_timeout، أو internal_error — فانتظر قبل كل
محاولة جديدة:
- انتظر ثانية واحدة بعد الرفض الأول.
- ضاعف مدة الانتظار بعد كل رفض متتالٍ: 2، ثم 4، ثم 8، ثم 16 ثانية.
- لا تنتظر أبدًا أكثر من 30 ثانية.
- أضف إلى كل انتظار مقدارًا عشوائيًا (jitter)، حتى لا تُعيد كل العمليات المتوازية لديك الإرسال في اللحظة نفسها.
- بعد أي نجاح، ابدأ من جديد من ثانية واحدة.
حين تكون نتيجة طلب الشراء غير معروفة، تستأنف كل محاولة جديدة معرّف العملية نفسه بالمحتوى نفسه. أما الرفض
النهائي بسبب سقف الإنفاق فمختلف: لم يُقدَّم شيء؛ وبعد Retry-After استخدم معرّف عملية جديدًا لطلب شراء جديد.
راجع طلبات الشراء والاستعادة.
مسار أوقفته أنيس
قد توقف أنيس مسارًا لبعض الوقت. ويُجيب هذا المسار بالرمز 404 مجرّدًا: بلا code — وفي الاستدعاءات التي توقَّع ردودها بلا توقيع من أنيس أيضًا. لا يستطيع
برنامجك التحقق منه، فلا تتصرّف بناءً عليه أبدًا على أنه ردّ. وفي طلب الشراء لا يقول شيئًا عن النتيجة — احتفظ بمعرّف
العملية واستأنفه لاحقًا. وإذا ظلّ مسار ما يُجيب بهذه الطريقة، فاكتب إلى support@anis.ly.