Semántica de verbos HTTP en APIs REST mantenibles

Capítulo 3

Tiempo estimado de lectura: 9 minutos

+ Ejercicio

Intención y efectos: más allá del “CRUD”

En una API REST mantenible, el verbo HTTP comunica la intención de la operación y acota sus efectos esperados (qué cambia y qué no). Elegir bien el verbo reduce ambigüedades, facilita reintentos seguros, mejora la observabilidad y evita “endpoints sorpresa” que rompen clientes.

Dos propiedades clave para diseñar con semántica correcta:

  • Safe (seguro): no debe modificar estado del servidor (ej.: GET, HEAD).
  • Idempotente: repetir la misma petición produce el mismo resultado final (ej.: PUT, DELETE; y PATCH solo si se diseña para serlo).

GET: leer representaciones sin efectos colaterales

Cuándo usarlo

  • Recuperar una representación de un recurso o una colección.
  • Ejecutar búsquedas/filtrados que no cambian estado.

Buenas prácticas

  • No uses GET para acciones que cambian estado (por ejemplo, “/users/123/activate”). Eso rompe cachés, prefetching y reintentos.
  • Si la consulta es compleja, sigue siendo GET si es lectura: usa parámetros de query o un recurso de búsqueda (ver sección de procesos asíncronos si la operación es pesada).

Ejemplos

GET /orders/123
Accept: application/json
GET /orders?status=paid&from=2026-01-01&to=2026-01-31

POST: crear en colección o disparar procesamiento no idempotente

Creación en una colección

POST a una colección suele significar: “crea un nuevo elemento bajo esta colección; el servidor decide el identificador”.

POST /orders
Content-Type: application/json

{
  "customerId": "c_9",
  "items": [{"sku":"A1","qty":2}]
}

Respuesta típica:

HTTP/1.1 201 Created
Location: /orders/987

{ "id": "987", "status": "created" }

Acciones puntuales (comandos) vs creación

POST también se usa para “procesar” cuando no encaja como actualización de un recurso existente o cuando la operación es inherentemente no idempotente (por ejemplo, “capturar pago” que puede fallar a mitad). Para mantener semántica REST, modela el comando como un subrecurso o como un recurso de operación (job) en vez de un verbo en la ruta.

Continúa en nuestra aplicación.
  • Escuche el audio con la pantalla apagada.
  • Obtenga un certificado al finalizar.
  • ¡Más de 5000 cursos para que explores!
O continúa leyendo más abajo...
Download App

Descargar la aplicación

  • Subrecurso de acción (cuando representa un evento/acción registrable):
POST /orders/987/captures

{ "amount": 120.00 }
  • Recurso de operación (recomendado para procesos largos):
POST /order-operations

{ "type": "capture", "orderId": "987", "amount": 120.00 }

Esto evita rutas tipo /orders/987/capture (verbo en URL) y permite seguimiento del estado.

PUT: reemplazo completo (o upsert) con idempotencia

Intención

PUT significa: “establece el estado del recurso en esta URI a la representación enviada”. Es idempotente: enviar el mismo cuerpo varias veces deja el recurso igual.

Cuándo usarlo

  • Actualizar un recurso conocido reemplazando su representación (o la parte que tu API define como representación completa).
  • Crear con identificador elegido por el cliente (si tu dominio lo permite): PUT a una URI concreta.

Ejemplos

PUT /profiles/u_123
Content-Type: application/json

{
  "displayName": "Ana",
  "email": "ana@example.com",
  "timezone": "Europe/Madrid"
}

Si el recurso no existía y lo creas con PUT, responde 201 Created; si existía y lo reemplazas, 200 OK o 204 No Content.

Regla práctica para evitar ambigüedad

  • Si el cliente envía un recurso “completo” según el contrato, usa PUT.
  • Si el cliente envía “solo cambios”, usa PATCH.

PATCH: actualización parcial sin sorpresas

Problema que resuelve

PATCH aplica cambios parciales. El riesgo es la ambigüedad: ¿un campo ausente significa “no cambiar” o “borrar”? Con PUT, un campo ausente suele interpretarse como “no está en la representación” (y podría eliminarse). Con PATCH, un campo ausente debe significar “no tocar”.

Cuándo usar PATCH

  • Cuando el cliente no puede o no debe enviar la representación completa.
  • Cuando quieres minimizar conflictos por concurrencia y tamaño de payload.

Dos enfoques recomendables (elige uno y documenta)

1) JSON Patch (RFC 6902): operaciones explícitas

Ventaja: no hay ambigüedad; puedes add, replace, remove con rutas JSON.

