Integração e APIs20 min

Códigos HTTP em uma API de negócio: o que responder quando se busca um contato e quanto custa errar

Por · fundador e CEO da Hábil ·

Quando responder 200, 404, 400, 422, 409, 429 ou 503 ao buscar um contato e quanto custa errar: novas tentativas, painéis enganosos e agentes.

Um integrador da empresa chama uma API para buscar um contato pelo e-mail. A resposta chega com o código 200 e um corpo que diz {"erro": "contato não encontrado"}. O programa do integrador vê o 200, dá a operação por boa e segue em frente com um contato que não existe. Ninguém percebe até que, semanas depois, alguém pergunta por que faltam pedidos.

Nada falhou na rede, nem no servidor, nem no banco de dados. Falhou uma decisão que parece menor: que número colocar na primeira linha da resposta. O código de status é a única coisa que os balanceadores de carga, os painéis de monitoramento, as bibliotecas de nova tentativa, os gateways e os programas dos parceiros leem sem interpretar. Uma frase no corpo é entendida por uma pessoa; o número é entendido por todo o resto.

Este artigo é para CTOs e líderes de tecnologia, e foi escrito para que um diretor que o leia por alto leve o essencial nas tabelas e nos fechamentos de seção. Percorre os códigos que de fato se usam em uma API de negócio com um único exemplo, buscar um contato, e explica quando cabe cada um e quanto custa errar. As afirmações técnicas se apoiam nos padrões do HTTP e são citadas no final; os critérios de projeto que são nossos aparecem marcados como tais.

Uma resposta HTTP traz um código, alguns cabeçalhos e um corpo. O código e os cabeçalhos são sinais padronizados que muitas ferramentas usam para decidir; o corpo é lido por uma pessoa ou, se o contrato o definir, por um programa.

Códigos HTTP em uma API de negócio: o que responder quando se busca um contato e quanto custa errar
Se a resposta é…A família é…Regra geral
Um sucesso real2xxSeguir em frente
Uma requisição que precisa ser corrigida4xxCorrigir, identificar-se ou esperar, conforme o código e o contrato
Uma falha do servidor ou de uma dependência5xxTentar de novo apenas se a operação for repetível ou o contrato oferecer idempotência verificável

As três partes de uma resposta, e por que o número manda

No HTTP/1.1, a resposta começa com uma linha de status: o código e, opcionalmente, uma frase; no HTTP/2 e nas versões posteriores, o código viaja como metadado da resposta, com o mesmo significado. O padrão esclarece que as frases são apenas recomendações: podem ser trocadas por equivalentes locais ou omitidas sem afetar o protocolo [1, §15.1]. O que conta é o número.

O primeiro dígito define a família, e o padrão exige que todo cliente a entenda, mesmo sem conhecer o código exato [1, §15]:

As três partes de uma resposta, e por que o número manda
FamíliaSignificado no padrãoO que diz a quem chama
2xxA requisição foi recebida, entendida e aceitaSiga em frente
3xxÉ preciso outra ação para completá-laVá a outro endereço ou reutilize o que já tem
4xxA requisição contém um erro ou não pode ser atendidaVerifique o código específico e o contrato; repeti-la idêntica raramente ajuda
5xxO servidor falhou ao atender uma requisição aparentemente válidaVerifique o código específico e o contrato; não repita às cegas uma escrita não idempotente

Há uma regra que explica por que escolher bem importa mais do que escolher com detalhe: um cliente que receba um código que não reconhece deve tratá-lo como o x00 da família. O próprio padrão ilustra: um hipotético 471 é tratado como um 400 [1, §15]. Dito de outro modo, a família é o mínimo que toda máquina vai entender. Se uma API responde 200 a um erro, a família que as máquinas leem é a do sucesso.

Os cabeçalhos completam o número. Alguns são obrigatórios com certos códigos: um 401 deve levar WWW-Authenticate e um 405 deve levar Allow com os métodos que o recurso de fato aceita [1, §15.5.2 e §15.5.6]. E o corpo, como se verá adiante, convém que tenha um formato padrão, para que um programa não precise adivinhar.

Quantas das respostas de erro das APIs atuais da empresa se distinguem de um sucesso olhando apenas a primeira linha?

O caso: buscar um contato

Imagine uma API de contatos de uma empresa, para uso dos canais e dos parceiros dela. Há duas formas de encontrar alguém:

