Si estás empezando a diseñar APIs o quieres asentar bien los fundamentos, este post recorre todo el proceso desde la primera decisión de diseño hasta la puesta en producción. No es un tutorial paso a paso con código, sino una guía de las decisiones técnicas que hay detrás de una API bien pensada y por qué se toman.
He intentado escribirlo como el recurso que me habría gustado tener cuando diseñé mis primeras APIs: algo que explique no solo el qué sino el por qué de cada decisión.
¿Por qué API REST?
El estilo arquitectónico Representational State Transfer (REST) continúa siendo la base sobre la cual se construye la web moderna. Alternativas como GraphQL resuelven bien las consultas complejas con múltiples relaciones y gRPC ofrece rendimiento superior para la comunicación interna entre microservicios. Aun así, para una API pública o para una capa de integración que deba ser fácil de entender, documentar, observar y evolucionar, REST sigue siendo el punto de partida más estable porque se apoya directamente en HTTP y en sus reglas de caché, semántica y observabilidad.
La decisión de usar REST no es solo una cuestión de popularidad. Su verdadera fortaleza está en dos principios de diseño que tienen consecuencias directas en la arquitectura de cualquier sistema:
Statelessness (sin estado). Cada solicitud del cliente al servidor contiene toda la información necesaria para ser procesada. El servidor no guarda contexto entre peticiones, lo que significa que cualquier instancia del backend puede atender cualquier solicitud. Esto es lo que permite escalar horizontalmente sin preocuparte por la afinidad de sesión, algo fundamental cuando tu aplicación crece y necesitas añadir o quitar servidores de forma elástica.
Orientación a recursos. Todo gira en torno a entidades con las que el cliente interactúa (usuarios, pedidos, contratos), no en torno a funciones o procedimientos remotos. Esto hace que la API sea predecible: si conoces la URL de un recurso y los métodos HTTP, ya sabes cómo interactuar con él sin necesidad de memorizar nombres de funciones arbitrarios.
REST es un excelente punto de partida para aprender diseño de APIs porque su modelo centrado en recursos resulta intuitivo y se alinea naturalmente con cómo pensamos los modelos de negocio. Es decir, te obliga a pensar en tus datos como recursos con relaciones claras, lo cual es una habilidad transferible a cualquier paradigma que uses después. Además, las operaciones CRUD (crear, leer, actualizar, eliminar), como veremos a continuación, se mapean directamente a los métodos HTTP (POST, GET, PUT/PATCH, DELETE), lo que reduce la curva de aprendizaje.
Comprender el propósito de la API
Antes de escribir una sola línea de código, el diseño de una API en un proyecto real comienza con una pregunta aparentemente sencilla: ¿qué problema resuelve esta API y para quién?
Un error frecuente es empezar directamente por la base de datos. Diseñas tus tablas, generas un CRUD automático sobre ellas, y lo que obtienes es una API que expone estructuras de datos internas en lugar de resolver necesidades reales de negocio. Esto se conoce como enfoque Database-First. Aquí surge un problema: cuando el esquema de la base de datos cambia (y siempre cambia), la API se rompe, y con ella todas las integraciones que dependen de ella.
La alternativa es el enfoque API-First, que hoy es el estándar en la industria. La idea es diseñar y validar el contrato de la API con todos los interesados antes de implementar nada. El diseño parte de las necesidades del usuario, no de las tablas.
En la práctica, esto suele comenzar con historias de usuario concretas. Por ejemplo: “Como representante de ventas, necesito ver el historial de compras de un cliente para recomendarle productos.” A partir de ahí diseñas los endpoints basándote en las acciones y los resultados que el usuario necesita, y solo después decides cómo almacenar esos datos.
¿Por qué importa tanto esta separación? Por varias razones prácticas. Primero, las APIs evolucionan con el tiempo, y vincular rígidamente los endpoints a un modelo de base de datos dificulta los cambios futuros. Segundo, en arquitecturas de microservicios, un único endpoint puede agregar datos de múltiples fuentes (bases de datos, servicios externos, colas de eventos), así que no puede seguir un único esquema. Y tercero, la API es tu contrato público con el mundo. Si cambias cómo almacenas los datos internamente, tus consumidores no deberían enterarse.
Además, en un ecosistema moderno los consumidores de tu API son cada vez más diversos: aplicaciones frontend de una sola página (SPA), apps móviles, sistemas de terceros o, cada vez más, agentes de IA que utilizan el Model Context Protocol (MCP) para ejecutar acciones de forma autónoma. Esta diversidad hace que diseñar desde las necesidades del consumidor en lugar de desde las tripas del sistema no sea solo una buena práctica, sino una necesidad.
Una API bien diseñada es el contrato claro entre el servidor y cualquier cliente. Las rutas deben planificarse partiendo de las necesidades del usuario, no del esquema de la base de datos.
Identificar los recursos
Una vez que tienes claro el propósito y los consumidores de tu API, el siguiente paso es identificar los recursos que vas a exponer. En REST, un recurso es cualquier entidad que pueda ser nombrada y manipulada: un usuario, un pedido, un contrato, una factura o una medición.
La pregunta útil no es “qué función necesito”, sino “qué objeto del dominio quiero exponer y manipular”. La identificación correcta de estos recursos es lo que hace que una API sea intuitiva de usar sin necesidad de estudiar la documentación de arriba a abajo.
La clave aquí no es solo listar las entidades de tu dominio, sino nombrarlas de forma que la API sea intuitiva para quien la consume. Las convenciones de REST son claras en este punto:
- Los recursos se nombran con sustantivos en plural, no con verbos. En lugar de
/getUserso/fetchOrders, usamos/usersy/orders. La acción la define el método HTTP, no la URL. - Se utiliza minúsculas y kebab-case (guiones para separar palabras, como
/energy-readings) para mejorar la legibilidad y evitar problemas de sensibilidad a mayúsculas en diferentes servidores o sistemas de archivos. - Cada recurso debe ser identificable de forma única mediante su URI, típicamente con un identificador en la ruta:
/contracts/123.
Estas convenciones pueden parecer menores, pero en una API con decenas de endpoints la consistencia marca la diferencia entre una interfaz que el desarrollador explora con confianza y una que requiere consultar la documentación para cada llamada.
Definir los endpoints y métodos HTTP
Una vez identificados los recursos, la siguiente decisión es cómo interactuamos con ellos.
La interacción con los recursos se realiza a través de los métodos estándar del protocolo HTTP. La clave es entender que el método define la acción y la URL define el recurso sobre el que actúas. Esta separación es lo que hace que REST sea tan expresivo con tan pocas convenciones.
Los métodos más comunes son:
- GET: recuperar datos. Puede ser un listado (
GET /contracts) o un recurso específico (GET /contracts/123). - POST: crear un recurso nuevo dentro de una colección.
- PUT: reemplazar un recurso existente por completo.
- PATCH: modificar solo algunos campos de un recurso.
- DELETE: eliminar un recurso.
Ejemplo para una API de una plataforma de consumo energético:
| Endpoint | Método | Descripción |
|---|---|---|
GET /contracts | GET | Listar todos los contratos |
GET /contracts/{id} | GET | Ver un contrato específico |
POST /contracts | POST | Crear un nuevo contrato |
PATCH /contracts/{id} | PATCH | Actualizar la potencia contratada |
DELETE /contracts/{id} | DELETE | Eliminar un contrato |
Idempotencia: por qué es importante
En sistemas distribuidos, las redes fallan. Las peticiones se pierden, los timeouts saltan y los clientes reintentan automáticamente. El concepto de idempotencia determina si esos reintentos son seguros o peligrosos: una operación es idempotente si ejecutarla múltiples veces produce el mismo resultado que ejecutarla una sola vez.
Un GET es naturalmente idempotente porque solo lee datos. Un DELETE también lo es porque borrar algo que ya no existe simplemente devuelve un 404. Pero un POST no lo es: si el cliente reintenta un POST /payments porque no recibió respuesta, podría crear un pago duplicado. Entender esto es fundamental para diseñar APIs que se comporten de forma predecible en entornos reales.
| Método HTTP | Acción CRUD | Idempotente | Seguro | Descripción |
|---|---|---|---|---|
| GET | Read | Sí | Sí | Recupera la representación de un recurso sin modificarlo. |
| POST | Create | No | No | Crea un nuevo recurso dentro de una colección. |
| PUT | Update (Full) | Sí | No | Reemplaza completamente un recurso existente por uno nuevo. |
| PATCH | Update (Partial) | No* | No | Modifica solo los campos especificados de un recurso. |
| DELETE | Delete | Sí | No | Elimina el recurso especificado del sistema. |
Nota: Aunque PATCH no es estrictamente idempotente según el RFC, muchas implementaciones se diseñan para que lo sean en la práctica.
Sub-colecciones para relaciones jerárquicas
Cuando un recurso depende totalmente de otro, la estructura de la URL debería reflejar esa relación. Esto permite que el desarrollador entienda la jerarquía de los datos de forma intuitiva.
Si usamos /consumptions/123, no queda claro si 123 es el ID del contrato o el ID de una lectura de consumo específica. Por eso, el recurso “hijo” se anida bajo el “padre”:
- Listar consumos de un contrato:
GET /contracts/123/consumptions - Registrar un nuevo consumo:
POST /contracts/123/consumptions
Una buena práctica es limitar el anidamiento a un máximo de dos niveles. Para relaciones más complejas o filtrados transversales, es mejor usar parámetros de consulta (query parameters) en lugar de seguir anidando rutas, ya que esto complica la escalabilidad y hace que las URLs se vuelvan difíciles de leer y mantener.
Solicitud y respuesta
Una vez que tienes definidos los endpoints, necesitas decidir qué información viaja en cada interacción. El formato estándar de la industria es JSON por su ligereza, legibilidad y compatibilidad prácticamente universal.
La solicitud (Request)
Una solicitud HTTP se compone de cuatro elementos:
- URL (Endpoint): la dirección del recurso al que quieres acceder.
- Método HTTP: la acción que quieres realizar (GET, POST, etc.).
- Headers (Cabeceras): metadatos que dan contexto, como el formato de los datos (
Content-Type: application/json) o las credenciales de autenticación. - Body (Cuerpo): la información que envías al servidor. Solo está presente en métodos que envían datos, como POST, PUT o PATCH.
El body merece atención especial porque define el esquema de lo que tu API va a procesar. Un error común es enviar demasiada información o usar nombres de campos ambiguos. Veamos un ejemplo para registrar una lectura de sensor de energía:
{
"kwh_reading": 12.5,
"sensor_id": "SN-99",
"timestamp": "2026-03-11T10:00:00Z"
}
El servidor debe validar esta solicitud antes de procesarla. Si falta el campo kwh_reading, la API no debería intentar guardar nada. Debería rechazar la petición de forma clara e informativa (veremos cómo en la sección de códigos de estado).
La respuesta (Response)
Una buena respuesta no solo confirma que la operación se realizó, sino que devuelve el recurso creado o modificado con toda la información que el cliente pueda necesitar, incluyendo el nuevo ID generado por el servidor:
{
"id": "cons_8892",
"contractId": "ctr_123",
"value": 45.2,
"unit": "kWh",
"sensorId": "SN-99",
"timestamp": "2026-03-11T10:00:00Z",
"createdAt": "2026-03-11T10:00:01Z"
}
Devolver el recurso completo tras la creación ahorra al cliente una segunda llamada GET y hace la integración más fluida. Es una práctica que parece menor, pero que a escala reduce significativamente el número de peticiones.
Códigos de estado HTTP
Los códigos de estado HTTP no son decorativos. Son el mecanismo por el que tu API comunica al cliente y a toda la infraestructura de red (proxies, cachés, balanceadores) qué pasó con la petición. Usar los códigos correctos hace que los sistemas intermedios puedan tomar decisiones sin inspeccionar el cuerpo de la respuesta.
Los más habituales:
| Código | Significado | Cuándo usarlo |
|---|---|---|
200 | OK | La operación se completó correctamente. |
201 | Created | Se creó un recurso nuevo (respuesta típica de POST). |
204 | No Content | Operación exitosa sin contenido en la respuesta (típica de DELETE). |
400 | Bad Request | La solicitud tiene datos inválidos o le faltan campos obligatorios. |
401 | Unauthorized | No se proporcionaron credenciales o son inválidas. |
403 | Forbidden | Las credenciales son válidas pero no tienes permisos para este recurso. |
404 | Not Found | El recurso solicitado no existe. |
429 | Too Many Requests | Se superó el límite de peticiones (rate limiting). |
500 | Internal Server Error | Error inesperado en el servidor. |
503 | Service Unavailable | El servidor no puede atender la petición temporalmente. |
Respuestas de error
Un aspecto que distingue una API bien diseñada es la calidad de sus respuestas de error. No basta con devolver un 400 y un mensaje genérico. La respuesta de error debe explicar exactamente qué falló y, si es posible, cómo corregirlo. Esto cobra aún más relevancia con la creciente adopción de agentes de IA como consumidores, ya que un error bien descrito permite al agente autocorregirse y reintentar sin intervención humana.
Por ejemplo, si un sensor envía una lectura sin el campo obligatorio kwh_reading, la respuesta debería ser:
{
"error": {
"code": 400,
"message": "El campo 'kwh_reading' es obligatorio.",
"request_id": "req-98765",
"details": [
{
"field": "kwh_reading",
"issue": "Campo requerido no proporcionado.",
"suggestion": "Incluye 'kwh_reading' con un valor numérico en kWh."
}
]
}
}
El campo request_id es especialmente importante en sistemas distribuidos. Permite correlacionar la petición del cliente con los logs del servidor, lo que convierte la depuración de errores en producción de algo imposible a algo manejable.
Estructura de los datos, formatos y paginación
JSON sigue siendo el formato de intercambio de datos predominante por su ubicuidad y facilidad de procesamiento. Pero más allá del formato, hay una decisión de arquitectura de datos que muchos equipos subestiman hasta que les explota en producción: la paginación.
El problema del offset
La paginación tradicional basada en offset y limit (e.g., GET /contracts?offset=100&limit=20) funciona bien para prototipos y conjuntos de datos pequeños. Pero tiene dos problemas graves que aparecen con la escala.
El primero es el rendimiento. Cuando un cliente solicita un offset elevado (digamos la página 10.000 con 20 elementos), la base de datos tiene que escanear y descartar 200.000 filas antes de devolver los 20 resultados que interesan. A medida que el offset crece, la latencia crece de forma lineal. En una tabla con millones de registros, esto se vuelve inaceptable.
El segundo es la consistencia. Si se inserta un nuevo registro mientras el cliente está navegando por la página 2, todos los elementos se desplazan una posición. El resultado es que el cliente puede ver registros duplicados o saltarse otros. En un pipeline de datos donde la completitud importa, esto es un defecto crítico.
Paginación basada en cursor (Keyset Pagination)
La paginación basada en cursor resuelve ambos problemas. En lugar de decirle a la base de datos “sáltate N filas”, le decimos “dame los siguientes 20 elementos a partir de este punto de referencia”. Ese punto de referencia es un cursor opaco (normalmente un valor de una columna indexada, como un ID o un timestamp codificado) que el servidor devuelve al cliente.
En la práctica, la respuesta se ve así:
{
"data": [
{
"id": "cons_0441",
"value": 38.1,
"unit": "kWh",
"captured_at": "2026-03-11T08:00:00Z"
},
{
"id": "cons_0442",
"value": 41.7,
"unit": "kWh",
"captured_at": "2026-03-11T08:15:00Z"
}
],
"pagination": {
"next_cursor": "eyJpZCI6ImNvbnNfMDQ0MiJ9",
"has_more": true
}
}
Para obtener la siguiente página, el cliente simplemente envía GET /contracts/123/consumptions?cursor=eyJpZCI6ImNvbnNfMDQ0MiJ9&limit=20. La base de datos realiza una búsqueda directa por índice, lo que mantiene el tiempo de respuesta constante sin importar cuántos registros haya en la tabla.
| Métrica | Offset Pagination | Cursor Pagination |
|---|---|---|
| Filas escaneadas (offset 0) | 20 filas | 20 filas |
| Filas escaneadas (offset 10.000) | 10.020 filas | 20 filas |
| Filas escaneadas (offset 500.000) | 500.020 filas | 20 filas |
| Tiempo de respuesta (1M filas) | Degrada linealmente | Constante |
| Consistencia ante escrituras concurrentes | Baja (duplicados y saltos) | Alta (punto de referencia estable) |
La contrapartida es que con cursores no puedes saltar directamente a “la página 347”, solo avanzar o retroceder. Esto es perfectamente válido para la mayoría de los casos de uso (feeds, listados, logs), pero si tu interfaz necesita navegación directa por número de página, el offset sigue siendo la opción más práctica.
Seguridad
La seguridad de una API no se añade al final como una capa extra. Es una decisión de diseño que afecta a la estructura de los endpoints, al modelado de las respuestas y a la propia arquitectura del sistema. La industria ha adoptado el modelo de Confianza Cero (Zero Trust), donde cada solicitud se autentica y autoriza de forma independiente, sin importar si viene de la red interna o externa.
Autenticación con OAuth 2.1
Para proteger una API, el mecanismo estándar es que el cliente envíe un token de acceso en las cabeceras de cada solicitud. Lo más habitual es usar tokens JWT (JSON Web Token) que viajan en el header Authorization: Bearer <token>.
La obtención de ese token se gestiona mediante un protocolo de autorización, y aquí es donde entra OAuth 2.1. Este estándar, que consolida y reemplaza a OAuth 2.0, no introduce conceptos radicalmente nuevos sino que elimina flujos que se han demostrado inseguros a lo largo de los años. En concreto, el Implicit Grant (que exponía tokens en la URL del navegador) y el Resource Owner Password Credentials (que requería que aplicaciones de terceros manejasen contraseñas de usuarios directamente) desaparecen por completo. Además, OAuth 2.1 hace obligatorio el uso de PKCE (Proof Key for Code Exchange) en todos los flujos de autorización, incluidos los de aplicaciones con backend, algo que en 2.0 solo se recomendaba para clientes públicos como las SPAs.
En la práctica, el flujo más habitual funciona así: el cliente obtiene un token de acceso (generalmente un JWT, JSON Web Token) a través del flujo de autorización, y lo envía en la cabecera de cada petición:
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
El servidor valida el token, extrae la identidad del usuario y sus permisos, y decide si la operación está permitida. Al ser stateless, este mecanismo encaja perfectamente con la arquitectura REST.
Autorización a nivel de objeto
Más allá de verificar que el usuario está autenticado, la API debe comprobar que tiene permisos sobre el recurso específico que solicita. El fallo en esta comprobación conduce a BOLA (Broken Object Level Authorization), que es consistentemente una de las vulnerabilidades más explotadas en APIs según los informes de OWASP.
Un ejemplo concreto: si el usuario A solicita GET /contracts/456 y el contrato 456 pertenece al usuario B, la API debe devolver un 403 Forbidden aunque el token de A sea perfectamente válido. Esto no puede delegarse solo al middleware de autenticación, debe verificarse explícitamente en la lógica de negocio.
Para escenarios más complejos, el modelo RBAC (Control de Acceso Basado en Roles) puede quedarse corto. El enfoque ABAC (Control de Acceso Basado en Atributos) permite definir políticas más granulares que evalúan factores como la hora de la solicitud, la ubicación geográfica del cliente o el nivel de sensibilidad de los datos que se intentan acceder.
La industria ha evolucionado hacia arquitecturas de Confianza Cero (Zero Trust), donde cada solicitud se autentica y autoriza independientemente de si proviene de la red interna o externa. La premisa es que el perímetro de red ya no es una frontera de seguridad fiable.
Versionado
Las APIs en proyectos reales son productos vivos. Llega un momento en que necesitas cambiar la estructura de una respuesta, renombrar campos o eliminar endpoints obsoletos. Sin una estrategia de versionado, cualquiera de estos cambios rompe las integraciones de tus clientes de un día para otro.
Imagina que tu API de consumo energético devuelve el valor en kWh como un número simple. Un año después, necesitas soportar energía reactiva y devolver un objeto más complejo con múltiples unidades de medida. Sin versionado, cambiar esa estructura obliga a todos los clientes a actualizar su código simultáneamente, algo que en la práctica no va a ocurrir.
Existen tres enfoques principales, cada uno con sus compromisos:
| Método | Ejemplo | Ventaja principal | Compromiso |
|---|---|---|---|
| URL Path | /api/v1/contracts | Claridad total, excelente para caché | El mismo recurso tiene dos URIs distintas, lo que rompe la pureza REST |
| Custom Header | X-API-Version: 2 | Mantiene las URIs limpias | Requiere configurar la cabecera Vary para un caching correcto |
| Content Negotiation | Accept: application/vnd.energy.v2+json | Máxima conformidad REST | Mayor complejidad para el consumidor |
Para APIs públicas, el versionado en la URL (/v1/, /v2/) es la opción más pragmática porque cualquier desarrollador puede ver la versión que está usando con solo mirar la URL. Para APIs internas en entornos empresariales donde la estabilidad de las URIs es prioritaria, las cabeceras o la negociación de contenido pueden ser más adecuadas.
La forma más común y recomendada por su claridad es incluir la versión en la URL:
https://api.energia.com/v1/contracts/...
Gestión del ciclo de vida
Un aspecto que muchos equipos ignoran es cómo retirar versiones antiguas. No basta con dejar de mantenerlas: la API debe comunicar activamente su estado utilizando cabeceras estándar como Deprecation: true y Sunset: Sat, 01 Mar 2027 00:00:00 GMT para informar a los clientes sobre la fecha programada de retiro. El margen habitual para la migración suele ser de 6 a 12 meses, dependiendo del tipo de consumidores.
Documentación y gobernanza
La documentación de una API no es un documento estático que se escribe al final del proyecto. En un enfoque API-First, la documentación es el diseño. La especificación OpenAPI 4.0 (conocida durante su desarrollo como Moonwalk) representa un salto importante respecto a la versión 3.x al reestructurar la forma en que se describen las operaciones. El concepto de Signatures reemplaza la organización basada estrictamente en paths, lo que permite describir de forma más natural operaciones que no encajan en el modelo clásico de rutas (como webhooks o callbacks) y facilita la reutilización de definiciones entre distintas APIs.
Sobre esta especificación se construyen portales de desarrollador interactivos que permiten explorar y probar endpoints en tiempo real. Pero el valor real va más allá de la documentación visual. Cuando el contrato OpenAPI es la fuente de verdad, se pueden generar automáticamente clientes SDK, validadores de peticiones, mocks para testing y reglas de linting que bloqueen el despliegue de endpoints que no cumplan los estándares de diseño del equipo (nombres de campos incorrectos, falta de esquemas de error, etc.). La gobernanza de APIs se automatiza.
Para arquitecturas que incluyen comunicación asíncrona a través de eventos, colas de mensajes o WebSockets, existe AsyncAPI, que aplica los mismos principios de diseño por contrato a estos patrones. Documentar tanto las interacciones síncronas (OpenAPI) como las asíncronas (AsyncAPI) garantiza que ningún flujo de datos quede sin especificar.
Testing
La calidad de una API en producción se garantiza con pruebas, pero no todas las estrategias de testing tienen el mismo retorno de inversión.
En sistemas de microservicios, las pruebas de integración end-to-end (E2E) son necesarias pero costosas: requieren levantar múltiples servicios, son lentas de ejecutar y se rompen con frecuencia por razones ajenas al código que estás probando. El Contract Testing (pruebas de contrato), usando frameworks como Pact o Specmatic, complementa este enfoque validando que el proveedor y el consumidor de la API estén de acuerdo en el formato de los datos sin necesidad de desplegar ambos servicios simultáneamente.
El flujo funciona en dos direcciones:
- Consumer-Driven Contracts. El equipo que consume la API define sus expectativas (qué campos espera, con qué tipos, en qué estructura). Esto genera un archivo de contrato que el equipo proveedor utiliza para validar su implementación.
- Provider Verification. El proveedor ejecuta ese contrato contra su código real y verifica que sus respuestas cumplen lo pactado. Cualquier cambio disruptivo en el esquema (como cambiar un campo de booleano a string) se detecta inmediatamente en el pipeline de CI/CD, no cuando las aplicaciones de los clientes fallan en producción.
Este enfoque encaja con la filosofía Shift-Left, donde las pruebas se desplazan lo más temprano posible en el ciclo de desarrollo para detectar problemas cuando son baratos de corregir.
APIs para agentes de IA y Model Context Protocol (MCP)
Hay un tipo de consumidor que está cambiando la forma en que diseñamos APIs: los agentes autónomos de inteligencia artificial. Estos agentes no leen documentación ni navegan por portales de desarrollador. Consumen metadatos semánticos para entender las capacidades de una herramienta y ejecutar acciones de forma independiente.
El Model Context Protocol (MCP), impulsado inicialmente por Anthropic y adoptado de forma creciente por la industria, es el estándar que permite conectar modelos de lenguaje (LLMs) con fuentes de datos y herramientas externas de manera segura y estructurada. MCP utiliza una arquitectura cliente-servidor basada en JSON-RPC 2.0 y define tres primitivas fundamentales: Tools (funciones que el modelo puede invocar), Resources (fuentes de datos que puede consultar) y Prompts (plantillas de interacción predefinidas).
Para que una API esté preparada para este tipo de consumidores, conviene seguir algunos principios de diseño:
- Descripciones basadas en intención. Las descripciones de los endpoints en el esquema OpenAPI deben explicar no solo qué hace el endpoint, sino por qué y cuándo se debería usar, en lenguaje natural rico. Un agente de IA que lea “Registra una lectura de consumo energético asociada a un contrato. Usa este endpoint cuando dispongas de datos de sensor con valores en kWh” puede razonar mucho mejor que con una descripción genérica como “Crea un consumo”.
- Feedback semántico en los errores. Ya lo vimos en la sección de códigos de estado: las respuestas de error deben incluir sugerencias de corrección. Para un agente de IA esto no es un detalle de usabilidad, es lo que le permite autocorregirse y reintentar la llamada con los parámetros correctos sin intervención humana.
- Diseño orientado al encadenamiento. Cuando los recursos de tu API representan pasos dentro de un flujo de negocio, un agente puede encadenar llamadas de forma autónoma. Por ejemplo, consultar consumos de un contrato, detectar una anomalía, crear una alerta y notificar al responsable, todo sin que un humano orqueste el proceso.
Implementación
Una API bien diseñada necesita una arquitectura interna que refleje la misma separación de responsabilidades que promueve hacia el exterior. La estructura por capas es lo que permite que distintos equipos trabajen en paralelo, que los tests sean aislados y que los cambios en la base de datos no se propaguen hasta los endpoints.
| Capa | Responsabilidad | Herramientas |
|---|---|---|
| Routes/Endpoints | Definición de URIs, validación de entrada y serialización. | Fastify, Express, FastAPI, Zod. |
| Controllers | Manejo de la solicitud/respuesta HTTP y orquestación. | Clases controladoras, Middlewares. |
| Services | Lógica de negocio pura, agnóstica de HTTP. | Inyección de dependencias. |
| Repositories | Abstracción del acceso a datos (SQL, NoSQL, APIs externas). | Prisma, TypeORM, SQLAlchemy. |
La capa de Services merece atención especial. Si toda la lógica de negocio vive aquí, desacoplada del transporte HTTP, puedes reutilizarla desde un endpoint REST, un worker que procesa eventos de una cola o un job programado. Es la misma lógica servida por distintos canales, sin duplicar código.
Observabilidad
La observabilidad es lo que cierra el ciclo de vida de una API en producción. No se trata solo de monitorizar si el servidor responde, sino de entender cómo se comporta el sistema bajo condiciones reales.
La instrumentación con OpenTelemetry permite recolectar trazas distribuidas que conectan una petición del cliente con todas las operaciones internas que genera (consultas a base de datos, llamadas a otros servicios, procesamiento en cola). Cuando un endpoint responde lento, las trazas te dicen exactamente dónde se pierde el tiempo, algo que con logs tradicionales requeriría horas de investigación.
Un detalle que parece menor pero importa mucho: implementar un health check que vaya más allá de devolver un 200 OK. Un health check útil verifica la conectividad con la base de datos, la disponibilidad de servicios externos críticos y el estado de las colas. Esto permite que los balanceadores de carga tomen decisiones informadas y no envíen tráfico a instancias que están técnicamente arrancadas pero no pueden procesar peticiones.
Si has llegado hasta aquí, ya tienes una visión completa del proceso. Ten en cuenta, que cada sección tiene sus matices y da para mucho más.
Para cerrar, después de recorrer todo el proceso, estos son los puntos con los que merece la pena quedarse:
- Diseña para quien consume, no para quien almacena. Los endpoints deben partir de las necesidades del usuario, no del esquema de la base de datos. Son dos problemas distintos y acoplarlos es una deuda técnica que pagarás antes de lo que esperas.
- La consistencia importa más que la perfección. Recursos en plural, kebab-case, métodos HTTP usados correctamente, códigos de estado precisos.
- La importancia de la idempotencia. En redes reales las peticiones se pierden y los clientes reintentan. Saber qué operaciones son seguras de reintentar y diseñar mecanismos para las que no lo son (como idempotency keys en los POST) evita los bugs más difíciles de diagnosticar en producción.
- Elige la paginación según el caso de uso. La paginación por offset es cómoda hasta que deja de serlo. Si tu API va a manejar colecciones grandes o datos que se actualizan con frecuencia, la paginación por cursor te ahorrará problemas de rendimiento y consistencia que con offset no tienen solución.
- La seguridad se construye en capas. Autenticación y autorización son problemas distintos.
- Versiona desde el primer día. Aunque solo exista la v1 y no tengas planes de cambio. El coste de añadir
/v1/a tus rutas es mínimo y te da la flexibilidad de evolucionar sin romper integraciones cuando inevitablemente necesites hacerlo. - La documentación es el producto. Una especificación OpenAPI es el contrato que permite generar SDKs, validar el cumplimiento de estándares y construir portales de desarrollador que hagan tu API accesible.
- Los contract tests previenen los fallos que las pruebas unitarias no ven. Un cambio de tipo en un campo o un campo renombrado puede pasar todos los tests del backend y romper cinco aplicaciones cliente en producción. Las pruebas de contrato detectan eso antes del despliegue.
- Diseña pensando en que tu próximo consumidor puede no ser humano. Los agentes de IA que usan MCP no leen documentación, interpretan metadatos. Descripciones claras basadas en intención, errores con sugerencias de corrección y recursos que representan metas de negocio encadenables ya no son un nice-to-have, son lo que hace que tu API sea utilizable por el ecosistema que se está construyendo ahora mismo.
- La observabilidad cierra el ciclo.
El diseño de una API es un ejercicio de equilibrio entre la adherencia a estándares probados y la capacidad de anticiparse a las necesidades de un ecosistema cada vez más interconectado y automatizado. No existe la API perfecta, pero sí existe la API que fue diseñada con intención, documentada con rigor y preparada para evolucionar.
