Tutoriales

De la comprobación de disponibilidad al pedido reservado

Comprueba el stock desde tu servidor con una caché y lotes que respeten las 30 peticiones por minuto de las operaciones de disponibilidad, crea el pedido con una clave de idempotencia, luego resérvalo y gestiona insufficient_stock.

  • Una comprobación de disponibilidad en el servidor para un sitio de reservas o un portal de clientes propio, que aguanta el tráfico porque usa caché y procesa por lotes.
  • Una alternativa de “pedir un presupuesto” siempre que se agote la cuota, para que el visitante nunca vea un error.
  • Un paso de reserva que crea un pedido, lo reserva y le dice al cliente qué ocurre cuando falta stock.
Permiso (scope)Se usa para
account:readLa primera llamada: zona horaria, moneda y los límites de peticiones de la clave.
availability:readPOST /api/v1/availability/check.
items:readGET /api/v1/items, para asignar tu catálogo a los ids de artículo de Filmo una sola vez. Opcional si ya guardas los ids.
contacts:read, contacts:writeBuscar o crear el contacto del cliente.
orders:writePOST /api/v1/orders y POST /api/v1/orders/{orderId}/reserve.
orders:readGET /api/v1/orders/{orderId}, para leer un pedido tras un conflicto o una respuesta perdida. Opcional.
  • Necesitas el id de Filmo del contacto del cliente. Obtenlo con el buscar o crear de los pasos 3 y 4 en Convertir un formulario web en una oportunidad.
  • Necesitas el id de Filmo de cada artículo (y variante) que vendes. Léelos una vez con GET /api/v1/items y guárdalos; los identificadores son opacos.
  • Configura el cliente con el paso 1 del primer tutorial (los mismos dos ficheros).
LímiteValor
Consultas de disponibilidadPOST /api/v1/availability/check, POST /api/v1/staff/availability y POST /api/v1/vehicles/availability comparten una cuota extra: como máximo 30 peticiones por minuto por clave. También cuentan para las 120 por minuto de todas las rutas, así que dejan el resto de las 120 para todo lo demás.
Artículos por consulta de disponibilidadDe 1 a 100 (items), todos sobre el mismo rango de fechas.
Líneas por pedidoDe 1 a 100, con de 1 a 9999 unidades por línea.
Cualquier otra llamada120 peticiones por minuto por clave.

Treinta por minuto es una consulta de disponibilidad cada dos segundos, para todo el sitio. Una página que llama a Filmo una vez por visitante, o una vez por artículo, la agota en un momento. El resto de este tutorial existe para evitarlo. Las cabeceras RateLimit-Remaining y RateLimit-Reset muestran lo que queda, y un 429 rate_limited lleva Retry-After (Límites de peticiones).

El navegador pregunta a tu servidor, y tu servidor pregunta a Filmo. Hay una sola clave para el sitio, guardada en el entorno del servidor: una clave en el código de la página la podría leer cualquier visitante, y además daría a cada uno su propia forma de agotar tus 30 por minuto.

BASE="${FILMO_BASE_URL:-https://app.filmo.io}"
curl -sS "$BASE/api/v1/account" \
  -H "Authorization: Bearer $FILMO_API_KEY"

3. Comprobar la disponibilidad con caché y lotes

Enlace a esta sección: 3. Comprobar la disponibilidad con caché y lotes

POST /api/v1/availability/check toma un rango de fechas y hasta 100 artículos, cada uno con la cantidad que piensas reservar (1 si la omites). Las fechas son instantes ISO 8601 con desplazamiento; constrúyelas a partir de la elección del visitante en el timezone de la cuenta. Devuelve una entrada por artículo, en el orden en que los enviaste: available, un status (ok, partial o none), el shortfall, los días laborables que faltan (conflictingPeriods), o un error con código not_found para un artículo que no existe.

  • **Agrupa en lotes.** Pon todos los artículos de una página para las mismas fechas en una sola consulta, hasta 100. No llames una vez por artículo.
  • **Guarda en caché durante poco tiempo.** Usa como clave de la caché las fechas, el artículo, la variante y la cantidad, con un TTL de aproximadamente un minuto. El TTL es tu equilibrio entre cuota y respuestas desactualizadas; el código de abajo usa 60 segundos. Pide solo las entradas que faltan.
  • **No muestres nunca los detalles internos.** La respuesta lista los pedidos que retienen el stock (commitments) con sus números. Son pedidos de otros clientes: elimínalos antes de que nada llegue al navegador.
