API-Fehler

So behandelst du Fehler der FourA API.

Format der Fehlerantwort

Die API gibt für alle Fehler flache JSON-Objekte zurück. Es gibt kein geschachteltes error-Objekt. Wenn ein Fehler einen maschinenlesbaren Code hat, ist dies ein Feld auf oberster Ebene: reason bei einem Plan-Limit, code bei einem Proxy-Aufruf ohne passenden Exit Node.

{
  "error": "Invalid API key"
}

Einige Fehler enthalten zusätzliche Felder wie status, service, retryAfter, current oder limits auf der obersten Ebene:

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

Einen Request tracken

Jede API-Response (Erfolg oder Fehler) enthält einen X-FourA-Request-Id-Header mit einer UUID für diesen Aufruf. Eine Ausnahme ist ein Body, den FourA überhaupt nicht lesen kann (ungültiges JSON oder ein Body über 100 KB): Dieser wird abgelehnt, bevor eine ID zugewiesen wird. Protokolliere sie auf deiner Seite. Wenn du den Support zu einem bestimmten Request fragen musst, finden wir ihn anhand dieser 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
# ...

Fehlertypen

400: Bad Request

Der Request-Body enthält nicht alle erforderlichen Felder, enthält ungültige Werte oder benennt ein Ziel, das die API nicht abrufen kann.

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

Derselbe 400er-Fehler deckt auch den SSRF-Schutz ab. Wenn deine url zu einem privaten, Loopback- oder anderweitig reservierten IP-Bereich auflöst (RFC 5735, RFC 6598, IPv6-reservierte Blöcke), wird der Request abgewiesen, bevor er das Netzwerk von FourA verlässt:

