Błędy API

Jak obsługiwać błędy z API FourA.

Format odpowiedzi błędu

API zwraca płaskie obiekty JSON dla wszystkich błędów. Nie ma zagnieżdżonego obiektu error. Gdy błąd ma kod czytelny maszynowo, jest on polem najwyższego poziomu: reason przy limicie planu, code przy wywołaniu proxy bez pasującego węzła wyjściowego.

{
  "error": "Invalid API key"
}

Niektóre błędy zawierają dodatkowe pola najwyższego poziomu, takie jak status, service, retryAfter, current lub limits:

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

Śledzenie żądania

Każda response API (sukces lub błąd) zawiera header X-FourA-Request-Id z identyfikatorem UUID dla danego wywołania, z wyjątkiem body, którego FourA nie jest w stanie w ogóle odczytać (nieprawidłowy format JSON lub body powyżej 100 KB): takie żądanie jest odrzucane przed przypisaniem ID. Zapisuj je w logach po swojej stronie. Jeśli musisz zapytać support o to, co stało się z konkretnym requestem, ten ID pozwoli nam go znaleźć.

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
# ...

Typy błędów

400: Bad Request

Treść żądania (request body) nie zawiera wymaganych pól, zawiera nieprawidłowe wartości lub wskazuje cel, którego API odmawia pobrania.

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

Ten sam błąd 400 obejmuje również ochronę przed SSRF. Jeśli Twój url rozwiązuje się na prywatny, loopback lub w inny sposób zarezerwowany zakres adresów IP (RFC 5735, RFC 6598, zarezerwowane bloki IPv6), request jest odrzucany, zanim opuści sieć 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> to adres lub nazwa hosta i adres, na który została rozwiązana. URL, którego nie można sparsować lub który nie jest http:// ani https://, zwraca ten sam błąd 400.

Nazwa hosta, której nie można rozwiązać, nie jest odrzucana. Wywołanie zwraca HTTP 200 z status: 0 oraz przyczyną (could not resolve <host>: <reason>), jak w przypadku każdego celu, do którego FourA nie może dotrzeć, i nie jest rozliczane.

Nieprawidłowo sformatowany JSON w treści jest odrzucany w ten sam sposób, zanim jakiekolwiek pole zostanie odczytane:

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

Pola proxy i ignoreProxies mają własne błędy 400. Oba przyjmują nieprzezroczyste identyfikatory proxy zwrócone we wcześniejszych odpowiedziach, więc każda inna wartość powoduje błąd dekodowania:

Komunikat Co się stało
Invalid proxy format Wartość proxy nie jest identyfikatorem proxy wydanym przez FourA. Bezpośredni adres proxy trafia tutaj.
Invalid ignoreProxies format Jeden z wpisów w ignoreProxies nie jest identyfikatorem proxy.
Proxy not found Identyfikator został poprawnie zdekodowany, ale nie wskazuje już na aktywny węzeł wyjściowy. Wybierz nowy.
Managed exit: this proxy id cannot be pinned to a request Węzeł wyjściowy istnieje, ale nie jest węzłem, który FourA utrzyma otwarty dla wskazanego żądania. Identyfikator węzła premium trafia tutaj, gdy w Twoim planie skończył się transfer premium. Użyj ponownie sesji, w której został zwrócony, lub wykonaj wywołanie przez POST /api/proxy/ i zaakceptuj dowolny wybrany węzeł.

Rozwiązanie: Upewnij się, że request zawiera wszystkie wymagane pola, adresy URL używają http:// lub https://, host wskazuje na publiczny adres, a każda wartość proxy jest identyfikatorem skopiowanym dosłownie z wcześniejszej odpowiedzi.

Są to rezultaty client_error: request nigdy nie opuścił FourA, więc żadne środki ani limity nie zostały zużyte.

401: Unauthorized

Twój klucz API jest nieprawidłowy lub go brakuje.

Brakujący klucz:

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

Nieprawidłowy klucz:

{
  "error": "Invalid API key"
}

Rozwiązanie: Sprawdź, czy Twój header X-API-Key zawiera poprawny klucz. W razie potrzeby wygeneruj nowy klucz w Dashboardzie.

403: Not in Your Plan

Wywołanie dotyczyło endpointu lub parametru, którego nie obejmuje Twój plan. Odpowiedź ustawia X-FourA-Limit i umieszcza ten sam kod w body pod 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 to plan_limit_feature dla endpointu wykluczonego z planu lub dla exitCountries w planie bez targetowania geograficznego, oraz plan_limit_premium dla exitClass: premium w planie bez wyjść premium. Ciąg error wskazuje nazwę endpointu lub parametru.

Błąd 403 z FourA nigdy nie dotyczy witryny docelowej: z celem w ogóle się nie połączono. Kod 403 zwrócony przez cel dociera jako HTTP 200 z status: 403 wewnątrz body.

Rozwiązanie: Usuń parametr, wywołaj endpoint zawarty w Twoim planie lub dokonaj uaktualnienia. Nagłówek Retry-After nie jest ustawiany, ponieważ czekanie nie zmienia odpowiedzi. Nic nie zostało pobrane: wynikiem jest rate_limit, a rozliczany jest wyłącznie success.

