# أخطاء API (/ar/integrate/errors/api-errors)

مغلّف الخطأ اللي بيرجعه كل استدعاء API فاشل. أنواع الأخطاء الثلاثة، والحقول اللي بيحملوها، ونمط المعالِج.

الصفحة دي بتغطّي الأخطاء **المتزامنة** اللي XPay بيرجّعها: لما السيرفر بتاعك يعمل استدعاء API ويرجعله رد غير 2xx، إيه اللي في الجسم، وإزاي تتعامل معاه.

لحالات فشل الدفع غير المتزامنة (رفض بطاقة بيقع على `lastPaymentError` بتاع Payment Intent)، ده مكان مختلف تمامًا. شوف [أخطاء الدفع](/integrate/errors/payment-errors).

## المغلّف [#المغلّف]

كل رد خطأ API ليه نفس الشكل:

```json
{
  "error": {
    "type": "invalid_request_error",
    "code": "parameter_missing",
    "message": "Missing required parameter: lineItems",
    "param": "lineItems",
    "doc_url": "https://docs.xpay.app/integrate/errors/api-error-codes#parameter_missing"
  },
  "request_id": "req_3STkwmFGhGHoO0IX13BRo5iU"
}
```

| الحقل           | النوع          | المعنى                                                                                                                          |
| --------------- | -------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `error.type`    | string         | واحد من الفئات الثلاثة تحت. تفرّع على ده الأول.                                                                                 |
| `error.code`    | string \| null | معرّف الخطأ المحدّد. ثابت عبر الرسائل والترجمات. استخدمه هو، مش الرسالة، للتحكم في المسار.                                      |
| `error.message` | string         | إنجليزي مقروء للبني آدمين. لسجلاتك وواجهة فريقك. ما تعرضوش حرفيًا لعملائك.                                                      |
| `error.param`   | string \| null | حقل جسم الطلب اللي فشل (زي `lineItems[0].price`، `customerDetails.email`). موجود في أخطاء التحقّق.                              |
| `error.doc_url` | string \| null | رابط مباشر لصف الكود ده في مرجع [أكواد أخطاء API](/integrate/errors/api-error-codes).                                           |
| `request_id`    | string         | الـ `req_*` ID للطلب الفاشل. سجّله دايمًا. استخدمه علشان تلاقي الطلب في [Workbench ← السجلات](/integrate/workbench/logs-panel). |

`code`، و`param`، و`doc_url` ممكن يكونوا غايبين. التلاتة التانيين موجودين دايمًا.

<Callout type="info">
  لو تقدر تتحمّل تفرّع واحد بس في المعالِج بتاعك، تفرّع على `type`، مش على `code` أو حالة الـ HTTP.
  قائمة الأنواع ثابتة عبر الإصدارات؛ وقائمة الأكواد بتكبر على مدار الوقت وبتتضاف أسباب أخطاء جديدة.
</Callout>

## الأنواع الثلاثة [#الأنواع-الثلاثة]

| النوع                   | حالة HTTP     | معناه إيه                                                                            | المعالِج المعتاد                                                                                                                            |
| ----------------------- | ------------- | ------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `invalid_request_error` | 400، 404، 409 | الطلب اترفض بسبب حاجة من ناحيتك: حقل ناقص أو غلط، أو مورد مش موجود، أو حالة متعارضة. | ما تعيدش المحاولة. صلّح الطلب. اعرض الرسالة لمين تسبّب فيها (فريقك، الفورم بتاعك).                                                          |
| `authentication_error`  | 401           | مفتاح الـ API أو توقيع الـ HMAC ناقص، أو غير صالح، أو معطّل، أو بصيغة غلط.           | ما تعيدش المحاولة. اتأكد من `XPAY_SECRET_KEY` في بيئتك، ومن حالة تفعيل المفتاح في لوحة التحكم، ومن إنك بتبعت `Authorization: Bearer <key>`. |
| `api_error`             | 500           | مشكلة غير متوقعة من ناحية السيرفر عند XPay.                                          | أعِد المحاولة بتراجع أسّي. لو استمرت، اذكر `request_id` للدعم.                                                                              |

