¿Qué estructura debería usar una respuesta de API?
En resumen: Los objetos desnudos son limpios hasta que necesitas metadatos; los envoltorios completos son seguros pero verbosos. Para la mayoría de proyectos, usa el estado HTTP para los resultados y un envoltorio estable para los datos.

En resumen: expresa los resultados con códigos de estado 4xx/5xx, lleva la carga útil en { "data": ..., "meta": ... } y cambia a un envoltorio error en caso de fallo; eso basta para API internas, y solo las plataformas públicas con muchos integradores necesitan JSON:API u OpenAPI.
Descarta primero la peor opción: devolver siempre 200 con el resultado en un campo code.
Tres formas
A. Objeto desnudo
{ "id": 1, "name": "demo", "price": 99 }
Limpio, y la semántica HTTP hace el trabajo (404 es 404). Pero en el momento en que necesitas paginación, advertencias o un id de traza, no hay dónde ponerlos sin cambiar el contrato.
B. Envoltorio estable
{
"data": { "id": 1, "name": "demo" },
"meta": { "requestId": "abc", "took": 12 }
}
La carga útil y los metadatos viven separados, así que los nuevos metadatos nunca remodelan data. Cuesta un nivel de anidamiento.
C. Envoltorio para todo (evítalo)
{ "code": 0, "message": "success", "data": {...} }
La forma no es el pecado; la práctica sí. Estado 200 para todos los resultados significa que CDN, gateways, interruptores de circuito, políticas de reintento y paneles ven éxito. Cada alerta entonces necesita análisis personalizado.
La recomendación
Estado HTTP para el resultado, envoltorio para la carga útil.
{
"data": { "id": 1, "name": "demo" },
"meta": { "requestId": "req_8f2a", "took": 12 }
}
{
"error": {
"code": "ORDER_NOT_FOUND",
"message": "Order does not exist or was deleted",
"details": [{ "field": "id", "issue": "not_found" }]
},
"meta": { "requestId": "req_8f2a" }
}
Estado 4xx o 5xx; code estable y legible por máquina; message para personas; details para errores de formulario a nivel de campo.
Reglas estrictas
- Los endpoints de lista deben llevar metadatos de paginación. Sin
totalni un cursor no hay paginador. - Las listas vacías devuelven
[], nuncanull.null.mapes una caída recurrente. - Marcas de tiempo en ISO 8601 con desplazamiento. Consulta qué formato para las marcas de tiempo.
- Una única convención de nombres.
snake_caseocamelCase, convertida una vez en el límite. - Dinero como unidades menores enteras o cadenas.
99.99no es exacto en coma flotante binaria. - No confundas
nullcon ausente. Consultanullu omitir el campo.
Cuándo adoptar una especificación
- Plataformas públicas con muchos integradores: JSON:API u OpenAPI más un envoltorio compartido
- API internas de un solo equipo: el envoltorio anterior basta
- Contratos sólidos: Schema primero, estructuras generadas a partir de él
Las especificaciones ahorran discusión y abaratan la incorporación. Para un equipo de cinco personas, JSON:API normalmente es trabajo con saldo negativo.
FAQ
¿Cómo debo elegir entre un objeto desnudo y un envoltorio?
Usa un envoltorio cuando necesites paginación, advertencias o un requestId, para que meta pueda crecer sin remodelar data. Devuelve el objeto desnudo solo cuando no necesites metadatos y puedas aceptar que añadir campos después cambie el contrato.
¿Por qué una API no debería devolver 200 para todos los resultados?
CDN, gateways, interruptores de circuito, políticas de reintento y paneles leen todos el estado HTTP para decidir si cachear, reintentar o alertar. Con 200 en todas partes tratan los fallos como éxitos, y cada alerta necesita un análisis personalizado del código de negocio.
¿Deberían los endpoints de lista llevar metadatos de paginación?
Sí. Sin total ni un cursor no hay paginador, y las listas vacías deben devolver [] en lugar de null, o la llamada .map del frontend se rompe.