413: Payload Too Large

Ciało żądania JSON jest większe niż FourA akceptuje (100 KB). Odpowiedź nie jest formatem JSON i nie zawiera X-FourA-Request-Id, ponieważ body jest odrzucane przed jego odczytaniem.

Rozwiązanie: Wyślij mniejszy ładunek data. Nic nie zostało pobrane.

429: Rate Limited

Dwa różne mechanizmy weryfikacji zwracają 429 i nie zawierają tych samych pól.

Własne limity Twojego planu. Odpowiedź ustawia nagłówek X-FourA-Limit określający, który limit odrzucił wywołanie, oraz umieszcza ten sam kod w body pod kluczem 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 przyjmuje jedną z wartości: plan_limit_concurrency, plan_limit_rate, plan_limit_browser_daily, plan_limit_credits lub plan_limit_bandwidth. Gdy odczekanie może pomóc, czas oczekiwania znajduje się w retry_after_seconds oraz w nagłówku Retry-After, nigdy w retryAfter. plan_limit_browser_daily nie zawiera żadnego z nich, ponieważ limit odnawia się o północy UTC, a nie w sekundach. Nic nie zostało zużyte: wynikiem jest rate_limit, a rozliczane jest tylko success.

Współdzielony limit platformy. Brak nagłówka X-FourA-Limit, a czas oczekiwania znajduje się w retryAfter:

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

current i limits opisuja stan uslugi dla calego ruchu, a nie Twojego konta. Odmowa w tym miejscu oznacza, ze FourA jest przeciazone.

Rozwiazanie: Odczekaj czas wskazany w Retry-After, retry_after_seconds lub retryAfter zwroconym w odpowiedzi. W przypadku limitu wspolbieznosci lub rate limitu ogranicz liczbe jednoczesnie otwartych zadan zamiast ponawiac odrzucona serie. W przypadku limitu dziennego lub okresu rozliczeniowego zatrzymaj wykonywanie zadan. Zobacz sekcje Rate Limits, aby poznac wszystkie pola, oraz Run Requests in Parallel, aby poznac wlasciwy wzorzec.

500: Server Error

Cos poszlo nie tak po naszej stronie.

Rozwiazanie: Ponow zadanie po krotkim czasie. Jesli blad nadal wystepuje, sprawdz strone ze statusem lub skontaktuj sie z pomoca techniczna, podajac X-FourA-Request-Id z nieudanej odpowiedzi.

502: Upstream Unavailable

FourA polaczylo sie z wlasnym silnikiem, ale nie moglo przetworzyc odpowiedzi.

{
  "error": "Upstream unavailable",
  "details": "..."
}

Rozwiązanie: Ponów próbę z krótkim backoffem. Problem leży po naszej stronie, więc nic Cię to nie kosztuje: wynikiem jest service_error i rozliczane jest wyłącznie success.

504: Upstream Timeout

Silnik nie ukończył działania w limicie czasu dla tego requestu.

{
  "error": "Upstream timeout",
  "details": "the backend did not finish inside the time budget for this request"
}

Błąd 504 dotyczy czasu trwania operacji, a nie Twojego klucza, parametrów czy proxy. Wolno działające cele, rozwiązywanie zabezpieczeń typu challenge bez wcześniejszego cache oraz duże strony to typowe przyczyny.

Rozwiązanie: Zwiększ timeout_ms w żądaniu (Single akceptuje do 120000, Browser do 120000, Auto do 180000) lub ponów próbę. FourA czeka przez zadeklarowany limit plus mały margines, więc prośba o więcej czasu rzeczywiście go zapewnia.

503: Service Disabled or At Capacity

Błąd 503 oznacza, że usługa jest tymczasowo niedostępna z powodu prac konserwacyjnych lub limit współbieżności platformy został wyczerpany. Obie formy zawierają te same klucze: error, status, service, retryAfter, current oraz limits. Rozróżnij je po ciągu error, a nie po obecności poszczególnych pól.

{
  "error": "Service disabled",
  "status": 503,
  "service": "single",
  "retryAfter": 60,
  "current": { "concurrency": 0, "rpm": 0 },
  "limits": { "maxConcurrency": 500, "maxRpm": 3000 }
}

Service disabled oznacza przerwę techniczną, a current zwraca 0 dla obu liczników, ponieważ request został odrzucony przed wykonaniem jakichkolwiek pomiarów. Service at capacity dotyczy limitu współbieżności i w tym przypadku current zawiera rzeczywiste użycie platformy. Zobacz Rate Limits, aby sprawdzić tę strukturę.

Rozwiązanie: Odczekaj retryAfter s, a następnie ponów próbę. Strona ze statusem zawiera listę aktywnych okien serwisowych.

Trzeci wariant błędu 503 nie zawiera nagłówka retryAfter. Oznacza to, że silnik obsługujący Twój endpoint restartował się w momencie nadejścia wywołania:

{
  "error": "Backend service unavailable",
  "backend_status": 503
}

Ponów próbę po sekundzie lub dwóch.