لما تعيد محاولة كتابة (`POST` أو `PATCH`)، ابعت `Idempotency-Key` علشان إعادة المحاولة ترجّع النتيجة الأصلية بدل ما تكرّر العملية. شوف [عدم التكرار (idempotency)](/integrate/idempotency).

## نمط المعالجة [#نمط-المعالجة]

المعالِج هو `try / catch` واحد. جوّه الـ catch، افصل الرد، وتفرّع على `error.type`، وضيّق على `error.code` للعدد القليل من الأكواد اللي بتهمّك، وسجّل كل حاجة، واعرض اللي مناسب.

<Tabs items="[&#x22;Node.js&#x22;, &#x22;Python&#x22;]">
  <Tab value="Node.js">
    ```typescript
    async function callXPay<T>(path: string, body: unknown): Promise<T> {
      const res = await fetch(`https://api.xpay.app${path}`, {
        method: "POST",
        headers: {
          Authorization: `Bearer ${process.env.XPAY_SECRET_KEY}`,
          "Content-Type": "application/json",
        },
        body: JSON.stringify(body),
      });

      if (res.ok) return res.json() as Promise<T>;

      // Read the canonical envelope. Every XPay error response has `error.type`.
      const payload = await res.json().catch(() => null);
      const xpayError = payload?.error;
      const requestId = payload?.request_id;

      if (!xpayError?.type) {
        // Transport-level failure: not XPay-shaped. Treat as a client-side bug.
        throw new Error(`Non-XPay response: ${res.status}`);
      }

      // Always log first, branch second.
      log.error("xpay api error", {
        requestId,
        type: xpayError.type,
        code: xpayError.code,
        param: xpayError.param,
        message: xpayError.message,
        docUrl: xpayError.doc_url,
      });

      switch (xpayError.type) {
        case "invalid_request_error":
          // Your request is wrong. Don't retry. Bubble up to whoever sent it.
          throw new BadRequest(xpayError);

        case "authentication_error":
          // Your secret key is wrong. Page yourself; don't retry.
          throw new ConfigError(xpayError);

        case "api_error":
          // XPay is having a moment. Retry with backoff.
          throw new RetryableError(xpayError);

        default:
          // Unknown type. Treat as retryable to be safe.
          throw new RetryableError(xpayError);
      }
    }
    ```
  </Tab>

  <Tab value="Python">
    ```python
    import os, requests

    def call_xpay(path: str, body: dict) -> dict:
        res = requests.post(
            f"https://api.xpay.app{path}",
            headers={
                "Authorization": f"Bearer {os.environ['XPAY_SECRET_KEY']}",
                "Content-Type": "application/json",
            },
            json=body,
            timeout=30,
        )

        if res.ok:
            return res.json()

        payload = res.json() if res.content else {}
        xpay_error = payload.get("error") or {}
        request_id = payload.get("request_id")

        if not xpay_error.get("type"):
            # Transport-level failure: not XPay-shaped.
            raise RuntimeError(f"Non-XPay response: {res.status_code}")

        log.error(
            "xpay api error",
            extra={
                "request_id": request_id,
                "type": xpay_error.get("type"),
                "code": xpay_error.get("code"),
                "param": xpay_error.get("param"),
                "message": xpay_error.get("message"),
                "doc_url": xpay_error.get("doc_url"),
            },
        )

        error_type = xpay_error["type"]
        if error_type == "invalid_request_error":
            raise BadRequest(xpay_error)
        if error_type == "authentication_error":
            raise ConfigError(xpay_error)
        if error_type == "api_error":
            raise RetryableError(xpay_error)
        raise RetryableError(xpay_error)  # unknown type, treat as retryable
    ```
  </Tab>
</Tabs>

## التضييق على `code` [#التضييق-على-code]

`type` بيقرّر التفرّع العريض. `code` بيقرّر الرسالة المحدّدة اللي بتعرضها. شوية أكواد تستاهل معاملة خاصة في واجهتك؛ والباقي يقدر يستخدم `error.message` زائد رابط "اعرف أكتر" لـ `error.doc_url`.

```typescript
if (xpayError.type === "invalid_request_error") {
  switch (xpayError.code) {
    case "resource_missing":
      // The ID you sent doesn't exist. Likely your code, not the user's.
      return show("That record was deleted or never existed.");

    case "checkout_session_expired":
      // The session is past expiresAt. Create a new one.
      return await createNewSession();

    case "price_sold_out":
      // Stock ran out between session creation and payment.
      return show("Sorry, that item is sold out.");

    case "merchant_not_activated":
      // Your account isn't approved for live mode yet.
      return show("Your account needs activation. Contact support.");

    default:
      // Generic invalid_request_error. Surface the message.
      return show(xpayError.message);
  }
}
```

للقائمة الكاملة بالأكواد اللي ممكن تشوفها، شوف [أكواد أخطاء API](/integrate/errors/api-error-codes).

## اللي ما تعملوش [#اللي-ما-تعملوش]

* **ما تعيدش محاولة `invalid_request_error`.** نفس الطلب هيفشل بنفس الطريقة. صلّح المدخلات وحاول تاني، بـ `Idempotency-Key` جديد لو بتستخدم واحد.
* **ما تعيدش محاولة `authentication_error` كمان.** المفتاح الغلط مفتاح غلط. إعادة المحاولة عشر مرات بتنتج بس عشر سطور في السجل بتقول كده.
* **ما تعرضش `error.message` لعملائك النهائيين حرفيًا.** هو إنجليزي وتقني وممكن يحتوي معرّفات داخلية. استخدمه لسجلاتك وواجهة فريق الدعم بتاعك. ابنِ النص الموجّه للعميل بناءً على `error.code`.
* **ما تتجاهلش `error.code` وتفصل `error.message` بدلًا منه.** نص الرسالة بيتغير؛ والكود ثابت. التفرّع على الرسالة هش.
* **ما تبلعش الأخطاء بصمت.** سجّل `request_id` جنب كل خطأ بتمسكه دايمًا. من غيره، تصحيح الأخطاء في [Workbench ← السجلات](/integrate/workbench/logs-panel) بيبقى تمرير لانهائي.
* **ما تفترضش إن الرد بشكل XPay.** الرد من غير `error.type` هو فشل على مستوى النقل (بروكسي معطّل، أو نوع محتوى غلط، أو حمولة كبيرة أوي). اعتبره خطأ من ناحية العميل لازم يتصلّح، مش تفرّعًا وقت التشغيل.

## رايح فين بعد كده [#رايح-فين-بعد-كده]

<Cards>
  <Card icon="<Hash />" title="أكواد أخطاء API" href="/integrate/errors/api-error-codes">
    القائمة الكاملة لقيم `error.code`، متجمّعة حسب المجال، مع وصف من سطر لكل واحد.
  </Card>

  <Card icon="<CreditCard />" title="أخطاء الدفع" href="/integrate/errors/payment-errors">
    المكان التاني: إزاي تتعامل مع عملية دفع فاشلة لعميل عن طريق `lastPaymentError`.
  </Card>

  <Card icon="<Terminal />" title="لوحة السجلات" href="/integrate/workbench/logs-panel">
    دوّر على أي استدعاء فاشل بـ `request_id` وشوف الطلب، والرد، وحقول الخطأ كاملين.
  </Card>

  <Card icon="<Layers />" title="مقدمة الأخطاء" href="/integrate/errors/introduction">
    رجوع لأول مجموعة الأخطاء. خريطة المكانين.
  </Card>
</Cards>