أخطاء API

كيفية التعامل مع الأخطاء الواردة من FourA API.

تنسيق استجابة الخطأ

يعيد API كائنات JSON مسطحة لجميع الأخطاء. لا يوجد كائن error متداخل أو رموز أخطاء.

{
  "error": "Invalid API key"
}

تتضمن بعض الأخطاء حقولاً إضافية مثل status أو service أو retryAfter أو current أو limits في المستوى الأعلى:

{
  "error": "Rate limit exceeded",
  "status": 429,
  "service": "single",
  "retryAfter": 5,
  "current": { "concurrency": 12, "rpm": 3000 },
  "limits": { "maxConcurrency": 500, "maxRpm": 3000 }
}

تتبع الـ Request

تتضمن كل response من API (سواء كانت ناجحة أو خطأ) header باسم X-Foura-Request-Id يحتوي على UUID لتلك المكالمة. قم بتسجيله في جانبك. إذا كنت بحاجة إلى سؤال الدعم عما حدث لـ request معين، فإن هذا المعرّف يتيح لنا العثور عليه.

curl -i -X POST https://eu.api.foura.ai/api/single/ \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"method": "GET", "url": "https://example.com"}'
# HTTP/1.1 200 OK
# X-Foura-Request-Id: 9f1c4e6c-7b2a-4d3e-8a1f-2c9d8e4a3b15
# Content-Type: application/json
# ...

أنواع الأخطاء

400: Bad Request

يكون جسم الـ request (request body) مفتقداً لحقول مطلوبة، أو يحتوي على قيم غير صالحة، أو يحدد هدفاً يرفض API جلبه.

{
  "error": "Invalid request body format"
}

يغطي رمز الخطأ 400 نفسه أيضاً الحماية من SSRF. إذا كان الـ url الخاص بك يؤدي إلى نطاق IP خاص أو محلي (loopback) أو محجوز بأي شكل آخر (RFC 5735، RFC 6598، كتل IPv6 المحجوزة)، فسيتم رفض الـ request قبل أن يغادر شبكة FourA:

{
  "error": "Target <ip> resolves to a private/reserved IP"
}

الحل: تحقق من أن الـ request الخاص بك يتضمن جميع الحقول المطلوبة، وأن عناوين URL تستخدم http:// أو https://، وأن المضيف (host) يؤدي إلى عنوان عام (public address).

401: Unauthorized

مفتاح API (API key) الخاص بك مفقود أو غير صالح.

المفتاح مفقود:

{
  "error": "Missing API key. Include X-API-Key header."
}

المفتاح غير صالح:

{
  "error": "Invalid API key"
}

الحل: تحقق من أن الـ header المسمى X-API-Key يحتوي على مفتاح صالح. قم بتوليد مفتاح جديد من Dashboard إذا لزم الأمر.

429: Rate Limited

لقد أرسلت عدداً كبيراً جداً من الـ requests في فترة زمنية قصيرة.

{
  "error": "Rate limit exceeded",
  "status": 429,
  "service": "single",
  "retryAfter": 5,
  "current": { "concurrency": 12, "rpm": 3000 },
  "limits": { "maxConcurrency": 500, "maxRpm": 3000 }
}

الحل: انتظر لعدد الثواني المحدد في retryAfter قبل إرسال المزيد من الـ requests. راجع Rate Limits للحصول على التفاصيل.

500: Server Error

حدث خطأ ما في جانبنا.

الحل: أعد محاولة الـ request بعد تأخير قصير. إذا استمر الخطأ، فتحقق من صفحة الحالة أو تواصل مع الدعم الفني مزوداً بـ X-Foura-Request-Id من الـ response الفاشل.

503: Service Disabled or At Capacity

تعني استجابة 503 إما أن الخدمة غير متاحة مؤقتاً لإجراء الصيانة أو أنك وصلت إلى حد التزامن (concurrency limit). يتضمن كلا الـ responses حقل retryAfter. يتضمن نموذج التزامن أيضاً حقول current و limits.

{
  "error": "Service disabled",
  "status": 503,
  "retryAfter": 60
}

الحل: انتظر لثوانٍ بعدد retryAfter ثم أعد المحاولة. تسرد صفحة الحالة فترات الصيانة النشطة.

الإخفاقات من جانب الهدف داخل 200 OK

لا يظهر كل إخفاق كحالة HTTP ليست من فئة 2xx. عندما يعيد الموقع المستهدف HTTP 200 مع حمولة خطأ (error payload)، فإن FourA لا يزال يسلمك الـ body ولكنه يصنف الـ request على أنه application_error. عندما يعيد الهدف حالة ليست من فئة 2xx لا تقبلها قواعد الـ validate الخاصة بك، تكون النتيجة application_fail ويصل الـ body دون تغيير.

كلتا الحالتين قابلة للتحصيل المالي (billable) كما لو أن الـ request قد نجح على مستوى الشبكة (wire level). يغطي مرجع Outcomes التصنيف الكامل.

ترميز الـ Response

يقوم FourA بفك ترميز أجسام الـ response تلقائياً إلى UTF-8. إذا كان الهدف يقدم windows-1251 أو gbk أو shift_jis أو iso-8859-* أو أي ترميز أحرف (charset) آخر معلن عنه في header الـ Content-Type أو في وسم HTML <meta charset>، فستتلقى سلسلة UTF-8 نظيفة في حقل data (في single و proxy) أو حقل body (في browser).

بالنسبة للحمولات الثنائية (binary payloads) مثل الصور أو protobuf أو الصوت الخام، اضبط returnBuffer: true في الـ request. سيعود الـ body كـ buffer بتنسيق base64 دون تطبيق أي تحويل لترميز الأحرف (charset transcoding).

استراتيجية إعادة المحاولة

سياسة إعادة محاولة عملية:

import time
import requests

def make_request(url, payload, api_key, max_retries=3):
    for attempt in range(max_retries):
        resp = requests.post(
            url,
            headers={"X-API-Key": api_key, "Content-Type": "application/json"},
            json=payload,
        )
        if resp.status_code == 200:
            return resp.json()

        body = resp.json() if resp.headers.get("content-type", "").startswith("application/json") else {}
        retry_after = body.get("retryAfter", 2 ** attempt)
        request_id = resp.headers.get("X-Foura-Request-Id", "?")

        if resp.status_code in (429, 503):
            time.sleep(retry_after)
            continue
        if resp.status_code >= 500:
            time.sleep(2 ** attempt)
            continue

        # 400/401/404 won't fix themselves
        raise RuntimeError(f"{resp.status_code} (request {request_id}): {body.get('error')}")

    raise RuntimeError(f"Exhausted {max_retries} retries")

مواضيع ذات صلة

  • Rate Limits: تفاصيل التزامن (Concurrency) وعدد الطلبات في الدقيقة (RPM)
  • Request Outcomes: شرح قيم النتائج السبع
  • Common Issues: الأعراض، الأسباب، والحلول
آخر تحديث: 18 يونيو 2026