Programação Por Equipe DevBras

Capa do artigo: Códigos de status HTTP que mudam o que o cliente faz em seguida

Códigos de status HTTP que mudam o que o cliente faz em seguida

Escolha o status certo para cada resposta da sua API, de 200 a 503, entenda como clientes e robôs reagem a cada faixa e pare de devolver 200 com erro no corpo.

A API devolveu 200 com um JSON dizendo "erro: falha ao salvar". O aplicativo mostrou "salvo com sucesso", a fila não tentou de novo e o monitoramento ficou verde. O status mentiu, e todo mundo que confiou nele fez a coisa certa. A resposta curta: o código de status é a instrução para quem chamou, e o corpo é o detalhe. Use 2xx só quando deu certo, 4xx quando o pedido do cliente tem problema e repetir igual não vai adiantar, e 5xx quando a falha é do servidor e tentar de novo mais tarde pode funcionar.

Gente lê a mensagem. Programa lê o número. Os dois precisam concordar.

Por que o número importa mais que a mensagem

Quem consome uma resposta HTTP raramente é só uma pessoa. É o fetch do front-end, uma biblioteca de cliente com política de nova tentativa, um balanceador de carga, um proxy de cache, um robô de busca e o seu próprio monitoramento. Nenhum deles lê o texto do JSON. Todos decidem pelo status.

  • Bibliotecas de nova tentativa repetem em 5xx e em 429, e em geral não repetem em 4xx.
  • Caches podem guardar respostas 200, 301 e 404, entre outras. Um 200 com erro dentro pode ficar guardado e ser servido para outras pessoas.
  • Robôs de busca trocam a URL indexada em 301 e 308, retiram páginas em 404 e 410 e voltam mais tarde em 503.
  • Painéis de monitoramento contam taxa de erro pela faixa 5xx. Erro disfarçado de 200 não aparece em gráfico nenhum.

As faixas têm significado próprio, definido no padrão HTTP atual, a RFC 9110: 1xx é informativo, 2xx é sucesso, 3xx é redirecionamento, 4xx é erro do cliente e 5xx é erro do servidor. A pergunta que decide a faixa é simples: se o cliente repetir exatamente o mesmo pedido, pode dar certo?

Os códigos que você mais vai usar

Sucesso

  • 200 OK: deu certo e há conteúdo na resposta.
  • 201 Created: um recurso novo nasceu. Inclua o cabeçalho Location com o endereço dele.
  • 204 No Content: deu certo e não há corpo, comum em exclusão.

Redirecionamento

  • 301 e 308: a URL mudou de vez. O 308 garante que o método e o corpo se mantêm, o que importa num POST.
  • 302 e 307: mudança temporária. O 307 também preserva o método.
  • 303 See Other: depois de um POST de formulário, manda o navegador buscar uma página de resultado com GET.
  • 304 Not Modified: o cliente já tem a versão atual em cache.

Erro do cliente

  • 400 Bad Request: o pedido veio malformado, como JSON inválido.
  • 401 Unauthorized: falta identidade ou ela é inválida, como token ausente ou expirado.
  • 403 Forbidden: a identidade existe, mas não tem permissão para aquilo.
  • 404 Not Found: o recurso não está lá.
  • 409 Conflict: o estado atual não aceita a ação, como pagar um pedido já cancelado.
  • 422 Unprocessable Content: o formato está certo, mas o conteúdo não passa na validação, como um e-mail sem arroba.
  • 429 Too Many Requests: limite de taxa atingido. Mande Retry-After dizendo quando tentar.

Erro do servidor

  • 500 Internal Server Error: falha inesperada no seu código.
  • 502 Bad Gateway e 504 Gateway Timeout: o proxy na frente não recebeu resposta válida, ou não recebeu a tempo, do serviço atrás dele.
  • 503 Service Unavailable: indisponível por agora, em manutenção ou sobrecarga. Também aceita Retry-After.

