Pular para o conteúdo principal

Envelope de resposta

Toda resposta tem o mesmo formato. Sucesso, erro, e também os erros que você não pediu — rota inexistente, validação, limite de requisições.

Um envelope que valesse "quase sempre" não seria um contrato, seriam dois, e o seu código acabaria com um if adivinhando o formato.

Sucesso​

{
"success": true,
"status": 200,
"body": { },
"meta": {
"requestId": "0f3c8a1e-4b2d-4a77-9c1e-2f5b8d9a0c31",
"timestamp": "2026-08-20T14:03:11Z"
}
}

body é o conteúdo. Numa listagem é um array; num recurso, um objeto.

Erro​

{
"success": false,
"status": 422,
"error": {
"code": "validation_failed",
"message": "O valor informado é inválido.",
"details": { "amount": ["O campo amount é obrigatório."] }
},
"meta": {
"requestId": "0f3c8a1e-4b2d-4a77-9c1e-2f5b8d9a0c31",
"timestamp": "2026-08-20T14:03:11Z"
}
}

Escreva sobre error.code, nunca sobre error.message​

code é estável. É o contrato, e é sobre ele que a sua lógica decide.

message é para humanos, pode ser reescrito a qualquer momento e pode mudar de idioma. Comparar strings de mensagem é a forma mais silenciosa de quebrar uma integração: nada falha, o if só para de casar.

meta.requestId​

Vai em toda resposta, e também no cabeçalho X-Request-Id.

Guarde-o nos seus logs. Quando você abrir um chamado, é o único dado que permite achar a sua requisição específica entre todas as outras daquele segundo.

Você também pode mandá-lo na requisição: se você já tem um id de correlação, ele é reaproveitado no lugar de um novo.

A única exceção​

204 não tem corpo. Envelopar "não há conteúdo" seria contradizê-lo.

Idioma dos textos​

Os rótulos (statusLabel, typeLabel…) e o error.message saem no idioma do cabeçalho Accept-Language: en* responde em inglês; qualquer outro valor, ou a falta dele, em português. Só vale a primeira preferência.

Os códigos (error.code, status, type) nunca mudam com o idioma. Escreva sua lógica sobre eles.