PATCH /profiles/u_123
Content-Type: application/json-patch+json

[
  {"op": "replace", "path": "/displayName", "value": "Ana G."},
  {"op": "remove", "path": "/timezone"}
]

2) Merge Patch (RFC 7396): objeto parcial con semántica de null

Ventaja: simple para clientes. Regla típica: propiedades ausentes = no cambiar; propiedades con null = eliminar/poner a null (según contrato).

PATCH /profiles/u_123
Content-Type: application/merge-patch+json

{
  "displayName": "Ana G.",
  "timezone": null
}

Si usas Merge Patch, define claramente qué significa null para cada campo (borrar, desasociar, o valor permitido).

Cómo diseñar PATCH para ser idempotente (si te importa el reintento)

  • Prefiere operaciones deterministas (replace a un valor) sobre operaciones relativas (incrementar).
  • Si necesitas “incrementar”, modela un recurso de evento/ajuste (POST) en lugar de PATCH con delta.

Ejemplo no idempotente (evitar si habrá reintentos automáticos):

PATCH /accounts/a1

{ "balanceDelta": 10 }

Alternativa mantenible:

POST /accounts/a1/adjustments

{ "amount": 10, "reason": "manual" }

DELETE: eliminar el recurso (o marcarlo como eliminado)

Semántica

DELETE indica intención de eliminar. Es idempotente: repetir DELETE sobre el mismo recurso debería dejarlo “no existente” (o “eliminado”) sin efectos adicionales.

Respuestas típicas

  • 204 No Content si se elimina y no devuelves cuerpo.
  • 200 OK si devuelves representación del resultado.
  • 404 Not Found o 204 en eliminaciones repetidas: elige una política consistente (muchas APIs devuelven 404 si el recurso ya no existe; otras prefieren 204 para ocultar existencia).

Borrado lógico

Si por requisitos no puedes borrar físicamente, DELETE puede representar “marcar como eliminado”. Mantén la semántica: tras DELETE, el recurso no debería comportarse como activo en GET estándar (puedes exponer vistas administrativas si aplica).

HEAD y OPTIONS: metadatos y descubrimiento cuando aportan valor

HEAD: igual que GET, sin cuerpo

Útil para:

  • Comprobar existencia sin transferir payload.
  • Validar caché con ETag / Last-Modified.
  • Obtener tamaño aproximado con Content-Length (si aplica).
HEAD /orders/123

Respuesta esperable: mismos headers que GET (cuando sea posible), sin body.

OPTIONS: capacidades del endpoint

OPTIONS puede ayudar en:

  • Descubrir métodos permitidos (header Allow).
  • Soporte CORS (preflight del navegador).
  • Clientes genéricos que negocian capacidades.
OPTIONS /orders/123
HTTP/1.1 204 No Content
Allow: GET, PUT, PATCH, DELETE, OPTIONS, HEAD

No conviertas OPTIONS en un “endpoint de documentación”; úsalo para capacidades operativas.

Guía práctica paso a paso para elegir el verbo correcto

Paso 1: ¿La operación modifica estado?

  • No modifica: usa GET (o HEAD si solo necesitas metadatos).
  • Modifica: continúa.

Paso 2: ¿Estás creando un nuevo elemento bajo una colección?

  • Sí, el servidor asigna ID: POST a la colección.
  • Sí, el cliente define la URI/ID: PUT a la URI del recurso.
  • No: continúa.

Paso 3: ¿Reemplazo completo o cambio parcial?

  • Reemplazo completo según contrato: PUT.
  • Cambio parcial: PATCH (idealmente JSON Patch o Merge Patch).

Paso 4: ¿Es una acción/operación que no encaja como actualización de estado simple?

  • Modela un subrecurso de eventos/acciones (POST) o un recurso de operación (job) si es largo o requiere seguimiento.

Paso 5: ¿Necesitas reintentos seguros?

  • Prefiere PUT/DELETE (idempotentes) y PATCH diseñado idempotente.
  • Si usas POST y habrá reintentos, considera idempotency keys (ver sección de reintentos).

Casos de borde: operaciones masivas (bulk)

Lecturas masivas

Usa GET con filtros/paginación. Si la consulta es pesada y puede tardar, considera un proceso asíncrono (job) en lugar de “GET que tarda minutos”.

Escrituras masivas: patrones comunes

Evita “PATCH a una colección” con semántica difusa. En su lugar, usa un recurso explícito de operación masiva.