GET /contatos/{id}                      -> obter um contato pelo identificador
GET /contatos?email=ana@exemplo.com     -> buscar contatos por um critério

Parecem a mesma coisa. Não são, e a diferença já produz a primeira decisão. Com o identificador, quem chama afirma «este contato existe; entregue-o». Com a busca, pergunta «existe algum que cumpra isto?». E a resposta correta a uma pergunta que admite «nenhum» não é um erro.

Um contrato em estilo OpenAPI, simplificado, para ter o mapa completo antes de percorrê-lo:

paths:
  /contatos/{id}:
    get:
      summary: Obter um contato pelo identificador
      responses:
        "200": { description: O contato }
        "400": { description: O identificador não tem forma válida }
        "401": { description: Falta identificar-se }
        "403": { description: Identificado, mas sem permissão }
        "404": { description: Não existe, ou não se revela que exista }
        "410": { description: Existiu e foi retirado de forma definitiva }
        "429": { description: Requisições demais; ver Retry-After }
        "503": { description: Serviço indisponível por ora; ver Retry-After }
  /contatos:
    get:
      summary: Buscar contatos por critério
      responses:
        "200": { description: Lista de correspondências (pode estar vazia) }
        "400": { description: Critério ilegível ou parâmetro desconhecido }
        "422": { description: Critério bem formado, mas que o negócio não admite }
    post:
      summary: Criar um contato
      responses:
        "201": { description: Criado; o cabeçalho Location aponta para ele }
        "409": { description: Já existe um que colide com este }
        "422": { description: Dados bem formados, mas que descumprem uma regra }

Percorremos os códigos da menor para a maior gravidade, começando pelo que dá certo.

Se a equipe tivesse de desenhar este mapa para a API de clientes ou de apólices, todos concordariam sobre quantas respostas distintas ela deve poder dar?

Quando dá certo: 200, e a tentação de usá-lo para tudo

A busca sem correspondências merece cuidado. Uma requisição como esta:

curl -i "https://api.exemplo.com/contatos?email=ninguem@exemplo.com"

deveria responder:

HTTP/1.1 200 OK
Content-Type: application/json

{ "resultados": [], "total": 0 }

O 200 é o código do sucesso de uma consulta, e o padrão define o conteúdo dele para GET como a representação do recurso-alvo [1, §15.3.1]. Aqui o recurso-alvo é a busca, que existe e foi executada corretamente; o resultado dela é uma lista vazia. É uma leitura razoável da definição e é um critério de projeto nosso: o que importa é que se decida, se documente e se aplique da mesma forma em todas as rotas. Se uma rota devolve lista vazia e outra devolve 404 para o mesmo tipo de pergunta, o cliente terá de escrever um caso especial por rota.

O contrário, o erro disfarçado de sucesso, é a fonte das falhas mais caras de detectar:

HTTP/1.1 200 OK
Content-Type: application/json

{ "erro": "contato não encontrado" }

Um monitor que olha apenas o código de status vai marcar isto como saudável. Uma biblioteca de novas tentativas vai dar o caso por resolvido. Um parceiro que integra a API só validará o corpo se alguém lhe disser para fazê-lo. Um 200 com um erro dentro é uma falha que entrou pela porta do escritório com o crachá do sucesso.

Há três irmãos do 200 que convém distinguir quando o caso pede, aos quais dedicaremos uma só linha cada: 201, quando algo foi criado (normalmente com a localização do novo recurso em Location); 202, quando o trabalho foi aceito mas ainda não terminou (se o contrato publica uma consulta de status, é ali que se verifica); e 204, quando deu certo e não há nada a devolver, por exemplo ao retirar um contato [1, §15.3].

A organização sabe que percentual do que os painéis chamam de «sucesso» inclui corpos com erros dentro?

Quando o cliente errou: a família 4xx

A família 4xx diz «o problema está na requisição». Para escolher dentro dela, pode servir uma política de precedência, isto é, uma ordem de perguntas que reduz as discussões. É critério nosso, não mandato do padrão, e um contrato pode alterá-la (por exemplo, autenticar antes de validar é uma decisão de segurança legítima):

  1. A requisição é compreensível? (400)
  2. Sabemos quem é? (401)
  3. Tem permissão? (403)
  4. Existe o que pede? (404, 410)
  5. O método é permitido sobre este recurso? (405)
  6. Vem em um formato que aceitamos? (415)
  7. O conteúdo, já compreendido, respeita as regras de negócio? (422)
  8. Colide com o que já está gravado? (409)

