apiresthttpbackend

Diseño de APIs REST: las convenciones que separan una buena API de un desastre

Recursos, verbos, códigos de estado, versionado, paginación e idempotencia: guía práctica para diseñar APIs REST coherentes que otros disfruten consumir.

26 de agosto de 2026·9 min de lectura

REST no es un estándar con validador: es un estilo arquitectónico que cada equipo interpreta a su manera, y ahí nacen las APIs donde POST /getUsers convive con DELETE /borrar?id=3. Las convenciones de este artículo no son capricho estético — son contratos que reducen fricción, bugs y preguntas de soporte a la mitad.

Recursos, no acciones

El principio rector: tus URLs nombran sustantivos, los verbos HTTP expresan la acción.

GET    /articulos           ← listar
POST   /articulos           ← crear
GET    /articulos/42        ← leer uno
PUT    /articulos/42        ← reemplazar completo
PATCH  /articulos/42        ← actualizar parcial
DELETE /articulos/42        ← eliminar

Comparado con el antipatrón RPC-sobre-HTTP (POST /obtenerArticulos, POST /eliminarArticulo?id=42), ganas uniformidad predecible: quien conoce un recurso sabe operarlo entero sin documentación adicional.

Reglas derivadas que evitan discusiones infinitas:

  • Plural siempre (/articulos, no /articulo).
  • Jerarquía para anidación máxima de dos niveles: /autores/7/articulos es legible; /autores/7/articulos/42/comentarios/9/likes ya pide subrecurso raíz (/likes?comentario=9).
  • Filtrado y orden por query string, jamás por ruta: /articulos?autor=7&estado=publicado&orden=-fecha&pagina=2.
  • Sin verbos en rutas ni extensiones (.json) ni mayúsculas.

Códigos de estado: el contrato semántico

Devolver 200 OK con { "error": "algo fue mal" } rompe clientes, caches, monitores y retries automáticos. El código es parte del mensaje. Los imprescindibles:

Código Cuándo
200 OK Éxito con cuerpo (lecturas, actualizaciones)
201 Created Recurso creado; añade cabecera Location del nuevo recurso
204 No Content Éxito sin cuerpo (borrado, PUT completo)
400 Bad Request Sintaxis/validación errónea del cliente
401 Unauthorized No autenticado (el nombre engaña: es "unauthenticated")
403 Forbidden Autenticado pero sin permiso
404 Not Found Recurso inexistente — también cuando no quieres revelar existencia
409 Conflict Conflicto de estado (email duplicado, versión obsoleta)
422 Unprocessable Semánticamente inválido (JSON válido, datos imposibles)
429 Too Many Requests Rate limit excedido
500 Error tuyo, nunca del cliente

El sistema completo está detallado en nuestra guía de códigos HTTP; aquí basta la disciplina: 4xx = culpa del cliente (no reintentes igual), 5xx = culpa del servidor (retry con backoff tiene sentido).

Errores útiles: el formato que sí ayuda

Un error bien diseñado ahorra tickets de soporte:

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "La petición contiene campos inválidos",
    "details": [
      { "field": "email", "issue": "formato inválido" },
      { "field": "fecha_nacimiento", "issue": "no puede ser futura" }
    ],
    "request_id": "req_8f3k2"
  }
}

Consistencia en TODOS los endpoints, request_id trazable en logs, y mensajes que digan qué corregir — nunca stack traces ni detalles internos (superficie de reconocimiento).

Idempotencia: la propiedad que habilita reintentos

Idempotente = repetir la petición produce el mismo resultado. GET, PUT y DELETE lo son naturalmente; POST no (dos POST = dos recursos). Por eso los reintentos automáticos tras timeout son seguros en unos métodos y peligrosos en otros.

Para pagos y operaciones críticas vía POST existe el patrón Idempotency-Key: el cliente genera una clave única por operación y la envía en cabecera; tu servidor detecta repetición y devuelve el resultado original en vez de ejecutar doble cargo. Stripe popularizó el patrón y debería ser default mental en cualquier API que mueva dinero o estado crítico.

Paginación, filtros y orden desde el día uno

Añadir paginación después de lanzar rompe consumidores. Diseña antes:

GET /articulos?page=2&limit=25&sort=-creado,titulo
  • Offset/limit: simple, pero salta elementos si hay inserciones concurrentes.
  • Cursor (?cursor=eyJpZCI6MTAwfQ): estable bajo escritura concurrente, ideal para feeds infinitos; opaco para el cliente.
  • Devuelve siempre metadatos (total, next_cursor) o cabeceras (Link: rel="next").

Y limita el limit máximo server-side: ?limit=1000000 es un ataque de recursos disfrazado de query param.

Versionado: decide antes de necesitarlo

Tres estrategias, una recomendación:

  • En la ruta (/v1/articulos): visible, cacheable, trivial de enrutar. La más común y suficiente para casi todos.
  • Cabecera (Accept: application/vnd.miapi.v2+json): purista, invisible en logs de acceso.
  • Sin versión: solo si tu API es interna y efímera.

Cuando lances v2: v1 sigue funcionando intacto (breaking changes = nueva versión), comunicala con fechas de deprecación y cabecera Sunset. Romper consumidores silenciosamente quema la confianza que toda API necesita.

Seguridad transversal

Autenticación por tokens Bearer (OAuth2/OIDC según contexto — flujo completo aquí), HTTPS obligatorio sin excepciones, rate limiting por clave/IP (por qué y cómo se combina con CORS correctamente configurado), y validación de entrada en el borde con esquemas estrictos (Zod, Pydantic): confiar en que "el cliente manda bien los datos" es la puerta de entrada de medio internet roto.

Preguntas frecuentes

¿GraphQL mata a REST? Conviven: GraphQL brilla con clientes móviles que necesitan composiciones exactas y odian over-fetching; REST sigue imbatible en simplicidad, caché HTTP y ecosistema. Muchas plataformas ofrecen ambos.

¿HATEOAS es obligatorio para ser REST? El purismo dice sí; la práctica, no. APIs maduras con hipervínculos son raras; nadie te invalidará por REST pragmático con buen uso de verbos, estados y recursos.

¿Cómo documento? OpenAPI/Swagger como fuente de verdad generada del código o viceversa. Documentación desincronizada es peor que ninguna: automatiza o muere en tickets.


Consulta al instante qué significa cualquier código de respuesta con nuestra guía de códigos HTTP, gratis y directamente en tu navegador.

Pruébalo sin código

Códigos HTTP

Referencia completa de códigos 1xx-5xx.

Abrir Códigos HTTP

Hecho por

Miguel Ángel Colorado Marin (MACM)

Full-Stack Developer · Guadalajara, España

Desarrollo aplicaciones web, herramientas digitales y proyectos completos — desde el diseño hasta el despliegue.

Contáctame