Грешки в 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 }
}

Проследяване на заявка

Всеки API response (успех или грешка) включва X-FourA-Request-Id header с UUID за това извикване, освен при body, което FourA изобщо не може да прочете (невалиден JSON или body над 100 KB): то се отхвърля преди да бъде присвоен ID. Записвайте го в логовете от ваша страна. Ако трябва да попитате поддръжката какво се е случило с конкретен request, този ID ни позволява да го открием.

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 body) липсват задължителни полета, съдържа невалидни стойности или посочва цел, която API отказва да извлече.

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

Същият статус 400 покрива и SSRF защитата. Ако вашият url се резолва до частен, loopback или друг вид резервиран IP диапазон (RFC 5735, RFC 6598, IPv6 резервирани блокове), заявката се отхвърля, преди да напусне мрежата на 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 в тялото се отхвърля по същия начин, преди да бъде прочетено което и да е поле:

{
  "error": "Invalid JSON in request body"
}

Полетата proxy и ignoreProxies имат свои собствени 400 грешки. И двете приемат непрозрачните proxy ID, върнати от по-ранни отговори, така че всичко останало не успява да се декодира:

Съобщение Какво се случи
Invalid proxy format Стойността proxy не е proxy ID, издадено от FourA. Суров proxy адрес попада тук.
Invalid ignoreProxies format Един от записите в ignoreProxies не е proxy ID.
Proxy not found Идентификаторът се декодира успешно, но вече не сочи към активен изход. Изберете нов.
Managed exit: this proxy id cannot be pinned to a request Изходът е реален, но не е такъв, който FourA ще държи отворен за именувана заявка. ID на премиум изход попада тук, когато планът ви няма останал премиум трафик. Използвайте повторно сесията, от която е върнат, или изпълнете извикването през POST /api/proxy/ и използвайте изхода, който то избере.

Решение: Проверете дали заявката ви съдържа всички задължителни полета, дали URL адресите използват http:// или https://, дали хостът сочи към публичен адрес и дали всяка стойност на proxy е ID, копирано точно от по-ранен отговор.

Това са резултати client_error: заявката изобщо не е напуснала FourA, така че нищо не е таксувано от вашия баланс.

401: Unauthorized

Вашият API key липсва или е невалиден.

Липсващ key:

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

Невалиден ключ:

{
  "error": "Invalid API key"
}

Решение: Проверете дали вашият X-API-Key header съдържа валиден ключ. Генерирайте нов ключ от Dashboard, ако е необходимо.

403: Не е включено във вашия план

Заявката изисква endpoint или параметър, който вашият план не включва. Response връща X-FourA-Limit и поставя същия код в тялото под 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 изходи. Стрингът error посочва конкретния endpoint или параметър.

Грешка 403 от FourA никога не се отнася до целевия сайт: с целевия сайт изобщо не е осъществен контакт. Грешка 403, върната от целевия сайт, пристига като HTTP 200 с status: 403 в тялото на отговора.

Решение: Премахнете параметъра, извикайте endpoint, включен във вашия план, или надградете плана си. Не се задава Retry-After, защото изчакването няма да промени резултата. Не са изразходвани средства: резултатът е rate_limit, а се таксува само success.

413: Payload Too Large

Тялото на JSON заявката е по-голямо от допустимото за FourA (100 KB). Отговорът не е JSON и не съдържа X-FourA-Request-Id, тъй като тялото се отхвърля преди да бъде прочетено.

Решение: Изпратете по-малък data payload. Не са изразходвани средства.

429: Rate Limited

Две различни проверки връщат статус 429 и те не съдържат едни и същи полета.

Лимити на вашия план. В отговора се задава header X-FourA-Limit, указващ кой лимит е отказал заявката, и се поставя същият код в тялото под 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 header, никога в retryAfter. plan_limit_browser_daily не съдържа нито едно от двете, тъй като лимитът се възстановява в полунощ UTC, а не в секунди. Нищо не е изразходвано: резултатът е rate_limit и се таксува само success.

Споделеният лимит на платформата. Без X-FourA-Limit header, а изчакването е в 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, върната в отговора. При лимит за едновременност (concurrency) или rate limit, ограничете броя на отворените заявки, вместо да изпращате повторно отхвърления пакет. При дневен лимит или такъв за отчетен период, спрете изпълнението. Вижте Rate Limits за всяко поле и Run Requests in Parallel за шаблона на изпълнение.

500: Server Error

Възникна проблем от наша страна.

Решение: Опитайте отново заявката след кратко забавяне. Ако грешката продължава, проверете status page или се свържете с поддръжката, като предоставите X-FourA-Request-Id от неуспешния отговор.

502: Upstream Unavailable

FourA достигна собствения си engine, но не успя да използва върнатия отговор.

{
  "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. Бавните целеви сайтове, първоначалното решаване на проверки и големите страници са обичайните причини.

Решение: Увеличете timeout_ms в заявката (Single приема до 120000, Browser до 120000, Auto до 180000) или опитайте отново. FourA изчаква зададения от вас лимит плюс малък допълнителен толеранс, така че заявяването на повече време реално осигурява повече време.

503: Услугата е изключена или капацитетът е запълнен

Грешка 503 означава, че услугата е временно недостъпна поради поддръжка или че лимитът за едновременни заявки на платформата е запълнен. И двата случая съдържат едни и същи ключове: 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 винаги, когато стълбицата се е изпълнила, дори когато всяко стъпало е неуспешно. Реалният резултат се намира в 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 или частен/резервиран target), 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 header като при директен отказ:

{
  "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

Не всеки проблем се проявява като non-2xx HTTP статус. Когато целевият сървър отговори с HTTP 200, но отговорът на FourA съдържа error (например вашите validate правила са отхвърлили съдържанието) или тялото е страница за проверка, разпозната от FourA, резултатът е application_error. Когато целевият сървър върне non-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-* или друг charset, деклариран в хедъра 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 заявката изчерпа опитите си.

Свързани

Обновено: 30 септември 2026 г.