Com essa política, quando uma requisição tem dois defeitos, responde o primeiro da lista que falha. Isso torna as respostas previsíveis, e o que é previsível pode ser testado e documentado.

400: não se entende

O padrão descreve o 400 como a resposta para quando o servidor não pode ou não quer processar a requisição por algo que percebe como erro do cliente: por exemplo, sintaxe malformada [1, §15.5.1]. No caso do contato:

curl -i "https://api.exemplo.com/contatos/abc%20%20%20"        # identificador ilegível
curl -i "https://api.exemplo.com/contatos?email=&cor=azul"     # parâmetro desconhecido

Se o contrato rejeita esses valores, ambas podem responder 400. O sinal é que a requisição não pôde ser lida como aquilo que diz ser. Repeti-la tal qual daria o mesmo resultado, e por isso um cliente bem feito não a repete: corrige-a ou a mostra a uma pessoa.

401 e 403: quem é e o que pode fazer

São duas perguntas distintas e costumam ser confundidas. O 401 diz que a requisição não inclui credenciais válidas, e o padrão obriga a incluir WWW-Authenticate [1, §15.5.2]. O 403 diz que o servidor entendeu a requisição, mas se recusa a atendê-la; se credenciais já foram enviadas, o cliente não deveria repetir automaticamente com as mesmas [1, §15.5.4].

A diferença tem consequências de operação. Diante de um 401, um cliente pode renovar a sessão e repetir. Diante de um 403, repetir com as mesmas credenciais não muda o resultado. Um servidor que confunde os dois põe os clientes num laço de renovar credenciais que não resolve nada.

Há um matiz de segurança que importa numa API de contatos: o padrão permite que um servidor que queira ocultar a existência de um recurso proibido responda 404 em vez de 403 [1, §15.5.4]. Se o contato de um cliente só é visível para o dono, dizer a outro cliente «proibido» confirma a ele que esse identificador existe. Responder «não encontrado» não confirma nada. É uma decisão de política que convém registrar por escrito, não improvisar rota a rota.

404 e 410: não está

O 404 indica que não se encontrou uma representação vigente do recurso ou que o servidor não quer revelar que ele existe; não diz se a ausência é temporária ou permanente [1, §15.5.5]. Se o servidor sabe que é permanente, o padrão prefere o 410 [1, §15.5.11].

Em um contato, essa diferença tem valor de negócio. «Esse identificador não existe» (404) e «existiu e foi fundido com outro ou retirado» (410) levam a ações distintas do lado do cliente: no primeiro, ele verifica se houve erro de digitação; no segundo, deixa de pedi-lo e procura o contato que o substituiu. Se a operação não distingue os dois casos, usa-se 404, que é o padrão, como o próprio padrão recomenda quando o servidor não sabe se a condição é permanente [1, §15.5.11].

Um dado prático que surpreende: o 404 está entre os códigos que o padrão qualifica como armazenáveis em cache por heurística [1, §15.1]. Uma resposta de «não existe» pode ficar guardada em um cache intermediário, de modo que um contato recém-criado continua sem aparecer por algum tempo. Se a criação e a consulta ocorrem em sequência imediata, convém revisar os cabeçalhos de cache dessa resposta.

405 e 415: a rota existe, a forma não

Um 405 diz que o método é conhecido, mas este recurso não o admite, e exige o cabeçalho Allow com o que admite [1, §15.5.6]. Um 415 diz que o formato do conteúdo enviado não é aceito. São erros de integração, não de negócio, e aparecem sobretudo nos primeiros dias de uma conexão nova.

Quando um parceiro recebe um erro da API, ele consegue saber pela primeira linha se lhe convém corrigir a requisição, renovar a sessão ou deixar de pedir esse recurso?

400 contra 422: a diferença mais discutida

A discussão mais frequente nas equipes é qual usar quando os dados chegam errados. A política que sugerimos para este contrato (não é uma regra universal do HTTP), apoiada nas definições do padrão, é curta:

