Erros da API
Como tratar erros da API FourA.
Formato de resposta de erro
A API retorna objetos JSON simples para todos os erros. Não há um objeto error aninhado. Quando uma falha possui um código legível por máquina, ele é um campo de nível superior: reason em um limite de plano, code em uma chamada de proxy sem saída elegível.
{
"error": "Invalid API key"
}
Alguns erros incluem campos adicionais como status, service, retryAfter, current ou limits no nível superior:
{
"error": "Rate limit exceeded",
"status": 429,
"service": "single",
"retryAfter": 5,
"current": { "concurrency": 12, "rpm": 3000 },
"limits": { "maxConcurrency": 500, "maxRpm": 3000 }
}
Rastreando uma Request
Toda response da API (sucesso ou erro) inclui um header X-FourA-Request-Id com um UUID para essa chamada, exceto um body que o FourA não consiga ler (JSON malformado ou um body com mais de 100 KB): isso é recusado antes que um ID seja atribuído. Registre-o no seu lado. Se você precisar perguntar ao suporte o que aconteceu com uma request específica, esse ID nos permite localizá-la.
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
# ...
Tipos de Erro
400: Bad Request
O body da request não contém campos obrigatórios, contém valores inválidos ou indica um destino que a API se recusa a buscar.
{
"error": "Invalid request body format"
}
O mesmo 400 também cobre a proteção contra SSRF. Se o seu url resolver para uma faixa de IP privada, loopback ou reservada de outra forma (RFC 5735, RFC 6598, blocos reservados de IPv6), a request será rejeitada antes de sair da rede da 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> é o endereço, ou o nome do host e o endereço para o qual ele resolveu. Uma URL que não pode ser analisada, ou não é http:// ou https://, recebe o mesmo 400.
Um nome de host que não pode ser resolvido não é recusado. A chamada retorna como HTTP 200 com status: 0 e o motivo (could not resolve <host>: <reason>), como qualquer destino que o FourA não consegue alcançar, e não é cobrada.
JSON malformado no corpo é recusado da mesma forma, antes que qualquer campo seja lido:
{
"error": "Invalid JSON in request body"
}
Os campos proxy e ignoreProxies têm seus próprios erros 400. Ambos aceitam os IDs de proxy opacos retornados por respostas anteriores, portanto qualquer outro valor falha ao decodificar:
| Message | What happened |
|---|---|
Invalid proxy format |
O valor de proxy não é um ID de proxy emitido pela FourA. Um endereço de proxy bruto cai aqui. |
Invalid ignoreProxies format |
Uma das entradas em ignoreProxies não é um ID de proxy. |
Proxy not found |
O ID foi decodificado corretamente, mas não resolve mais para uma saída ativa. Escolha um novo. |
Managed exit: this proxy id cannot be pinned to a request |
A saída existe, mas não é uma que a FourA manterá aberta para uma requisição nomeada. O ID de uma saída premium cai aqui quando seu plano não tem mais tráfego premium disponível. Reutilize a sessão em que ele foi retornado ou execute a chamada via POST /api/proxy/ e use a saída escolhida automaticamente. |
Correção: Verifique se a sua requisição inclui todos os campos obrigatórios, se as URLs usam http:// ou https://, se o host resolve para um endereço público e se qualquer valor de proxy é um ID copiado exatamente de uma resposta anterior.
Estes são resultados client_error: a requisição nunca saiu da FourA, portanto nenhum crédito foi gasto.
401: Unauthorized
Sua chave de API está ausente ou é inválida.
Chave ausente:
{
"error": "Missing API key. Include X-API-Key header."
}
Chave inválida:
{
"error": "Invalid API key"
}
Correção: Verifique se o seu header X-API-Key contém uma chave válida. Gere uma nova chave a partir do Dashboard se necessário.
403: Não Incluído no Seu Plano
A chamada solicitou um endpoint ou um parâmetro que o seu plano não inclui. A resposta define X-FourA-Limit e inclui o mesmo código no body em 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 para um endpoint que o plano exclui ou para exitCountries em um plano sem segmentação geográfica, e plan_limit_premium para exitClass: premium em um plano sem saídas premium. A string error indica o endpoint ou parâmetro.
Um 403 do FourA nunca tem relação com o site de destino: o destino nunca foi contatado. Um 403 retornado pelo destino chega como HTTP 200 com status: 403 dentro do body.
Correção: Remova o parâmetro, chame um endpoint incluído no seu plano ou faça um upgrade. Nenhum Retry-After é definido, pois esperar não altera a resposta. Nada foi gasto: o resultado é rate_limit, e apenas success é cobrado.
413: Payload Too Large
O JSON do request body é maior do que o FourA aceita (100 KB). A resposta não é JSON e não contém X-FourA-Request-Id, pois o body é recusado antes de ser lido.
Correção: Envie um payload data menor. Nada foi gasto.
429: Rate Limited
Duas verificações diferentes respondem com 429, e elas não trazem os mesmos campos.
Os limites do seu próprio plano. A response define um header X-FourA-Limit indicando qual limite recusou a chamada e coloca o mesmo código no body sob 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 é um de plan_limit_concurrency, plan_limit_rate, plan_limit_browser_daily, plan_limit_credits ou plan_limit_bandwidth. Quando uma espera ajuda, ela fica em retry_after_seconds e no header Retry-After, nunca em retryAfter. plan_limit_browser_daily não contém nenhum dos dois, pois o limite é renovado à meia-noite UTC e não em segundos. Nada foi gasto: o resultado é rate_limit, e apenas success é faturado.
O limite compartilhado da plataforma. Nenhum header X-FourA-Limit, e a espera está em retryAfter:
{
"error": "Rate limit exceeded",
"status": 429,
"service": "single",
"retryAfter": 5,
"current": { "concurrency": 12, "rpm": 3000 },
"limits": { "maxConcurrency": 500, "maxRpm": 3000 }
}
current e limits descrevem o serviço em todo o tráfego, não na sua conta. Uma recusa aqui significa que o FourA está ocupado.
Solução: Aguarde o tempo indicado por Retry-After, retry_after_seconds ou retryAfter retornado na response. Em caso de limite de concorrência ou rate limit, limite quantas requests você mantém abertas em vez de reenviar o lote recusado. Em um limite diário ou do período de faturamento, interrompa a execução. Consulte Rate Limits para cada campo e Executar Requests em Paralelo para o padrão.
500: Server Error
Algo deu errado do nosso lado.
Solução: Tente novamente a request após um breve intervalo. Se o erro persistir, verifique a página de status ou entre em contato com o suporte informando o X-FourA-Request-Id da response com falha.
502: Upstream Unavailable
O FourA alcançou seu próprio mecanismo, mas não conseguiu usar a resposta.
{
"error": "Upstream unavailable",
"details": "..."
}
Correção: Tente novamente com um backoff curto. Isso ocorre do nosso lado, portanto não custa nada para você: o resultado é service_error e apenas success é cobrado.
504: Upstream Timeout
O engine não finalizou dentro do tempo limite para esta request.
{
"error": "Upstream timeout",
"details": "the backend did not finish inside the time budget for this request"
}
Um 504 está relacionado ao tempo que a tarefa levou, não à sua chave, aos seus parâmetros ou ao seu proxy. Destinos lentos, resoluções de desafios a frio e páginas grandes são as causas comuns.
Correção: Aumente timeout_ms na request (Single aceita até 120000, Browser até 120000, Auto até 180000) ou tente novamente. O FourA aguarda o tempo limite declarado por você mais uma pequena margem, portanto solicitar mais tempo realmente concede mais tempo.
503: Service Disabled or At Capacity
Um 503 significa que o serviço está temporariamente indisponível para manutenção ou que o limite de concorrência da plataforma foi atingido. Ambas as formas contêm as mesmas chaves: error, status, service, retryAfter, current e limits. Diferencie-as pela string error, e não pelos campos presentes.
{
"error": "Service disabled",
"status": 503,
"service": "single",
"retryAfter": 60,
"current": { "concurrency": 0, "rpm": 0 },
"limits": { "maxConcurrency": 500, "maxRpm": 3000 }
}
Service disabled é manutenção e current indica 0 para ambos os contadores, pois a request foi recusada antes que algo fosse medido. Service at capacity é o formato de simultaneidade, e nele current contém o uso real da plataforma. Consulte Rate Limits para obter esse formato.
Correção: Aguarde retryAfter segundos e tente novamente. A página de status lista as janelas de manutenção ativas.
Um terceiro formato 503 não possui retryAfter. Isso significa que o mecanismo por trás do seu endpoint estava reiniciando quando sua chamada chegou:
{
"error": "Backend service unavailable",
"backend_status": 503
}
Tente novamente após um ou dois segundos.
Lendo falhas de /api/auto/
POST /api/auto/ responde com HTTP 200 sempre que a ladder for executada, mesmo quando todas as etapas falharem. O resultado real fica no body:
{
"status": 403,
"error": "exit blocked by the target defense",
"attempts": 7,
"meta": { "rung": "fail", "solved": false, "attempts": 7, "credits": 47 }
}
status é o último status retornado pelo destino, ou 502 quando nenhuma tentativa o alcançou (504 quando o tempo limite expirou antes). Um campo de request que o Auto não pode aceitar (um timeout_ms abaixo de 5000 ou acima de 180000, por exemplo) retorna da mesma forma: HTTP 200 com "status": 400 e o motivo em error, antes de qualquer tentativa ser feita e sem custo.
Portanto, não faça condicionais baseadas no status de transporte para o Auto. Em vez disso, leia status e error do body. Um não-200 autêntico de /api/auto/ significa que o FourA rejeitou a chamada antes de iniciar a sequência, ou não conseguiu concluí-la: 400 (JSON malformado, ou um destino privado ou reservado), 401, 413, 502, 503 ou 504. Limites, sejam seus ou da plataforma, retornam dentro do 200 com seu status no body.
Quando um site falha em várias chamadas do Auto em sequência, o Auto responde imediatamente por um período sem tentar: "error": "target temporarily unservable, retry later", "status": 503 e um retryAfter em segundos. Isso não tem custo; aguarde retryAfter segundos.
Um limite de plano atingido por uma das subchamadas também retorna como HTTP 200. O body é a própria recusa, com seu reason, além de status e meta, e a response traz o mesmo header X-FourA-Limit de uma recusa direta:
{
"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 }
}
Quais limites interrompem o ladder e quais apenas fecham um rung é abordado em Smart Fetch (Auto).
Falhas do Lado do Destino Dentro de 200 OK
Nem toda falha aparece como um status HTTP não-2xx. Quando o destino responde HTTP 200, mas a resposta do FourA carrega um error (suas regras de validate rejeitaram o body, por exemplo) ou o body é uma página de verificação que o FourA reconhece, o resultado é application_error. Quando o destino retorna um não-2xx que suas regras de validate não aceitam, o resultado é application_fail e o body é entregue sem alterações.
Nenhum dos casos é cobrado: apenas success é faturado. O Browser também pode responder HTTP 200 com "error": "No available browser slot" quando todos os browsers do FourA estiverem ocupados. Isso não é cobrado; tente novamente após alguns segundos. A referência de Outcomes cobre a taxonomia completa.
Uma chamada Single por meio de um proxy fixado por você também pode responder HTTP 200 com "error": "The exit gave the same answer for <n> different sites" junto ao body. O FourA detectou que essa saída entregou a mesma página para sites não relacionados, portanto a página pertence à própria saída, não sendo a que você solicitou. O resultado é application_error e não é cobrado. Obtenha uma nova saída do POST /api/proxy/, que descarta essa saída automaticamente.
Codificação de Resposta
O FourA decodifica automaticamente os bodies de resposta para UTF-8. Se o destino fornecer windows-1251, gbk, shift_jis, iso-8859-* ou qualquer outro charset declarado no header Content-Type ou em uma tag HTML <meta charset>, você recebe uma string UTF-8 limpa no campo data (single, proxy) ou body (browser).
Para payloads binários (imagens, protobuf, áudio bruto), defina returnBuffer: true na request. Single e Proxy então retornam data como um objeto contendo os bytes brutos, {"type": "Buffer", "data": [<byte values>]}, sem nenhuma transcodificação de charset aplicada.
Estratégia de Retry
Uma política prática de retry:
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")
Falhas de Proxy Incluem um Relatório
Uma chamada POST /api/proxy/ que esgota as tentativas retorna como HTTP 200 com um envelope de erro, não como um código de erro HTTP. A string de erro é curta e sempre no mesmo formato, portanto um objeto attemptReport é enviado junto com as contagens:
{
"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
}
Registre attemptReport.summary junto ao erro e você saberá se as saídas foram bloqueadas, caíram ou entregaram páginas que suas próprias regras validate rejeitaram. Referência de campos e o que fazer com cada contagem: Por que uma Proxy Request esgotou as tentativas.
Relacionado
- Rate Limits: Limites do plano, concorrência e detalhes de RPM
- Executar Requests em Paralelo: Permanecendo dentro da concorrência do seu plano
- Resultados de Requests: Os sete valores de resultado explicados
- Problemas Comuns: Sintomas, causas, soluções
- Verificações de Site: Quando o body é uma página de desafio em vez de um erro
- Por que uma Proxy Request Esgotou as Tentativas: Lendo
attemptReport