Cuándo usar: al crear o modificar endpoints, diseñar el contrato de un servicio, definir el formato de errores, decidir versionado, paginación o idempotencia, revisar una API antes de exponerla, o al elegir entre REST/GraphQL/gRPC/webhooks. Si estás escribiendo una ruta, un DTO o un código de estado, esta guía aplica.
Una API es un contrato público: una vez que un cliente depende de ella, cambiarla cuesta caro. Diseña pensando en que vas a vivir con esa decisión durante años. La consistencia vale más que la perfección: es mejor una convención "buena" aplicada en todos lados que una convención "perfecta" aplicada a medias.
1. Principios fundamentales
El contrato es lo primero (contract-first). Diseña el request/response y los errores antes de escribir lógica. El contrato es la única parte que no puedes romper sin avisar.
Orientación a recursos, no a acciones. La URL identifica cosas (recursos); el verbo HTTP identifica la acción. POST /users en vez de POST /createUser.
Predecible y consistente. Si GET /users devuelve una lista paginada, GET /orders debe hacerlo igual. Un desarrollador debe poder adivinar tu API tras ver tres endpoints.
Explícito sobre implícito. Estados, errores y formatos declarados. Nada de "si el campo viene vacío significa X".
Robusto en lo que aceptas, estricto en lo que prometes. Valida entrada con firmeza; mantén tu salida estable y documentada.
Diseña para el fallo. Reintentos, timeouts, idempotencia y rate limiting no son extras: son parte del contrato.
Seguridad y privacidad por defecto. No expongas internals, IDs autoincrementales sensibles ni detalles de stack. Ver skill security.
La documentación es parte del entregable. Una API sin OpenAPI actualizado es una API rota a medias.
2. Reglas de oro (Haz / Evita)
Haz ✅
Evita ❌
GET /users/123/orders
GET /getUserOrders?id=123
Sustantivos en plural: /products
Verbos en la ruta: /createProduct
Códigos de estado semánticos (201, 404, 422)
Devolver 200 OK con {"error": true}
Errores en formato application/problem+json
Errores con forma distinta en cada endpoint
Paginación por cursor en listas grandes
Devolver 50k registros sin límite
Idempotency-Key en POST de pagos
POST no idempotente que cobra dos veces al reintentar
Fechas en ISO 8601 UTC
12/06/2026 (ambiguo día/mes)
Versionar desde el día 1 (/v1)
Romper el contrato sin versión
Validar y devolver 422 con detalle de campos
500 genérico ante entrada inválida
Naming y casing consistente en todo
userName aquí y user_name allá
3. Diseño orientado a recursos y nomenclatura de URLs
Un recurso es un sustantivo del dominio: user, order, invoice. La URL es su dirección.
Reglas de nomenclatura:
Sustantivos, no verbos. El verbo lo pone HTTP.
Plural para colecciones:/users (colección), /users/123 (elemento). Mantén el plural incluso para el elemento; no mezcles /user/123.
Jerarquía para relaciones de pertenencia:/users/123/orders/456. Evita anidar más de 2 niveles; a partir de ahí usa filtros: /orders?userId=123.
minúsculas siempre en el path.
kebab-case para segmentos de varias palabras: /purchase-orders, no /purchaseOrders ni /purchase_orders.
Sin extensiones (.json) ni sufijos de tecnología. La negociación de formato va en Accept.
IDs opacos si es posible (UUID o ULID) para no filtrar volumen ni permitir enumeración. Ver security.
Sub-recursos para acciones que no encajan en CRUD, modeladas como recurso: POST /orders/456/cancellations en lugar de POST /orders/456/cancel. Cuando es imposible, un endpoint de "acción" acotado es tolerable: POST /orders/456/actions/cancel.
GET /articles # lista de artículos
POST /articles # crea un artículo
GET /articles/{id} # un artículo
PATCH /articles/{id} # modifica parcialmente
DELETE /articles/{id} # elimina
GET /articles/{id}/comments # comentarios de ese artículo
4. Verbos HTTP: semántica, seguridad e idempotencia
Verbo
Uso
¿Seguro?
¿Idempotente?
Body request
GET
Leer un recurso o colección
Sí
Sí
No
POST
Crear recurso / operación no idempotente
No
No
Sí
PUT
Reemplazo completo (o crear con ID conocido)
No
Sí
Sí (completo)
PATCH
Modificación parcial
No
No garantizado*
Sí (parcial)
DELETE
Eliminar
No
Sí
Opcional
Seguro = no altera estado del servidor (solo lectura). Nunca uses GET para mutar; los proxies y prefetchers pueden repetirlo.
Idempotente = repetir la misma petición N veces deja el mismo estado final que hacerla 1 vez. Clave para reintentos seguros ante timeouts de red.
PUT vs PATCH:PUT reemplaza el recurso entero (los campos omitidos se borran/resetean); PATCH toca solo lo enviado. No uses PUT para updates parciales.
* PATCH puede ser idempotente según cómo lo diseñes. Un PATCH que fija valores absolutos ({"status": "paid"}) es idempotente; uno que hace deltas ({"balance": "+10"}) no lo es. Prefiere el primero.
POST es el verbo por defecto para acciones no idempotentes; protégelo con Idempotency-Key (sección 8).
5. Códigos de estado HTTP
Usa el código que comunica el resultado real. El status es la primera señal que lee el cliente y su lógica de reintento depende de él.
Código
Significado
Cuándo usarlo
200 OK
Éxito con cuerpo
GET, PATCH/PUT con respuesta
201 Created
Recurso creado
POST que crea; incluye header Location
202 Accepted
Aceptado, procesamiento async
Jobs, colas; devuelve URL de seguimiento
204 No Content
Éxito sin cuerpo
DELETE, o PUT/PATCH sin retorno
301/308
Movido permanente
Cambio de URL estable
304 Not Modified
Caché válido
Respuesta a If-None-Match/If-Modified-Since
400 Bad Request
Petición malformada
JSON inválido, sintaxis rota
401 Unauthorized
Falta o falla autenticación
Token ausente/inválido
403 Forbidden
Autenticado pero sin permiso
Autorización denegada
404 Not Found
Recurso inexistente
ID no encontrado (o para ocultar existencia)
405 Method Not Allowed
Verbo no soportado en esa ruta
Incluye header Allow
409 Conflict
Conflicto de estado
Duplicado, versión desactualizada
410 Gone
Recurso eliminado permanentemente
Recursos retirados
412 Precondition Failed
Falló condicional
If-Match con ETag viejo
422 Unprocessable Entity
Sintaxis OK, semántica inválida
Validación de negocio (email inválido, campo requerido)
429 Too Many Requests
Rate limit excedido
Incluye Retry-After
500 Internal Server Error
Fallo del servidor no controlado
Bug; nunca filtres stack trace
502/503/504
Upstream/indisponible/timeout
Degradación; el cliente puede reintentar
Claves:
Distingue 401 vs 403: 401 = "no sé quién eres"; 403 = "sé quién eres y no puedes".
Distingue 400 vs 422: 400 = no pude parsear; 422 = parseé pero los datos violan reglas. Muchos frameworks usan 400 para ambos; si eliges 422 para validación, sé consistente.
4xx = culpa del cliente (no reintentar igual); 5xx = culpa del servidor (reintentable con backoff). Esta división guía la lógica de reintentos del cliente.
Nunca 200 con un error dentro. Rompe caches, monitoreo y clientes.
6. Formato de errores consistente (RFC 7807)
Todos los errores deben tener la misma forma en toda la API. Usa application/problem+json (RFC 9457, antes RFC 7807).
{
"type": "https://api.example.com/errors/validation",
"title": "La solicitud contiene campos inválidos",
"status": 422,
"detail": "El campo 'email' no tiene un formato válido.",
"instance": "/users",
"traceId": "b7d3f1a2-...",
"errors": [
{ "field": "email", "code": "invalid_format", "message": "Debe ser un email válido." },
{ "field": "age", "code": "out_of_range", "message": "Debe ser mayor o igual a 18." }
]
}
Campos:
type (URI): identificador estable y documentable del tipo de error. La clave que el cliente puede usar para ramificar lógica.
title: resumen legible, constante por type.
status: repite el código HTTP (útil cuando el status se pierde en logs).
detail: mensaje específico de esta ocurrencia.
instance: URI de la ocurrencia.
traceId (extensión): correlaciona con logs/observabilidad. Ver skill error-handling-observability.
errors[] (extensión): detalle por campo para validación. Usa code legible por máquina, no solo message.
Reglas:
Nunca filtres stack traces, nombres de tablas, queries SQL ni rutas internas en detail. Ver security.
Mensajes accionables para el cliente, genéricos para lo interno (loguea lo detallado del lado servidor con el mismo traceId).
El code por campo permite i18n y ramificación programática sin parsear texto.
7. Versionado de API
Versiona desde el primer release. No versionar es apostar a que nunca cambiarás nada incompatible.
Estrategia
Ejemplo
Pros
Contras
URL path
GET /v1/users
Visible, cacheable, fácil de rutear y probar en navegador
"No purista" REST; versiona toda la API a la vez
Header custom
Api-Version: 2026-06-30
URL limpia; granularidad fina
Menos visible; difícil de probar a mano; cache más complejo
Media type
Accept: application/vnd.example.v2+json
Purista; versiona por recurso
Complejo; poco intuitivo para consumidores
Recomendación:versionado en URL path (/v1) para la mayoría de APIs públicas y de producto: es explícito, trivial de rutear, cachear y depurar. Reserva versionado por header/media-type para APIs muy grandes con ciclos de vida por recurso.
Reglas de evolución:
Cambios aditivos son compatibles: agregar campos opcionales o endpoints no rompe. Los clientes deben ignorar campos desconocidos (diséñalos tolerantes).
Cambios incompatibles (renombrar/eliminar campos, cambiar tipos, endurecer validación) exigen nueva versión mayor.
Publica política de deprecación: header Deprecation + Sunset (RFC 8594), fecha de retiro y guía de migración. Da meses, no días.
Fechas como versión (2026-06-30) funcionan bien para APIs con evolución continua (estilo Stripe).
8. Paginación, filtrado, ordenamiento y búsqueda
Nunca devuelvas una colección sin límite. Pagina siempre.
Offset/limit — simple, permite saltar a página N:
GET /users?limit=20&offset=40
Pros: fácil, "página 5" directo.
Contras: se degrada en datasets grandes (el motor cuenta y descarta filas); inconsistente si se insertan/borran filas mientras paginas (saltos o duplicados).
Cursor (keyset) — recomendado para listas grandes o en tiempo real:
Pros: rendimiento estable (usa índice WHERE id > ?), consistente ante inserciones. Ver database-design y performance.
Contras: no permite "ir a la página 7"; solo siguiente/anterior. El cursor debe ser opaco (codifica el keyset, no lo expongas crudo).
Filtrado, orden y búsqueda por query params, con convención estable:
GET /orders?status=paid&createdAfter=2026-01-01&sort=-createdAt,total&q=laptop&fields=id,total
Filtros: campo=valor. Para rangos, prefijos claros: createdAfter, minPrice.
Orden: sort=-createdAt (- = descendente); múltiples separados por coma.
Búsqueda de texto libre: q=.
Selección de campos (sparse fieldsets): fields=id,name para reducir payload.
Documenta y valida los campos permitidos: no permitas ordenar/filtrar por columnas arbitrarias (riesgo de rendimiento e inyección). Ver security.
9. Idempotencia y reintentos seguros
Las redes fallan después de que el servidor procesó pero antes de que la respuesta llegue. El cliente reintenta y —sin protección— duplica la operación (doble cobro, doble pedido).
GET, PUT, DELETE son idempotentes por naturaleza: reintentar es seguro.
POSTno lo es. Protégelo con Idempotency-Key:
POST /payments
Idempotency-Key: 8f14e45f-...
Content-Type: application/json
Mecánica del lado servidor:
El cliente genera una clave única (UUID) por intento lógico de operación y la reutiliza en cada reintento.
El servidor almacena (idempotency-key → resultado) con TTL (p. ej. 24h).
Si llega una clave ya vista: devuelve el resultado guardado sin re-ejecutar.
Si la clave está en proceso: responde 409 Conflict o espera.
Valida que el body coincida con el de la primera vez; si difiere, 422.
Reglas:
El cliente reintenta con backoff exponencial + jitter, solo ante 5xx, 429 y timeouts de red. Nunca reintenta 4xx (salvo 429).
Operaciones intrínsecamente no idempotentes (crear pago, enviar email) requierenIdempotency-Key.
Persiste las claves en almacenamiento durable (no solo memoria) para sobrevivir reinicios. Ver database-design.
10. Validación de entrada y contratos (schema-first)
Schema-first / OpenAPI-first: define el contrato en OpenAPI y genera validadores, tipos y stubs desde ahí. El schema es la fuente de verdad, no el código.
Valida todo lo que entra en el borde: tipos, formatos, rangos, longitudes, enums, campos requeridos. Rechaza lo desconocido según política (estricto para writes sensibles).
DTOs separados de entidades de dominio. No serialices tu modelo de base de datos directo: filtras internals y acoplas el contrato al esquema de datos. Define CreateUserRequest, UserResponse explícitos.
No confíes en el cliente para nada de seguridad: precios, roles, ownerId se derivan del servidor/token, nunca del body.
Devuelve 422 con la lista de campos inválidos (sección 6), no un 500.
// CreateUserRequest (lo que aceptas)
{ "email": "a@b.com", "password": "secret", "name": "Ada" }
// UserResponse (lo que devuelves — sin password, sin campos internos)
{ "id": "usr_01H...", "email": "a@b.com", "name": "Ada", "createdAt": "2026-06-30T10:00:00Z" }
Fácil; sin identidad de usuario ni granularidad. Rotables, revocables.
OAuth2
Acceso delegado de terceros, apps de usuario
Estándar para "inicia sesión con"; scopes por permiso.
JWT (Bearer)
Sesiones stateless, microservicios
Autocontenido, verificable sin DB; no revocable fácilmente → usa expiración corta + refresh tokens.
Reglas:
Credenciales siempre por header Authorization: Bearer <token>, nunca en query string (se filtran en logs y caches).
Solo HTTPS. Sin TLS, cualquier token viaja en claro.
Nunca devuelvas datos sensibles (password hash, tokens internos, PII innecesaria) en respuestas. Usa DTOs.
Autorización por recurso: verifica que el usuario es dueño de /orders/456, no solo que está autenticado. El fallo aquí es una de las vulnerabilidades más comunes (BOLA/IDOR).
401 para auth ausente/inválida; 403 para permiso denegado. Para no revelar existencia de recursos ajenos, 404 puede ser preferible a 403.
Detalle profundo en la skill security.
12. Rate limiting
Protege la API de abuso y garantiza equidad. Comunica los límites por headers:
HTTP/1.1 429 Too Many Requests
Retry-After: 30
RateLimit-Limit: 100
RateLimit-Remaining: 0
RateLimit-Reset: 30
429 cuando se excede la cuota.
Retry-After: segundos (o fecha HTTP) que el cliente debe esperar. El cliente debe respetarlo.
Expón el estado de cuota en cada respuesta (RateLimit-*) para que el cliente se autorregule antes de chocar.
Define límites por clave/usuario/IP y por endpoint (los caros más estrictos). Ver performance y security.
13. Caché HTTP
Reduce latencia y carga sin lógica extra en el cliente. Ver performance.
Cache-Control controla la cacheabilidad:
public, max-age=3600 — cacheable por proxies y navegador 1h.
private, max-age=0, no-cache — solo cliente, revalida siempre.
no-store — datos sensibles, nunca se guardan.
ETag (validador de versión) + petición condicional:
# Respuesta
ETag: "a1b2c3"
# Petición posterior del cliente
GET /articles/1
If-None-Match: "a1b2c3"
# → 304 Not Modified si no cambió (ahorra ancho de banda)
ETag + If-Match también sirve para concurrencia optimista: PUT/PATCH con If-Match: "a1b2c3" → 412 Precondition Failed si otro modificó el recurso. Evita el "last write wins".
Last-Modified + If-Modified-Since es la alternativa basada en fecha.
14. HATEOAS y madurez de Richardson (breve)
El Modelo de Madurez de Richardson clasifica cuán RESTful es una API:
Nivel 0 — un solo endpoint, todo por POST (RPC sobre HTTP). No REST.
Nivel 1 — Recursos: múltiples URLs por recurso, pero un solo verbo.
Nivel 2 — Verbos HTTP: verbos y códigos de estado correctos. Aquí vive la mayoría de las APIs "REST" reales y es un objetivo perfectamente válido.
Nivel 3 — HATEOAS: las respuestas incluyen links que indican las acciones siguientes posibles.
Pragmatismo: apunta a Nivel 2 como mínimo sólido. HATEOAS (Nivel 3) es elegante pero pocos clientes lo aprovechan; adóptalo solo si tu consumidor navegará dinámicamente. No te obsesiones con la pureza.
APIs de navegador (soporte limitado), consumidores públicos heterogéneos
Webhooks
Push del servidor por evento
Notificar al cliente de eventos async (pagos, cambios de estado)
Necesitas respuesta síncrona; el cliente no puede exponer endpoint
Claves:
REST es el default seguro para APIs de producto y públicas.
GraphQL brilla con front-ends que consumen datos heterogéneos; su costo es caché, rate limiting por complejidad de query y evitar N+1 (ver performance, database-design).
gRPC para el interior de tu sistema (service-to-service).
Webhooks complementan REST para lo asíncrono; firma el payload (HMAC) y hazlos idempotentes del lado receptor (reintentos duplican). Ver security.
Casing de campos: elige uno y aplícalo en TODA la API. snake_case es común en REST; camelCase si tu ecosistema es JS/JSON. Nunca los mezcles.
Fechas y horas: siempre ISO 8601 en UTC con offset: 2026-06-30T10:00:00Z. Nunca formatos locales ambiguos ni timestamps epoch sin documentar. Incluye zona.
Booleanos con nombres claros: isActive, hasShipped (no flag, no status=1).
Enums como strings estables ("pending", "paid"), no números mágicos. Documenta los valores posibles.
Dinero: entero en la unidad mínima (centavos) + código de moneda ISO 4217 ({ "amount": 1050, "currency": "USD" }), o string decimal. Nunca float para dinero.
Envelope de respuesta: decide si envuelves o no y sé consistente:
Valida y sanitiza toda entrada. Trata cada input como hostil: previene inyección SQL/NoSQL/comando (ver database-design).
No filtres internals en errores: ni stack traces, ni nombres de tabla, ni versiones de software. Mensaje genérico al cliente, detalle en logs con traceId.
CORS restrictivo: whitelist explícita de orígenes; no uses Access-Control-Allow-Origin: * en endpoints autenticados.
Rate limiting contra fuerza bruta y abuso (sección 12).
Autorización por objeto (evita IDOR/BOLA): verifica ownership en cada acceso, no solo autenticación.
Mínima exposición de datos: devuelve solo lo necesario; nada de PII o secretos de más.
Payloads con límite de tamaño y timeouts, para frenar DoS.
Secretos fuera del código y de las URLs. Tokens por header, nunca en query params.
18. Documentación como contrato (OpenAPI)
OpenAPI (Swagger) es el contrato ejecutable: describe endpoints, schemas, ejemplos, errores y auth. Genera docs, SDKs, mocks y tests desde él.
Contract-first: escribe/actualiza el OpenAPI antes de implementar. Que el código cumpla el spec, no al revés.
Versiona el contrato junto al código (en el repo) y trátalo como parte de la definición de "hecho": un PR que cambia la API sin actualizar OpenAPI está incompleto.
Valida en CI que la implementación cumple el spec (contract testing). Detecta breaking changes automáticamente comparando versiones del spec.
Publica ejemplos reales de request/response por endpoint, incluidos los errores.
Anti-patrones comunes
Verbos en la URL:/getUser, /createOrder, /user/delete. Usa recursos + verbo HTTP.
Túnel POST: todo por POST con un campo action (Richardson Nivel 0).
200 OK con error dentro del body. Rompe clientes, caché y monitoreo.
Filtrar internals en errores (stack trace, SQL, rutas). Fuga de seguridad.
No paginar y devolver colecciones ilimitadas. Bomba de latencia/memoria.
Naming/casing inconsistente entre endpoints (user_id vs userId).
No versionar y luego romper a todos los clientes con un cambio incompatible.
Serializar la entidad de dominio/DB directo, exponiendo campos internos y acoplando el contrato al esquema.
POST de cobro sin idempotencia: el reintento por timeout cobra dos veces.
Confiar en el cliente para precio, rol u ownership (viene en el body).
Ignorar códigos de estado: todo 200 o todo 500.
Anidamiento profundo de rutas (/a/1/b/2/c/3/d/4): usa filtros.
Fechas locales/ambiguas sin zona horaria.
Breaking changes silenciosos en producción sin Deprecation/Sunset.
Checklist antes de dar por buena una API
URLs orientadas a recursos: sustantivos, plural, minúsculas, kebab-case.
Verbos HTTP con semántica correcta; sin verbos en la ruta.
Códigos de estado precisos por caso (201 al crear, 422 en validación, 404/403 diferenciados).
Formato de error único (problem+json) con type, title, status, detail, traceId.
Sin fugas de internals en errores; mensajes genéricos + logs correlacionados.
Versionado definido y visible (/v1) con política de deprecación.
Toda colección paginada (cursor para grandes); filtros/orden validados y documentados.
Idempotency-Key en POST no idempotentes; reintentos con backoff definidos.
Entrada validada en el borde; DTOs de request/response separados del dominio.
AuthN y AuthZ implementadas; autorización por objeto (sin IDOR); solo HTTPS.
Rate limiting con 429 + Retry-After y headers RateLimit-*.
Caché donde aplique: ETag/Cache-Control; concurrencia optimista con If-Match.
Naming, casing, fechas ISO 8601 UTC y formato de dinero consistentes en toda la API.
Envelope de respuesta y semántica de null/ausencia definidas y uniformes.
OpenAPI actualizado, versionado en el repo y validado en CI.
Ejemplos de request/response y de errores documentados por endpoint.
Skills relacionadas:security (auth, CORS, inyección, secretos), database-design (paginación por keyset, N+1, persistencia de idempotencia), error-handling-observability (traceId, logging, correlación) y performance (caché, rate limiting, payloads).
1---2name: api-design3description: Diseño de APIs y endpoints: REST, versionado, errores, paginación, idempotencia, contratos.4---56# Diseño de APIs y Endpoints78> **Cuándo usar:** al crear o modificar endpoints, diseñar el contrato de un servicio, definir el formato de errores, decidir versionado, paginación o idempotencia, revisar una API antes de exponerla, o al elegir entre REST/GraphQL/gRPC/webhooks. Si estás escribiendo una ruta, un DTO o un código de estado, esta guía aplica.910Una API es un **contrato público**: una vez que un cliente depende de ella, cambiarla cuesta caro. Diseña pensando en que vas a vivir con esa decisión durante años. La consistencia vale más que la perfección: es mejor una convención "buena" aplicada en todos lados que una convención "perfecta" aplicada a medias.1112---1314## 1. Principios fundamentales15161. **El contrato es lo primero (contract-first).** Diseña el request/response y los errores antes de escribir lógica. El contrato es la única parte que no puedes romper sin avisar.172. **Orientación a recursos, no a acciones.** La URL identifica *cosas* (recursos); el verbo HTTP identifica la *acción*. `POST /users` en vez de `POST /createUser`.183. **Predecible y consistente.** Si `GET /users` devuelve una lista paginada, `GET /orders` debe hacerlo igual. Un desarrollador debe poder *adivinar* tu API tras ver tres endpoints.194. **Explícito sobre implícito.** Estados, errores y formatos declarados. Nada de "si el campo viene vacío significa X".205. **Robusto en lo que aceptas, estricto en lo que prometes.** Valida entrada con firmeza; mantén tu salida estable y documentada.216. **Diseña para el fallo.** Reintentos, timeouts, idempotencia y rate limiting no son extras: son parte del contrato.227. **Seguridad y privacidad por defecto.** No expongas internals, IDs autoincrementales sensibles ni detalles de stack. Ver skill **security**.238. **La documentación es parte del entregable.** Una API sin OpenAPI actualizado es una API rota a medias.2425---2627## 2. Reglas de oro (Haz / Evita)2829| Haz ✅ | Evita ❌ |30|--------|----------|31| `GET /users/123/orders` | `GET /getUserOrders?id=123` |32| Sustantivos en plural: `/products` | Verbos en la ruta: `/createProduct` |33| Códigos de estado semánticos (201, 404, 422) | Devolver `200 OK` con `{"error": true}` |34| Errores en formato `application/problem+json` | Errores con forma distinta en cada endpoint |35| Paginación por cursor en listas grandes | Devolver 50k registros sin límite |36| `Idempotency-Key` en POST de pagos | POST no idempotente que cobra dos veces al reintentar |37| Fechas en ISO 8601 UTC | `12/06/2026` (ambiguo día/mes) |38| Versionar desde el día 1 (`/v1`) | Romper el contrato sin versión |39| Validar y devolver 422 con detalle de campos | 500 genérico ante entrada inválida |40| Naming y casing consistente en todo | `userName` aquí y `user_name` allá |4142---4344## 3. Diseño orientado a recursos y nomenclatura de URLs4546Un **recurso** es un sustantivo del dominio: `user`, `order`, `invoice`. La URL es su dirección.4748Reglas de nomenclatura:4950- **Sustantivos, no verbos.** El verbo lo pone HTTP.51- **Plural para colecciones:** `/users` (colección), `/users/123` (elemento). Mantén el plural incluso para el elemento; no mezcles `/user/123`.52- **Jerarquía para relaciones de pertenencia:** `/users/123/orders/456`. Evita anidar más de 2 niveles; a partir de ahí usa filtros: `/orders?userId=123`.53- **minúsculas** siempre en el path.54- **kebab-case** para segmentos de varias palabras: `/purchase-orders`, no `/purchaseOrders` ni `/purchase_orders`.55- **Sin extensiones** (`.json`) ni sufijos de tecnología. La negociación de formato va en `Accept`.56- **IDs opacos si es posible** (UUID o ULID) para no filtrar volumen ni permitir enumeración. Ver **security**.57- **Sub-recursos para acciones que no encajan en CRUD**, modeladas como recurso: `POST /orders/456/cancellations` en lugar de `POST /orders/456/cancel`. Cuando es imposible, un endpoint de "acción" acotado es tolerable: `POST /orders/456/actions/cancel`.5859```60GET /articles # lista de artículos61POST /articles # crea un artículo62GET /articles/{id} # un artículo63PATCH /articles/{id} # modifica parcialmente64DELETE /articles/{id} # elimina65GET /articles/{id}/comments # comentarios de ese artículo66```6768---6970## 4. Verbos HTTP: semántica, seguridad e idempotencia7172| Verbo | Uso | ¿Seguro? | ¿Idempotente? | Body request |73|-------|-----|:--------:|:-------------:|--------------|74| `GET` | Leer un recurso o colección | Sí | Sí | No |75| `POST` | Crear recurso / operación no idempotente | No | **No** | Sí |76| `PUT` | Reemplazo completo (o crear con ID conocido) | No | Sí | Sí (completo) |77| `PATCH` | Modificación parcial | No | No garantizado* | Sí (parcial) |78| `DELETE` | Eliminar | No | Sí | Opcional |7980- **Seguro** = no altera estado del servidor (solo lectura). Nunca uses `GET` para mutar; los proxies y prefetchers pueden repetirlo.81- **Idempotente** = repetir la misma petición N veces deja el mismo estado final que hacerla 1 vez. Clave para reintentos seguros ante timeouts de red.82- **PUT vs PATCH:** `PUT` reemplaza el recurso entero (los campos omitidos se borran/resetean); `PATCH` toca solo lo enviado. No uses `PUT` para updates parciales.83- \* **PATCH** puede ser idempotente según cómo lo diseñes. Un `PATCH` que fija valores absolutos (`{"status": "paid"}`) es idempotente; uno que hace deltas (`{"balance": "+10"}`) no lo es. Prefiere el primero.84- **POST** es el verbo por defecto para acciones no idempotentes; protégelo con `Idempotency-Key` (sección 8).8586---8788## 5. Códigos de estado HTTP8990Usa el código que comunica el resultado real. El status es la primera señal que lee el cliente y su lógica de reintento depende de él.9192| Código | Significado | Cuándo usarlo |93|--------|-------------|---------------|94| `200 OK` | Éxito con cuerpo | GET, PATCH/PUT con respuesta |95| `201 Created` | Recurso creado | POST que crea; incluye header `Location` |96| `202 Accepted` | Aceptado, procesamiento async | Jobs, colas; devuelve URL de seguimiento |97| `204 No Content` | Éxito sin cuerpo | DELETE, o PUT/PATCH sin retorno |98| `301/308` | Movido permanente | Cambio de URL estable |99| `304 Not Modified` | Caché válido | Respuesta a `If-None-Match`/`If-Modified-Since` |100| `400 Bad Request` | Petición malformada | JSON inválido, sintaxis rota |101| `401 Unauthorized` | Falta o falla autenticación | Token ausente/inválido |102| `403 Forbidden` | Autenticado pero sin permiso | Autorización denegada |103| `404 Not Found` | Recurso inexistente | ID no encontrado (o para ocultar existencia) |104| `405 Method Not Allowed` | Verbo no soportado en esa ruta | Incluye header `Allow` |105| `409 Conflict` | Conflicto de estado | Duplicado, versión desactualizada |106| `410 Gone` | Recurso eliminado permanentemente | Recursos retirados |107| `412 Precondition Failed` | Falló condicional | `If-Match` con ETag viejo |108| `422 Unprocessable Entity` | Sintaxis OK, semántica inválida | Validación de negocio (email inválido, campo requerido) |109| `429 Too Many Requests` | Rate limit excedido | Incluye `Retry-After` |110| `500 Internal Server Error` | Fallo del servidor no controlado | Bug; nunca filtres stack trace |111| `502/503/504` | Upstream/indisponible/timeout | Degradación; el cliente puede reintentar |112113Claves:114- **Distingue 401 vs 403**: 401 = "no sé quién eres"; 403 = "sé quién eres y no puedes".115- **Distingue 400 vs 422**: 400 = no pude parsear; 422 = parseé pero los datos violan reglas. Muchos frameworks usan 400 para ambos; si eliges 422 para validación, sé consistente.116- **4xx = culpa del cliente** (no reintentar igual); **5xx = culpa del servidor** (reintentable con backoff). Esta división guía la lógica de reintentos del cliente.117- Nunca `200` con un error dentro. Rompe caches, monitoreo y clientes.118119---120121## 6. Formato de errores consistente (RFC 7807)122123Todos los errores deben tener **la misma forma** en toda la API. Usa `application/problem+json` (RFC 9457, antes RFC 7807).124125```http126HTTP/1.1 422 Unprocessable Entity127Content-Type: application/problem+json128```129```json130{131 "type": "https://api.example.com/errors/validation",132 "title": "La solicitud contiene campos inválidos",133 "status": 422,134 "detail": "El campo 'email' no tiene un formato válido.",135 "instance": "/users",136 "traceId": "b7d3f1a2-...",137 "errors": [138 { "field": "email", "code": "invalid_format", "message": "Debe ser un email válido." },139 { "field": "age", "code": "out_of_range", "message": "Debe ser mayor o igual a 18." }140 ]141}142```143144Campos:145- `type` (URI): identificador estable y documentable del tipo de error. La clave que el cliente puede usar para ramificar lógica.146- `title`: resumen legible, constante por `type`.147- `status`: repite el código HTTP (útil cuando el status se pierde en logs).148- `detail`: mensaje específico de *esta* ocurrencia.149- `instance`: URI de la ocurrencia.150- `traceId` (extensión): correlaciona con logs/observabilidad. Ver skill **error-handling-observability**.151- `errors[]` (extensión): detalle por campo para validación. Usa `code` legible por máquina, no solo `message`.152153Reglas:154- **Nunca** filtres stack traces, nombres de tablas, queries SQL ni rutas internas en `detail`. Ver **security**.155- Mensajes accionables para el cliente, genéricos para lo interno (loguea lo detallado del lado servidor con el mismo `traceId`).156- El `code` por campo permite i18n y ramificación programática sin parsear texto.157158---159160## 7. Versionado de API161162Versiona **desde el primer release**. No versionar es apostar a que nunca cambiarás nada incompatible.163164| Estrategia | Ejemplo | Pros | Contras |165|-----------|---------|------|---------|166| **URL path** | `GET /v1/users` | Visible, cacheable, fácil de rutear y probar en navegador | "No purista" REST; versiona toda la API a la vez |167| **Header custom** | `Api-Version: 2026-06-30` | URL limpia; granularidad fina | Menos visible; difícil de probar a mano; cache más complejo |168| **Media type** | `Accept: application/vnd.example.v2+json` | Purista; versiona por recurso | Complejo; poco intuitivo para consumidores |169170**Recomendación:** **versionado en URL path (`/v1`)** para la mayoría de APIs públicas y de producto: es explícito, trivial de rutear, cachear y depurar. Reserva versionado por header/media-type para APIs muy grandes con ciclos de vida por recurso.171172Reglas de evolución:173- **Cambios aditivos son compatibles**: agregar campos opcionales o endpoints no rompe. Los clientes deben ignorar campos desconocidos (diséñalos tolerantes).174- **Cambios incompatibles** (renombrar/eliminar campos, cambiar tipos, endurecer validación) exigen nueva versión mayor.175- Publica **política de deprecación**: header `Deprecation` + `Sunset` (RFC 8594), fecha de retiro y guía de migración. Da meses, no días.176- Fechas como versión (`2026-06-30`) funcionan bien para APIs con evolución continua (estilo Stripe).177178---179180## 8. Paginación, filtrado, ordenamiento y búsqueda181182**Nunca** devuelvas una colección sin límite. Pagina siempre.183184**Offset/limit** — simple, permite saltar a página N:185```186GET /users?limit=20&offset=40187```188- Pros: fácil, "página 5" directo.189- Contras: se degrada en datasets grandes (el motor cuenta y descarta filas); **inconsistente** si se insertan/borran filas mientras paginas (saltos o duplicados).190191**Cursor (keyset)** — recomendado para listas grandes o en tiempo real:192```193GET /users?limit=20&cursor=eyJpZCI6MTIzfQ==194```195```json196{197 "data": [ /* ... */ ],198 "pagination": {199 "nextCursor": "eyJpZCI6MTQzfQ==",200 "hasMore": true201 }202}203```204- Pros: rendimiento estable (usa índice `WHERE id > ?`), consistente ante inserciones. Ver **database-design** y **performance**.205- Contras: no permite "ir a la página 7"; solo siguiente/anterior. El cursor debe ser **opaco** (codifica el keyset, no lo expongas crudo).206207**Filtrado, orden y búsqueda** por query params, con convención estable:208```209GET /orders?status=paid&createdAfter=2026-01-01&sort=-createdAt,total&q=laptop&fields=id,total210```211- Filtros: `campo=valor`. Para rangos, prefijos claros: `createdAfter`, `minPrice`.212- Orden: `sort=-createdAt` (`-` = descendente); múltiples separados por coma.213- Búsqueda de texto libre: `q=`.214- Selección de campos (sparse fieldsets): `fields=id,name` para reducir payload.215- **Documenta y valida** los campos permitidos: no permitas ordenar/filtrar por columnas arbitrarias (riesgo de rendimiento e inyección). Ver **security**.216217---218219## 9. Idempotencia y reintentos seguros220221Las redes fallan **después** de que el servidor procesó pero **antes** de que la respuesta llegue. El cliente reintenta y —sin protección— duplica la operación (doble cobro, doble pedido).222223- `GET`, `PUT`, `DELETE` son idempotentes por naturaleza: reintentar es seguro.224- `POST` **no** lo es. Protégelo con **`Idempotency-Key`**:225226```http227POST /payments228Idempotency-Key: 8f14e45f-...229Content-Type: application/json230```231232Mecánica del lado servidor:2331. El cliente genera una clave única (UUID) por *intento lógico* de operación y la reutiliza en cada reintento.2342. El servidor almacena `(idempotency-key → resultado)` con TTL (p. ej. 24h).2353. Si llega una clave ya vista: devuelve el **resultado guardado** sin re-ejecutar.2364. Si la clave está en proceso: responde `409 Conflict` o espera.2375. Valida que el body coincida con el de la primera vez; si difiere, `422`.238239Reglas:240- El cliente reintenta con **backoff exponencial + jitter**, solo ante `5xx`, `429` y timeouts de red. Nunca reintenta `4xx` (salvo `429`).241- Operaciones intrínsecamente no idempotentes (crear pago, enviar email) **requieren** `Idempotency-Key`.242- Persiste las claves en almacenamiento durable (no solo memoria) para sobrevivir reinicios. Ver **database-design**.243244---245246## 10. Validación de entrada y contratos (schema-first)247248- **Schema-first / OpenAPI-first:** define el contrato en OpenAPI y genera validadores, tipos y stubs desde ahí. El schema es la fuente de verdad, no el código.249- **Valida todo lo que entra** en el borde: tipos, formatos, rangos, longitudes, enums, campos requeridos. Rechaza lo desconocido según política (estricto para writes sensibles).250- **DTOs separados de entidades de dominio.** No serialices tu modelo de base de datos directo: filtras internals y acoplas el contrato al esquema de datos. Define `CreateUserRequest`, `UserResponse` explícitos.251- **No confíes en el cliente** para nada de seguridad: precios, roles, `ownerId` se derivan del servidor/token, nunca del body.252- Devuelve **422** con la lista de campos inválidos (sección 6), no un 500.253254```jsonc255// CreateUserRequest (lo que aceptas)256{ "email": "a@b.com", "password": "secret", "name": "Ada" }257258// UserResponse (lo que devuelves — sin password, sin campos internos)259{ "id": "usr_01H...", "email": "a@b.com", "name": "Ada", "createdAt": "2026-06-30T10:00:00Z" }260```261262---263264## 11. Autenticación y autorización265266- **Autenticación** = quién eres. **Autorización** = qué puedes hacer. Son distintas; implementa ambas.267268| Mecanismo | Cuándo | Notas |269|-----------|--------|-------|270| **API Keys** | Server-to-server, integraciones simples | Fácil; sin identidad de usuario ni granularidad. Rotables, revocables. |271| **OAuth2** | Acceso delegado de terceros, apps de usuario | Estándar para "inicia sesión con"; scopes por permiso. |272| **JWT (Bearer)** | Sesiones stateless, microservicios | Autocontenido, verificable sin DB; **no revocable** fácilmente → usa expiración corta + refresh tokens. |273274Reglas:275- Credenciales **siempre** por header `Authorization: Bearer <token>`, **nunca** en query string (se filtran en logs y caches).276- **Solo HTTPS.** Sin TLS, cualquier token viaja en claro.277- **Nunca** devuelvas datos sensibles (password hash, tokens internos, PII innecesaria) en respuestas. Usa DTOs.278- Autorización **por recurso**: verifica que el usuario es dueño de `/orders/456`, no solo que está autenticado. El fallo aquí es una de las vulnerabilidades más comunes (BOLA/IDOR).279- `401` para auth ausente/inválida; `403` para permiso denegado. Para no revelar existencia de recursos ajenos, `404` puede ser preferible a `403`.280- Detalle profundo en la skill **security**.281282---283284## 12. Rate limiting285286Protege la API de abuso y garantiza equidad. Comunica los límites por headers:287288```http289HTTP/1.1 429 Too Many Requests290Retry-After: 30291RateLimit-Limit: 100292RateLimit-Remaining: 0293RateLimit-Reset: 30294```295296- `429` cuando se excede la cuota.297- `Retry-After`: segundos (o fecha HTTP) que el cliente debe esperar. El cliente **debe** respetarlo.298- Expón el estado de cuota en cada respuesta (`RateLimit-*`) para que el cliente se autorregule antes de chocar.299- Define límites por clave/usuario/IP y por endpoint (los caros más estrictos). Ver **performance** y **security**.300301---302303## 13. Caché HTTP304305Reduce latencia y carga sin lógica extra en el cliente. Ver **performance**.306307- **`Cache-Control`** controla la cacheabilidad:308 - `public, max-age=3600` — cacheable por proxies y navegador 1h.309 - `private, max-age=0, no-cache` — solo cliente, revalida siempre.310 - `no-store` — datos sensibles, nunca se guardan.311- **`ETag`** (validador de versión) + petición condicional:312313```http314# Respuesta315ETag: "a1b2c3"316317# Petición posterior del cliente318GET /articles/1319If-None-Match: "a1b2c3"320# → 304 Not Modified si no cambió (ahorra ancho de banda)321```322323- **`ETag` + `If-Match`** también sirve para **concurrencia optimista**: `PUT`/`PATCH` con `If-Match: "a1b2c3"` → `412 Precondition Failed` si otro modificó el recurso. Evita el "last write wins".324- `Last-Modified` + `If-Modified-Since` es la alternativa basada en fecha.325326---327328## 14. HATEOAS y madurez de Richardson (breve)329330El **Modelo de Madurez de Richardson** clasifica cuán RESTful es una API:331332- **Nivel 0** — un solo endpoint, todo por POST (RPC sobre HTTP). No REST.333- **Nivel 1 — Recursos:** múltiples URLs por recurso, pero un solo verbo.334- **Nivel 2 — Verbos HTTP:** verbos y códigos de estado correctos. **Aquí vive la mayoría de las APIs "REST" reales y es un objetivo perfectamente válido.**335- **Nivel 3 — HATEOAS:** las respuestas incluyen links que indican las acciones siguientes posibles.336337```json338{339 "id": "ord_123",340 "status": "pending",341 "_links": {342 "self": { "href": "/orders/ord_123" },343 "cancel": { "href": "/orders/ord_123/cancellations", "method": "POST" },344 "pay": { "href": "/orders/ord_123/payments", "method": "POST" }345 }346}347```348349**Pragmatismo:** apunta a **Nivel 2** como mínimo sólido. HATEOAS (Nivel 3) es elegante pero pocos clientes lo aprovechan; adóptalo solo si tu consumidor navegará dinámicamente. No te obsesiones con la pureza.350351---352353## 15. REST vs GraphQL vs gRPC vs Webhooks354355| Estilo | Modelo | Mejor para | Evita cuando |356|--------|--------|-----------|--------------|357| **REST** | Recursos + HTTP | APIs públicas, CRUD, caché HTTP, amplia interoperabilidad | Necesitas agregación compleja de muchos recursos por vista |358| **GraphQL** | Grafo, query del cliente | Clientes que arman vistas variadas, evitar over/under-fetching, front-ends ricos | Caché HTTP simple; equipos sin apetito por su complejidad (N+1, rate limiting por costo) |359| **gRPC** | RPC + Protobuf/HTTP2 | Microservicios internos, baja latencia, streaming, contratos tipados | APIs de navegador (soporte limitado), consumidores públicos heterogéneos |360| **Webhooks** | Push del servidor por evento | Notificar al cliente de eventos async (pagos, cambios de estado) | Necesitas respuesta síncrona; el cliente no puede exponer endpoint |361362Claves:363- **REST** es el default seguro para APIs de producto y públicas.364- **GraphQL** brilla con front-ends que consumen datos heterogéneos; su costo es caché, rate limiting por complejidad de query y evitar N+1 (ver **performance**, **database-design**).365- **gRPC** para el interior de tu sistema (service-to-service).366- **Webhooks** complementan REST para lo asíncrono; **firma el payload** (HMAC) y hazlos idempotentes del lado receptor (reintentos duplican). Ver **security**.367368---369370## 16. Consistencia: naming, fechas, casing, envelopes371372- **Casing de campos:** elige **uno** y aplícalo en TODA la API. `snake_case` es común en REST; `camelCase` si tu ecosistema es JS/JSON. Nunca los mezcles.373- **Fechas y horas: siempre ISO 8601 en UTC** con offset: `2026-06-30T10:00:00Z`. Nunca formatos locales ambiguos ni timestamps epoch sin documentar. Incluye zona.374- **Booleanos** con nombres claros: `isActive`, `hasShipped` (no `flag`, no `status=1`).375- **Enums** como strings estables (`"pending"`, `"paid"`), no números mágicos. Documenta los valores posibles.376- **Dinero:** entero en la unidad mínima (centavos) + código de moneda ISO 4217 (`{ "amount": 1050, "currency": "USD" }`), o string decimal. **Nunca** float para dinero.377- **Envelope de respuesta:** decide si envuelves o no y sé consistente:378 ```json379 { "data": { ... }, "meta": { ... } } // colección o recurso380 { "data": [ ... ], "pagination": { ... } } // lista381 ```382 Un envelope da lugar para metadata/paginación/links sin romper el contrato. Alternativa válida: recurso "desnudo" + paginación en headers. Elige una.383- **Null vs ausente:** define la semántica. Omitir un campo ≠ enviarlo `null`. Documenta cuál usas (relevante para `PATCH`).384385---386387## 17. Seguridad de API (esenciales)388389Complementa con la skill **security**; aquí lo mínimo innegociable:390391- **HTTPS obligatorio.** Redirige HTTP→HTTPS; considera HSTS.392- **Valida y sanitiza toda entrada.** Trata cada input como hostil: previene inyección SQL/NoSQL/comando (ver **database-design**).393- **No filtres internals en errores:** ni stack traces, ni nombres de tabla, ni versiones de software. Mensaje genérico al cliente, detalle en logs con `traceId`.394- **CORS restrictivo:** whitelist explícita de orígenes; no uses `Access-Control-Allow-Origin: *` en endpoints autenticados.395- **Rate limiting** contra fuerza bruta y abuso (sección 12).396- **Autorización por objeto** (evita IDOR/BOLA): verifica ownership en cada acceso, no solo autenticación.397- **Mínima exposición de datos:** devuelve solo lo necesario; nada de PII o secretos de más.398- **Payloads con límite de tamaño** y timeouts, para frenar DoS.399- **Secretos fuera del código y de las URLs.** Tokens por header, nunca en query params.400401---402403## 18. Documentación como contrato (OpenAPI)404405- **OpenAPI (Swagger)** es el contrato ejecutable: describe endpoints, schemas, ejemplos, errores y auth. Genera docs, SDKs, mocks y tests desde él.406- **Contract-first:** escribe/actualiza el OpenAPI *antes* de implementar. Que el código cumpla el spec, no al revés.407- **Versiona el contrato junto al código** (en el repo) y trátalo como parte de la definición de "hecho": un PR que cambia la API sin actualizar OpenAPI está incompleto.408- Valida en CI que la implementación cumple el spec (contract testing). Detecta breaking changes automáticamente comparando versiones del spec.409- Publica ejemplos reales de request/response por endpoint, incluidos los errores.410411---412413## Anti-patrones comunes414415- **Verbos en la URL:** `/getUser`, `/createOrder`, `/user/delete`. Usa recursos + verbo HTTP.416- **Túnel POST:** todo por `POST` con un campo `action` (Richardson Nivel 0).417- **`200 OK` con error dentro** del body. Rompe clientes, caché y monitoreo.418- **Filtrar internals** en errores (stack trace, SQL, rutas). Fuga de seguridad.419- **No paginar** y devolver colecciones ilimitadas. Bomba de latencia/memoria.420- **Naming/casing inconsistente** entre endpoints (`user_id` vs `userId`).421- **No versionar** y luego romper a todos los clientes con un cambio incompatible.422- **Serializar la entidad de dominio/DB directo,** exponiendo campos internos y acoplando el contrato al esquema.423- **`POST` de cobro sin idempotencia:** el reintento por timeout cobra dos veces.424- **Confiar en el cliente** para precio, rol u ownership (viene en el body).425- **Ignorar códigos de estado:** todo `200` o todo `500`.426- **Anidamiento profundo de rutas** (`/a/1/b/2/c/3/d/4`): usa filtros.427- **Fechas locales/ambiguas** sin zona horaria.428- **Breaking changes silenciosos** en producción sin `Deprecation`/`Sunset`.429430---431432## Checklist antes de dar por buena una API433434- [ ] URLs orientadas a recursos: sustantivos, plural, minúsculas, kebab-case.435- [ ] Verbos HTTP con semántica correcta; sin verbos en la ruta.436- [ ] Códigos de estado precisos por caso (201 al crear, 422 en validación, 404/403 diferenciados).437- [ ] Formato de error único (`problem+json`) con `type`, `title`, `status`, `detail`, `traceId`.438- [ ] Sin fugas de internals en errores; mensajes genéricos + logs correlacionados.439- [ ] Versionado definido y visible (`/v1`) con política de deprecación.440- [ ] Toda colección paginada (cursor para grandes); filtros/orden validados y documentados.441- [ ] `Idempotency-Key` en `POST` no idempotentes; reintentos con backoff definidos.442- [ ] Entrada validada en el borde; DTOs de request/response separados del dominio.443- [ ] AuthN y AuthZ implementadas; autorización por objeto (sin IDOR); solo HTTPS.444- [ ] Rate limiting con `429` + `Retry-After` y headers `RateLimit-*`.445- [ ] Caché donde aplique: `ETag`/`Cache-Control`; concurrencia optimista con `If-Match`.446- [ ] Naming, casing, fechas ISO 8601 UTC y formato de dinero consistentes en toda la API.447- [ ] Envelope de respuesta y semántica de `null`/ausencia definidas y uniformes.448- [ ] OpenAPI actualizado, versionado en el repo y validado en CI.449- [ ] Ejemplos de request/response y de errores documentados por endpoint.450451---452453## Referencias454455- RFC 9457 — Problem Details for HTTP APIs (reemplaza RFC 7807): https://www.rfc-editor.org/rfc/rfc9457456- RFC 9110 — HTTP Semantics (métodos y códigos de estado): https://www.rfc-editor.org/rfc/rfc9110457- RFC 8594 — Sunset HTTP Header: https://www.rfc-editor.org/rfc/rfc8594458- MDN — HTTP: https://developer.mozilla.org/es/docs/Web/HTTP459- MDN — Códigos de estado HTTP: https://developer.mozilla.org/es/docs/Web/HTTP/Status460- OpenAPI Specification: https://spec.openapis.org/oas/latest.html461- Richardson Maturity Model (Martin Fowler): https://martinfowler.com/articles/richardsonMaturityModel.html462- Stripe API (referencia de diseño y idempotencia): https://docs.stripe.com/api463- Google API Design Guide: https://cloud.google.com/apis/design464- OWASP API Security Top 10: https://owasp.org/API-Security/465466---467468**Skills relacionadas:** **security** (auth, CORS, inyección, secretos), **database-design** (paginación por keyset, N+1, persistencia de idempotencia), **error-handling-observability** (traceId, logging, correlación) y **performance** (caché, rate limiting, payloads).
Run npx skillmds@latest add julianramirezreyes/api-design in your terminal (requires Node.js), paste this page's agent-chat prompt into Claude, Cursor, or any MCP-connected agent, or download the SKILL.md file and copy it into your agent's skills directory.
Diseño de APIs y endpoints: REST, versionado, errores, paginación, idempotencia, contratos. It is listed under Integrations & APIs on SkillMD.
This skill has not completed SkillMD's automated safety review yet. Capability flags: makes network calls, reads secrets. SkillMD never runs a skill's scripts for you; review the SKILL.md before installing.
This skill is tagged as working with Claude Code, Claude.ai, OpenAI Codex. SKILL.md is an open format, so most agents that read a skills directory can load it too.
Yes. Installing skills from SkillMD is free, and the skill stays under its author's original license.
julianramirezreyes (@julianramirezreyes) published this skill. Their other Agent Skills are listed on their SkillMD profile.