400 contra 422: a diferença mais discutida
SituaçãoCódigoPor quê
O JSON está quebrado, falta uma chave de fechamento ou um parâmetro não existe400A requisição não pode ser lida [1, §15.5.1]
O JSON é válido e o formato é aceito, mas o e-mail não tem arroba ou o telefone não cumpre a regra do país422O servidor entende o conteúdo, a sintaxe está correta, mas não pode processar o que se pede [1, §15.5.21]
O conteúdo é válido, mas colide com o que já está gravado (outro contato com o mesmo e-mail)409Conflito com o estado atual do recurso [1, §15.5.10]

O 422 é definido como a resposta em que o servidor entende o tipo de conteúdo da requisição (por isso o 415 não se aplica) e a sintaxe está correta, mas não conseguiu processar as instruções contidas [1, §15.5.21]. Muitas equipes usam 400 para tudo, e isso não é um erro de protocolo: o 400 é amplo. A vantagem de separar é de operação: com 400, o cliente sabe que o código dele produziu algo ilegível (um defeito de integração); com 422, sabe que os dados não passaram por uma regra (um caso para mostrar a um usuário). Duas causas distintas, dois reparos distintos, duas equipes distintas atendendo o alerta.

Um exemplo de criação com um dado inválido:

curl -i -X POST https://api.exemplo.com/contatos \
  -H "Content-Type: application/json" \
  -d '{"nome": "Ana Pereira", "email": "ana-at-exemplo"}'
HTTP/1.1 422 Unprocessable Content
Content-Type: application/problem+json

{
  "type": "https://api.exemplo.com/problemas/email-invalido",
  "title": "O e-mail não tem um formato válido",
  "status": 422,
  "detail": "O campo 'email' deve conter um endereço de e-mail.",
  "instance": "/contatos"
}

O formato do corpo não é casual; é explicado na seção sobre Problem Details.

Se hoje a API responde 400 a tudo, a equipe de suporte consegue distinguir uma integração mal feita de um dado mal digitado sem abrir os logs?

409: quando colide com o que já existe

O 409 se reserva para a requisição que não pode ser concluída por um conflito com o estado atual do recurso, em situações nas quais quem chama poderia resolvê-lo e reenviar; o padrão pede que o servidor inclua informação suficiente para reconhecer a origem do conflito [1, §15.5.10].

Em contatos, aparece de duas formas. A primeira é a óbvia: criar um contato cujo e-mail já pertence a outro. A segunda é mais cara e menos visível: dois canais editam o mesmo contato quase ao mesmo tempo. Se ambos fazem PUT com a versão completa que leram, o que chega em segundo lugar apaga em silêncio a alteração do primeiro. Ninguém viu um erro; o sistema gravou o que lhe pediram.

O padrão do HTTP tem ferramentas para isso: as requisições condicionais. Um contrato pode devolver uma etiqueta de versão (ETag) em cada leitura e exigir que o cliente a devolva em If-Match ao modificar; se a versão já mudou, a resposta é 412 Precondition Failed [1, §15.5.13] e, se o cliente nem sequer enviou a condição e o servidor a exige, 428 Precondition Required, pensado justamente para evitar a «atualização perdida» [3, §3]. É a forma de transformar uma alteração perdida em um erro visível, em vez de um buraco silencioso. O detalhe de cada um desses dois códigos fica fora deste artigo; o que importa aqui é o princípio: um conflito pode ser detectado, e detectá-lo é mais barato do que descobri-lo em uma auditoria.

O que acontece nos sistemas da empresa quando dois canais modificam o mesmo cliente ao mesmo tempo: há um erro visível ou vale a última gravação?

429, 500, 502, 503 e 504: quando o problema é do servidor, ou da capacidade dele

A família 5xx é onde mais dinheiro custa escolher mal, porque o efeito prático é dizer às máquinas «pode ser boa ideia tentar de novo». Tentar de novo da maneira certa salva uma operação; tentar da maneira errada a duplica ou derruba o servidor.

429: requisições demais

Embora não esteja no padrão principal do HTTP, e sim em uma extensão, o 429 é a ferramenta correta para limitar o ritmo. Indica que quem chama enviou requisições demais em um período, e pode incluir Retry-After para dizer quanto esperar; a mesma especificação esclarece que não define como o usuário é identificado nem como as requisições são contadas, e que essas respostas não devem ser guardadas em cache [3, §4].

HTTP/1.1 429 Too Many Requests
Retry-After: 30
Content-Type: application/problem+json

