Qué es una API REST y cómo diseñarla bien

Compara estas dos formas de pedir lo mismo a un servidor:

POST /api/obtenerPedidosDelCliente {"idCliente": 42, "accion": "listar"}
GET /api/v1/clientes/42/pedidosLas dos pueden funcionar. Pero la segunda se entiende sin leer documentación, se puede guardar en caché, se puede probar desde el navegador y sigue las mismas reglas que miles de otras APIs. Esa es, en pocas palabras, la diferencia entre una API que solo funciona y una API REST bien diseñada.
Qué significa REST
REST (Representational State Transfer) es un estilo de arquitectura descrito por Roy Fielding en su tesis doctoral del año 2000. No es un protocolo ni una librería: es un conjunto de restricciones para diseñar sistemas que se comunican por HTTP. Las ideas centrales son cuatro:
- Recursos. Todo lo que la API expone es un recurso con una dirección propia: un cliente, un pedido, una factura.
- Representaciones. El cliente no recibe el recurso en sí, sino una representación de él, normalmente en JSON.
- Interfaz uniforme. Las operaciones se expresan con los métodos estándar de HTTP, no con verbos inventados en la URL.
- Sin estado. Cada petición lleva toda la información necesaria para procesarla. El servidor no recuerda "en qué paso iba" el cliente entre una petición y otra.
En la práctica, muy pocas APIs cumplen todas las restricciones de la tesis original. Cuando la gente dice "API REST" suele referirse a una API HTTP que organiza sus URLs alrededor de recursos y usa bien los métodos y códigos de estado. Eso es lo que vamos a diseñar aquí.
Diseña las URLs alrededor de sustantivos
La URL identifica qué recurso quieres; el método HTTP dice qué quieres hacer con él. Si tus rutas contienen verbos, estás mezclando las dos cosas.
| Evita | Prefiere |
|---|---|
GET /obtenerUsuarios | GET /usuarios |
POST /crearUsuario | POST /usuarios |
GET /usuario?id=42 | GET /usuarios/42 |
POST /borrarUsuario/42 | DELETE /usuarios/42 |
GET /usuarios/42/obtenerPedidos | GET /usuarios/42/pedidos |
Unas reglas que ayudan a mantener la coherencia:
- Usa sustantivos en plural para las colecciones:
/productos,/facturas. - Usa minúsculas y separa palabras con guiones:
/metodos-de-pago, no/MetodosDePago. - Anida como máximo un nivel cuando la relación es de pertenencia clara:
/clientes/42/pedidos. Más niveles vuelven las URLs frágiles; para casos complejos, usa filtros:/pedidos?cliente=42. - Para acciones que no encajan en el modelo de recursos, como "enviar una factura por correo", es aceptable una subruta con sustantivo:
POST /facturas/15/envios.
Usa cada método para lo que es
| Método | Uso | ¿Idempotente? | Ejemplo |
|---|---|---|---|
| GET | Leer uno o varios recursos | Sí | GET /pedidos/7 |
| POST | Crear un recurso en una colección | No | POST /pedidos |
| PUT | Reemplazar un recurso completo | Sí | PUT /pedidos/7 |
| PATCH | Modificar algunos campos | No necesariamente | PATCH /pedidos/7 |
| DELETE | Eliminar un recurso | Sí | DELETE /pedidos/7 |
Idempotente significa que repetir la misma petición produce el mismo estado final. Borrar el pedido 7 dos veces deja el sistema igual que borrarlo una vez. Crear un pedido con POST dos veces, en cambio, crea dos pedidos. Esto importa cuando la red falla: un cliente puede reintentar un PUT o un DELETE con tranquilidad, pero reintentar un POST puede duplicar datos.
Para resolver ese problema en operaciones sensibles, como pagos, varias APIs aceptan una cabecera Idempotency-Key: el cliente genera un identificador único por operación y el servidor, si recibe la misma clave dos veces, devuelve la respuesta original en lugar de repetir la acción.
Y una regla sin excepciones: un GET nunca debe modificar datos. Los navegadores, los rastreadores y los sistemas de caché asumen que pueden repetir peticiones GET sin consecuencias.
Responde con el código de estado correcto
Devolver siempre 200 OK con un campo "error": true en el cuerpo obliga a cada cliente a revisar el contenido para saber si algo salió mal. Los códigos de estado existen para eso.
| Código | Cuándo usarlo |
|---|---|
| 200 OK | Lectura o actualización exitosa con cuerpo de respuesta |
| 201 Created | Recurso creado; incluye la cabecera Location con su URL |
| 204 No Content | Éxito sin cuerpo, típico de un DELETE |
| 400 Bad Request | La petición está mal formada: JSON inválido, parámetro con tipo incorrecto |
| 401 Unauthorized | Falta autenticación o el token no es válido |
| 403 Forbidden | El usuario está autenticado pero no tiene permiso |
| 404 Not Found | El recurso no existe |
| 409 Conflict | Choca con el estado actual: email ya registrado, versión desactualizada |
| 422 Unprocessable Content | El formato es correcto pero los datos no pasan las reglas de negocio |
| 429 Too Many Requests | El cliente superó el límite de peticiones |
| 500 Internal Server Error | Fallo inesperado del servidor |
La frontera entre 400 y 422 genera debates. Lo importante es elegir un criterio, documentarlo y aplicarlo igual en toda la API.
Errores que el cliente pueda entender
Un buen mensaje de error dice qué pasó, por qué y, si es posible, cómo corregirlo. Existe un estándar para esto: el formato Problem Details, definido en el RFC 9457, que se sirve con el tipo application/problem+json:
{
"type": "https://api.ejemplo.com/errores/validacion",
"title": "Los datos enviados no son válidos",
"status": 422,
"detail": "Hay 2 campos con errores",
"instance": "/pedidos",
"errores": [
{ "campo": "email", "mensaje": "No tiene un formato válido" },
{ "campo": "cantidad", "mensaje": "Debe ser mayor que cero" }
]
}El estándar permite agregar campos propios, como errores en el ejemplo. Lo que nunca debe aparecer en una respuesta de error es información interna: trazas de excepción, consultas SQL o rutas del servidor. Regístralas en tus logs, no se las muestres al cliente.
Paginación, filtros y orden
Ninguna colección debería devolver todos sus elementos de una vez. Hay dos estrategias principales de paginación:
Por desplazamiento (?limite=20&desplazamiento=40). Es fácil de implementar y permite saltar a cualquier página. Su problema: con tablas grandes, los desplazamientos altos son lentos, y si se insertan elementos mientras el usuario navega, puede ver duplicados o saltarse algunos.
Por cursor (?limite=20&despues=eyJpZCI6MTQwfQ). El servidor devuelve un cursor opaco que marca dónde terminó la página, y el cliente lo envía para pedir la siguiente. Es estable y eficiente, aunque no permite saltar a la página 37 directamente. Es la opción preferida para listas largas o que cambian a menudo.
Una respuesta paginada clara incluye los datos y la información para continuar:
{
"datos": [ { "id": 141, "total": "125000.00" } ],
"paginacion": {
"limite": 20,
"siguiente": "/pedidos?limite=20&despues=eyJpZCI6MTYwfQ"
}
}Para filtrar y ordenar, usa parámetros de consulta con nombres predecibles: /pedidos?estado=pagado&desde=2025-01-01&orden=-creado_en, donde el signo menos indica orden descendente. Documenta qué campos admiten filtro y valida los valores: aceptar cualquier nombre de columna en orden y pasarlo directo a SQL es una puerta abierta a inyecciones.
Formato de los datos
- Elige una convención de nombres y no la mezcles.
creadoEnocreado_en, pero no ambas en la misma API. - Fechas en ISO 8601 con zona horaria, preferiblemente en UTC:
2025-06-30T14:05:00Z. - Dinero sin decimales flotantes. Envía los montos como texto (
"125000.00") o en la unidad mínima de la moneda como entero, y acompáñalos del código de moneda. - Identificadores como texto si pueden superar el rango de enteros seguros de JavaScript, que llega a unos 9.000 billones (2 elevado a 53).
Versiona desde el primer día
Tarde o temprano necesitarás un cambio que rompa la compatibilidad: renombrar un campo, cambiar un tipo, eliminar un recurso. La forma más sencilla de manejarlo es incluir la versión en la URL (/v1/pedidos) y mantener la versión anterior durante un periodo anunciado. Otras APIs usan cabeceras para versionar; es más elegante, pero más difícil de probar y depurar. Para la mayoría de proyectos, la versión en la URL es suficiente.
Los cambios que no rompen nada, como agregar un campo nuevo a una respuesta, no necesitan nueva versión. Por eso los clientes deben ignorar los campos que no conocen.
Seguridad básica que no es opcional
- Sirve la API únicamente por HTTPS.
- Autentica cada petición; no confíes en que "nadie conoce la URL".
- Verifica permisos sobre cada recurso, no solo sobre la ruta. Que un usuario pueda ver
/pedidos/7no significa que pueda ver/pedidos/8. Este fallo, el acceso a objetos de otros usuarios cambiando un identificador, está entre los más frecuentes en las listas de riesgos de OWASP para APIs. - Aplica límites de peticiones y responde 429 cuando se superen.
- Valida todo lo que entra: tipos, longitudes, rangos.
Documenta con OpenAPI
La especificación OpenAPI permite describir tu API en un archivo YAML o JSON: rutas, parámetros, cuerpos y respuestas. A partir de ese archivo hay herramientas que generan documentación navegable, clientes en varios lenguajes y pruebas. Un fragmento mínimo se ve así:
openapi: 3.1.0
info:
title: API de pedidos
version: 1.0.0
paths:
/pedidos/{id}:
get:
summary: Obtiene un pedido
parameters:
- name: id
in: path
required: true
schema:
type: integer
responses:
"200":
description: El pedido solicitado
"404":
description: El pedido no existeUn ejercicio para cerrar
Toma una API que conozcas, propia o de un proyecto en el que trabajes, y revísala con tres preguntas: ¿las URLs contienen verbos?, ¿los errores usan códigos de estado distintos de 200?, ¿las colecciones están paginadas? Si alguna respuesta es "no", ya tienes por dónde empezar a mejorarla, y la próxima API que diseñes desde cero va a nacer con esas decisiones tomadas.
Si quieres conocer otros artículos parecidos a Qué es una API REST y cómo diseñarla bien puedes visitar la categoría Programacion.
Deja una respuesta

Contenido que te pude interesar