# الأخطاء وحدود الطلبات في واجهة مُجيب البرمجية

> حدود الطلبات في واجهة مُجيب البرمجية لكل مفتاح، وشكل كل خطأ ورمزه، وترويسات X-RateLimit، وما تفعله عند الخطأ 429 ومتى تعيد المحاولة.

حدود الطلبات في واجهة مُجيب البرمجية 60 طلبًا في الدقيقة لكل مفتاح، وكل خطأ يعود بالشكل نفسه: رمز HTTP وجسم JSON فيه `code` ثابت و`message` يشرحه. هذه الصفحة تجمع الرموز كلها، وما تفعله عند كل منها، وكيف تبقى داخل الحد.

## كيف يبدو الخطأ في الواجهة؟

كل خطأ جسمه كائن واحد اسمه `error`، فيه الحقلان `code` و`message`:

```json
{ "error": { "code": "NOT_FOUND", "message": "Contact not found" } }
```

- **`code`** رمز ثابت بحروف إنجليزية كبيرة. ابنِ برنامجك عليه.
- **`message`** شرح بالإنجليزية للمطوّر، وقد يتغيّر نصه، فلا تبنِ عليه شيئًا.
- **`issues`** و**`issueCount`** يظهران فقط مع الخطأ 400 عندما يكون معامل في الرابط غير صالح: `issues` قائمة بأول 20 مشكلة وُجدت، كل منها يسمّي المعامل في `path`، و`issueCount` عدد المشكلات كلها.

```json
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Invalid input",
    "issues": [
      { "code": "too_big", "maximum": 100, "type": "number", "inclusive": true, "exact": false, "message": "Number must be less than or equal to 100", "path": ["limit"] }
    ],
    "issueCount": 1
  }
}
```

## ما رموز الأخطاء وما معنى كل منها؟

هذه كل الرموز التي تعيدها الواجهة:

