DEV Community

Cover image for Melhores Práticas de Tratamento de Erros em APIs REST: Códigos de Status, RFC 9457 e Erros Retentáveis
Lucas
Lucas

Posted on Originally published at apidog.com

Melhores Práticas de Tratamento de Erros em APIs REST: Códigos de Status, RFC 9457 e Erros Retentáveis

Melhores práticas para tratamento de erros em APIs REST

As respostas de erro da sua API fazem parte do contrato. Clientes as analisam, a lógica de retentativa se baseia nelas e engenheiros de suporte as procuram às 2 da manhã. Ainda assim, muitas equipes detalham o caminho feliz e deixam os erros seguirem o padrão do framework — resultando em formatos inconsistentes, respostas 200 com "success": false e rastreamentos de pilha expondo o esquema do banco de dados.

Experimente o Apidog hoje

Este guia mostra como projetar um contrato de erros consistente: escolher o código HTTP correto, padronizar o corpo com RFC 9457 Detalhes do Problema, separar códigos de mensagens humanas, indicar se o erro pode ser retentado, proteger informações sensíveis e testar cada caminho de falha no Apidog.

Comece com o código de status

O HTTP já fornece uma camada de semântica. A RFC 9110 define que:

  • 4xx indica um erro do cliente; repetir a mesma requisição normalmente falhará.
  • 5xx indica uma falha do servidor ou de uma dependência; a requisição pode estar correta.

Clientes genéricos, proxies, caches e bibliotecas de retentativa usam essa informação sem necessariamente ler o JSON do corpo. Portanto, escolha o status antes de definir o payload.

Consulte a referência de códigos de status HTTP do MDN e use esta tabela como guia:

Situação Usar Não usar Por quê
JSON inválido, tipo de conteúdo incorreto ou campo obrigatório ausente 400 Bad Request 422 O servidor não consegue analisar ou entender a requisição
Requisição válida, mas que viola regras de negócio, como valor negativo ou moeda não suportada 422 Unprocessable Content 400 A sintaxe está correta; os valores não estão
Credenciais ausentes, expiradas ou inválidas 401 Unauthorized 403 O cliente ainda não provou sua identidade; envie WWW-Authenticate
Credenciais válidas, mas sem permissão 403 Forbidden 401 A identidade é conhecida, mas o acesso foi negado
Recurso inexistente ou cuja existência não será confirmada 404 Not Found 410 É o padrão seguro e também evita expor recursos sondados sem autorização
Recurso removido deliberada e permanentemente 410 Gone 404 Permite que clientes e rastreadores removam suas referências
Chave duplicada, versão antiga ou colisão de edição 409 Conflict 400 A requisição é válida, mas conflita com o estado atual
Cliente excedeu o limite de requisições 429 Too Many Requests 503 Inclua Retry-After para orientar o recuo
Exceção não tratada no seu código 500 Internal Server Error 502 O próprio servidor falhou
Upstream retornou uma resposta inválida ao gateway 502 Bad Gateway 500 A falha ocorreu a jusante da borda
Servidor sobrecarregado ou em manutenção 503 Service Unavailable 500 É uma condição temporária; inclua Retry-After quando possível
Upstream expirou 504 Gateway Timeout 500 Diferencia uma dependência lenta de um código quebrado

Dois casos exigem atenção especial:

  1. 401 vs. 403 é uma decisão de segurança. Retornar 403 para um chamador não autenticado pode revelar que o recurso existe.
  2. 429 deve incluir Retry-After. Sem essa orientação, clientes podem repetir a requisição em loops apertados. Consulte também o guia de limitação de taxa de API.

Padronize o corpo com RFC 9457

Depois de escolher o status, use um formato consistente para todas as respostas de erro. O padrão recomendado é a RFC 9457 Detalhes do Problema, com o tipo de mídia:

Content-Type: application/problem+json
Enter fullscreen mode Exit fullscreen mode

O formato define cinco membros principais:

  • type: URI que identifica a categoria do erro;
  • title: resumo curto e legível;
  • status: código HTTP, repetido por conveniência;
  • detail: descrição desta ocorrência;
  • instance: URI que identifica a falha específica.

Outros campos entram como membros de extensão. O explicador da RFC 9457 detalha a especificação e sua relação com a RFC 7807.

Exemplo de erro de validação em um endpoint de pagamentos:

POST /v1/payments HTTP/1.1
Content-Type: application/json

{ "amount": -1400, "currency": "USD", "source": "card_8xKt2" }
Enter fullscreen mode Exit fullscreen mode
HTTP/1.1 422 Unprocessable Content
Content-Type: application/problem+json

