401 e 403: os nomes atrapalham
O 401 se chama Unauthorized, mas significa
não autenticado. É a confusão mais comum em API, e vem do
próprio nome do código.
| 401 | 403 | |
|---|---|---|
| Quer dizer | “não sei quem você é” | “sei quem você é, e você não pode” |
| Situação | sem token, token expirado, token inválido | token válido, permissão insuficiente |
| Adianta tentar de novo? | sim, com credencial válida | não, sem mudar a permissão |
| Exige cabeçalho | WWW-Authenticate |
— |
A regra prática: se um token novo resolveria, é 401. Se nem o
melhor token do usuário resolveria, é 403.
400 e 422: a discussão sem fim
A divisão que funciona é sintaxe contra semântica:
400 Bad Request — o servidor não conseguiu
nem entender. JSON malformado, parâmetro obrigatório ausente, tipo errado
onde o formato não permite. A requisição está quebrada.
422 Unprocessable Content — entendeu
perfeitamente, e os valores é que não servem. E-mail sem arroba, data de
nascimento no futuro, quantidade negativa. É erro de validação de negócio.
{"nome": "Ana", ← 400: falta fecha-chaves, nem parseia
{"email": "ana-arroba-exemplo"} ← 422: parseou, mas o valor não vale
400 numa rota
e 422 em outra pelo mesmo motivo.
Quando devolver 404 no lugar de 403
Esta é de segurança, e passa despercebida. Imagine que o usuário pede
/pedidos/8842, que existe mas é de outra pessoa. Se você
devolve 403, acabou de confirmar que o pedido 8842
existe. Repetindo isso em sequência, alguém mapeia o seu banco
inteiro sem nunca ver um dado.
Por isso muitas APIs devolvem 404 deliberadamente para recurso
que existe mas não pertence a quem pediu: do ponto de vista daquele
usuário, o recurso realmente não existe.
Use 403 quando a existência não é segredo — o usuário sabe que
a rota administrativa existe, só não pode entrar.
O anti-padrão do 200 com erro no corpo
Aparece muito, e é caro:
HTTP/1.1 200 OK
{"success": false, "error": "usuário não encontrado"}
O status é a única parte da resposta que a infraestrutura entende sem abrir
o corpo. Devolvendo 200 num erro, você quebra quatro coisas de
uma vez:
- Monitoramento mostra 100% de sucesso enquanto a API falha.
- Retry automático não acontece, porque cliente nenhum tenta de novo depois de um 200.
- Cache guarda a resposta de erro e serve para os próximos.
- Quem consome precisa ler o corpo para saber se deu certo — e um dia esquece.
Os cabeçalhos que alguns status exigem
Devolver o código sem o cabeçalho companheiro deixa o cliente sem a informação que ele precisa para agir:
| Status | Cabeçalho | Para quê |
|---|---|---|
201 | Location | onde o recurso criado ficou |
401 | WWW-Authenticate | como se autenticar |
405 | Allow | quais métodos a rota aceita |
429 | Retry-After | quanto esperar |
503 | Retry-After | idem, em manutenção |
Erro de servidor: 500, 502, 503 ou 504
Os quatro dizem “não é culpa sua”, mas contam histórias diferentes — e a diferença importa para quem está de plantão:
| Status | O que houve | Onde olhar |
|---|---|---|
500 | exceção não tratada no seu código | o log da aplicação |
502 | a dependência respondeu coisa inválida, ou nem conectou | a aplicação por trás do proxy |
503 | fora do ar de propósito, ou sobrecarregado | o deploy e a capacidade |
504 | a dependência não respondeu a tempo | a lentidão, não o timeout |
Se o seu proxy está devolvendo 502, o
diagnóstico de Nginx 502
identifica qual das treze causas é pela linha do log.
Os três que são fáceis de errar
204 No Content não pode ter corpo. Se você
precisa devolver alguma coisa, é 200.
202 Accepted é para processamento
assíncrono: aceitei, mas ainda não fiz. Devolva junto uma forma de
acompanhar. Usar 202 para algo já concluído engana quem
consome.
409 Conflict é conflito de
estado, não de validação: cadastro com e-mail que já existe,
atualização em cima de versão desatualizada. Se é só campo inválido, é
422.
Outras ferramentas: erro de CORS, JWT rejeitado, auditoria de Dockerfile.