Design de API REST
APIs são produtos: o modelo mental do consumidor, não seu esquema de banco de dados, decide a forma. O erro custoso que esta skill previne é entregar uma superfície inconsistente — nomenclatura mista, códigos de status errados, paginação por offset em uma tabela crescente — que todo consumidor então codifica diretamente, tornando-a efetivamente impossível de corrigir após a integração.
Procedimento operacional
Trabalhe nesta ordem. Recursos vêm antes de métodos porque a semântica do método depende do que o recurso é; contratos de erro e paginação vêm antes da especificação ser escrita porque adaptá-los depois quebra os consumidores.
Passo 1: coletar entradas
Colete antes de projetar. Rotule qualquer suposição como palpite.
- Os consumidores: serviços internos, clientes primários ou desenvolvedores externos. Consumidores externos elevam a barra de consistência — padronize para externo se desconhecido.
- Os substantivos centrais do domínio e quais possuem quais (determina o aninhamento).
- Tamanhos esperados da coleção e crescimento. Uma lista que pode exceder ~10.000 linhas descarta paginação por offset.
- Proporção leitura/escrita e expectativas de latência por endpoint.
- Modelo de autenticação (afeta a correção de 401 vs 403).
Passo 2: nomear recursos
- Substantivos no plural: /users, /orders, /products.
- Aninhe um nível para posse: /users/{id}/orders. Não aninhe mais de dois níveis — achate com filtros (/orders?user_id=... é melhor que /users/{id}/orders/{oid}/items/{iid}).
- Sem verbos nos caminhos; o método HTTP é o verbo. Para ações genuínas que não se encaixam em nenhum verbo CRUD (ex: enviar uma fatura), modele um subrecurso POST: POST /invoices/{id}/send-attempts.
- Case consistente: kebab-case para segmentos de caminho com várias palavras, snake_case para campos JSON — escolha uma vez, aplique em todos os lugares.
Passo 3: atribuir métodos e códigos de status
- GET: leitura, idempotente, sem corpo de requisição. 200 com corpo.
- POST: criar. Retorne 201 mais um cabeçalho Location apontando para o novo recurso. Retorne a representação criada no corpo para que o cliente pule um GET subsequente.
- PUT: substituição completa, idempotente. Campos ausentes significam "limpar", não "manter".
- PATCH: atualização parcial (JSON Merge Patch ou JSON Patch — indique qual na especificação).
- DELETE: remover, idempotente, retorna 204. Excluir um recurso já excluído retorna 204 ou 404 — escolha um e documente.
- Erros: 400 para entrada malformada, 401 para credenciais ausentes/inválidas, 403 para credenciais válidas sem permissão, 404 para recursos ausentes (use 404 em vez de 403 quando revelar a existência é um vazamento), 409 para conflitos de estado, 422 para entrada bem formada mas semanticamente inválida, 429 para limites de taxa (com Retry-After), 500 para falhas do servidor — nunca exponha stack traces ou identificadores internos.
Passo 4: fixar o contrato de erro
Um formato para cada erro, código legível por máquina primeiro:
{
"error": "validation_error",
"message": "Email is required",
"field": "email",
"request_id": "req_abc123"
}
O request_id deve ser registrado no lado do servidor para que o suporte possa correlacionar. A mensagem humana é para desenvolvedores lendo logs, nunca para exibição ao usuário final.
Passo 5: fixar paginação e filtragem
- Paginação baseada em cursor para qualquer coleção que cresce: retorne um cursor opaco e um sinalizador has_more. Paginação por offset é aceitável apenas para listas pequenas, limitadas e voltadas para administração — ela degrada linearmente e pula/duplica linhas sob escritas concorrentes.
- Tamanho de página padrão 25, máximo 100. Rejeite tamanhos de página acima do máximo com 400, não aplique silenciosamente.
- Filtragem e ordenação via parâmetros de consulta: ?status=active&sort=created_at:desc. Documente quais campos são filtráveis; rejeite parâmetros de filtro desconhecidos com 400 em vez de ignorá-los (ignorar silenciosamente esconde bugs do cliente).
Passo 6: aplicar orçamentos de performance e payload
- Orçamento de latência p95: 300 ms para endpoints interativos, 1 s para relatórios/exportações. Se uma operação não puder atingir 1 s, torne-a assíncrona: 202 Accepted mais um recurso de status para consultar.
- Orçamento de payload de resposta: mantenha respostas típicas abaixo de 100 KB; qualquer coisa rotineiramente acima de 1 MB precisa de paginação, seleção de campos (?fields=) ou um fluxo de exportação dedicado.
- Versionamento de mudanças disruptivas no caminho da URL (/v1/). Mudanças aditivas (novos campos opcionais) não exigem um aumento de versão. Passe a linha do tempo de depreciação para api-versioning-strategist.
Contraste trabalhado
Ruim:
POST /getUserOrders?userId=42&page=3
→ 200 { "data": [...], "error": null }
Verbo no caminho, semântica GET em um POST, paginação por offset, 200 encapsulando um campo de erro para que clientes precisem analisar o corpo para saber se a chamada falhou.
Bom:
GET /users/42/orders?status=shipped&limit=25&cursor=eyJpZCI6OTh9
→ 200 { "orders": [...], "next_cursor": "eyJpZCI6MTIzfQ", "has_more": true }
→ 404 { "error": "not_found", "message": "User 42 does not exist", "request_id": "req_9f2" }
Caminho com substantivo, aninhamento de posse, paginação por cursor, códigos de status carregam o resultado.
Entregável
Produza uma especificação de endpoint contendo: a tabela de recursos (caminho, métodos, códigos de status por método), o formato de erro compartilhado, o contrato de paginação (formato do cursor, padrões, máximo), a lista de permissões de filtragem e o orçamento de latência/payload por endpoint.
NÃO FAÇA
- Não espelhe o esquema do banco de dados na API — consumidores ficam acoplados a internos que você vai querer mudar.
- Não retorne 200 para falhas com um corpo de erro; clientes e monitoramento dependem de códigos de status.
- Não use paginação por offset em coleções ilimitadas — ela duplica e pula linhas sob escritas e degrada linearmente.
- Não aninhe caminhos mais de dois níveis; aninhamento profundo codifica um caminho de acesso e proíbe todos os outros.
- Não ignore silenciosamente parâmetros de consulta desconhecidos — um filtro digitado errado que retorna dados não filtrados é um bug da classe de vazamento de dados.
Nível de qualidade
- Um consumidor poderia implementar um cliente a partir da especificação sem fazer uma única pergunta.
- Cada endpoint declara seus códigos de status, incluindo casos de erro.
- Todos os endpoints de coleção compartilham um contrato de paginação; todos os erros compartilham um formato.
- Conceitos idênticos têm nomes idênticos em todos os caminhos e payloads.
- Cada endpoint tem um orçamento de latência explícito e aqueles >1 s são assíncronos.