*API REST › 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`.

## Lo que construyes

- 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.

## Antes de empezar

| Permiso (scope) | Se usa para |
| --- | --- |
| `account:read` | La primera llamada: zona horaria, moneda y los límites de peticiones de la clave. |
| `availability:read` | `POST /api/v1/availability/check`. |
| `items:read` | `GET /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:write` | Buscar o crear el contacto del cliente. |
| `orders:write` | `POST /api/v1/orders` y `POST /api/v1/orders/{orderId}/reserve`. |
| `orders:read` | `GET /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](https://docs.filmo.io/es/rest-api/tutorials/web-form-to-opportunity).
- Necesitas el id de Filmo de cada artículo (y variante) que vendes. Léelos una vez con [`GET /api/v1/items`](https://docs.filmo.io/es/rest-api/endpoint-reference/items/list-items) y guárdalos; los identificadores son opacos.
- Configura el cliente con el paso 1 del primer tutorial (los mismos dos ficheros).

## 1. Conocer tus límites

| Límite | Valor |
| --- | --- |
| Consultas de disponibilidad | `POST /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 disponibilidad | De 1 a 100 (`items`), todos sobre el mismo rango de fechas. |
| Líneas por pedido | De 1 a 100, con de 1 a 9999 unidades por línea. |
| Cualquier otra llamada | 120 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`](https://docs.filmo.io/es/rest-api/errors/rate_limited) lleva `Retry-After` ([Límites de peticiones](https://docs.filmo.io/es/rest-api/guides/rate-limits)).

## 2. Guardar la clave en tu servidor

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.

**cURL**

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

**Node**

```javascript
import { filmo } from "./filmo-client.mjs";

const res = await filmo("GET", "/api/v1/account");
if (!res.ok) throw new Error("getAccount " + res.status + " " + res.body?.code);

const { timezone, currency, addons, trial, apiKey } = res.body.data;
console.log({ timezone, currency, addons, trial: trial.active, scopes: apiKey.scopes });
```

**Python**

```python
from filmo_client import filmo

status, body, _ = filmo("GET", "/api/v1/account")
if status != 200:
    raise RuntimeError(f"getAccount {status} {(body or {}).get('code')}")

account = body["data"]
print(account["timezone"], account["currency"], account["addons"],
      account["trial"]["active"], account["apiKey"]["scopes"])
```

## 3. Comprobar la disponibilidad con caché y lotes

[`POST /api/v1/availability/check`](https://docs.filmo.io/es/rest-api/endpoint-reference/availability/check-item-availability) 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.

**cURL**

```bash
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
```

**Node**

```javascript
import { filmo } from "./filmo-client.mjs";

const TTL_MS = 60_000;
const cache = new Map(); // "start|end|item|variant|quantity" -> { at, entry }
const keyOf = (start, end, i) => [start, end, i.itemId, i.variantId ?? "", i.quantity ?? 1].join("|");

// What the visitor may see: no order numbers, no stock figures of the business.
function toPublic(item, entry) {
  if ("error" in entry) return { itemId: item.itemId, state: "unknown" };
  if (entry.status === "ok") return { itemId: item.itemId, state: "available" };
  return {
    itemId: item.itemId,
    state: entry.status === "partial" ? "partial" : "unavailable",
    maxQuantity: Math.max(0, (item.quantity ?? 1) - entry.shortfall),
  };
}

// items: [{ itemId, quantity, variantId? }] for ONE date range.
// Returns { mode: "live", results } or { mode: "quote" } when you should not promise anything.
export async function checkRange(start, end, items) {
  const entries = new Array(items.length);
  const missing = [];
  items.forEach((item, index) => {
    const hit = cache.get(keyOf(start, end, item));
    if (hit && Date.now() - hit.at < TTL_MS) entries[index] = hit.entry;
    else missing.push(index);
  });

  try {
    for (let from = 0; from < missing.length; from += 100) {
      const slice = missing.slice(from, from + 100); // at most 100 items per query
      const res = await filmo("POST", "/api/v1/availability/check", {
        retries: 0, // never make a visitor wait out a 429
        body: { startDate: start, endDate: end, items: slice.map((index) => items[index]) },
      });
      if (!res.ok) return { mode: "quote", reason: res.status };
      slice.forEach((index, n) => {
        // One entry per requested item, in request order.
        cache.set(keyOf(start, end, items[index]), { at: Date.now(), entry: res.body.data[n] });
        entries[index] = res.body.data[n];
      });
    }
  } catch {
    return { mode: "quote", reason: "network" };
  }
  return { mode: "live", results: entries.map((entry, i) => toPublic(items[i], entry)) };
}
```

**Python**

```python
import time

from filmo_client import filmo

TTL = 60  # seconds
cache = {}  # "start|end|item|variant|quantity" -> (stored_at, entry)


def key_of(start, end, item):
    return "|".join([start, end, item["itemId"], item.get("variantId", ""), str(item.get("quantity", 1))])


# What the visitor may see: no order numbers, no stock figures of the business.
def to_public(item, entry):
    if "error" in entry:
        return {"itemId": item["itemId"], "state": "unknown"}
    if entry["status"] == "ok":
        return {"itemId": item["itemId"], "state": "available"}
    return {
        "itemId": item["itemId"],
        "state": "partial" if entry["status"] == "partial" else "unavailable",
        "maxQuantity": max(0, item.get("quantity", 1) - entry["shortfall"]),
    }


# items: [{"itemId", "quantity", "variantId"?}] for ONE date range.
# Returns {"mode": "live", "results": [...]} or {"mode": "quote"} when you should not promise anything.
def check_range(start, end, items):
    entries = [None] * len(items)
    missing = []
    for index, item in enumerate(items):
        hit = cache.get(key_of(start, end, item))
        if hit and time.monotonic() - hit[0] < TTL:
            entries[index] = hit[1]
        else:
            missing.append(index)

    try:
        for offset in range(0, len(missing), 100):  # at most 100 items per query
            chunk = missing[offset:offset + 100]
            status, body, _ = filmo(
                "POST", "/api/v1/availability/check",
                body={"startDate": start, "endDate": end, "items": [items[i] for i in chunk]},
                retries=0,  # never make a visitor wait out a 429
            )
            if status != 200:
                return {"mode": "quote", "reason": status}
            for n, index in enumerate(chunk):
                # One entry per requested item, in request order.
                cache[key_of(start, end, items[index])] = (time.monotonic(), body["data"][n])
                entries[index] = body["data"][n]
    except Exception:
        return {"mode": "quote", "reason": "network"}
    return {"mode": "live", "results": [to_public(items[i], e) for i, e in enumerate(entries)]}
```

*Una respuesta (abreviada)*

```json
{
  "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.

## 4. Recurrir a un presupuesto ante un 429

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.

## 5. Crear el pedido

[`POST /api/v1/orders`](https://docs.filmo.io/es/rest-api/endpoint-reference/orders/create-an-order) 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`](https://docs.filmo.io/es/rest-api/objects/order#response-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.

**cURL**

```bash
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
```

**Node**

```javascript
// order.mjs
import { filmo } from "./filmo-client.mjs";

// booking: { id, orderId?, contactId, start, end, lines: [{ itemId, quantity, variantId? }] }
// booking.id is YOUR id of the booking; both keys derive from it.
export async function createBookingOrder(booking) {
  if (booking.orderId) return { ok: true }; // created before: skip straight to the reserve
  const res = await filmo("POST", "/api/v1/orders", {
    idempotencyKey: "booking-" + booking.id + "-order",
    body: { contactId: booking.contactId, startDate: booking.start, endDate: booking.end, lines: booking.lines },
  });
  if (!res.ok) {
    return { ok: false, outcome: res.status === 400 ? "invalid" : "retry-later", problem: res.body };
  }
  booking.orderId = res.body.data.id; // save it now, before reserving
  return { ok: true };
}
```

**Python**

```python
# order.py
from filmo_client import filmo


# booking: {"id", "order_id"?, "contact_id", "start", "end", "lines": [{"itemId", "quantity", "variantId"?}]}
# booking["id"] is YOUR id of the booking; both keys derive from it.
def create_booking_order(booking):
    if booking.get("order_id"):
        return {"ok": True}  # created before: skip straight to the reserve
    status, body, _ = filmo(
        "POST", "/api/v1/orders",
        body={
            "contactId": booking["contact_id"],
            "startDate": booking["start"],
            "endDate": booking["end"],
            "lines": booking["lines"],
        },
        idempotency_key="booking-" + booking["id"] + "-order",
    )
    if status != 201:
        return {"ok": False, "outcome": "invalid" if status == 400 else "retry-later", "problem": body}
    booking["order_id"] = body["data"]["id"]  # save it now, before reserving
    return {"ok": True}
```

## 6. Reservarlo

[`POST /api/v1/orders/{orderId}/reserve`](https://docs.filmo.io/es/rest-api/endpoint-reference/orders/reserve-an-order) 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`](https://docs.filmo.io/es/rest-api/endpoint-reference/orders/unreserve-an-order) 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](https://docs.filmo.io/es/rest-api/guides/testing)) y filtra por origen el trigger de cualquier workflow que no deba reaccionar, para excluir `api` y `mcp`.

**cURL**

```bash
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"
```

**Node**

```javascript
// order.mjs, continued
export async function reserveBookingOrder(booking) {
  const res = await filmo("POST", "/api/v1/orders/" + booking.orderId + "/reserve", {
    idempotencyKey: "booking-" + booking.id + "-reserve",
  });
  if (res.ok) return { outcome: "reserved", tasksCreated: res.body.meta.tasksCreated };
  if (res.status === 409 && res.body?.code === "insufficient_stock") {
    // Nothing was reserved; the order is still a draft.
    const short = res.body.errors.map(({ path, itemId, requested, available, shortfall }) => ({
      path, itemId, requested, available, shortfall,
    }));
    return { outcome: "short", short };
  }
  return { outcome: "retry-later", problem: res.body };
}

export async function book(booking) {
  const created = await createBookingOrder(booking);
  if (!created.ok) return created;
  return { orderId: booking.orderId, ...(await reserveBookingOrder(booking)) };
}
```

**Python**

```python
# order.py, continued
def reserve_booking_order(booking):
    status, body, _ = filmo(
        "POST", "/api/v1/orders/" + booking["order_id"] + "/reserve",
        idempotency_key="booking-" + booking["id"] + "-reserve",
    )
    if status == 200:
        return {"outcome": "reserved", "tasks_created": body["meta"]["tasksCreated"]}
    if status == 409 and (body or {}).get("code") == "insufficient_stock":
        # Nothing was reserved; the order is still a draft.
        short = [
            {k: e.get(k) for k in ("path", "itemId", "requested", "available", "shortfall")}
            for e in body["errors"]
        ]
        return {"outcome": "short", "short": short}
    return {"outcome": "retry-later", "problem": body}


def book(booking):
    created = create_booking_order(booking)
    if not created["ok"]:
        return created
    return {"order_id": booking["order_id"], **reserve_booking_order(booking)}
```

## 7. Gestionar el stock insuficiente

Si no se puede atender una línea, [`POST /api/v1/orders/{orderId}/reserve`](https://docs.filmo.io/es/rest-api/endpoint-reference/orders/reserve-an-order) responde 409 [`insufficient_stock`](https://docs.filmo.io/es/rest-api/errors/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`](https://docs.filmo.io/es/rest-api/errors/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)*

```json
{
  "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}`](https://docs.filmo.io/es/rest-api/endpoint-reference/orders/update-the-header-of-an-order) 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`](https://docs.filmo.io/es/rest-api/endpoint-reference/orders/cancel-an-order), 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é guardar por tu parte

| Qué | Por qué |
| --- | --- |
| Tu id de reserva | La raíz de ambas claves de idempotencia. |
| El id del pedido de Filmo, en cuanto la creación devuelve respuesta | Los 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 tarde | Qué se le dijo al cliente y qué debe seguir el equipo. |
| `requestId` de las llamadas fallidas | Lo que indicas a soporte. |

## Gestionar errores

| Respuesta | Qué hacer |
| --- | --- |
| 429 `rate_limited` en disponibilidad | No reintentes por el visitante. Recurre al presupuesto. `RateLimit-*` indica qué cuota se agotó. |
| 429 `rate_limited` al crear o reservar | Espera `Retry-After` (también `retryAfter` en el cuerpo del problema) y repite con la misma clave. |
| 400 `validation_error` | Lee `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_stock` | Paso 7. |
| 409 `conflict` | Lee `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_reused` | Se envió el mismo id de reserva con contenido distinto. Usa un id de reserva nuevo para una reserva modificada. |
| 403 `insufficient_scope`, 403 `forbidden` | Configuració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_error` | Repite 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_unavailable` | Ninguna 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`. |

## Siguientes pasos

- [Límites de peticiones](https://docs.filmo.io/es/rest-api/guides/rate-limits) y [Idempotencia](https://docs.filmo.io/es/rest-api/guides/idempotency).
- [Convertir un formulario web en una oportunidad](https://docs.filmo.io/es/rest-api/tutorials/web-form-to-opportunity): busca o crea el cliente y captura las solicitudes de presupuesto.
- [Errores](https://docs.filmo.io/es/rest-api/guides/errors): las reglas de reintento para cada estado.
- [Pasar a producción](https://docs.filmo.io/es/rest-api/guides/going-live).
- En la app: [Reservar y liberar stock](https://www.filmo.io/es/help/reference/pedidos/precios-y-disponibilidad/como-funciona-la-disponibilidad-del-material).

---

URL canónica: https://docs.filmo.io/es/rest-api/tutorials/availability-to-reserved-order  
Última actualización: 2026-10-09