{
  "type": "https://api.example.com/problems/validation-error",
  "title": "A validação da requisição falhou",
  "status": 422,
  "detail": "Um ou mais campos falharam na validação.",
  "instance": "/v1/payments/requests/req_9f3c1a7b",
  "code": "PAYMENT_VALIDATION_FAILED",
  "errors": [
    {
      "field": "amount",
      "code": "AMOUNT_NOT_POSITIVE",
      "message": "o valor deve ser um inteiro positivo em unidades menores"
    }
  ],
  "request_id": "req_9f3c1a7b"
}
Enter fullscreen mode Exit fullscreen mode

O membro de extensão errors[] permite que um frontend associe cada erro ao campo exato do formulário, em vez de exibir uma mensagem genérica. Escolha um formato estável para os caminhos — JSON Pointer ou notação pontilhada — e use-o em toda a API.

Aplique o mesmo envelope aos erros gerados pelo framework, gateway e balanceador. Se os handlers retornam application/problem+json, mas o balanceador devolve uma página HTML para 502, cada cliente precisará manter dois parsers.

Separe códigos de mensagens humanas

O exemplo contém code e message porque eles têm funções diferentes.

Códigos legíveis por máquina

Códigos como estes fazem parte do contrato:

AMOUNT_NOT_POSITIVE
CURRENCY_UNSUPPORTED
IDEMPOTENCY_KEY_REUSED
Enter fullscreen mode Exit fullscreen mode

Eles devem ser estáveis, documentados e enumeráveis. Nunca obrigue clientes a analisar texto:

if (message.includes("positive")) {
  // ...
}
Enter fullscreen mode Exit fullscreen mode

Nesse momento, alterar uma frase passa a ser uma mudança incompatível.

Mensagens humanas

Mensagens são voltadas para desenvolvedores que leem logs e devem explicar o problema e sua correção. Prefira:

o valor deve ser um inteiro positivo em unidades menores
Enter fullscreen mode Exit fullscreen mode

a:

valor inválido
Enter fullscreen mode Exit fullscreen mode

Você pode melhorar ou localizar a mensagem sem alterar o código. Essa separação é ainda mais importante quando os consumidores incluem agentes autônomos: clientes baseados em LLM se recuperam melhor de erros estruturados e autoexplicativos. Veja também o guia sobre mensagens de erro de API para agentes de IA.

Nunca exponha informações sensíveis

Respostas de erro são uma fonte valiosa de reconhecimento para atacantes. O middleware de erro deve impedir que estes dados cheguem ao cliente:

  • rastreamentos de pilha, nomes de classes e caminhos de arquivo;
  • SQL bruto, fragmentos de consulta e erros de ORM;
  • nomes de host internos, IPs, portas e nomes de serviço;
  • versões de bibliotecas e banners de frameworks;
  • segredos, tokens e strings de conexão presentes na exceção;
  • confirmação de que uma conta existe, especialmente em login e redefinição de senha.

Capture a exceção na fronteira, registre os detalhes completos no servidor e associe tudo a um ID de requisição. Para o cliente, retorne apenas uma mensagem genérica:

{
  "type": "https://api.example.com/problems/internal-error",
  "title": "Erro interno do servidor",
  "status": 500,
  "detail": "Um erro interno ocorreu.",
  "request_id": "req_51ad0"
}
Enter fullscreen mode Exit fullscreen mode

Os logs ficam com a informação técnica; o cliente e o suporte ficam com o identificador necessário para localizar a ocorrência.

Indique quais erros podem ser retentados

Cada erro responde à pergunta: “devo tentar novamente?”. Torne essa decisão explícita no contrato.

Como regra geral:

  • 429, 502, 503 e 504 podem ser retentados com backoff exponencial e jitter;
  • 500 pode justificar uma retentativa cautelosa;
  • a maioria dos demais 4xx é terminal;
  • repetir 401, 403, 404 ou 422 com os mesmos dados normalmente desperdiça cota e polui os logs.

408 Request Timeout merece cuidado: a requisição pode ter sido processada depois que o cliente desistiu. Endpoints de mutação devem aceitar chaves de idempotência para evitar cobranças ou criações duplicadas.

Você também pode adicionar uma extensão explícita:

{
  "type": "https://api.example.com/problems/rate-limited",
  "title": "Muitas requisições",
  "status": 429,
  "code": "RATE_LIMITED",
  "retryable": true,
  "retry_after_seconds": 30
}
Enter fullscreen mode Exit fullscreen mode

A flag retryable permite substituir a regra padrão. Por exemplo, um erro 500 específico pode ser marcado como terminal quando a retentativa puder corromper o estado. Documente esse comportamento uma vez para que todos os SDKs adotem a mesma estratégia.

