JSON API Best Practices for Developers
Design better JSON APIs with consistent naming, structured responses, proper pagination, error handling, and performance optimizations used by top engineering teams.
Por que o Design de API é Importante
Uma API JSON bem projetada é a diferença entre uma integração que leva uma tarde e uma que leva uma semana. Um bom design de API reduz tickets de suporte, torna sua documentação mais curta e permite que os consumidores prevejam como as coisas funcionam antes de ler a documentação.
Os padrões neste guia não são teóricos — eles são extraídos de APIs da Stripe, GitHub, Slack e outras empresas conhecidas pela excelente experiência do desenvolvedor. Vamos percorrer cada um deles.
Convenções de Nomenclatura
Os dois estilos dominantes para nomes de propriedades JSON são camelCase e snake_case. Qualquer um deles é aceitável, desde que você escolha um e o use em todos os lugares.
| Convenção | Exemplo | Usado Por | Ecossistema |
|---|---|---|---|
camelCase | firstName, createdAt | Google APIs, Azure | Frontends JavaScript / TypeScript |
snake_case | first_name, created_at | Stripe, GitHub, Slack | Backends Python / Ruby |
Diretriz: Escolha a convenção que corresponda ao seu ecossistema de consumidores principal. Se sua API é consumida principalmente por frontends JavaScript, camelCase evita atritos. Se seu backend é Python ou Ruby e a API é B2B, snake_case é idiomático.
O que quer que você escolha, seja absolutamente consistente. Misturar user_name e firstName na mesma resposta erode a confiança mais rápido do que quase qualquer outra coisa.
Estrutura de Resposta Consistente
Cada resposta da sua API deve seguir um envelope previsível. Aqui está um padrão que funciona para a maioria das APIs:
Resposta de Sucesso
{
"data": {
"id": "usr_abc123",
"email": "alice@example.com",
"name": "Alice Johnson",
"created_at": "2025-01-15T09:30:00Z"
},
"meta": {
"request_id": "req_7f2a9c"
}
}
Resposta de Lista
{
"data": [
{ "id": "usr_abc123", "name": "Alice Johnson" },
{ "id": "usr_def456", "name": "Bob Smith" }
],
"meta": {
"total": 142,
"page": 1,
"per_page": 20,
"request_id": "req_8b3d1e"
}
}
Resposta de Erro
{
"error": {
"code": "validation_error",
"message": "O corpo da requisição contém campos inválidos.",
"details": [
{
"field": "email",
"message": "Deve ser um endereço de e-mail válido."
},
{
"field": "age",
"message": "Deve ter pelo menos 13 anos."
}
]
},
"meta": {
"request_id": "req_4c8e2a"
}
}
O princípio chave: data está presente em caso de sucesso, error está presente em caso de falha, e meta aparece em ambos para rastreamento de requisições e paginação. Os consumidores podem escrever um único manipulador de resposta que verifica error primeiro.
Experimente você mesmo: Cole uma resposta de API em nosso Formatador JSON para inspecionar a estrutura e identificar inconsistências.
Padrões de Paginação
A maioria dos endpoints de lista precisa de paginação. Existem três abordagens comuns, cada uma com diferentes trade-offs:
1. Paginação por Offset
A abordagem mais simples. O cliente especifica um número de página ou offset:
GET /api/users?page=3&per_page=20
{
"data": [...],
"meta": {
"total": 142,
"page": 3,
"per_page": 20,
"total_pages": 8
}
}
2. Paginação por Cursor
Em vez de números de página, o servidor retorna um cursor opaco que aponta para o próximo conjunto de resultados:
GET /api/users?limit=20&after=cursor_abc123
{
"data": [...],
"meta": {
"has_more": true,
"next_cursor": "cursor_def456"
}
}
3. Paginação por Keyset
Usa um valor de coluna real (como um timestamp ou ID) como o cursor. Esta é a paginação por cursor sem a opacidade:
GET /api/users?limit=20&created_after=2025-01-15T09:30:00Z
{
"data": [...],
"meta": {
"has_more": true,
"next_created_after": "2025-01-16T14:22:00Z"
}
}
Comparação de Paginação
| Aspecto | Offset | Cursor | Keyset |
|---|---|---|---|
| Pular para a página N | Sim | Não | Não |
| Contagem total | Fácil de incluir | Caro | Caro |
| Desempenho em tabelas grandes | Degrada (OFFSET escaneia linhas) | Consistente | Consistente |
| Lida com inserções/exclusões | Pode pular ou duplicar itens | Estável | Estável |
| Complexidade de implementação | Baixa | Média | Média |
| Usado por | Aplicativos web tradicionais | Stripe, Slack | Twitter / X |
Diretriz: Use paginação por offset para painéis administrativos e ferramentas internas onde os usuários precisam pular para páginas específicas. Use paginação por cursor ou keyset para APIs públicas, UIs de rolagem infinita e qualquer conjunto de dados que mude com frequência.
Tratamento de Erros
Boas respostas de erro informam ao consumidor o que deu errado, onde, e idealmente como corrigir isso. Aqui está a estrutura que recomendamos:
{
"error": {
"code": "rate_limit_exceeded",
"message": "Você excedeu o limite de taxa de 100 requisições por minuto.",
"details": [],
"doc_url": "https://docs.example.com/errors/rate-limit"
}
}
O code é uma string legível por máquina (não um código de status HTTP — isso vai na resposta HTTP real). A message é legível por humanos. O array details fornece erros em nível de campo para falhas de validação. E doc_url vincula à documentação sobre o erro.
Códigos de erro comuns e suas mapeações de status HTTP:
| Status HTTP | Código de Erro | Quando Usar |
|---|---|---|
| 400 | validation_error | O corpo da requisição falha na validação do esquema |
| 401 | unauthorized | Autenticação ausente ou inválida |
| 403 | forbidden | Autenticado, mas sem permissão |
| 404 | not_found | O recurso não existe |
| 409 | conflict | Recurso duplicado ou conflito de estado |
| 422 | unprocessable_entity | JSON válido, mas semanticamente errado |
| 429 | rate_limit_exceeded | Muitas requisições |
| 500 | internal_error | Falha inesperada do servidor |
Tipos de Dados e Formatação
Acertar os tipos de dados previne uma categoria inteira de bugs de integração:
Datas e Horas
Sempre use o formato ISO 8601 com informações de fuso horário:
{
"created_at": "2025-01-15T09:30:00Z",
"expires_at": "2025-02-15T23:59:59+05:30"
}
Nunca use timestamps Unix nos corpos das respostas — eles não são legíveis por humanos e criam ambiguidade de fuso horário. Se você precisar de timestamps Unix (por exemplo, para reivindicações JWT), documente isso claramente.
Números Grandes
O tipo Number do JavaScript perde precisão além de 2^53 - 1 (9.007.199.254.740.991). Se seus IDs ou valores monetários excederem isso, use strings:
{
"id": "9223372036854775807",
"amount": "99.99",
"currency": "USD"
}
Stripe, Twitter e Discord usam IDs de string exatamente por esse motivo.
Nulo vs. Ausente
Defina uma convenção clara e documente-a:
null— O campo existe, mas não tem valor (por exemplo, o usuário ainda não configurou uma biografia).- Chave ausente — O campo não é relevante para este recurso ou não foi solicitado (por exemplo, em uma resposta parcial).
Muitas APIs incluem campos null para manter uma forma consistente, o que facilita para os clientes escreverem interfaces tipadas sem encadeamento opcional em toda parte.
Booleanos
Use booleanos JSON reais, não strings ou inteiros:
// Bom
{ "is_active": true }
// Ruim
{ "is_active": "true" }
{ "is_active": 1 }
Estratégias de Versionamento
APIs evoluem. Você precisa de uma estratégia para fazer alterações sem quebrar consumidores existentes. As três principais abordagens:
- Versionamento por URL:
/v1/users,/v2/users. A abordagem mais comum e mais visível. Usada pela Stripe, GitHub e Twilio. Fácil de entender, fácil de roteirizar, mas pode levar à duplicação de código. - Versionamento por Cabeçalho:
Accept: application/vnd.api+json;version=2. URLs mais limpas, mas menos descobertas. Usada pelo GitHub (como uma alternativa) e Azure. - Parâmetro de Consulta:
/users?version=2. Simples, mas polui a string de consulta. Menos comum na prática.
Diretriz: O versionamento por URL é o padrão pragmático. É explícito, cacheável e funciona com todos os clientes HTTP. Apenas desvie se você tiver uma razão técnica específica.
Compressão
JSON é texto e comprime excepcionalmente bem. Sempre habilite a compressão para respostas de API:
| Algoritmo | Taxa de Compressão | Velocidade | Suporte a Navegadores |
|---|---|---|---|
| gzip | Bom (redução de 70–80%) | Rápido | Universal |
| Brotli (br) | Melhor (redução de 75–85%) | Um pouco mais lento para comprimir | Todos os navegadores modernos |
Para uma resposta JSON típica de 50KB, gzip a reduz para cerca de 10–15KB, e Brotli para cerca de 8–12KB. O servidor deve respeitar o cabeçalho Accept-Encoding e escolher de acordo. A maioria dos proxies reversos (Nginx, Cloudflare) lida com isso automaticamente.
Dicas de Desempenho
Respostas Parciais (Seleção de Campos)
Permita que os consumidores solicitem apenas os campos de que precisam. Isso reduz o tamanho do payload e a carga no banco de dados:
GET /api/users?fields=id,name,email
{
"data": [
{ "id": "usr_abc123", "name": "Alice", "email": "alice@example.com" }
]
}
As APIs do Google chamam isso de "respostas parciais" e suportam em todos os endpoints. É especialmente valioso para clientes móveis em conexões lentas.
Conjuntos de Campos Esparsos
Para APIs que retornam recursos relacionados, permita que os consumidores controlem quais campos são incluídos para cada tipo de recurso:
GET /api/articles?include=author&fields[article]=title,body&fields[author]=name
Este é um padrão da especificação JSON:API. Ele elimina o problema da consulta N+1 no cliente enquanto mantém os payloads mínimos.
ETags e Requisições Condicionais
Retorne um cabeçalho ETag com as respostas. Os clientes podem enviar If-None-Match em requisições subsequentes, e você retorna 304 Not Modified (corpo vazio) se nada mudou. Isso economiza largura de banda e tempo de processamento para recursos que não mudam com frequência.
Lista de Verificação Resumida
Antes de enviar sua API, passe por esta lista de verificação:
- Convenção de nomenclatura consistente (
camelCaseousnake_case) em todos os endpoints. - Envelope de resposta previsível com
data,erroremeta. - Paginação em todos os endpoints de lista com limites documentados.
- Respostas de erro estruturadas com códigos legíveis por máquina, mensagens e detalhes em nível de campo.
- Datas ISO 8601, IDs de string para números grandes, booleanos reais.
- Estratégia de versionamento da API documentada e aplicada.
- Compressão gzip ou Brotli habilitada.
- Seleção de campos ou conjuntos de campos esparsos para consumidores sensíveis ao desempenho.
Experimente você mesmo: Formate e valide suas respostas de API com nosso Formatador JSON para garantir que sigam uma estrutura limpa e consistente.