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 qué importa el diseño de API
Una API JSON bien diseñada es la diferencia entre una integración que toma una tarde y una que toma una semana. Un buen diseño de API reduce los tickets de soporte, hace que tu documentación sea más corta y permite a los consumidores predecir cómo funcionan las cosas antes de leer la documentación.
Los patrones en esta guía no son teóricos; están extraídos de APIs en Stripe, GitHub, Slack y otras empresas conocidas por su excelente experiencia para desarrolladores. Vamos a repasar cada uno.
Convenciones de Nombres
Los dos estilos dominantes para los nombres de propiedades JSON son camelCase y snake_case. Cualquiera de los dos está bien siempre y cuando elijas uno y lo uses en todas partes.
| Convención | Ejemplo | Usado Por | Ecosistema |
|---|---|---|---|
camelCase | firstName, createdAt | Google APIs, Azure | Frontends de JavaScript / TypeScript |
snake_case | first_name, created_at | Stripe, GitHub, Slack | Backends de Python / Ruby |
Directriz: Elige la convención que coincida con tu ecosistema de consumidores principal. Si tu API es consumida principalmente por frontends de JavaScript, camelCase evita fricciones. Si tu backend es Python o Ruby y la API es B2B, snake_case es idiomático.
Cualquiera que elijas, sé absolutamente consistente. Mezclar user_name y firstName en la misma respuesta erosiona la confianza más rápido que casi cualquier otra cosa.
Estructura de Respuesta Consistente
Cada respuesta de tu API debe seguir un sobre predecible. Aquí hay un patrón que funciona para la mayoría de las APIs:
Respuesta de Éxito
{
"data": {
"id": "usr_abc123",
"email": "alice@example.com",
"name": "Alice Johnson",
"created_at": "2025-01-15T09:30:00Z"
},
"meta": {
"request_id": "req_7f2a9c"
}
}
Respuesta 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"
}
}
Respuesta de Error
{
"error": {
"code": "validation_error",
"message": "El cuerpo de la solicitud contiene campos inválidos.",
"details": [
{
"field": "email",
"message": "Debe ser una dirección de correo electrónico válida."
},
{
"field": "age",
"message": "Debe tener al menos 13 años."
}
]
},
"meta": {
"request_id": "req_4c8e2a"
}
}
El principio clave: data está presente en caso de éxito, error está presente en caso de fallo, y meta aparece en ambos para el rastreo de solicitudes y la paginación. Los consumidores pueden escribir un único manejador de respuestas que verifique error primero.
Inténtalo tú mismo: Pega una respuesta de API en nuestro JSON Formatter para inspeccionar la estructura y detectar inconsistencias.
Patrones de Paginación
La mayoría de los endpoints de lista necesitan paginación. Hay tres enfoques comunes, cada uno con diferentes compensaciones:
1. Paginación por Desplazamiento
El enfoque más simple. El cliente especifica un número de página o desplazamiento:
GET /api/users?page=3&per_page=20
{
"data": [...],
"meta": {
"total": 142,
"page": 3,
"per_page": 20,
"total_pages": 8
}
}
2. Paginación por Cursor
En lugar de números de página, el servidor devuelve un cursor opaco que apunta al siguiente conjunto de resultados:
GET /api/users?limit=20&after=cursor_abc123
{
"data": [...],
"meta": {
"has_more": true,
"next_cursor": "cursor_def456"
}
}
3. Paginación por Conjunto de Claves
Utiliza un valor de columna real (como una marca de tiempo o ID) como cursor. Esta es paginación por cursor sin la opacidad:
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"
}
}
Comparación de Paginación
| Aspecto | Desplazamiento | Cursor | Conjunto de Claves |
|---|---|---|---|
| Saltar a la página N | Sí | No | No |
| Conteo total | Fácil de incluir | Costoso | Costoso |
| Rendimiento en tablas grandes | Degrada (OFFSET escanea filas) | Consistente | Consistente |
| Maneja inserciones/eliminaciones | Puede omitir o duplicar elementos | Estable | Estable |
| Complejidad de implementación | Baja | Media | Media |
| Usado por | Aplicaciones web tradicionales | Stripe, Slack | Twitter / X |
Directriz: Usa paginación por desplazamiento para paneles de administración y herramientas internas donde los usuarios necesitan saltar a páginas específicas. Usa paginación por cursor o conjunto de claves para APIs públicas, interfaces de desplazamiento infinito y cualquier conjunto de datos que cambie con frecuencia.
Manejo de Errores
Las buenas respuestas de error informan al consumidor qué salió mal, dónde, y idealmente cómo solucionarlo. Aquí está la estructura que recomendamos:
{
"error": {
"code": "rate_limit_exceeded",
"message": "Has superado el límite de tasa de 100 solicitudes por minuto.",
"details": [],
"doc_url": "https://docs.example.com/errors/rate-limit"
}
}
El code es una cadena legible por máquina (no un código de estado HTTP — eso va en la respuesta HTTP real). El message es legible para humanos. El array details proporciona errores a nivel de campo para fallos de validación. Y doc_url enlaza a la documentación sobre el error.
Códigos de error comunes y sus mapeos de estado HTTP:
| Estado HTTP | Código de Error | Cuándo Usar |
|---|---|---|
| 400 | validation_error | El cuerpo de la solicitud falla en la validación del esquema |
| 401 | unauthorized | Autenticación faltante o inválida |
| 403 | forbidden | Autenticado pero carece de permiso |
| 404 | not_found | El recurso no existe |
| 409 | conflict | Recurso duplicado o conflicto de estado |
| 422 | unprocessable_entity | JSON válido pero semánticamente incorrecto |
| 429 | rate_limit_exceeded | Demasiadas solicitudes |
| 500 | internal_error | Fallo inesperado del servidor |
Tipos de Datos y Formato
Obtener los tipos de datos correctos previene una categoría entera de errores de integración:
Fechas y Horas
Siempre usa el formato ISO 8601 con información de zona horaria:
{
"created_at": "2025-01-15T09:30:00Z",
"expires_at": "2025-02-15T23:59:59+05:30"
}
Nunca uses marcas de tiempo Unix en los cuerpos de respuesta; no son legibles para humanos y crean ambigüedad de zona horaria. Si necesitas marcas de tiempo Unix (por ejemplo, para reclamos JWT), documenta esto claramente.
Números Grandes
El tipo Number de JavaScript pierde precisión más allá de 2^53 - 1 (9,007,199,254,740,991). Si tus IDs o valores monetarios exceden esto, usa cadenas:
{
"id": "9223372036854775807",
"amount": "99.99",
"currency": "USD"
}
Stripe, Twitter y Discord utilizan IDs de cadena por esta razón exacta.
Null vs. Faltante
Define una convención clara y documentala:
null— El campo existe pero no tiene valor (por ejemplo, el usuario no ha establecido una biografía aún).- Clave faltante — El campo no es relevante para este recurso o no fue solicitado (por ejemplo, en una respuesta parcial).
Muchas APIs incluyen campos null para mantener una forma consistente, lo que facilita a los clientes escribir interfaces tipadas sin encadenamiento opcional en todas partes.
Booleanos
Usa booleanos JSON reales, no cadenas o enteros:
// Bueno
{ "is_active": true }
// Malo
{ "is_active": "true" }
{ "is_active": 1 }
Estrategias de Versionado
Las APIs evolucionan. Necesitas una estrategia para hacer cambios sin romper a los consumidores existentes. Los tres enfoques principales:
- Versionado de URL:
/v1/users,/v2/users. El enfoque más común y visible. Usado por Stripe, GitHub y Twilio. Fácil de entender, fácil de enrutar, pero puede llevar a duplicación de código. - Versionado de encabezado:
Accept: application/vnd.api+json;version=2. URLs más limpias pero menos descubribles. Usado por GitHub (como alternativa) y Azure. - Parámetro de consulta:
/users?version=2. Simple pero contamina la cadena de consulta. Menos común en la práctica.
Directriz: El versionado de URL es el predeterminado pragmático. Es explícito, cacheable y funciona con cualquier cliente HTTP. Solo desvíate si tienes una razón técnica específica.
Compresión
JSON es texto y se comprime excepcionalmente bien. Siempre habilita la compresión para las respuestas de API:
| Algoritmo | Tasa de Compresión | Velocidad | Soporte de Navegadores |
|---|---|---|---|
| gzip | Bueno (reducción del 70–80%) | Rápido | Universal |
| Brotli (br) | Mejor (reducción del 75–85%) | Ligeramente más lento para comprimir | Todos los navegadores modernos |
Para una respuesta JSON típica de 50KB, gzip la reduce a aproximadamente 10–15KB, y Brotli a aproximadamente 8–12KB. El servidor debe respetar el encabezado Accept-Encoding y elegir en consecuencia. La mayoría de los proxies inversos (Nginx, Cloudflare) manejan esto automáticamente.
Consejos de Rendimiento
Respuestas Parciales (Selección de Campos)
Permite a los consumidores solicitar solo los campos que necesitan. Esto reduce el tamaño de la carga útil y la carga de la base de datos:
GET /api/users?fields=id,name,email
{
"data": [
{ "id": "usr_abc123", "name": "Alice", "email": "alice@example.com" }
]
}
Las APIs de Google llaman a esto "respuestas parciales" y lo soportan en todos los endpoints. Es especialmente valioso para clientes móviles en conexiones lentas.
Conjuntos de Campos Escasos
Para APIs que devuelven recursos relacionados, permite a los consumidores controlar qué campos se incluyen para cada tipo de recurso:
GET /api/articles?include=author&fields[article]=title,body&fields[author]=name
Este es un patrón de la especificación JSON:API. Elimina el problema de consulta N+1 en el cliente mientras mantiene las cargas útiles mínimas.
ETags y Solicitudes Condicionales
Devuelve un encabezado ETag con las respuestas. Los clientes pueden enviar If-None-Match en solicitudes posteriores, y tú devuelves 304 Not Modified (cuerpo vacío) si nada ha cambiado. Esto ahorra ancho de banda y tiempo de procesamiento para recursos que no cambian con frecuencia.
Lista de Verificación Resumida
Antes de enviar tu API, repasa esta lista de verificación:
- Convención de nombres consistente (
camelCaseosnake_case) en todos los endpoints. - Sobre de respuesta predecible con
data,errorymeta. - Paginación en todos los endpoints de lista con límites documentados.
- Respuestas de error estructuradas con códigos legibles por máquina, mensajes y detalles a nivel de campo.
- Fechas ISO 8601, IDs de cadena para números grandes, booleanos reales.
- Estrategia de versionado de API documentada y aplicada.
- Compresión gzip o Brotli habilitada.
- Selección de campos o conjuntos de campos escasos para consumidores sensibles al rendimiento.
Inténtalo tú mismo: Formatea y valida tus respuestas de API con nuestro JSON Formatter para asegurarte de que sigan una estructura limpia y consistente.