{ "type": "https://api.exemplo.com/problemas/limite-excedido",
  "title": "O limite de requisições foi excedido",
  "status": 429 }

Note que o 429 pertence à família dos erros do cliente, embora trate de capacidade; pode refletir uma cota ou política compartilhada, e não apenas o ritmo de quem chama. E note o Retry-After, que é opcional: se vier, deve ser respeitado; se não vier, segue-se a política documentada, porque sem nenhum sinal o cliente adivinha. Em uma integração que consulta contatos em lote, a diferença é a que existe entre um cliente que se acomoda e outro que insiste a cada segundo e agrava a saturação que se queria evitar.

500: não esperávamos isto

O 500 indica uma condição inesperada que impediu o cumprimento da requisição [1, §15.6.1]. É o código de «ocorreu uma condição inesperada do nosso lado»; o servidor pode conhecer a causa, mas a resposta não deve divulgá-la. Duas regras para um 500 saudável. Primeira: o corpo não deve mostrar a pilha de execução, a consulta, o caminho de arquivos nem chaves; o padrão de Problem Details alerta expressamente contra expor detalhes de implementação, como um despejo de pilha, porque podem revelar informação explorável [2, §5]. Segunda: a mensagem deve ser geral e trazer um identificador que a equipe possa buscar nos logs.

502 e 504: um intermediário não obteve resposta

Quando a API depende de outro sistema (o CRM, o ERP, o core) e atua como gateway ou proxy, dois códigos descrevem o que ocorre entre os dois (em outras topologias, o contrato pode escolher outra resposta). O 502 é a resposta quando o servidor recebeu uma resposta inválida do sistema de trás; o 504, quando não recebeu resposta a tempo [1, §15.6.3 e §15.6.5].

Um 502 ou um 504 em uma escrita é especialmente delicado, e o mesmo vale para um 500 ou um 503 que chegue depois de a operação ter sido aplicada, porque não diz se a operação foi aplicada ou não. Se o cliente repete às cegas o cadastro de um contato, pode criar dois. A saída está na seção sobre novas tentativas.

503: agora não posso, volte daqui a pouco

O 503 indica que o servidor não pode atender a requisição neste momento, por sobrecarga temporária ou manutenção programada, algo que provavelmente se alivie com o tempo; pode vir acompanhado de Retry-After [1, §15.6.4]. O mesmo padrão esclarece que a existência do código não obriga um servidor sobrecarregado a usá-lo: alguns simplesmente recusam a conexão [1, §15.6.4].

Retry-After aceita um número de segundos ou uma data [1, §10.2.3]. Com um 503, indica por quanto tempo se espera que o serviço fique indisponível para o cliente [1, §10.2.3]. Em uma manutenção planejada, esse dado permite que as integrações dos parceiros se pausem em ordem, em vez de falhar em cascata.

Resumo da família, com a pergunta que mais dói em cada caso:

503: agora não posso, volte daqui a pouco
CódigoO que dizO que quem chama deveria fazerO erro típico
429Está indo rápido demaisEsperar o que disser Retry-After e voltarNão enviar Retry-After; o cliente insiste
500Falhou algo inesperadoTentar de novo com cautela se a operação for repetível; avisarMostrar pilha, consulta ou caminhos
502O sistema de trás respondeu algo inválidoVerificar se a operação foi aplicada antes de repetirRepetir um cadastro às cegas
503Agora não posso atenderEsperar e tentar de novoDevolver 200 com «em manutenção» no corpo
504O sistema de trás não respondeu a tempoVerificar o estado antes de repetirConfundi-lo com uma falha do cliente

Quando um dos sistemas internos fica lento, as APIs dizem a quem chama quanto esperar ou deixam a decisão por conta da sorte?

Problem Details (RFC 9457): um corpo de erro que um programa consegue ler

Com os códigos escolhidos, falta o corpo. Sem acordo, cada API inventa um formato próprio: {"erro": "..."}, {"mensagem": "..."}, {"codigo": 7}. O padrão RFC 9457 (que substitui o 7807) define um formato comum para isso [2]. Identificado pelo tipo de mídia application/problem+json, define cinco membros básicos que podem ser incluídos [2, §3.1]:

