Erros
Toda resposta fora da faixa 2xx usa o mesmo envelope JSON. Seu cliente pode tratar erros a partir de um só formato.
Envelope
{
"code": "validation_failed",
"message": "Revise os campos informados e tente novamente.",
"detail": "Revise os campos informados e tente novamente.",
"hint": "Confira o tipo e o formato de cada campo.",
"details": {
"errors": [
{ "loc": ["body", "email"], "msg": "value is not a valid email address" }
]
},
"debug_id": "8e1f2c3a4b5c6d7e8f90",
"request_id": "8e1f2c3a4b5c6d7e8f90"
}
Campos
| Campo | Tipo | Descrição |
|---|---|---|
code | string | Código canônico, legível por máquina (validation_failed, forbidden, etc). Use este para lógica no cliente. |
message | string | Mensagem legível ao humano, traduzida conforme o header x-locale. |
detail | string | Mesma coisa que message, mantido por compatibilidade. |
hint | string | null | Sugestão de ação para o usuário/desenvolvedor. Pode ser null quando não há dica específica. |
details | object | Contexto adicional específico da falha (campos inválidos, IDs envolvidos, etc). Estrutura varia por erro. |
debug_id | string | Identificador único da requisição — use no suporte para investigação. |
request_id | string | Mesmo valor de debug_id; exposto também no header X-Request-Id da resposta. |
Códigos canônicos
| HTTP | code | Significado | Como reagir |
|---|---|---|---|
| 400 | bad_request | Requisição mal formada (JSON inválido, tipo errado). | Corrija o payload e repita. |
| 401 | unauthorized | Token/chave ausente, inválido ou expirado. | Autentique novamente. |
| 403 | forbidden | Autenticado, mas sem permissão. | Use uma chave com escopo maior, ou peça a permissão ao admin. |
| 404 | not_found | Recurso não existe ou não está no seu escopo. | Confirme o ID e o contexto de organização/projeto. |
| 409 | conflict | Estado atual incompatível (versão já publicada, email duplicado, etc). | Atualize o estado local e tente de novo. |
| 413 | payload_too_large | Body acima de 8 MB. | Divida o payload ou reduza o conteúdo. |
| 422 | validation_failed / request_validation_failed | Campo inválido segundo o schema. | Consulte details.errors e ajuste. |
| 429 | rate_limited | Limite de requisições excedido. | Leia Retry-After e espere. Veja Limites. |
| 500 | internal_error | Erro inesperado no servidor. | Pode repetir com backoff. Se persistir, reporte com o debug_id. |
Alguns endpoints retornam códigos mais específicos dentro de code (por exemplo plan_quota_exceeded, api_key_scope_insufficient, project_not_found). Trate qualquer code desconhecido como "erro do tipo HTTP" — o HTTP status é a fonte da verdade de alto nível.
Validação de schema (422)
Requisições com body em formato inválido para o schema retornam:
{
"code": "request_validation_failed",
"message": "Dados inválidos enviados na requisição.",
"hint": "Revise os campos obrigatórios e os formatos.",
"details": {
"errors": [
{
"type": "string_type",
"loc": ["body", "email"],
"msg": "Input should be a valid string",
"input": 42
}
]
},
"debug_id": "..."
}
details.errors[] segue o formato padrão do Pydantic v2. Os campos relevantes:
loc: caminho do campo problemático dentro do payload (["body", "items", 0, "name"]).type: tipo do erro (missing,string_too_short,int_parsing, etc).msg: mensagem detalhada.input: valor rejeitado (pode ser omitido).
Autenticação e permissão (401 / 403)
code | Detalhe típico |
|---|---|
unauthorized | Mensagem como "Sessão expirada ou revogada", "API key inválida ou expirada". |
forbidden | "Permissão insuficiente", "Escopo da API key insuficiente", "Organização fora do escopo da sessão". |
Diferença prática:
401: o cliente não pode continuar sem renovar credenciais.403: o cliente está autenticado, mas a credencial não é suficiente para a operação. Pode ser escopo da API key, papel na organização, ou tentativa de acessar um projeto de outra organização.
Rate limiting (429)
Resposta quando o limite por minuto ou por hora é estourado:
{
"code": "rate_limited",
"message": "Rate limit excedido.",
"hint": "Aguarde o reset informado e tente novamente.",
"details": {
"route": "analysis.validate",
"reset_at": "2026-04-24T15:31:00Z"
},
"debug_id": "..."
}
Headers adicionais nessa resposta:
Retry-After: <segundos>— quanto esperar antes de tentar de novo.X-RateLimit-Limit,X-RateLimit-Remaining— estado do limite no momento do bloqueio.
Veja Limites e uso para políticas por plano.
Payload grande (413)
{
"code": "payload_too_large",
"message": "Payload acima do limite permitido (8388608 bytes).",
"hint": "Reduza o tamanho do payload antes de tentar novamente."
}
O limite padrão é 8 MB. Para eventos de logtest acima disso, divida em chamadas separadas.
Internacionalização das mensagens
Mensagens de erro (message, hint) são resolvidas no idioma do cliente. A ordem de resolução:
- Header
x-locale(valores aceitos:pt-BR,en-US). - Header
Accept-Language. - Fallback para
pt-BR.
Os codes nunca são traduzidos — são a sua interface estável com a API. Use sempre code para branching de lógica; deixe message e hint para exibir ao usuário final.
Debug IDs e correlação
O campo debug_id aparece também no header X-Request-Id da resposta. Ele é gerado automaticamente (ou aceito do cliente pelo mesmo header na requisição).
Use esse ID para:
- Anexar ao reporte de bug/suporte — facilita a busca no log do servidor.
- Correlacionar chamadas em cadeia — você pode enviar o mesmo
X-Request-Idnas sub-chamadas para um rastreio único.
Para rastreamento distribuído (OpenTelemetry), veja o cabeçalho Traceparent descrito em Convenções.
Exemplos de tratamento
Python
import httpx
resp = httpx.post("https://ruleforge.example.com/api/v1/analysis/validate",
headers={"X-API-Key": key},
json=payload)
if resp.status_code == 429:
retry_after = int(resp.headers.get("Retry-After", "60"))
time.sleep(retry_after)
# retry
elif resp.status_code >= 400:
body = resp.json()
raise RuleForgeError(
code=body["code"],
message=body["message"],
debug_id=body.get("debug_id"),
)
JavaScript
const res = await fetch(`${base}/analysis/validate`, {
method: "POST",
headers: { "X-API-Key": key, "Content-Type": "application/json" },
body: JSON.stringify(payload),
})
if (!res.ok) {
const err = await res.json()
console.error(`[${err.code}] ${err.message} (debug_id=${err.debug_id})`)
throw err
}