Cómo diseñar APIs en las que los clientes puedan confiar: guía práctica desde el contrato

Una buena API hace predecible la próxima acción del cliente. Diseña una inscripción a un curso con reintentos seguros, errores útiles y un plan de evolución.
Una API es una promesa para otro equipo. Una aplicación móvil, una integración externa o una versión futura de tu frontend pueden depender de su comportamiento. Cuando eso ocurre, cambiar el nombre de un campo ya no es una refactorización local. Por eso conviene definir primero el comportamiento, los fallos y los reintentos; el controlador viene después.
Diseñemos una API de inscripción a cursos. El estudiante consulta un curso, solicita una plaza y recibe un identificador y un estado. El curso puede estar completo; la petición puede repetirse; la red puede fallar después de guardar la inscripción. Esos casos normales revelan la calidad del diseño.
1. Escribe el recorrido y las reglas
El cliente muestra el curso, el estudiante autenticado solicita la inscripción y recibe una respuesta clara. Si hay un tiempo de espera, puede reintentar sin duplicar la inscripción. Establece reglas de servidor: una inscripción activa por estudiante y curso, una ventana de inscripción abierta y capacidad que nunca se excede. Si hay pago, decide cuándo se reserva la plaza y cuándo se libera si falla.
Comparte una solicitud y una respuesta de ejemplo con el equipo cliente antes de programar. Es más fácil corregir un estado ambiguo o un campo ausente en esta fase.
2. Organiza recursos según el trabajo
| Solicitud | Objetivo | Respuesta habitual |
|---|---|---|
GET /v1/courses/{courseId} |
Leer el curso | 200 OK |
GET /v1/courses?cursor=...&limit=20 |
Explorar cursos | 200 OK |
POST /v1/courses/{courseId}/enrollments |
Inscribirse | 201 Created |
GET /v1/enrollments/{enrollmentId} |
Ver el estado | 200 OK |
DELETE /v1/enrollments/{enrollmentId} |
Cancelar si se permite | 204 No Content |
Si el trabajo continúa en segundo plano, 202 Accepted con un recurso de estado puede representar mejor la realidad. Usa GET para lectura y POST para crear o iniciar una operación. Que ciertos métodos HTTP sean idempotentes no vuelve seguro automáticamente cualquier POST. Evita exponer nombres de controladores o tablas en la ruta pública.
3. Diseña petición y respuesta a la vez
POST /v1/courses/crs_42/enrollments HTTP/1.1
Authorization: Bearer <access-token>
Content-Type: application/json
Idempotency-Key: 9bfa06f8-3eb5-4d5d-85cd-43b02b56bf11
{"source":"course_page"}
Obtén la identidad del estudiante de la credencial validada, no de un learnerId editable en el cuerpo. Una respuesta posible:
HTTP/1.1 201 Created
Location: /v1/enrollments/enr_7f3
Content-Type: application/json
{"id":"enr_7f3","courseId":"crs_42","status":"confirmed","createdAt":"2026-09-29T10:30:00Z"}
Documenta si los ID son opacos, los cambios posibles de estado y qué campos admiten null. No devuelvas la fila completa de la base de datos con notas internas o datos de otros alumnos. Para listas, usa un sobre consistente como data y page.nextCursor; especifica filtros y orden. Un cursor sin orden estable puede omitir o repetir elementos.
4. Convierte los errores en información útil
«Algo salió mal» no indica qué hacer. Distingue un curso cerrado de una sesión caducada, una falta de permiso y un fallo del servidor. RFC 9457 Problem Details ofrece un formato común:
{
"type": "https://api.example.com/problems/enrollment-closed",
"title": "Enrollment is closed",
"status": 409,
"detail": "This course stopped accepting enrollments."
}
En producción, type debe apuntar a una definición real. Documenta también los errores por campo. No muestres trazas, mensajes SQL ni secretos; conserva diagnósticos en registros seguros con un ID de correlación. Acordad el uso de 401 para autenticación, 403 para permiso, 404 según la política de visibilidad, 409 para conflicto y 429 para límites de frecuencia.
5. Prepara reintentos y concurrencia
El servidor puede confirmar la inscripción y perderse la respuesta. Con Idempotency-Key, el cliente envía una clave única para ese intento; el servidor guarda el resultado y lo devuelve ante la misma clave y petición. Rechaza la clave reutilizada con otro contenido. Define alcance, duración y respuesta. No es una garantía automática de HTTP para POST.
Protege la regla en la base de datos con restricciones adecuadas al ciclo de vida. Si queda una plaza, dos solicitudes simultáneas no deben ocuparla ambas: usa una transacción o reserva segura y una prueba concurrente. Los webhooks y trabajos en cola también pueden repetirse; diseña su procesamiento para ello.
6. Separa autenticación de autorización
La primera responde «¿quién llama?»; la segunda «¿puede acceder a este recurso?». Un token válido no permite leer la inscripción de otro estudiante. Aplica reglas de alumno, docente y administrador en el servidor. No confíes en un rol enviado en el cuerpo. Evita registrar tokens y documenta cómo debe esperar el cliente después de 429, además de si el límite es por usuario, token o cuenta.
7. Documenta, prueba y prepara el cambio
Describe rutas, esquemas, seguridad, respuestas y ejemplos en OpenAPI. Pide a un desarrollador cliente que integre usando solo ese documento: sus dudas mostrarán huecos. Prueba 201, el mismo intento repetido, una clave reutilizada con otra petición, curso completo, acceso indebido, carrera por la última plaza y orden de paginación.
Una ruta /v1 no es por sí sola un plan de versiones. Mantén un registro de cambios, anuncia la retirada de funciones, observa los clientes antiguos y publica una guía de migración. No cambies silenciosamente el significado de un campo. Si agregas pending_payment, considera cómo reaccionarán clientes que solo conocen confirmed y cancelled.
Revisión antes de publicar
¿Entiende el cliente los éxitos y los fallos accionables? ¿Compruebas permisos sobre el recurso? ¿Qué ocurre al repetir o competir dos peticiones? ¿Están claros filtros, orden y paginación? ¿Coincide OpenAPI con el comportamiento? ¿Puedes investigar fallos sin exponer datos privados? ¿Hay plan para cambios?
Una API no inspira confianza porque el caso feliz funcione una vez. La inspira cuando el cliente puede recuperarse de un corte de red, manejar un curso lleno y seguir funcionando tras tu siguiente versión.
Referencias
Artículos destacados

Kafka en producción: particiones, lag, fiabilidad y respuesta a incidentes
Una guía práctica para tomar decisiones de orden, capacidad y recuperación en Kafka, interpretar el lag y responder a fallos sin perder de vista el resultado de negocio.

Laravel y Kafka sin eventos perdidos: outbox transaccional y consumidores idempotentes con PostgreSQL
Una confirmación en PostgreSQL y una publicación en Kafka no forman una transacción ordinaria. Aprende a evitar pérdidas y duplicación de efectos.

Apache Kafka explicado: temas, particiones, grupos de consumidores y tu primer flujo de eventos
Sigue un evento de pedido desde el productor hasta varios consumidores y prueba en local cómo funcionan las particiones, el orden y la reproducción.
Comentarios
0 comentariosTodavía no hay comentarios aprobados. Las respuestas nuevas pueden esperar moderación.