Problem Details (RFC 9457): um corpo de erro que um programa consegue ler
MembroPara que serve
typeUm identificador do tipo de problema, em forma de URI; os consumidores devem usá-lo como identificador principal do problema [2, §3.1.1]
titleUm resumo breve do tipo de problema, legível por pessoas [2, §3.1.3]
statusO código HTTP; é informativo, e o gerador deve usar o mesmo código na resposta real [2, §3.1.2]
detailUma explicação desta ocorrência concreta; os consumidores não devem analisá-la para extrair informação [2, §3.1.4]
instanceUm identificador desta ocorrência, para rastreá-la [2, §3.1.5]

Se não há type, assume-se about:blank, que significa que o problema não tem mais semântica além da do código HTTP [2, §3.1.1 e §4.2.1]. Os clientes devem ignorar os membros de extensão que não reconheçam [2, §3.2], o que permite acrescentar campos próprios (por exemplo, a lista de campos inválidos) sem quebrar ninguém.

O valor para o negócio está em duas regras. Primeira: o programa decide pelo type, não pela frase. Se a frase de detail muda de «tente de novo» para «tente mais tarde», o comportamento do cliente não deve mudar. Segunda: o status do corpo e o código da primeira linha devem coincidir; o próprio padrão adverte que duplicar a informação abre a possibilidade de as duas divergirem [2, §5]. Um erro com status: 404 no corpo sobre uma resposta 200 é o caso do início, vestido de formato padrão.

Com um type estável (e normalmente um title estável), um mesmo problema produz o mesmo identificador; detail, instance e as extensões podem variar por ocorrência. Quem gera a resposta é quem deve fazer o status coincidir com o código; um intermediário pode alterá-la depois. Isso permite contar ocorrências por tipo em um painel, e não por texto.

A organização conseguiria hoje contar quantas vezes ocorreu um mesmo tipo de erro em uma semana, sem ler mensagens uma a uma?

Novas tentativas: o que se repete e o que não

Escolher o código é metade do trabalho. A outra metade é o que o cliente faz com ele. A regra de fundo vem do padrão de métodos: uma requisição é idempotente se repeti-la produz o mesmo efeito que fazê-la uma única vez. PUT, DELETE e os métodos seguros, como GET, são; POST não é [1, §9.2.2]. E o padrão indica que um cliente não deveria repetir automaticamente um método não idempotente, a menos que tenha como saber que a semântica é idempotente naquele caso ou como detectar que a requisição original não foi aplicada [1, §9.2.2].

Aplicado ao contato:

Novas tentativas: o que se repete e o que não
RequisiçãoPropriedade HTTP do métodoConsequência de errar
GET /contatos/{id}Seguro e idempotenteCarga adicional
PUT /contatos/{id} com o contato completoIdempotenteMesmo efeito pretendido
DELETE /contatos/{id}IdempotenteNa segunda vez pode responder 404/410, e está correto
POST /contatos (cadastro)Não idempotente por definiçãoDois contatos iguais, a menos que o contrato o evite

Para tornar o cadastro seguro existe uma prática difundida: o cliente envia uma chave de idempotência em um cabeçalho Idempotency-Key, de modo que o servidor reconheça a repetição. Uma nota de cautela: o cabeçalho é descrito em um rascunho do IETF (revisão 07) que, em 20 de outubro de 2026, consta como vencido (18-abr-2026) e não é um padrão publicado [4]; deve ser usado como referência, não como norma. Se o contrato adotar o padrão, convém documentar a retenção das chaves e das respostas. O que o rascunho propõe: a mesma chave com a mesma requisição devolve o resultado original; a mesma chave com outra carga, 422; e uma repetição enquanto a original ainda está em processamento, 409 [4].

A segurança de repetição depende também do contrato, dos efeitos colaterais e da incerteza da rede, não apenas do método. Sobre essa base, o cliente pode ter uma tabela de decisão por código. Uma versão comentada para o nosso caso:

Novas tentativas: o que se repete e o que não
RespostaAção do clientePor quê
200, 201, 204ContinuarDeu certo
400, 422Não repetir; corrigir ou mostrarRepetir igual dá no mesmo
401Renovar as credenciais uma vez; se voltar, pararUm laço de renovação não resolve nada
403Não repetir; escalarHá uma política que o nega
404, 410Não repetir; tratar «não existe» como um resultadoÉ informação, não uma falha
409, 412Reler o estado, decidir, reenviarO estado mudou desde a leitura
429Esperar Retry-After e voltarRespeita a capacidade do servidor
502, 504 em escritaConsultar o estado antes de repetirPode ter sido aplicada
500, 503Somente se o método for idempotente ou o contrato oferecer idempotência verificável: repetir com espera crescente, aleatória e com teto, respeitando Retry-After se vier; caso contrário, consultar o estadoFalha provavelmente temporária, mas uma escrita pode ter sido aplicada
Código não reconhecidoTratá-lo como o x00 da família [1, §15]Regra do padrão

