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

Dominar o tratamento de erros de API para REST: escolher os códigos de status corretos, retornar detalhes de problema RFC 9457, marcar erros que podem ser retentados e testar todas as falhas com Apidog.

INEZA Felin-Michel

INEZA Felin-Michel

31 agosto 2026

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

Apidog para empresas

Implantação local

SSO & RBAC

Conforme SOC 2

Explorar Apidog Enterprise

As respostas de erro da sua API são parte do seu contrato. Clientes as analisam, a lógica de retentativa se baseia nelas, e engenheiros de suporte as procuram às 2 da manhã. No entanto, a maioria das equipes projeta o caminho feliz em detalhes e deixa que os erros saiam do que o framework faz por padrão. É assim que você acaba com três formatos de erro diferentes em uma API, uma resposta 200 envolvendo "success": false, e um rastreamento de pilha vazando seu esquema de banco de dados para a internet pública.

Este guia aborda as melhores práticas de tratamento de erros de API para serviços REST de ponta a ponta: escolher o código de status correto, padronizar um corpo de erro com Detalhes do Problema RFC 9457, separar códigos legíveis por máquina de mensagens humanas, marcar erros como retentáveis e manter segredos fora das respostas. Ele se baseia na nossa análise sobre quais códigos de status HTTP as APIs REST devem usar e adiciona as decisões de nível de contrato que esse guia deixa em aberto. Você também verá como testar cada caminho de falha no Apidog, porque um contrato de erro que você nunca testa é um contrato que você não tem.

Comece com o código de status, não com o corpo

O HTTP já oferece uma primeira camada de semântica de erro gratuitamente. A RFC 9110 define as famílias de códigos de status: 4xx significa que o cliente fez algo errado e repetir a mesma requisição falhará novamente; 5xx significa que o servidor falhou e a requisição do cliente pode ter sido boa. Acerte essa divisão antes de escrever uma única linha do corpo do erro, porque clientes genéricos, proxies, caches e bibliotecas de retentativa se baseiam nela sem nunca ler seu JSON.

Os erros mais comuns se agrupam em torno de alguns pares semelhantes. Mantenha a referência de códigos de status HTTP do MDN aberta enquanto projeta, e use esta tabela de decisão para os códigos que confundem as equipes.

Situação Usar Não usar Por que
Requisição malformada: JSON quebrado, tipo de conteúdo errado, campo obrigatório faltando 400 Bad Request 422 O servidor não consegue analisar ou entender a requisição de forma alguma
Requisição bem-formada que viola regras semânticas: valor negativo, moeda não suportada 422 Unprocessable Content 400 A sintaxe está correta; os valores não estão
Sem credenciais, ou token expirado/inválido 401 Unauthorized 403 O cliente não provou quem é. Envie WWW-Authenticate
Credenciais válidas, permissões insuficientes 403 Forbidden 401 A identidade é conhecida; o acesso é negado. Reautenticar não ajudará
Recurso nunca existiu, ou você não confirmará que existe 404 Not Found 410 Padrão seguro; também oculta recursos de sondagem não autorizada
Recurso existia e foi removido deliberadamente, permanentemente 410 Gone 404 Informa a clientes e rastreadores para deletarem suas referências
Conflito de estado: chave duplicada, versão antiga, colisão de edição 409 Conflict 400 A requisição é válida, mas entra em conflito com o estado atual do recurso
Cliente excedeu um limite de taxa 429 Too Many Requests 503 Sempre inclua Retry-After para que os clientes se afastem corretamente
Exceção não tratada em seu código 500 Internal Server Error 502 Seu servidor quebrou
Serviço upstream retornou lixo para seu gateway 502 Bad Gateway 500 A falha está a jusante da borda, não nela
Servidor sobrecarregado ou em manutenção 503 Service Unavailable 500 Temporário por definição; adicione Retry-After quando puder
Serviço upstream expirou 504 Gateway Timeout 500 Distingue "dependência lenta" de "código quebrado"

Dois deles merecem ênfase extra. Primeiro, 401 vs 403 é um limite de segurança, não uma escolha de estilo: retornar 403 para um chamador não autenticado vaza o fato de que o recurso existe. Segundo, 429 sem Retry-After treina os clientes para te bombardear em loops apertados. Se você limita a taxa, e deveria, emparelhe o status com um sinal concreto de recuo; nosso guia sobre limitação de taxa de API cobre o cálculo do cabeçalho e os algoritmos por trás disso.

Um formato de corpo de erro: RFC 9457 Detalhes do Problema