Odczytywanie błędów z /api/auto/

POST /api/auto/ odpowiada kodem HTTP 200 zawsze, gdy drabina została uruchomiona, nawet jeśli każdy szczebel zakończył się niepowodzeniem. Rzeczywisty wynik znajduje się w treści odpowiedzi:

{
  "status": 403,
  "error": "exit blocked by the target defense",
  "attempts": 7,
  "meta": { "rung": "fail", "solved": false, "attempts": 7, "credits": 47 }
}

status to ostatni status zwrócony przez cel, lub 502, gdy żadna próba do niego nie dotarła (504, gdy wcześniej wyczerpał się limit czasu). Pole request, którego Auto nie może zaakceptować (na przykład timeout_ms poniżej 5000 lub powyżej 180000), zwracane jest w ten sam sposób: HTTP 200 z "status": 400 i powodem w error, przed wykonaniem jakiejkolwiek próby i bez naliczania kosztów.

Dlatego w przypadku Auto nie należy stosować rozgałęzień na podstawie statusu transportowego. Zamiast tego odczytaj status oraz error z body. Rzeczywisty kod inny niż 200 z /api/auto/ oznacza, że FourA odrzuciło wywołanie przed uruchomieniem sekwencji prób lub nie mogło go ukończyć: 400 (nieprawidłowy JSON lub prywatny/zastrzeżony cel), 401, 413, 502, 503 lub 504. Limity, Twoje lub platformy, są zwracane w ramach 200 ze swoim statusem w body.

Gdy witryna zakończy się niepowodzeniem w kilku kolejnych wywołaniach Auto, Auto odpowiada natychmiast przez pewien czas bez podejmowania prób: "error": "target temporarily unservable, retry later", "status": 503 i retryAfter w sekundach. To nic nie kosztuje; odczekaj retryAfter s.

Osiągnięcie limitu planu przez jedno z podwywołań również jest zwracane jako HTTP 200. Body stanowi samo odrzucenie z jego reason, a także status i meta, a response zawiera ten sam header X-FourA-Limit, co bezpośrednia odmowa:

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

To, które limity zatrzymują całą sekwencję, a które zamykają tylko dany stopień, opisano w sekcji Smart Fetch (Auto).

Błędy po stronie celu wewnątrz 200 OK

Nie każdy błąd objawia się statusem HTTP innym niż 2xx. Gdy cel zwraca HTTP 200, ale odpowiedź FourA zawiera error (na przykład Twoje reguły validate odrzuciły body) lub treść jest stroną weryfikacyjną rozpoznawaną przez FourA, wynikiem jest application_error. Gdy cel zwraca status inny niż 2xx, którego Twoje reguły validate nie akceptują, wynikiem jest application_fail, a body jest przekazywane bez zmian.

Żaden z tych przypadków nie jest rozliczany: naliczanie dotyczy wyłącznie success. Browser może również zwrócić HTTP 200 z "error": "No available browser slot", gdy wszystkie instancje przeglądarek FourA są zajęte. To nie podlega opłacie; ponów próbę po kilku sekundach. Dokumentacja Outcomes opisuje pełną taksonomię.

Wywołanie Single przez przypięty proxy może również zwrócić HTTP 200 z "error": "The exit gave the same answer for <n> different sites" obok body. FourA wykryło, że ten węzeł wyjściowy zwraca tę samą stronę dla niepowiązanych witryn, więc strona pochodzi z samego węzła, a nie z żądanego adresu. Ma status application_error i nie podlega opłacie. Pobierz nowy węzeł wyjściowy z POST /api/proxy/, który automatycznie omija takie węzły.

Kodowanie odpowiedzi

FourA automatycznie dekoduje body odpowiedzi do UTF-8. Jeśli cel zwraca windows-1251, gbk, shift_jis, iso-8859-* lub dowolny inny zestaw znaków zadeklarowany w nagłówku Content-Type lub tagu HTML <meta charset>, otrzymujesz poprawny ciąg UTF-8 w polu data (single, proxy) lub body (browser).

Dla danych binarnych (obrazy, protobuf, surowe audio) ustaw returnBuffer: true w żądaniu. Single oraz Proxy zwracają wtedy data jako obiekt zawierający surowe bajty, {"type": "Buffer", "data": [<byte values>]}, bez stosowania transkodowania znaków.

Strategia ponawiania prób

Praktyczna polityka ponawiania prób:

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

Błędy proxy zawierają raport

Wywołanie POST /api/proxy/, które wyczerpie limit prób, zwraca kod HTTP 200 z kopertą błędu (error envelope), a nie kod błędu HTTP. Ciąg błędu jest krótki i ma zawsze taką samą strukturę, dlatego dołączany jest do niego obiekt attemptReport ze zliczeniami:

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

Zaloguj attemptReport.summary obok błędu, a dowiesz się, czy węzły wyjściowe były zablokowane, martwe, czy też zwracały strony odrzucone przez Twoje własne reguły validate. Opis pól i zalecane działania dla każdej wartości licznika: Dlaczego żądanie proxy wyczerpało limit prób.

Powiązane

Aktualizacja: 30 września 2026