أخطاء API
كيفية معالجة الأخطاء الناتجة عن FourA API.
Error Response Format
تُرجع API كائنات JSON مسطحة لجميع الأخطاء. لا يوجد كائن error متداخل. عندما يحتوي الفشل على رمز قابل للقراءة آليًا، فإنه يكون حقلاً في المستوى الأعلى: reason عند تجاوز حد الخطة، وcode عند إجراء استدعاء proxy دون وجود مخرج مؤهل.
{
"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 خاص بذلك الاستدعاء، باستثناء الـ body الذي لا يستطيع FourA قراءته على الإطلاق (JSON غير صالح، أو body يتجاوز 100 KB): حيث يتم رفضه قبل تعيين معرف له. سجله في الـ logs من جانبك. إذا كنت بحاجة إلى الاستفسار من الدعم عما حدث لـ 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 إلى حقول مطلوبة، أو يحتوي على قيم غير صالحة، أو يحدد هدفاً يرفض الـ API جلبه.
{
"error": "Invalid request body format"
}
يغطي رمز 400 نفسه أيضاً الحماية من SSRF. إذا تم حل url الخاص بك إلى نطاق IP خاص أو loopback أو محجوز بأي شكل آخر (RFC 5735 و RFC 6598 ونطاقات IPv6 المحجوزة)، فسيتم رفض الـ request قبل مغادرته شبكة FourA:
{
"error": "Refusing to fetch <target>: target resolves to a private or reserved IP range. The FourA scraping API only forwards requests to public internet hosts."
}
<target> هو العنوان، أو اسم المضيف والعنوان الذي تم تحليله إليه. أي URL يتعذر تحليله، أو لا يكون http:// أو https://، يتلقى رمز الحالة 400 نفسه.
اسم المضيف الذي يتعذر البحث عنه لا يتم رفضه. يعود الاستدعاء كـ HTTP 200 مع status: 0 والسبب (could not resolve <host>: <reason>)، مثل أي هدف لا يستطيع FourA الوصول إليه، ولا تتم فوترته.
يتم رفض الـ JSON المشوه في الـ body بالطريقة نفسها، قبل قراءة أي حقل:
{
"error": "Invalid JSON in request body"
}
يحتوي الحقلان proxy و ignoreProxies على أخطاء 400 خاصة بهما. يقبل كلاهما معرّفات proxy المعتمة التي أرجعتها استجابات سابقة، لذا سيفشل فك تشفير أي شيء آخر:
| الرسالة | ما حدث |
|---|---|
Invalid proxy format |
قيمة proxy ليست معرّف proxy أصدرته FourA. يقع عنوان proxy المباشر هنا. |
Invalid ignoreProxies format |
أحد الإدخالات في ignoreProxies ليس معرّف proxy. |
Proxy not found |
تم فك تشفير المعرّف بنجاح ولكنه لم يعد يشير إلى منفذ خروج نشط. اختر معرّفًا جديدًا. |
Managed exit: this proxy id cannot be pinned to a request |
منفذ الخروج حقيقي، ولكنه ليس منفذًا ستبقيه FourA مفتوحًا لطلب مخصص. يقع معرّف منفذ الخروج المميز هنا عندما لا يتبقى في خطتك أي رصيد حركة بيانات مميزة لاستهلاكه. أعد استخدام الجلسة التي تم إرجاعه عبرها، أو نفّذ الطلب من خلال POST /api/proxy/ واستخدم أي منفذ خروج يختاره. |
الحل: تحقق من أن طلبك يتضمن جميع الحقول المطلوبة، وأن عناوين URL تستخدم http:// أو https://، وأن المضيف يشير إلى عنوان عام، وأن أي قيمة لـ proxy هي معرّف تم نسخه حرفيًا من استجابة سابقة.
هذه نتائج client_error: لم يغادر الطلب FourA مطلقًا، وبالتالي لم يتم استهلاك أي رصيد بالنيابة عنك.
401: Unauthorized
مفتاح API الخاص بك مفقود أو غير صالح.
مفتاح مفقود:
{
"error": "Missing API key. Include X-API-Key header."
}
مفتاح غير صالح:
{
"error": "Invalid API key"
}
الحل: تحقق من أن ترويسة X-API-Key تحتوي على مفتاح صالح. يمكنك إنشاء مفتاح جديد من لوحة التحكم إذا لزم الأمر.
403: غير مشمول في خطتك
طلب الاستدعاء endpoint أو معلمة غير مشمولة في خطتك. يحدد الـ response القيمة X-FourA-Limit ويضع نفس الرمز في body ضمن reason:
{
"error": "The browser endpoint is not included in your plan (Free). Upgrade to use it.",
"reason": "plan_limit_feature",
"documentation": "https://foura.ai/prices"
}
reason تكون plan_limit_feature لـ endpoint لا تتضمنها الخطة أو لـ exitCountries على خطة بدون استهداف جغرافي، وتكون plan_limit_premium لـ exitClass: premium على خطة بدون premium exits. تذكر سلسلة error اسم الـ endpoint أو الـ parameter.
رمز 403 من FourA لا يتعلق أبدا بالموقع الهدف: لم يتم الاتصال بالهدف إطلاقا. رمز 403 الذي يعيده الهدف يصل كـ HTTP 200 مع status: 403 داخل الـ body.
الحل: قم بإزالة الـ parameter، أو استدعاء endpoint تتضمنها خطتك، أو الترقية. لم يتم تعيين Retry-After، لأن الانتظار لن يغير النتيجة. لم يتم إنفاق أي شيء: النتيجة هي rate_limit، وتتم فوترة success فقط.
413: Payload Too Large
حجم JSON request body أكبر مما تقبله FourA (100 KB). الاستجابة ليست JSON ولا تحتوي على X-FourA-Request-Id، لأن الـ body يُرفض قبل قراءته.
الحل: أرسل حمولة data أصغر. لم يتم إنفاق أي شيء.
429: Rate Limited
يقوم فحصان مختلفان بالرد برمز 429، ولا يحتويان على الحقول نفسها.
حدود خطتك الخاصة. تحدد الاستجابة header باسم X-FourA-Limit يوضح أي حد قام برفض الاستدعاء، ويضع الرمز نفسه في الـ body تحت reason:
{
"error": "Concurrency limit reached: your plan allows 50 simultaneous proxy request(s). Retry when an in-flight request finishes.",
"reason": "plan_limit_concurrency",
"documentation": "https://foura.ai/prices",
"limit": 50,
"in_flight": 51,
"retry_after_seconds": 1
}
reason تكون إما plan_limit_concurrency، أو plan_limit_rate، أو plan_limit_browser_daily، أو plan_limit_credits، أو plan_limit_bandwidth. عندما يكون الانتظار مفيدًا، فإن مدة الانتظار تتواجد في retry_after_seconds وفي ترويسة Retry-After، ولا تكون أبدًا في retryAfter. لا يتضمن plan_limit_browser_daily أيًا منهما، لأن الحصة تُجدد عند منتصف الليل بتوقيت UTC وليس بالثواني. لم يتم استهلاك أي شيء: النتيجة هي rate_limit، وتتم فوترة success فقط.
الحصة المشتركة للمنصة. لا توجد ترويسة X-FourA-Limit، والانتظار موجود في retryAfter:
{
"error": "Rate limit exceeded",
"status": 429,
"service": "single",
"retryAfter": 5,
"current": { "concurrency": 12, "rpm": 3000 },
"limits": { "maxConcurrency": 500, "maxRpm": 3000 }
}
يصف كل من current و limits حالة الخدمة عبر كافة حركات المرور، وليس حسابك فقط. الرفض هنا يعني أن FourA مشغول.
الحل: انتظر أياً من Retry-After أو retry_after_seconds أو retryAfter التي يحملها الـ response. عند الوصول إلى حد التزامن أو الـ rate limit، حدد عدد الـ requests المفتوحة بدلاً من إعادة إرسال الدفعة المرفوضة. عند الوصول إلى الحد اليومي أو حد فترة الفوترة، أوقف التشغيل. راجع Rate Limits لكل حقل و Run Requests in Parallel لمعرفة النمط.
500: Server Error
حدث خطأ ما من جانبنا.
الحل: أعد محاولة إرسال الـ request بعد تأخير قصير. إذا استمر الخطأ، فتحقق من status page أو تواصل مع الدعم الفني مع تضمين X-FourA-Request-Id من الـ response الفاشل.
502: Upstream Unavailable
وصل FourA إلى المحرك الخاص به لكنه لم يتمكن من استخدام الرد.
{
"error": "Upstream unavailable",
"details": "..."
}
الحل: أعد المحاولة مع فترة انتظار قصيرة (backoff). هذا الخطأ من جانبنا، لذا لن يكلفك شيئا: النتيجة هي service_error وتتم محاسبة success فقط.
504: Upstream Timeout
لم ينته المحرك ضمن المهلة الزمنية المحددة لهذا الـ request.
{
"error": "Upstream timeout",
"details": "the backend did not finish inside the time budget for this request"
}
يشير الخطأ 504 إلى المدة التي استغرقها تنفيذ العمل، ولا يتعلق بمفتاحك أو معلماتك أو الـ proxy الخاص بك. وتعد الأهداف البطيئة، وحل التحديات لأول مرة (cold challenges)، والصفحات الكبيرة هي الأسباب المعتادة.
الحل: قم بزيادة timeout_ms في الـ request (يقبل وضع Single ما يصل إلى 120000، وBrowser حتى 120000، وAuto حتى 180000)، أو أعد المحاولة. ينتظر FourA المهلة التي حددتها بالإضافة إلى هامش صغير، لذا فإن طلب المزيد من الوقت يمنحك بالفعل وقتا إضافيا.
503: الخدمة معطلة أو وصلت إلى السعة القصوى
يعني الخطأ 503 إما أن الخدمة غير متاحة مؤقتا للصيانة أو أن حد التزامن (concurrency allowance) للمنصة قد اكتمل. يحمل كلا النموذجين المفاتيح نفسها: error، وstatus، وservice، وretryAfter، وcurrent، وlimits. يمكنك التمييز بينهما عبر نص error، وليس من خلال الحقول الموجودة.
{
"error": "Service disabled",
"status": 503,
"service": "single",
"retryAfter": 60,
"current": { "concurrency": 0, "rpm": 0 },
"limits": { "maxConcurrency": 500, "maxRpm": 3000 }
}
Service disabled تعني وجود صيانة، وتكون قيمة current هي 0 لكلا العدادين، نظراً لرفض الـ request قبل قياس أي شيء. Service at capacity هي صيغة التزامن، وهناك يحتوي current على الاستخدام الفعلي للمنصة. راجع Rate Limits للاطلاع على هذه الصيغة.
الحل: انتظر retryAfter ثانية، ثم أعد المحاولة. تعرض صفحة الحالة فترات الصيانة النشطة.
هناك شكل ثالث للرمز 503 لا يحتوي على retryAfter. ويعني ذلك أن المحرك المرتبط بنقطة النهاية endpoint الخاصة بك كان قيد إعادة التشغيل عند وصول اتصالك:
{
"error": "Backend service unavailable",
"backend_status": 503
}
أعد المحاولة بعد ثانية أو ثانيتين.
قراءة حالات الفشل من /api/auto/
يستجيب POST /api/auto/ برمز HTTP 200 كلما تم تشغيل ladder، حتى عند فشل كل خطوة (rung). تظهر النتيجة الفعلية في body:
{
"status": 403,
"error": "exit blocked by the target defense",
"attempts": 7,
"meta": { "rung": "fail", "solved": false, "attempts": 7, "credits": 47 }
}
status هي آخر حالة استجاب بها الهدف، أو 502 عندما لا تصل أي محاولة إليه (504 عندما تنفد المهلة الزمنية المحددة أولا). حقل الطلب الذي لا يمكن لـ Auto قبوله (مثل timeout_ms أقل من 5000 أو أكثر من 180000) يعود بالطريقة نفسها: HTTP 200 مع "status": 400 والسبب في error، قبل إجراء أي محاولة وبدون أي تكلفة.
لذا لا تقم بتفريع منطقك البرمجي بناء على حالة النقل لـ Auto. اقرأ status و error من جسم الاستجابة بدلا من ذلك. أي رمز غير 200 حقيقي من /api/auto/ يعني أن FourA رفض الاستدعاء قبل بدء سلسلة المحاولات، أو لم يتمكن من إكماله: 400 (تنسيق JSON غير صالح، أو هدف خاص أو محجوز)، أو 401، أو 413، أو 502، أو 503، أو 504. الحدود، سواء الخاصة بك أو بالمنصة، تعود داخل الرمز 200 مع بيان حالتها داخل جسم الاستجابة.
عندما يفشل موقع ما في عدة استدعاءات Auto متتالية، يستجيب Auto على الفور لفترة من الوقت دون محاولة: "error": "target temporarily unservable, retry later" و "status": 503 و retryAfter بالثواني. هذا لا يكلف شيئا؛ انتظر retryAfter ثانية.
الوصول إلى حد الخطة عبر أحد الاستدعاءات الفرعية يعود أيضا كـ HTTP 200. جسم الاستجابة هو الرفض نفسه، متضمنا reason الخاص به، بالإضافة إلى status و meta، وتحمل الاستجابة نفس ترويسة X-FourA-Limit كما في حالة الرفض المباشر:
{
"status": 429,
"error": "Credit limit reached: 75,000 of 75,000 credits used this billing period. Upgrade your plan or wait for the reset.",
"reason": "plan_limit_credits",
"documentation": "https://foura.ai/prices",
"used": 75000,
"hard_stop": 75000,
"retry_after_seconds": 86400,
"resets_at": "2026-10-01T00:00:00.000Z",
"meta": { "rung": "fail", "solved": false, "attempts": 1, "credits": 0 }
}
الحدود التي توقف التدرج بالكامل وتلك التي تغلق درجة واحدة فقط مشروحة في Smart Fetch (Auto).
إخفاقات جهة الهدف ضمن 200 OK
لا يظهر كل إخفاق على هيئة كود حالة HTTP غير 2xx. عندما يستجيب الهدف بـ HTTP 200 ولكن تتضمن استجابة FourA كود error (على سبيل المثال، رفضت قواعد validate الخاصة بك جسم الاستجابة) أو كان الجسم صفحة فحص يتعرف عليها FourA، فإن النتيجة تكون application_error. وعندما يرجع الهدف كود غير 2xx لا تقبله قواعد validate الخاصة بك، فإن النتيجة تكون application_fail ويصل جسم الاستجابة دون تغيير.
لا تتم فوترة أي من الحالتين: تتم فوترة success فقط. يمكن لـ Browser أيضا الاستجابة بـ HTTP 200 مع "error": "No available browser slot" عندما تكون جميع متصفحات FourA مشغولة. لا تتم فوترة ذلك؛ أعد المحاولة بعد بضع ثوان. يغطي مرجع Outcomes التصنيف الكامل.
يمكن لطلب Single عبر proxy قمت بتثبيته أن يستجيب أيضا بـ HTTP 200 مع "error": "The exit gave the same answer for <n> different sites" بجانب جسم الاستجابة. رصد FourA أن نقطة الخروج تلك تقدم نفس الصفحة لمواقع غير مرتبطة، وبالتالي فإن الصفحة خاصة بنقطة الخروج وليست الصفحة التي طلبتها. النتيجة هي application_error ولا تتم فوترتها. احصل على مخرج جديد من POST /api/proxy/، والذي يتجاوز مثل نقطة الخروج هذه تلقائيا.
ترميز الاستجابة
يقوم FourA بفك ترميز أجسام الاستجابة تلقائيا إلى UTF-8. إذا كان الهدف يقدم windows-1251 أو gbk أو shift_jis أو iso-8859-* أو أي ترميز أحرف آخر محدد في ترويسة Content-Type أو وسم HTML <meta charset>، فستتلقى سلسلة UTF-8 نقية في حقل data (في single و proxy) أو حقل body (في browser).
بالنسبة للحمولات الثنائية (الصور، protobuf، الصوت الخام)، اضبط returnBuffer: true في الطلب. عندئذ يرجع Single و Proxy الحقل data ككائن يحتوي على البايتات الخام، {"type": "Buffer", "data": [<byte values>]}، دون تطبيق أي تحويل لترميز الأحرف.
استراتيجية إعادة المحاولة
سياسة إعادة محاولة عملية:
import time
import requests
# Plan limits that no short wait will clear: stop the run instead of retrying.
STOP_ON = {
"plan_limit_feature",
"plan_limit_premium",
"plan_limit_browser_daily",
"plan_limit_credits",
"plan_limit_bandwidth",
}
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 {}
request_id = resp.headers.get("X-FourA-Request-Id", "?")
limit = resp.headers.get("X-FourA-Limit")
if limit in STOP_ON:
raise RuntimeError(f"{limit} (request {request_id}): {body.get('error')}")
# Plan limits send Retry-After and retry_after_seconds; platform limits send retryAfter.
header = resp.headers.get("Retry-After")
retry_after = (
int(header) if header and header.isdigit()
else body.get("retry_after_seconds") or body.get("retryAfter") or 2 ** attempt
)
if resp.status_code in (429, 503):
time.sleep(retry_after)
continue
if resp.status_code >= 500: # 500, 502, 503, 504 are all ours to fix
time.sleep(2 ** attempt)
continue
# 400/401/403/404 won't fix themselves
raise RuntimeError(f"{resp.status_code} (request {request_id}): {body.get('error')}")
raise RuntimeError(f"Exhausted {max_retries} retries")
إخفاقات الـ Proxy تتضمن تقريرًا
أي استدعاء لـ POST /api/proxy/ يستنفد المحاولات يعود بحالة HTTP 200 مع غلاف خطأ (error envelope)، وليس كرمز خطأ HTTP. تكون سلسلة الخطأ النصية قصيرة وتأتي دائمًا بنفس البنية، لذلك يُرفق كائن attemptReport بجانبها متضمنًا عمليات العد:
{
"error": "Download maxTry limit reached",
"attemptReport": {
"total": 25,
"noResponse": 0,
"defense": 0,
"contentRejected": 25,
"statusRejected": 0,
"other": 0,
"vendors": [],
"profilesTried": ["default"],
"summary": "25 attempt(s): 25 returned HTTP 200 with no defense present and were rejected only by your validate.data - the page was fetched, your content rule did not match it"
},
"total": 34.812
}
سجّل attemptReport.summary بجانب الخطأ وستعرف ما إذا كانت نقاط الخروج محظورة، أو معطلة، أو تُرجع صفحات رفضتها قواعد validate الخاصة بك. مرجع الحقول وكيفية التعامل مع كل عدد: سبب استنفاد محاولات طلب Proxy.
ذات صلة
- Rate Limits: حدود الخطة، والتزامن، وتفاصيل RPM
- تنفيذ الطلبات بالتوازي: البقاء ضمن حدود التزامن لخطتك
- نتائج الطلبات: شرح قيم النتائج السبع
- المشكلات الشائعة: الأعراض، والأسباب، والحلول
- فحوصات الموقع: عندما يكون المحتوى صفحة تحدٍ بدلا من خطأ
- سبب استنفاد محاولات طلب Proxy: قراءة
attemptReport