Uma vez que o código de status esteja correto, cada erro que sua API retorna deve compartilhar um tipo de mídia e um esquema. A resposta padrão é RFC 9457 Detalhes do Problema, servido como application/problem+json. Ele define cinco membros centrais: type (uma URI que identifica a categoria do erro), title (um breve resumo humano), status (o código HTTP, repetido por conveniência), detail (o que deu errado nesta ocorrência) e instance (uma URI para esta falha específica). Qualquer outra coisa vai em membros de extensão que você define.

Não vamos derivar a especificação aqui; nosso explicador da RFC 9457 aborda cada membro, as regras de registro e como ela substitui a RFC 7807. O que importa para o seu contrato é o padrão: envelope padrão, extensões personalizadas. Aqui está uma falha 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" }
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"
}

O array errors[] é um membro de extensão, e é o que os clientes mais gostam: ele permite que um frontend mapeie cada falha para o campo exato do formulário em vez de mostrar um banner vago. Mantenha os caminhos dos campos em um formato estável (JSON Pointer ou caminhos pontilhados, escolha um) para que o código do cliente possa vinculá-los programaticamente.

Uma regra te poupa da maior dor de cabeça: retorne este formato para cada erro, incluindo aqueles que seu framework ou gateway gera. Um cliente que recebe Detalhes do Problema de seus manipuladores, mas HTML da página 502 do seu balanceador de carga, ainda precisa escrever dois parsers.

Códigos legíveis por máquina vs. mensagens humanas

Observe que o exemplo contém ambos os campos code e message. Isso é deliberado. Eles atendem a públicos diferentes e nunca devem ser colapsados em uma única string.

Os códigos legíveis por máquina (AMOUNT_NOT_POSITIVE, CURRENCY_UNSUPPORTED, IDEMPOTENCY_KEY_REUSED) são contrato. Os clientes se baseiam neles, então eles devem ser estáveis, documentados e enumeráveis. Nunca faça os clientes analisarem prosa; no momento em que alguém escreve if (message.includes("positive")), sua edição de cópia se torna uma mudança quebra-código.

As mensagens humanas são o oposto: livres para serem melhoradas a qualquer momento, escritas para um desenvolvedor lendo logs e nunca de suporte. Indique o que falhou e como corrigi-lo: "o valor deve ser um inteiro positivo em unidades menores" é melhor do que "valor inválido". Se você localizar, localize a mensagem e deixe o código intocado.

Essa divisão importa ainda mais agora que os consumidores de API incluem agentes autônomos. Clientes baseados em LLM se recuperam muito melhor de erros estruturados e auto-descritivos; abordamos esse ângulo em design de mensagens de erro de API para agentes de IA.

O que nunca vai em uma resposta de erro

As respostas de erro são um canal de reconhecimento favorito para atacantes, porque falhas não tratadas tendem a ser verbosas. Seu middleware de erro deve garantir que nenhum dos seguintes itens jamais chegue a um cliente:

O padrão é simples: capture tudo na fronteira, registre a exceção completa no lado do servidor com um ID de requisição e retorne um corpo genérico de Detalhes do Problema com o mesmo ID. O cliente recebe "detail": "Um erro interno ocorreu", "request_id": "req_51ad0", seus logs recebem a verdade, e o suporte pode juntar os dois.

Marque erros como retentáveis ou terminais

Cada erro que você retorna responde a uma pergunta que o cliente está prestes a fazer: devo tentar isso novamente? Incorpore a resposta ao contrato em vez de deixar cada equipe de cliente adivinhar.

Os códigos de status carregam a semântica padrão. 429, 502, 503 e 504 são retentáveis com backoff exponencial e jitter. 500 é ambíguo, mas geralmente vale uma retentativa cautelosa. Quase todos os outros códigos 4xx são terminais: retentar um 401, 403, 404 ou 422 com a mesma requisição desperdiça cota e polui os logs. Os timeouts merecem atenção própria, já que a requisição pode ter sido bem-sucedida depois que o cliente desistiu; esse é o clássico problema de timeout da requisição 408, e é por isso que os endpoints de mutação devem aceitar chaves de idempotência para que um pagamento retentado não possa cobrar duas vezes.

Você também pode tornar a capacidade de retentar explícita com um membro de extensão:

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

Uma flag explícita retryable permite que você substitua os padrões quando necessário, como sinalizar um subcódigo 500 específico como terminal porque retentá-lo corrompe o estado. Documente a flag uma vez e todo SDK de cliente que você enviar terá um comportamento de backoff uniforme.

IDs de correlação e versionamento do contrato de erro

Duas decisões menores completam o contrato, e ambas são baratas agora, caras depois.

Dê um ID a cada requisição. Aceite um cabeçalho X-Request-Id de entrada (ou gere um), estampe-o em cada linha de log e ecoe-o em cada corpo de erro como request_id. Quando um cliente cola um erro em um tíquete de suporte, esse único campo transforma uma hora de busca de logs em uma única consulta. Em configurações distribuídas, propague um traceparent W3C junto com ele para que o ID siga a requisição por todos os serviços.