A linha de 500 e 503 pede um esclarecimento: a espera crescente com um componente aleatório e um teto é uma prática comum de engenharia, não um mandato do padrão. O propósito é que mil clientes não voltem todos no mesmo segundo e derrubem de novo um serviço que estava se recuperando.

Se o fornecedor responde 503 durante quinze minutos, o que fazem as integrações: esperam em ordem, insistem sem parar ou se detêm sem avisar?

Quanto custa errar: três cenários

O que segue são cenários ilustrativos, sem cifras; servem para ver o mecanismo do dano.

1. A integração que repete o que não deve. Um aplicativo de vendas cadastra contatos na API. Um dia o banco de dados fica lento e a API responde 504 em alguns cadastros. O código do aplicativo trata todo 5xx como «tentar de novo», e alguns cadastros já haviam sido aplicados. Resultado: contatos duplicados, campanhas que escrevem duas vezes ao mesmo cliente e um trabalho de depuração que ninguém planejou. Se cliente, servidor e contrato aplicam a política de «verifique o estado antes de repetir» e uma chave de idempotência no cadastro, o mesmo incidente tem muito menos probabilidade de deixar duplicados.

2. O painel que parece verde. Uma equipe mede a saúde da API pela taxa de respostas 5xx. A API devolve 200 com um erro no corpo quando o CRM não responde. O painel mostra verde enquanto os canais não conseguem consultar contatos. No sentido contrário também há custo: se cada busca sem resultados é devolvida como 404, os consumidores da API acumulam «erros» que não são erros, e a equipe de plantão aprende a ignorar o alerta. Um painel útil precisa que o código signifique algo: 4xx para o que corrige quem chama, 5xx para o que corrige a equipe dona do serviço.

3. O agente que não sabe o que fazer. Cada vez mais APIs são consumidas por um programa que decide sozinho, incluindo assistentes e agentes de inteligência artificial que chamam ferramentas. Um agente que recebe 200 com a frase «não consegui encontrá-lo» precisa interpretar linguagem natural para saber se houve falha. Um que recebe 404 com type estável sabe que pode informar «não existe». Um que recebe 429 com Retry-After sabe quanto esperar; um que recebe 503 sem nenhuma indicação pode insistir ou desistir, conforme tenha sido programado. Um detail em linguagem livre não deve ser analisado para tomar decisões [2, §3.1.4]; o type, sim. Um código e um tipo bem escolhidos reduzem a ambiguidade que o agente precisa administrar. O mesmo tema, visto do ângulo do que o sistema precisa para que os agentes o usem, é tratado em «APIs prontas para agentes de IA».

Qual destes três cenários poderia estar ocorrendo hoje em alguma integração da empresa sem que a equipe saiba?

Um percurso completo: o contato, de ponta a ponta

Para fechar o exemplo, uma sequência com as respostas que daria uma API bem projetada. Cada linha é uma chamada hipotética contra o contrato acima.

Um percurso completo: o contato, de ponta a ponta
#ChamadaRespostaLeitura
1GET /contatos?email=ana@exemplo.com (existe)200, lista com um contatoSucesso da consulta
2GET /contatos?email=ninguem@exemplo.com200, lista vaziaA busca funcionou; não há correspondências
3GET /contatos/c-0001 (existe)200, o contato, com ETagSucesso com versão
4GET /contatos/c-9999 (não existe)404, Problem DetailsResultado, não falha
5GET /contatos/c-0002 (fundido com outro)410, Problem Details com o substitutoDeixar de pedi-lo
6GET /contatos/%%%400Requisição ilegível
7POST /contatos com e-mail sem arroba422Dado que não cumpre a regra
8POST /contatos com um e-mail já registrado409Colide com o que está gravado
9PUT /contatos/c-0001 com If-Match vencido412Alguém alterou antes
10Uma rajada de buscas que supera o limite combinado429 com Retry-AfterRitmo excedido
11GET /contatos/c-0001 com o CRM fora do ar503 com Retry-AfterVolte daqui a pouco
12POST /contatos e o CRM não responde a tempo504Consulte antes de repetir

