Skip to main content

Convenciones API

La API v1 sigue un modelo consistente en toda la superficie: cada recurso lleva un campo object, las listas se entregan con cursor pagination, los errores siempre tienen el mismo shape, y los headers de rate limit están en cada respuesta.
Algunas rutas legacy (chat, surveys, sync) mantienen sus shapes históricos para no romper la app mobile y los webhooks de Windmill. Las nuevas rutas viven bajo /v1/ y siguen 100% estas convenciones. Las cross-cutting (request_id, headers de rate limit, errores estructurados) aplican a todas las rutas.

Resource envelope

Cada recurso lleva un campo object que identifica su tipo:
{
  "object": "event",
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "type": "booking.created",
  "status": "pending",
  "created_at": "2026-05-05T10:00:00.000Z"
}
El campo object permite a los consumidores discriminar tipos sin parsear el path o leer headers.

Listas paginadas

Las listas envuelven los items en un objeto con object: "list" y campos de paginación cursor:
{
  "object": "list",
  "data": [
    { "object": "event", "id": "...", "type": "booking.created", ... },
    { "object": "event", "id": "...", "type": "booking.cancelled", ... }
  ],
  "has_more": true,
  "next_cursor": "eyJpZCI6IjEyMyIsImNyZWF0ZWRfYXQiOiIyMDI2LTA1LTA1In0"
}
Detalle completo en Paginación.

Request ID

Toda respuesta incluye el header X-Request-ID. Si el cliente envía uno, el servidor lo respeta y lo propaga (a logs, errores, etc.). Si no, el servidor genera uno (req_<random>).
HTTP/1.1 202 Accepted
X-Request-ID: req_a1b2c3d4e5f6
Content-Type: application/json
Útil para soporte: incluye el request_id cuando reportes un problema.

Errores estructurados

Todos los errores siguen el envelope:
{
  "error": {
    "type": "invalid_request_error",
    "code": "validation_error",
    "message": "Payload does not match schema for booking.created",
    "param": "aggregate_id",
    "request_id": "req_a1b2c3d4e5f6",
    "details": [
      { "path": "aggregate_id", "message": "Required" }
    ]
  }
}
Para no romper consumers existentes, la respuesta también incluye los campos legacy statusCode, timestamp, path, method, message. Los nuevos clientes deben leer sólo el envelope error. Catálogo completo en Códigos de error.

Idempotency-Key

Mutaciones sensibles aceptan el header Idempotency-Key (string arbitrario, máx 255 chars). Repetir el mismo POST con la misma key devuelve la misma respuesta sin re-ejecutar la lógica.
POST /api/public/events/ingest
Idempotency-Key: invoice_123_event
Detalle en Idempotencia.

Rate limits

Cada respuesta a una ruta con throttling expone:
HeaderSignificado
X-RateLimit-LimitCantidad máxima de requests por ventana
X-RateLimit-RemainingRequests restantes en la ventana actual
X-RateLimit-ResetSegundos hasta que la ventana se reinicia
Retry-After(sólo en 429) Segundos a esperar antes del próximo intento
El default global es 100 req/min por IP. Algunos endpoints específicos tienen overrides (ej. /public/events/ingest permite 200/min). Cuando se excede el límite, la API devuelve 429 Too Many Requests con el envelope de error:
{
  "error": {
    "type": "rate_limit_error",
    "code": "rate_limit_exceeded",
    "message": "ThrottlerException: Too Many Requests",
    "request_id": "req_..."
  }
}

Versioning

Endpoints legacy viven directamente bajo /api/... y mantienen su shape histórico. Endpoints nuevos o migrados viven bajo /api/v1/... y siguen 100% estas convenciones (envelope object, paginación cursor, etc.). Ejemplos:
/api/chat/bookings              ← legacy, shape histórico
/api/v1/events                  ← v1, envelope nuevo
/api/v1/events/:id              ← v1, envelope nuevo
/api/public/events/ingest       ← público (sin /v1/), shape ya alineado
Para migrar, los nuevos PRs cambian el frontend o el integrador a la ruta /v1/ correspondiente y deprecan la legacy.

Headers que el servidor siempre setea

HeaderCuándo
X-Request-IDSiempre
X-RateLimit-Limit/Remaining/ResetEn rutas con throttling
Retry-AfterEn 429

Headers que el cliente puede enviar

HeaderEfecto
X-Request-IDSi llega (string ≤200 chars), el server lo respeta. Si no, genera uno.
Idempotency-KeyActiva la idempotencia en endpoints que la soportan.
Authorization: Bearer <token>Auth0 JWT.
x-api-keyAPI Key (rutas públicas como /public/events/ingest).

Próximos pasos