NecesidadPatrón recomendadoVerbo
Crear muchos elementosCrear un lote (batch) como recursoPOST
Actualizar muchos elementosJob de actualización con criterios + cambiosPOST
Eliminar muchos elementosJob de borrado con criteriosPOST

Ejemplo: crear un job de actualización masiva:

POST /bulk-jobs
Content-Type: application/json

{
  "type": "update",
  "target": "orders",
  "filter": {"status": "pending"},
  "patch": {
    "contentType": "application/merge-patch+json",
    "document": {"status": "cancelled"}
  }
}

La respuesta devuelve el job para seguimiento:

HTTP/1.1 202 Accepted
Location: /bulk-jobs/bj_001

{ "id": "bj_001", "state": "queued" }

Casos de borde: reintentos, duplicados e idempotencia

Qué puede pasar en producción

  • El cliente reintenta por timeout, pero el servidor sí procesó la primera petición.
  • Un proxy reenvía una petición.
  • Un usuario hace doble clic.

Estrategias por verbo

  • PUT/DELETE: diseñados para reintentos; asegúrate de que el resultado final sea estable.
  • PATCH: hazlo determinista (replace/remove) o usa precondiciones.
  • POST: si crea o ejecuta algo que no debe duplicarse, usa una Idempotency-Key (header) y almacena el resultado asociado durante una ventana de tiempo.
POST /orders
Idempotency-Key: 6f1c2a2b-2b0d-4c2d-9b0d-0f9b2a1c3d77
Content-Type: application/json

{ "customerId": "c_9", "items": [{"sku":"A1","qty":2}] }

Regla práctica: si el cliente puede reintentar automáticamente, documenta qué endpoints soportan Idempotency-Key y qué respuestas se “reproducen” ante duplicados.

Procesos asíncronos sin violar semánticas

Cuándo necesitas asincronía

  • Operaciones largas (exportaciones, recomputaciones, conciliaciones).
  • Operaciones con colas o dependencias externas.
  • Operaciones masivas.

Patrón: crear un recurso “job” con POST y consultar con GET

En vez de hacer un GET que dispara trabajo o un POST “/doSomething”, crea un job como recurso. POST expresa “crear una solicitud de procesamiento”.

POST /exports
Content-Type: application/json

{ "resource": "orders", "format": "csv", "filter": {"status":"paid"} }
HTTP/1.1 202 Accepted
Location: /exports/ex_77

{ "id": "ex_77", "state": "running" }

Luego:

GET /exports/ex_77
HTTP/1.1 200 OK

{ "id": "ex_77", "state": "completed", "downloadUrl": "/exports/ex_77/file" }

Cancelación y reintentos del job

  • Cancelar: DELETE /exports/ex_77 (si semánticamente “eliminar el job” implica cancelarlo) o PATCH para cambiar estado a cancelled si lo modelas así.
  • Reintentar: crea un nuevo job (POST) o expón un subrecurso de reintentos (POST /exports/ex_77/retries) si necesitas historial.

Tabla rápida de decisión

ObjetivoVerboNotas de semántica
Leer recurso/colecciónGETSafe, cacheable
Leer solo metadatosHEADSafe, sin body
Descubrir métodos/capacidadesOPTIONSAllow, CORS
Crear en colección (ID servidor)POSTNo idempotente por defecto
Crear/establecer recurso en URI conocidaPUTIdempotente
Reemplazar representaciónPUTContrato de “completo”
Actualizar parcialmentePATCHEvitar ambigüedad (JSON Patch/Merge Patch)
EliminarDELETEIdempotente
Operación larga/masivaPOSTCrear job + GET para estado

Ahora responde el ejercicio sobre el contenido:

En una API REST, ¿qué diseño se alinea mejor con la semántica correcta cuando necesitas ejecutar una operación larga o pesada sin convertir un GET en un disparador de trabajo?

¡Tienes razón! Felicitaciones, ahora pasa a la página siguiente.

¡Tú error! Inténtalo de nuevo.

Para procesos largos, lo mantenible es modelar un job: POST crea la solicitud de procesamiento y GET permite consultar su estado. Esto evita que GET tenga efectos colaterales y mejora seguimiento y reintentos.

Siguiente capítulo

Códigos de estado HTTP y contratos de respuesta consistentes

Arrow Right Icon
Portada de libro electrónico gratuitaDiseño de APIs REST: buenas prácticas, errores comunes y estándares
30%

Diseño de APIs REST: buenas prácticas, errores comunes y estándares

Nuevo curso

10 páginas

Descarga la aplicación para obtener una certificación gratuita y escuchar cursos en segundo plano, incluso con la pantalla apagada.