Que estrutura uma resposta de API deve usar
Resumo: Objetos crus são limpos até você precisar de metadados; envelopes completos são seguros mas verbosos. Para a maioria dos projetos use status HTTP para resultados e um envelope estável para dados.

Resumo da ópera: expresse resultados com códigos de status 4xx/5xx, carregue o payload em { "data": ..., "meta": ... } e mude para um envelope error em caso de falha; isso basta para APIs internas, e só plataformas públicas com muitos integradores precisam de JSON:API ou OpenAPI.
Descarte primeiro a pior opção: sempre retornar 200 com o resultado em um campo code.
Três formatos
A. Objeto cru
{ "id": 1, "name": "demo", "price": 99 }
Limpo, e a semântica HTTP faz o trabalho (404 é 404). Mas no momento em que você precisa de paginação, avisos ou um trace id, não há onde colocá-los sem mudar o contrato.
B. Envelope estável
{
"data": { "id": 1, "name": "demo" },
"meta": { "requestId": "abc", "took": 12 }
}
Payload e metadados vivem separados, então novos metadados nunca remodelam data. Custa um nível de aninhamento.
C. Envelope para tudo (evite)
{ "code": 0, "message": "success", "data": {...} }
O formato não é o pecado; a prática é. Status 200 para todo resultado significa que CDNs, gateways, circuit breakers, políticas de retentativa e dashboards todos veem sucesso. Todo alerta então precisa de análise customizada.
A recomendação
Status HTTP para o resultado, envelope para o payload.
{
"data": { "id": 1, "name": "demo" },
"meta": { "requestId": "req_8f2a", "took": 12 }
}
{
"error": {
"code": "ORDER_NOT_FOUND",
"message": "O pedido não existe ou foi excluído",
"details": [{ "field": "id", "issue": "not_found" }]
},
"meta": { "requestId": "req_8f2a" }
}
Status 4xx ou 5xx; code estável e legível por máquina; message para humanos; details para erros de formulário em nível de campo.
Regras rígidas
- Endpoints de lista precisam carregar metadados de paginação. Sem
totalou um cursor, não há paginador. - Listas vazias retornam
[], nuncanull.null.mapé uma indisponibilidade recorrente. - Timestamps em ISO 8601 com offset. Veja que formato para timestamps.
- Uma convenção de nomeação.
snake_caseoucamelCase, convertida uma vez na fronteira. - Dinheiro como unidades menores inteiras ou strings.
99.99não é exato em ponto flutuante binário. - Não confunda
nullcom ausente. Vejanullou omitir o campo.
Quando adotar uma especificação
- Plataformas públicas com muitos integradores: JSON:API ou OpenAPI mais um envelope compartilhado
- APIs internas de uma única equipe: o envelope acima basta
- Contratos fortes: Schema primeiro, estruturas geradas a partir dele
Especificações economizam discussão e custam onboarding. Para uma equipe de cinco pessoas, JSON:API normalmente é trabalho negativo.
Perguntas frequentes
Como devo escolher entre um objeto cru e um envelope?
Use um envelope quando precisar de paginação, avisos ou um requestId, para que meta possa crescer sem remodelar data. Retorne o objeto cru apenas quando não precisar de metadados e aceitar que adicionar campos depois muda o contrato.
Por que uma API nunca deve retornar 200 para todo resultado?
CDNs, gateways, circuit breakers, políticas de retentativa e dashboards todos leem o status HTTP para decidir se devem fazer cache, retentar ou alertar. Com 200 em toda parte, eles tratam falhas como sucessos, e todo alerta precisa de análise customizada do código de negócio.
Endpoints de lista devem carregar metadados de paginação?
Sim. Sem total ou um cursor não há paginador, e listas vazias precisam retornar [] em vez de null, ou a chamada .map do frontend quebra.