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.

Equipo JSONTechFebruary 1, 202510 min read

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ónEjemploUsado PorEcosistema
camelCasefirstName, createdAtGoogle APIs, AzureFrontends de JavaScript / TypeScript
snake_casefirst_name, created_atStripe, GitHub, SlackBackends 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

AspectoDesplazamientoCursorConjunto de Claves
Saltar a la página NNoNo
Conteo totalFácil de incluirCostosoCostoso
Rendimiento en tablas grandesDegrada (OFFSET escanea filas)ConsistenteConsistente
Maneja inserciones/eliminacionesPuede omitir o duplicar elementosEstableEstable
Complejidad de implementaciónBajaMediaMedia
Usado porAplicaciones web tradicionalesStripe, SlackTwitter / 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 HTTPCódigo de ErrorCuándo Usar
400validation_errorEl cuerpo de la solicitud falla en la validación del esquema
401unauthorizedAutenticación faltante o inválida
403forbiddenAutenticado pero carece de permiso
404not_foundEl recurso no existe
409conflictRecurso duplicado o conflicto de estado
422unprocessable_entityJSON válido pero semánticamente incorrecto
429rate_limit_exceededDemasiadas solicitudes
500internal_errorFallo 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:

AlgoritmoTasa de CompresiónVelocidadSoporte de Navegadores
gzipBueno (reducción del 70–80%)RápidoUniversal
Brotli (br)Mejor (reducción del 75–85%)Ligeramente más lento para comprimirTodos 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 (camelCase o snake_case) en todos los endpoints.
  • Sobre de respuesta predecible con data, error y meta.
  • 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.

Herramientas relacionadas