BASE="${FILMO_BASE_URL:-https://app.filmo.io}"
curl -sS -X POST "$BASE/api/v1/availability/check" \
  -H "Authorization: Bearer $FILMO_API_KEY" \
  -H "Content-Type: application/json" \
  --data @- <<'JSON'
{
  "startDate": "2026-11-02T09:00:00+01:00",
  "endDate": "2026-11-04T18:00:00+01:00",
  "items": [
    { "itemId": "ITEM_ID_1", "quantity": 2 },
    { "itemId": "ITEM_ID_2" }
  ]
}
JSON
Una respuesta (abreviada)
{
  "data": [
    {
      "itemId": "ITEM_ID_1",
      "variantId": null,
      "itemName": "Cinema camera body",
      "quantity": 2,
      "available": false,
      "status": "partial",
      "totalStock": 3,
      "reserved": 2,
      "minRemaining": 1,
      "shortfall": 1,
      "conflictingPeriods": ["2026-11-03"],
      "commitments": [ { "orderId": "...", "orderNumber": "...", "quantity": 2, "startDate": "...", "endDate": "..." } ],
      "checkedAt": "2026-10-09T09:12:00Z"
    },
    { "itemId": "ITEM_ID_2", "variantId": null, "error": { "code": "not_found", "message": "..." } }
  ]
}

Una respuesta en caché puede tener un minuto de antigüedad, así que la comprobación informa al visitante; no reserva nada. Dos visitantes pueden ver como disponible la misma última unidad. Lo que decide es la reserva del paso 6, y el paso 7 gestiona al que pierde.

El código anterior pide al cliente que no reintente (retries en 0) y devuelve mode: "quote" ante un 429, ante cualquier otro fallo y ante un error de red. Cuando eso ocurra, muestra la misma página con “Pedir un presupuesto” en lugar de una etiqueta de stock, y registra la petición como un lead con el flujo del formulario web. El visitante nunca ve un error y el lead no se pierde. Opcionalmente, lee RateLimit-Remaining de las cabeceras de la respuesta y cambia al modo presupuesto antes de que se agote la cuota.

POST /api/v1/orders crea un pedido en borrador con el material solicitado. Es el equivalente validado de una petición desde un formulario: cada identificador se comprueba contra la cuenta de la clave antes de escribir nada, y uno solo incorrecto rechaza toda la petición con 400 y no crea nada.

El pedido nace en cero

La operación de creación no acepta importes de ningún tipo: ni precios, descuentos, seguros ni fianzas, ni estado, unidades serializadas, accesorios o notas. Los totales del pedido son cero hasta que el equipo le pone precio en Filmo. Muestra al cliente tu propio precio o “el equipo confirmará el precio”; no leas un precio del pedido nuevo. El pedido tampoco admite una referencia propia (externalReference se devuelve en las lecturas, pero la API no puede escribirlo), así que guarda juntos tu id de reserva y el id del pedido de Filmo.

Envía la Idempotency-Key booking-<your booking id>-order. Si tu servidor agota el tiempo de espera, repite la llamada con la misma clave y el mismo cuerpo en 24 horas y recibirás la primera respuesta, con Idempotent-Replay: true, en lugar de un segundo pedido. Un cuerpo distinto con la misma clave se rechaza con 422.

BASE="${FILMO_BASE_URL:-https://app.filmo.io}"
curl -sS -X POST "$BASE/api/v1/orders" \
  -H "Authorization: Bearer $FILMO_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: booking-1042-order" \
  --data @- <<JSON
{
  "contactId": "$CONTACT_ID",
  "startDate": "2026-11-02T09:00:00+01:00",
  "endDate": "2026-11-04T18:00:00+01:00",
  "lines": [
    { "itemId": "ITEM_ID_1", "quantity": 2 },
    { "itemId": "ITEM_ID_2", "quantity": 1 }
  ]
}
JSON

POST /api/v1/orders/{orderId}/reserve no toma cuerpo. Mueve el pedido a reserved, comprueba el stock de cada línea, lo descuenta y crea las tareas de entrega y devolución, así que la agenda del equipo cambia con esta llamada; meta.tasksCreated indica cuántas tareas se crearon. Reservar un pedido que ya está reservado no es un error: no cambia nada y responde 200, así que repetirlo tras un tiempo de espera agotado es seguro. Responde 404 si el pedido no existe o está archivado. La reserva se puede deshacer: POST /api/v1/orders/{orderId}/unreserve devuelve el pedido a borrador y libera el stock, y el pedido se puede reservar de nuevo; cancelarlo no se puede deshacer.

Decide si reservar siquiera. Un pedido que se crea a cero y se reserva de inmediato toma stock antes de que nadie le haya puesto precio. Si tu negocio revisa primero las reservas, detente tras el paso 5 y deja que el equipo reserve el borrador en Filmo.

Automatizaciones: crear y reservar disparan los workflows de evento de la cuenta (pedido creado, reservado, cambio de estado y las tareas que crea la reserva). Se ejecutan después de la respuesta, en la medida de lo posible, y no puedes suprimirlos por petición. Lista antes los workflows de evento activos (consulta Pruebas) y filtra por origen el trigger de cualquier workflow que no deba reaccionar, para excluir api y mcp.

BASE="${FILMO_BASE_URL:-https://app.filmo.io}"
curl -sS -X POST "$BASE/api/v1/orders/$ORDER_ID/reserve" \
  -H "Authorization: Bearer $FILMO_API_KEY" \
  -H "Idempotency-Key: booking-1042-reserve"