Doze linhas e dez códigos distintos. As linhas 11 e 12 são uma política possível do contrato, para uma API que atua como intermediária. Uma tabela de contrato documentada e verificada reduz a ambiguidade entre equipes e transforma um acordo verbal em algo que pode ser conferido.

Existe hoje, para as APIs mais usadas, uma tabela assim (cenário, código, leitura) ou cada integrador a reconstrói por tentativa e erro?

Seis perguntas para a próxima revisão de API

Para levar à próxima reunião de arquitetura, sem necessidade de abrir código:

  1. Algum erro sai com 200? Peça uma amostra de respostas de erro e olhe a primeira linha.
  2. O «não encontrado» tem uma política única? A política distingue busca sem correspondências (200), recurso inexistente (404) e recurso retirado (410), e é aplicada de forma consistente a casos equivalentes em todas as rotas?
  3. Distingue-se 400 de 422 e de 409? Se tudo é 400, as equipes de suporte pagam a diferença.
  4. Os 429 e 503 dizem quanto esperar? Retry-After é opcional; quando falta, cada cliente decide por conta própria, de modo que convém documentar a política.
  5. Os erros têm um formato comum e um type estável? É o que permite a um painel, a um parceiro e a um agente decidir sem ler prosa.
  6. Os cadastros podem ser repetidos sem duplicar? Se não, cada 504 é um risco de dado em dobro.

Muitas decisões começam no contrato; a implementação pode exigir mudanças de aplicação ou de plataforma (limites de ritmo, persistência de chaves de idempotência, observabilidade).

Com quantas das seis perguntas a empresa se sentiria hoje confortável diante de uma auditoria ou de um parceiro que integra a API?

O que a Hábil faz

A Hábil trabalha no espaço entre os sistemas que já operam na empresa e as camadas novas que precisam deles: canais, parceiros, assistentes e agentes. Parte deste tema (que cada resposta de uma API diga com clareza o que aconteceu e o que fazer) é o que faz uma integração funcionar quando algo dá errado, e é parte do que oferecemos em Middleware e plataformas de API. Para que um assistente ou um agente de inteligência artificial use os sistemas com critério e limites claros, veja Inteligência artificial empresarial.

A primeira conversa, sem custo, parte do mapa das integrações e dos pontos em que uma resposta ambígua poderia custar dinheiro, tempo ou evidência. Escreva para nós pelo WhatsApp.

Consultadas em 20-out-2026 (os textos dos RFCs foram lidos na versão oficial do RFC Editor e os trechos citados foram conferidos).

Referências

  1. IETF, RFC 9110, HTTP Semantics (junho de 2022). Seções: §9.2.2 métodos idempotentes; §10.2.3 Retry-After; §15 e §15.1 classes de códigos, frases recomendadas, código não reconhecido tratado como x00, códigos armazenáveis em cache por heurística; §15.3.1 200; §15.5.1 400; §15.5.2 401; §15.5.4 403; §15.5.5 404; §15.5.6 405; §15.5.10 409; §15.5.11 410; §15.5.13 412; §15.5.21 422; §15.6.1 500; §15.6.3 502; §15.6.4 503; §15.6.5 504. https://www.rfc-editor.org/rfc/rfc9110.html
  2. IETF, RFC 9457, Problem Details for HTTP APIs (substitui o RFC 7807). §3 e §3.1 membros e tipo de mídia application/problem+json; §3.1.1 type; §3.1.2 status; §3.1.4 detail; §3.2 extensões; §4.2.1 about:blank; §5 considerações de segurança. https://www.rfc-editor.org/rfc/rfc9457.html
  3. IETF, RFC 6585, Additional HTTP Status Codes, §3 (428 Precondition Required) e §4 (429 Too Many Requests, Retry-After, sem armazenamento em cache). https://www.rfc-editor.org/rfc/rfc6585.html
  4. IETF HTTPAPI, The Idempotency-Key HTTP Header Field (Internet-Draft draft-ietf-httpapi-idempotency-key-header, revisão 07; rascunho, não padrão publicado; vencimento registrado em 18-abr-2026 no Datatracker). https://datatracker.ietf.org/doc/draft-ietf-httpapi-idempotency-key-header/