| HTTP | `code` | متى يظهر | ما تفعله |
| --- | --- | --- | --- |
| 400 | `VALIDATION_ERROR` | معامل غير معروف، أو `limit` خارج المسموح، أو تاريخ غير صحيح، أو `from` بعد `to`، أو `status` غير معروف، أو مؤشر صفحة لا يُقرأ | أصلح الطلب ولا تعِده كما هو |
| 401 | `UNAUTHORIZED` | لا مفتاح، أو مفتاح غير صالح أو ملغى أو منتهٍ، أو حساب نشاط غير فعّال | راجع [مفتاح API](https://docs.mujib.ai/developers/authentication/) |
| 403 | `FORBIDDEN` | المفتاح صالح لكن ينقصه `contacts.read` أو `bookings.read` | أنشئ مفتاحًا بالصلاحية المطلوبة |
| 404 | `NOT_FOUND` | معرّف غير موجود في نشاطك، أو معرّف بصيغة غير صحيحة، أو مسار غير موجود | تحقق من المعرّف والمسار |
| 429 | `RATE_LIMITED` | تجاوزت حدود الطلبات | انتظر ثم أعد المحاولة |
| 500 | `INTERNAL` | خطأ غير متوقع في الخادم | أعد المحاولة بعد قليل |

المعرّف الذي ينتمي إلى نشاط آخر يعود بالخطأ 404 نفسه الذي يعود به المعرّف غير الموجود، فلا يكشف الرد أي شيء عن بيانات غيرك.

## ما حدود الطلبات في واجهة مُجيب البرمجية؟

لكل مفتاح 60 طلبًا في كل دقيقة، ويبدأ العدّ من جديد مع بداية الدقيقة التالية. كل طلب بمفتاح صالح يُحسب، حتى الذي يعود بالخطأ 400 أو 403 أو 404.

يحمل كل رد بمفتاح صالح ثلاث ترويسات تصف حد مفتاحك:

| الترويسة | معناها |
| --- | --- |
| `X-RateLimit-Limit` | عدد الطلبات المسموح بها للمفتاح في الدقيقة |
| `X-RateLimit-Remaining` | ما بقي للمفتاح في الدقيقة الحالية |
| `X-RateLimit-Reset` | عدد الثواني حتى تبدأ دقيقة المفتاح من جديد |

وفوق حد المفتاح يوجد حد أوسع لكل عنوان شبكة، تشترك فيه كل الطلبات القادمة من العنوان نفسه، بأي مفتاح كانت. إذا وصلت إليه فإن ترويسات رد الخطأ 429 تصف ذلك الحد لا حد مفتاحك. أما رد الخطأ 401 فلا تعتمد على ترويساته في حساب حد مفتاحك.

## ماذا أفعل عند الخطأ 429؟

انتظر عدد الثواني المذكور في الترويسة `Retry-After`، ثم أعد الطلب نفسه. هكذا يبدو الرد عندما يستنفد المفتاح دقيقته:

```http
HTTP/1.1 429 Too Many Requests
Retry-After: 60
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 17
Content-Type: application/json; charset=utf-8

{"error":{"code":"RATE_LIMITED","message":"At most 60 requests a minute per key."}}
```

القيمة في `X-RateLimit-Reset` تقول متى تبدأ الدقيقة الجديدة فعلًا، وهي غالبًا أقل من `Retry-After`. انتظار أي منهما كافٍ. هذه دالة تنتظر وتعيد المحاولة:

```js
async function getWithRetry(url, tries = 3) {
  for (let i = 0; ; i++) {
    const res = await fetch(url, { headers: { Authorization: `Bearer ${process.env.MUJIB_API_KEY}` } });
    if (res.status !== 429 || i === tries - 1) return res;
    const wait = Number(res.headers.get('x-ratelimit-reset') ?? res.headers.get('retry-after') ?? '60');
    await new Promise((r) => setTimeout(r, wait * 1000));
  }
}
```

```python
import os
import time
import requests

def get_with_retry(url, params=None, tries=3):
    for i in range(tries):
        res = requests.get(url, params=params, timeout=30,
                           headers={"Authorization": f"Bearer {os.environ['MUJIB_API_KEY']}"})
        if res.status_code != 429 or i == tries - 1:
            return res
        wait = int(res.headers.get("X-RateLimit-Reset") or res.headers.get("Retry-After") or 60)
        time.sleep(wait)
```

## ما الأخطاء التي تستحق إعادة المحاولة؟

أعد المحاولة عند 429 و500 فقط، وأصلح الطلب في كل حالة أخرى.

- **429:** انتظر كما سبق، ثم أعد المحاولة.
- **500:** أعد المحاولة بعد ثوانٍ، وضاعف الانتظار إذا تكرر الخطأ.
- **400 و401 و403 و404:** إعادة الطلب نفسه تعيد الخطأ نفسه، وتستهلك من حدك. أصلح المعامل أو المفتاح أو المعرّف أولًا.

## كيف أبقى داخل حدود الطلبات؟

اطلب صفحات أكبر، واطلب ما تغيّر فقط، ودع مُجيب يخبرك بالأحداث.

- اجعل `limit` في القوائم 100، فتقرأ أكبر عدد ممكن في كل طلب. انظر [التقسيم إلى صفحات](https://docs.mujib.ai/developers/pagination/).
- في العملاء استخدم `since` لتطلب الجدد فقط، وفي الحجوزات استخدم `from` و`to` لتطلب الأيام التي تهمك.
- إذا كنت تسأل الواجهة كل دقيقة لتعرف الجديد، فاستخدم [Webhooks](https://docs.mujib.ai/developers/webhooks/) بدلًا من ذلك.
- اجعل لكل نظام مفتاحًا مستقلًا، فلكل مفتاح حده. وتذكّر أن الطلبات من خادم واحد تشترك في حد العنوان.

## الأسئلة الشائعة

### هل تُحسب الطلبات التي ترجع بخطأ ضمن الحد؟

نعم. كل طلب بمفتاح صالح يُحسب، حتى لو عاد بالخطأ 400 أو 403 أو 404. أما الطلب الذي يُرفض مفتاحه بالخطأ 401 فلا يُحسب على المفتاح.

### هل لكل مفتاح حدّه الخاص؟

نعم. لكل مفتاح 60 طلبًا في الدقيقة، ولا يؤثر مفتاح في حد مفتاح آخر. لكن كل الطلبات من عنوان الشبكة نفسه تخضع أيضًا لحد أوسع مشترك.

### هل أبني برنامجي على نص message في الخطأ؟

لا. ابنِ على رمز code فهو ثابت. نص message شرح بالإنجليزية للمطوّر وقد يتغيّر.

### متى يبدأ العدّ من جديد؟

مع بداية كل دقيقة. الترويسة X-RateLimit-Reset تقول كم ثانية بقيت حتى ذلك.

---

المصدر: https://docs.mujib.ai/developers/errors-limits/ · آخر تحديث: 2026-11-04