{
  "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> ist die Adresse oder der Hostname und die Adresse, zu der er aufgelöst wurde. Eine URL, die sich nicht parsen lässt oder nicht http:// bzw. https:// ist, erhält denselben 400er-Fehler.

Ein Hostname, der nicht aufgelöst werden kann, wird nicht abgewiesen. Der Aufruf wird als HTTP 200 mit status: 0 und dem Grund (could not resolve <host>: <reason>) zurückgegeben, wie bei jedem Ziel, das FourA nicht erreichen kann, und wird nicht abgerechnet.

Ungültiges JSON im Body wird auf dieselbe Weise abgewiesen, bevor ein Feld gelesen wird:

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

Die Felder proxy und ignoreProxies haben eigene 400er-Fehler. Beide erwarten die opaken Proxy-IDs aus früheren Responses, alles andere schlägt beim Decodieren fehl:

Nachricht Ursache
Invalid proxy format Der Wert für proxy ist keine von FourA ausgestellte Proxy-ID. Eine reine Proxy-Adresse führt hierher.
Invalid ignoreProxies format Einer der Einträge in ignoreProxies ist keine Proxy-ID.
Proxy not found Die ID wurde fehlerfrei decodiert, verweist aber nicht mehr auf einen aktiven Exit Node. Wähle einen neuen.
Managed exit: this proxy id cannot be pinned to a request Der Exit Node existiert, wird von FourA für einen benannten Request jedoch nicht offengehalten. Die ID eines Premium-Exit-Nodes landet hier, wenn dein Tarif kein verbleibendes Premium-Guthaben mehr hat. Verwende die Session wieder, über die sie zurückgegeben wurde, oder sende den Aufruf über POST /api/proxy/ und nutze den Exit Node, der automatisch gewählt wird.

Behebung: Prüfe, ob dein Request alle Pflichtfelder enthält, URLs http:// oder https:// nutzen, der Host zu einer öffentlichen Adresse auflöst und jeder proxy-Wert eine exakt aus einer früheren Response kopierte ID ist.

Dies sind client_error-Ergebnisse: Der Request hat FourA nie verlassen, daher wurde kein Guthaben verbraucht.

401: Unauthorized

Dein API-Key fehlt oder ist ungültig.

Fehlender Key:

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

Ungültiger Key:

{
  "error": "Invalid API key"
}

Behebung: Stelle sicher, dass dein X-API-Key-Header einen gültigen Key enthält. Generiere bei Bedarf einen neuen Key im Dashboard.

403: Not in Your Plan

Der Request hat einen Endpoint oder einen Parameter angefordert, der nicht in deinem Plan enthalten ist. Die Response setzt X-FourA-Limit und gibt denselben Code im Body unter reason zurück:

{
  "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 ist plan_limit_feature für einen im Plan nicht enthaltenen Endpoint oder für exitCountries bei einem Plan ohne Geo-Targeting, sowie plan_limit_premium für exitClass: premium bei einem Plan ohne Premium-Exits. Der String error nennt den Endpoint oder Parameter.

Ein 403 von FourA betrifft nie die Zielseite: Das Ziel wurde nie kontaktiert. Ein vom Ziel zurückgegebener 403 kommt als HTTP 200 mit status: 403 im Body an.

Behebung: Entferne den Parameter, rufe einen in deinem Plan enthaltenen Endpoint auf oder führe ein Upgrade durch. Es wird kein Retry-After gesetzt, da Warten das Ergebnis nicht ändert. Es wurde nichts verbraucht: Das Ergebnis ist rate_limit, und nur success wird abgerechnet.

413: Payload Too Large

Der JSON-Request-Body ist größer als von FourA erlaubt (100 KB). Die Antwort ist kein JSON und enthält kein X-FourA-Request-Id, da der Body vor dem Lesen abgelehnt wird.

Behebung: Sende einen kleineren data-Payload. Es wurde nichts verbraucht.

429: Rate Limited

Zwei verschiedene Prüfungen antworten mit 429, und sie enthalten nicht dieselben Felder.

Eigene Limits deines Plans. Die Response setzt einen X-FourA-Limit-Header, der das ablehnende Limit nennt, und gibt denselben Code im Body unter reason an:

{
  "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 ist einer von plan_limit_concurrency, plan_limit_rate, plan_limit_browser_daily, plan_limit_credits oder plan_limit_bandwidth. Wenn Warten hilft, steht die Wartezeit in retry_after_seconds und im Retry-After-Header, niemals in retryAfter. plan_limit_browser_daily enthält keines von beiden, da das Kontingent um Mitternacht UTC und nicht in Sekunden zurückgesetzt wird. Es wurde nichts verbraucht: Das Ergebnis ist rate_limit, und nur success wird abgerechnet.

Das geteilte Kontingent der Plattform. Kein X-FourA-Limit-Header, und die Wartezeit steht in retryAfter:

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

current und limits beschreiben den Dienst über den gesamten Traffic hinweg, nicht dein Konto. Eine Ablehnung hier bedeutet, dass FourA ausgelastet ist.

Lösung: Warte die Zeitspanne ab, die in Retry-After, retry_after_seconds oder retryAfter der Response angegeben ist. Begrenze bei einem Concurrency- oder Rate Limit die Anzahl deiner offenen Requests, anstatt den abgelehnten Batch erneut zu senden. Stoppe den Lauf bei einem Tages- oder Abrechnungszeitraum-Limit. Siehe Rate Limits für jedes Feld und Run Requests in Parallel für das Pattern.

500: Server Error

Auf unserer Seite ist ein Fehler aufgetreten.

Lösung: Wiederhole den Request nach einer kurzen Verzögerung. Wenn der Fehler weiterhin besteht, prüfe die Status-Seite oder kontaktiere den Support mit der X-FourA-Request-Id aus der fehlgeschlagenen Response.

502: Upstream Unavailable

FourA hat die eigene Engine erreicht, konnte die Antwort aber nicht verarbeiten.

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

Lösung: Wiederhole die Anfrage mit einem kurzen Backoff. Der Fehler liegt auf unserer Seite und kostet dich nichts: Das Ergebnis ist service_error und nur success wird abgerechnet.

504: Upstream Timeout

Die Engine wurde für diesen Request nicht innerhalb des Zeitlimits fertig.

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

Ein 504 bezieht sich darauf, wie lange die Verarbeitung gedauert hat, nicht auf deinen Key, deine Parameter oder deinen Proxy. Langsame Ziele, Cold-Challenge-Lösungen und große Seiten sind die üblichen Ursachen.

Lösung: Erhöhe timeout_ms beim Request (Single akzeptiert bis zu 120000, Browser bis zu 120000, Auto bis zu 180000) oder versuche es erneut. FourA wartet das von dir festgelegte Budget plus eine kleine Marge ab. Mehr Zeit anzufordern verschafft dir also tatsächlich mehr Zeit.

503: Service deaktiviert oder ausgelastet

Ein 503 bedeutet entweder, dass der Service wegen Wartungsarbeiten vorübergehend nicht verfügbar ist, oder dass das Concurrency-Limit der Plattform erreicht ist. Beide Varianten enthalten dieselben Keys: error, status, service, retryAfter, current und limits. Unterscheide sie anhand des error-Strings, nicht anhand der vorhandenen Felder.

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

Service disabled bedeutet Wartung und current zeigt 0 für beide Zähler an, da die Request abgewiesen wurde, bevor etwas gemessen werden konnte. Service at capacity ist das Concurrency-Format, und dort enthält current die tatsächliche Nutzung der Plattform. Siehe Rate Limits für diese Struktur.

Lösung: Warte retryAfter Sekunden und versuche es dann erneut. Die Statusseite listet aktive Wartungsfenster auf.

Eine dritte 503-Variante hat kein retryAfter. Sie bedeutet, dass die Engine hinter deinem Endpoint neu gestartet wurde, als dein Aufruf einging:

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

Versuche es nach ein oder zwei Sekunden erneut.

Fehler aus /api/auto/ auslesen

POST /api/auto/ antwortet mit HTTP 200, sobald die Ladder lief, selbst wenn jede Sprosse fehlschlug. Das tatsächliche Ergebnis steht im Body:

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

status ist der letzte Status, mit dem das Ziel geantwortet hat, oder 502, wenn kein Versuch es erreicht hat (504, wenn das Zeitbudget vorher abgelaufen ist). Ein Request-Feld, das Auto nicht akzeptieren kann (etwa ein timeout_ms unter 5000 oder über 180000), wird auf dieselbe Weise zurückgegeben: HTTP 200 mit "status": 400 und dem Grund in error, bevor ein Versuch unternommen wird und ohne Kosten.

Verzweige für Auto also nicht anhand des Transport-Status. Lies stattdessen status und error aus dem Body. Ein echter Nicht-200-Status von /api/auto/ bedeutet, dass FourA den Aufruf abgelehnt hat, bevor die Ladder gestartet wurde, oder ihn nicht abschließen konnte: 400 (ungültiges JSON oder ein privates bzw. reserviertes Ziel), 401, 413, 502, 503 oder 504. Limits, deine oder die der Plattform, werden innerhalb des 200 mit ihrem Status im Body zurückgegeben.

Wenn eine Website bei mehreren Auto-Aufrufen hintereinander fehlgeschlagen ist, antwortet Auto für eine Weile direkt, ohne es zu versuchen: "error": "target temporarily unservable, retry later", "status": 503 und ein retryAfter in Sekunden. Das kostet nichts; warte retryAfter Sekunden.

Ein Plan-Limit, das von einem der Sub-Calls erreicht wird, kommt ebenfalls als HTTP 200 zurück. Der Body ist die Ablehnung selbst mit ihrem reason plus status und meta, und die Response enthält denselben X-FourA-Limit-Header wie eine direkte Ablehnung:

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

Welche Limits die Leiter stoppen und welche nur eine Sprosse schließen, wird in Smart Fetch (Auto) behandelt.

Zielseitige Fehler innerhalb von 200 OK

Nicht jeder Fehler zeigt sich als Nicht-2xx-HTTP-Status. Wenn das Ziel mit HTTP 200 antwortet, FourAs Antwort jedoch ein error enthält (wenn z. B. deine validate-Regeln den Body abgelehnt haben) oder der Body eine von FourA erkannte Prüfseite ist, lautet das Ergebnis application_error. Wenn das Ziel einen Nicht-2xx-Status zurückgibt, den deine validate-Regeln nicht akzeptieren, lautet das Ergebnis application_fail und der Body wird unverändert durchgereicht.

Keiner der beiden Fälle wird abgerechnet: Nur success wird berechnet. Browser kann auch mit HTTP 200 und "error": "No available browser slot" antworten, wenn alle Browser von FourA ausgelastet sind. Dies wird nicht abgerechnet; versuche es nach ein paar Sekunden erneut. Die Referenz Outcomes deckt die vollständige Taxonomie ab.

Ein Single-Aufruf über einen von dir gepinnten proxy kann ebenfalls mit HTTP 200 und "error": "The exit gave the same answer for <n> different sites" neben dem Body antworten. FourA hat festgestellt, dass dieser Exit dieselbe Seite an unabhängige Websites ausgeliefert hat; die Seite stammt also vom Exit selbst und ist nicht die von dir angeforderte. Das Ergebnis ist application_error und wird nicht berechnet. Beziehe einen neuen Exit über POST /api/proxy/, wodurch ein solcher Exit automatisch übersprungen wird.

Response-Codierung

FourA decodiert Response-Bodies automatisch nach UTF-8. Wenn das Ziel windows-1251, gbk, shift_jis, iso-8859-* oder ein anderes im Header Content-Type oder in einem HTML-<meta charset>-Tag deklariertes Charset liefert, erhältst du einen sauberen UTF-8-String im Feld data (Single, Proxy) oder body (Browser).

Setze für binäre Payloads (Bilder, Protobuf, Raw Audio) returnBuffer: true im Request. Single und Proxy geben data dann als Objekt zurück, das die Rohdaten-Bytes {"type": "Buffer", "data": [<byte values>]} enthält, ohne dass eine Charset-Transcodierung angewendet wird.

Retry-Strategie

Eine praktische Retry-Policy:

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-Fehler enthalten einen Report

Ein POST /api/proxy/-Aufruf, bei dem keine Versuche mehr übrig sind, wird als HTTP 200 mit einem Error-Envelope zurückgegeben, nicht als HTTP-Fehlercode. Der Error-String ist kurz und hat immer dieselbe Struktur, daher enthält ein zusätzliches attemptReport-Objekt die Zähler:

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

Protokolliere attemptReport.summary zusammen mit dem Fehler, um zu sehen, ob die Exits blockiert oder offline waren oder Seiten geliefert haben, die deine eigenen validate-Regeln abgelehnt haben. Feldreferenz und Maßnahmen für jeden Zähler: Warum ein Proxy-Request keine Versuche mehr übrig hatte.

Verwandte Themen

Aktualisiert: 30. September 2026