Use IDs de correlação e versione o contrato

Duas decisões simples evitam problemas futuros.

IDs de requisição

Aceite um cabeçalho X-Request-Id ou gere um quando ele não existir. Inclua o valor em:

  • todas as linhas de log;
  • todos os corpos de erro;
  • tickets e respostas de suporte.

Em sistemas distribuídos, propague também o traceparent do W3C para acompanhar a requisição entre serviços.

Versionamento

Trate o contrato de erro como parte da versão da API.

Mudanças normalmente compatíveis:

  • adicionar um membro de extensão;
  • adicionar um novo código de erro.

Mudanças incompatíveis:

  • renomear errors[].field;
  • alterar o significado de um código existente;
  • trocar um formato ad hoc por Detalhes do Problema sem planejar a migração.

Mantenha as URIs de type antigas estáveis e crie novas URIs para novas semânticas. Documente que clientes devem ignorar membros e códigos desconhecidos, em vez de tratá-los como falhas. Essa regra de compatibilidade permite evoluir o contrato sem necessariamente criar uma versão v2.

Teste todos os caminhos de erro no Apidog

Contratos de erro apodrecem quando ninguém os exercita. O caminho feliz aparece em todas as demonstrações; o 422 só é executado quando um cliente real o encontra.

Transforme cada caso de falha em um cenário de primeira classe no seu conjunto de testes.

Cenários de teste no servidor

Para cada endpoint, cubra casos como:

  • autenticação ausente → 401;
  • permissão insuficiente → 403;
  • valor negativo → 422, com errors[0].code igual a AMOUNT_NOT_POSITIVE;
  • tráfego acima do limite → 429, com o cabeçalho Retry-After;
  • dependência indisponível → 503;
  • dependência expirada → 504.

As asserções do Apidog verificam status, cabeçalhos e campos do corpo sem scripts complexos. Valide também o payload inteiro contra o esquema JSON de Detalhes do Problema, fazendo o CI falhar quando o formato se desviar. Consulte o guia de asserções de API.

Servidores mock para clientes

Frontend e SDKs precisam ser desenvolvidos contra respostas 4xx e 5xx antes que o backend consiga produzi-las sob demanda.

Os servidores mock do Apidog retornam os corpos exatos de Detalhes do Problema definidos na especificação. Assim, é possível simular:

  • 503 com Retry-After: 120;
  • 409 causado por submissão duplicada;
  • payloads completos de validação com errors[].

Depois, observe como o cliente renderiza a mensagem e decide se deve retentar — sem stubs Express feitos à mão ou alterações temporárias no backend.

Modele o contrato, transforme-o em cenários e mocks e conecte tudo ao CI. Você pode baixar o Apidog e começar gratuitamente; importar uma especificação OpenAPI existente permite criar respostas de erro mockáveis em poucos minutos.

FAQ

Devo usar 400 ou 422 para erros de validação?

Use 400 quando a requisição estiver malformada e o servidor não puder entendê-la: JSON inválido, tipo de conteúdo incorreto ou campo obrigatório ausente.

Use 422 quando a requisição for analisada corretamente, mas os valores violarem regras de domínio, como um pagamento negativo ou uma moeda não suportada.

A distinção ajuda o cliente a diagnosticar o problema: 422 significa “corrija seus dados”; 400 significa “corrija o formato da requisição”. O mais importante é aplicar a mesma regra em todos os endpoints.

O que é application/problem+json?

É o tipo de mídia definido pela RFC 9457 para Detalhes do Problema, um formato padrão de erro JSON para APIs HTTP. Ele contém type, title, status, detail e instance, além de extensões como errors[].

Como o tipo de mídia é padronizado, clientes genéricos e middleware podem reconhecer o formato sem configuração específica.

Quais erros HTTP os clientes devem retentar automaticamente?

Retente 429, 502, 503 e 504 usando backoff exponencial com jitter e respeitando Retry-After. Trate 500 como candidato a uma retentativa cautelosa.

Não retente outras respostas 4xx sem corrigir a requisição. Em endpoints de mutação, use chaves de idempotência para impedir cobranças ou criações duplicadas.

Como testar respostas de erro sem quebrar o backend?

Simule-as. Aponte o cliente para um servidor mock do Apidog que retorne os corpos 4xx e 5xx definidos na especificação. Teste a renderização, a lógica de retentativa e cada variação do contrato.

No servidor, crie cenários para payloads inválidos, autenticação ausente, permissões insuficientes e tráfego intenso. Faça asserções sobre status, cabeçalhos e esquema do corpo. Execute os dois lados no CI para manter o contrato íntegro sem forçar falhas manualmente.

Top comments (0)