A separação entre 401 e 403 é a que mais se confunde. 401 diz "não sei quem você é", e o cliente deve se autenticar ou renovar o token. 403 diz "sei quem você é e a resposta é não", e renovar o token não muda nada.

Como aplicar numa API, passo a passo

  1. Liste os resultados possíveis de cada rota: sucesso, entrada inválida, sem permissão, não encontrado, conflito de estado, falha interna.
  2. Associe um status a cada resultado, usando a pergunta "repetir igual adianta?".
  3. Padronize o corpo de erro, com um código legível por máquina e uma mensagem para gente. O formato application/problem+json, da RFC 9457, é uma boa base.
  4. Faça o tratamento de erro global devolver 500 para exceção não tratada, sem vazar stack trace.
  5. Escreva testes que verificam o status, não só o corpo.

Um exemplo em Node.js com Express, numa rota que paga um pedido:

app.post('/pedidos/:id/pagamento', async (req, res, next) => {
  try {
    const { valorCentavos } = req.body ?? {};
    if (!Number.isInteger(valorCentavos) || valorCentavos <= 0) {
      return res.status(422).json({ erro: 'valor_invalido', mensagem: 'Informe o valor em centavos.' });
    }

    const pedido = await pedidos.buscar(req.params.id);
    if (!pedido) {
      return res.status(404).json({ erro: 'pedido_nao_encontrado' });
    }
    if (pedido.clienteId !== req.usuario.id) {
      return res.status(403).json({ erro: 'sem_permissao' });
    }
    if (pedido.status === 'cancelado') {
      return res.status(409).json({ erro: 'pedido_cancelado', mensagem: 'Pedido cancelado não aceita pagamento.' });
    }

    const pagamento = await pagamentos.criar(pedido, valorCentavos);
    return res.status(201).location('/pagamentos/' + pagamento.id).json(pagamento);
  } catch (err) {
    return next(err);
  }
});

app.use((err, req, res, next) => {
  console.error(err);
  res.status(500).json({ erro: 'erro_interno' });
});

Repare que o 401 nem aparece na rota: quem confere o token é um middleware antes dela, e é lá que mora o 401. Para conferir o que a sua API devolve de fato, olhe só o número:

curl -s -o /dev/null -w "%{http_code}\n" -X POST https://api.exemplo.com.br/pedidos/999/pagamento

Na troca de endereço de páginas, a escolha entre 301 e 302 decide o que a busca faz com a URL antiga, como mostra redirecionar URL do jeito certo. E o 403 do lado do servidor web tem causas próprias, descritas em erro 403 no Apache.

Erros comuns

  • 200 com erro no corpo. O cliente mostra sucesso, a nova tentativa não acontece e o monitoramento não registra a falha.
  • 500 para validação de formulário. O cliente repete um pedido que nunca vai passar, e o alerta de erro do servidor dispara por culpa de um CPF digitado errado.
  • 401 no lugar de 403. O front-end entende que a sessão caiu, desloga a pessoa e ela entra de novo só para receber a mesma recusa.
  • 302 numa mudança definitiva de domínio. A busca continua tratando o endereço velho como o oficial.
  • 404 genérico para tudo que deu errado. Você perde a diferença entre "não existe" e "existe mas você não pode", e o suporte não sabe por onde começar.
  • 429 ou 503 sem Retry-After. Os clientes tentam de novo todos ao mesmo tempo e pioram a sobrecarga.

Checklist

  • Nenhuma rota devolve 2xx quando a operação falhou.
  • Validação de entrada devolve 400 ou 422, nunca 500.
  • 401 e 403 são usados com significados distintos.
  • Criação de recurso devolve 201 com Location.
  • Exceção não tratada vira 500, sem stack trace na resposta.
  • Limite de taxa devolve 429 com Retry-After.
  • Os testes automatizados verificam o status de cada caso de erro.

Escolha uma chamada da sua API que você sabe que falha, rode o curl acima e olhe só o número. Ele diz "sucesso"?

Avalie este artigo

Referência: https://developer.mozilla.org/pt-BR/docs/Web/HTTP/Status