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.

Equipe JSONTechFebruary 1, 202510 min read

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çãoExemploUsado PorEcossistema
camelCasefirstName, createdAtGoogle APIs, AzureFrontends JavaScript / TypeScript
snake_casefirst_name, created_atStripe, GitHub, SlackBackends 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

AspectoOffsetCursorKeyset
Pular para a página NSimNãoNão
Contagem totalFácil de incluirCaroCaro
Desempenho em tabelas grandesDegrada (OFFSET escaneia linhas)ConsistenteConsistente
Lida com inserções/exclusõesPode pular ou duplicar itensEstávelEstável
Complexidade de implementaçãoBaixaMédiaMédia
Usado porAplicativos web tradicionaisStripe, SlackTwitter / 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 HTTPCódigo de ErroQuando Usar
400validation_errorO corpo da requisição falha na validação do esquema
401unauthorizedAutenticação ausente ou inválida
403forbiddenAutenticado, mas sem permissão
404not_foundO recurso não existe
409conflictRecurso duplicado ou conflito de estado
422unprocessable_entityJSON válido, mas semanticamente errado
429rate_limit_exceededMuitas requisições
500internal_errorFalha 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:

AlgoritmoTaxa de CompressãoVelocidadeSuporte a Navegadores
gzipBom (redução de 70–80%)RápidoUniversal
Brotli (br)Melhor (redução de 75–85%)Um pouco mais lento para comprimirTodos 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 (camelCase ou snake_case) em todos os endpoints.
  • Envelope de resposta previsível com data, error e meta.
  • 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.

Ferramentas relacionadas