Versione seu contrato de erro como a própria API. Adicionar um novo membro de extensão ou um novo código de erro é seguro. Renomear errors[].field, mudar o significado de um código ou passar de um formato ad-hoc para Detalhes do Problema é uma quebra, e quebra os caminhos de código que as equipes menos testam. A URI type oferece um mecanismo limpo: mantenha as URIs de tipo antigas estáveis para sempre, introduza novas para novas semânticas e declare em sua documentação que membros de extensão desconhecidos e códigos desconhecidos devem ser ignorados, não tratados como falhas. Essa cláusula de compatibilidade com versões futuras é o que permite que você evolua sem um v2.

Teste cada caminho de erro no Apidog

Aqui está a verdade incômoda: os contratos de erro apodrecem porque nada os exercita. O caminho feliz é executado em todas as demos; o branch 422 é executado quando um cliente o atinge. A solução é tornar os casos de falha cidadãos de primeira classe em seu conjunto de testes, e é aqui que o Apidog ganha seu lugar no fluxo de trabalho.

Duas funcionalidades se mapeiam diretamente para este problema.

Cenários de teste para o lado do servidor. Para cada endpoint, construa um cenário por caso de falha: autenticação ausente espera 401, função insuficiente espera 403, valor negativo espera 422 com errors[0].code igual a AMOUNT_NOT_POSITIVE, tráfego intenso espera 429 com um cabeçalho Retry-After. As asserções visuais do Apidog verificam status, cabeçalhos e campos do corpo sem script, e você pode validar todo o payload contra seu esquema JSON de Detalhes do Problema para que qualquer desvio no formato do erro falhe no CI, não na produção. Nosso guia de asserções de API mostra os padrões de asserção em detalhes.

Servidores mock para o lado do cliente. Suas equipes de frontend e SDK precisam construir contra respostas 4xx e 5xx antes que o backend possa produzi-las sob demanda. Os servidores mock do Apidog retornam os corpos exatos dos Detalhes do Problema de sua especificação de API, então você pode simular um 503 com Retry-After: 120, um 409 em dupla submissão, ou um payload de validação completo de errors[], e então observar como o cliente renderiza e retenta. Sem stubs Express feitos à mão, sem comentar código de backend para forçar uma falha.

Projete o contrato de erro, codifique-o como cenários e mocks, e conecte ambos ao CI. Baixe o Apidog e experimente gratuitamente; importar uma especificação OpenAPI existente permite obter respostas de erro mockáveis em poucos minutos.

botão

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 errado, um campo obrigatório faltando. Use 422 quando a requisição for analisada corretamente, mas os valores violarem suas regras de domínio, como um valor de pagamento negativo ou uma moeda não suportada. O benefício prático é diagnóstico: um 422 diz ao cliente "corrija seus dados", enquanto um 400 diz "corrija o formato da sua requisição". Qualquer que seja a divisão que você escolher, aplique-a consistentemente em todos os endpoints.

O que é application/problem+json?

É o tipo de mídia definido pela RFC 9457 para Detalhes do Problema, o formato de erro JSON padrão para APIs HTTP. Uma resposta com este tipo de conteúdo carrega os membros type, title, status, detail e instance, além de quaisquer extensões que você defina, como um array errors[] para falhas de validação em nível de campo. Usar o tipo de mídia registrado permite que clientes genéricos e middleware reconheçam seus erros sem configuração personalizada. Nosso explicador da RFC 9457 cobre a especificação completa.

Quais erros HTTP os clientes devem retentar automaticamente?

Retente 429, 502, 503 e 504 com backoff exponencial mais jitter, respeitando Retry-After quando presente. Trate 500 como valendo uma retentativa cautelosa. Não retente outras respostas 4xx; a requisição falhará da mesma forma todas as vezes. Para endpoints de mutação, emparelhe as retentativas com chaves de idempotência para que uma requisição repetida não possa cobrar ou criar duas vezes.

Como testar respostas de erro de API sem quebrar meu backend?

Simule-as. Aponte seu cliente para um servidor mock do Apidog que retorna os corpos 4xx e 5xx exatos da sua especificação, e então verifique a renderização e o comportamento de retentativa contra cada um. No lado do servidor, escreva cenários de teste que enviem payloads inválidos, autenticação ausente e tráfego intenso, e então faça asserções nos códigos de status, cabeçalhos e no esquema do corpo do erro. Ambas as partes são executadas no CI, então o contrato de erro permanece íntegro sem que ninguém force falhas manualmente.

Pratique o design de API no Apidog

Descubra uma forma mais fácil de construir e usar APIs