Si no se puede atender una línea, POST /api/v1/orders/{orderId}/reserve responde 409 insufficient_stock. No se reserva nada y el pedido sigue en borrador. errors[] tiene una entrada por cada artículo que se queda corto, con el itemId, las unidades requested, las unidades available para las fechas del pedido y el shortfall. Un path de spaces en lugar de items significa que falta un espacio. El mismo estado 409 también lleva conflict por otras causas, así que ramifica primero por code y luego por errors[0].code del conflicto: invalid_state (el pedido no es un borrador, un número de serie está ocupado), in_progress (una petición con la misma clave sigue en curso) o lock_timeout (otra escritura retuvo el pedido más de cinco segundos).

El cuerpo del 409 (abreviado)
{
  "type": "https://docs.filmo.io/errors/insufficient_stock",
  "status": 409,
  "code": "insufficient_stock",
  "errors": [
    { "path": "items", "itemId": "ITEM_ID_1", "requested": 2, "available": 1, "shortfall": 1 }
  ],
  "requestId": "req_..."
}

Qué hacer después es una decisión de negocio. La API no puede editar las líneas de un pedido (PATCH /api/v1/orders/{orderId} cambia solo el cliente, el proyecto y las fechas), así que no puedes reducir cantidades en el mismo pedido. Tus opciones:

  • Conserva el borrador, dile al cliente lo que hay disponible (available por artículo) y deja que el equipo lo ajuste en Filmo. Así no se pierde nada.
  • Ofrece otras fechas o cantidades menores e inicia una nueva reserva con un nuevo id de reserva, lo que crea un pedido nuevo. El primer borrador se queda; archívalo con el equipo, o cancélalo con POST /api/v1/orders/{orderId}/cancel, algo que no se puede deshacer a través de la API.
  • Pide al cliente que pida un presupuesto para las unidades que faltan.
Información:

Vuelve a comprobar la disponibilidad tras un conflicto o un resultado corto: la caché que usaste para la página ya es más antigua que el stock.

QuéPor qué
Tu id de reservaLa raíz de ambas claves de idempotencia.
El id del pedido de Filmo, en cuanto la creación devuelve respuestaLos reintentos se saltan la creación y van directos a la reserva, incluso pasadas las 24 horas de la clave.
El resultado: reservado, insuficiente, no válido o reintentar más tardeQué se le dijo al cliente y qué debe seguir el equipo.
requestId de las llamadas fallidasLo que indicas a soporte.
RespuestaQué hacer
429 rate_limited en disponibilidadNo reintentes por el visitante. Recurre al presupuesto. RateLimit-* indica qué cuota se agotó.
429 rate_limited al crear o reservarEspera Retry-After (también retryAfter en el cuerpo del problema) y repite con la misma clave.
400 validation_errorLee errors[].path: contactId o lines[0].itemId con not_found significa un id que no pertenece a la cuenta; endDate con must_be_after significa que las fechas están al revés. No se escribió nada.
409 insufficient_stockPaso 7.
409 conflictLee errors[0].code. in_progress y lock_timeout: espera y repite con la misma clave; no envíes una segunda petición en paralelo. invalid_state: no reintentes; lee el pedido con GET /api/v1/orders/{orderId} para ver su estado.
422 idempotency_key_reusedSe envió el mismo id de reserva con contenido distinto. Usa un id de reserva nuevo para una reserva modificada.
403 insufficient_scope, 403 forbiddenConfiguración de la clave (requiredScope nombra el scope que falta) o el propietario de la clave carece del permiso; corrígelo en la app. Pon la reserva en cola y avisa.
500 internal_errorRepite con la misma clave y esperas crecientes. Si la respuesta incluye Idempotent-Replay: true, se está reproduciendo el error guardado: deja de repetir y busca el pedido con GET /api/v1/orders antes de crear otro.
503 service_unavailableNinguna operación de este tutorial lo documenta. Si llega, Filmo controla el interruptor que hay detrás: no lo reintentes como un fallo transitorio. Encola la reserva y avisa con el requestId.
  • Guías

    Límites de peticiones

    Cada clave de API tiene 120 peticiones por minuto, y las tres comprobaciones de disponibilidad comparten su propia cuota de 30 por minuto. Al pasar un límite, la API responde 429 con Retry-After.

  • Guías

    Idempotencia y reintentos seguros

    Envía una cabecera Idempotency-Key con cada escritura y reutiliza el mismo valor cuando reintentes esa operación: Filmo devuelve el resultado almacenado del primer intento en lugar de escribir dos veces.

  • Tutoriales

    Convertir un formulario web en una oportunidad

    Tu servidor recibe el formulario, busca o crea el contacto por correo electrónico y abre una oportunidad en el embudo, con una clave derivada del envío para que ningún reintento cree un segundo lead.

  • Guías

    Errores

    Los errores son documentos de problema RFC 9457. Ramifica según el code estable, nunca según el texto, y usa la tabla de reintentos para decidir si volver a intentarlo.

  • Guías

    Lista de comprobación de salida a producción

    Repasa estas ocho comprobaciones antes de que tu integración funcione con datos de producción. Cada una remite a la guía que la explica.