Tutorials

From availability check to reserved order

Check stock from your server with a cache and batches that respect the 30 requests per minute of the availability operations, create the order with an idempotency key, then reserve it and handle insufficient_stock.

  • A server-side availability check for a booking site or a customer portal of your own, which survives traffic because it caches and batches.
  • A fallback to “request a quote” whenever the quota is exhausted, so the visitor never sees an error.
  • A booking step that creates an order, reserves it and tells the customer what happens when stock runs short.
ScopeUsed for
account:readThe first call: timezone, currency and the rate limits of the key.
availability:readPOST /api/v1/availability/check.
items:readGET /api/v1/items, to map your catalog to Filmo item ids once. Optional if you already store the ids.
contacts:read, contacts:writeFinding or creating the customer's contact.
orders:writePOST /api/v1/orders and POST /api/v1/orders/{orderId}/reserve.
orders:readGET /api/v1/orders/{orderId}, to read an order after a conflict or a lost response. Optional.
  • You need the Filmo id of the customer's contact. Get it with the find-or-create of steps 3 and 4 in Turn a web form into an opportunity.
  • You need the Filmo id of each item (and variant) you sell. Read them once with GET /api/v1/items and store them; identifiers are opaque.
  • Set up the client from the first tutorial's step 1 (the same two files).
LimitValue
Availability queriesPOST /api/v1/availability/check, POST /api/v1/staff/availability and POST /api/v1/vehicles/availability share one extra quota: at most 30 requests per minute per key. They also count against the 120 per minute for all routes, so they leave the rest of the 120 for everything else.
Items per availability query1 to 100 (items), all over the same date range.
Lines per order1 to 100, with 1 to 9999 units per line.
Every other call120 requests per minute per key.

Thirty a minute is one availability query every two seconds, for the whole site. A page that calls Filmo once per visitor, or once per item, uses it up in moments. The rest of this tutorial exists to avoid that. The headers RateLimit-Remaining and RateLimit-Reset show what is left, and a 429 rate_limited carries Retry-After (Rate limits).

The browser asks your server, and your server asks Filmo. There is one key for the site, held in the server's environment: a key in page code would be readable by every visitor, and would also give each of them their own way to use up your 30 per minute.

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

3. Check availability with a cache and batches

Link to this section: 3. Check availability with a cache and batches

POST /api/v1/availability/check takes one date range and up to 100 items, each with the quantity you intend to book (1 if you omit it). Dates are ISO 8601 instants with an offset; build them from the visitor's choice in the account's timezone. It returns one entry per item, in the order you sent them: available, a status (ok, partial or none), the shortfall, the business days that are short (conflictingPeriods), or an error with code not_found for an item that does not exist.

  • **Batch.** Put every item of a page for the same dates into one query, up to 100. Do not call once per item.
  • **Cache for a short time.** Key the cache by dates, item, variant and quantity, with a TTL of about a minute. The TTL is your trade-off between quota and stale answers; the code below uses 60 seconds. Ask only for the entries that are missing.
  • **Never show internals.** The response lists the orders that hold the stock (commitments) with their numbers. They are other customers' orders: strip them before anything reaches the browser.
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
A response (abridged)
{
  "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": "..." } }
  ]
}

A cached answer can be a minute old, so the check informs the visitor; it does not reserve anything. Two visitors can see the same last unit as available. The reservation in step 6 is what decides, and step 7 handles the loser.

The code above asks the client not to retry (retries set to 0) and returns mode: "quote" on a 429, on any other failure and on a network error. When that happens, show the same page with “Request a quote” instead of a stock label, and capture the request as a lead with the web form flow. The visitor never sees an error and the lead is not lost. Optionally, read RateLimit-Remaining from the response headers and switch to the quote mode before the quota runs out.

POST /api/v1/orders creates a draft order with the requested material. It is the validated equivalent of a request from a form: every identifier is checked against the account of the key before anything is written, and a single wrong one rejects the whole request with 400 and creates nothing.

The order is born at zero

The create operation accepts no amounts of any kind: no prices, discounts, insurance or deposits, and no status, serial units, accessories or notes. The order's totals are zero until the team prices it in Filmo. Show the customer your own price or “the team will confirm the price”; do not read a price from the new order. The order also takes no reference of your own (externalReference is returned on reads but the API cannot write it), so keep your booking id and the Filmo order id together.

Send the Idempotency-Key booking-<your booking id>-order. If your server times out, repeat the call with the same key and body within 24 hours and you receive the first response, with Idempotent-Replay: true, instead of a second order. A different body with the same key is rejected with 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 takes no body. It moves the order to reserved, checks the stock of every line, deducts it and creates the delivery and return tasks, so the team's schedule changes with this call; meta.tasksCreated says how many tasks were created. Reserving an order that is already reserved is not an error: it changes nothing and answers 200, so repeating it after a timeout is safe. It answers 404 if the order does not exist or is archived. The reservation can be undone: POST /api/v1/orders/{orderId}/unreserve returns the order to draft and frees the stock, and the order can be reserved again; cancelling it cannot be undone.

Decide whether to reserve at all. An order that is created at zero and reserved straight away takes stock before anyone has priced it. If your business reviews bookings first, stop after step 5 and let the team reserve the draft in Filmo.

Automations: creating and reserving fire the account's event workflows (order created, reserved, status changed, and the tasks the reservation creates). They run after the response, on a best-effort basis, and you cannot suppress them per request. List the active event workflows first (see Testing) and filter the trigger of any workflow that should not react by origin to exclude api and 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"

If a line cannot be served, POST /api/v1/orders/{orderId}/reserve answers 409 insufficient_stock. Nothing is reserved and the order stays a draft. errors[] has one entry per item that is short, with the itemId, the units requested, the units available for the order's dates and the shortfall. A path of spaces instead of items means a space is short. The same 409 status also carries conflict for other causes, so branch on code first and then on errors[0].code of the conflict: invalid_state (the order is not a draft, a serial number is taken), in_progress (a request with the same key is still running) or lock_timeout (another write held the order for more than five seconds).

The 409 body (abridged)
{
  "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_..."
}

What next is a business decision. The API cannot edit the lines of an order (PATCH /api/v1/orders/{orderId} changes the customer, project and dates only), so you cannot reduce quantities on the same order. Your options:

  • Keep the draft, tell the customer what is available (available per item), and let the team adjust it in Filmo. This loses nothing.
  • Offer different dates or smaller quantities and start a new booking with a new booking id, which creates a new order. The first draft stays; archive it with the team, or cancel it with POST /api/v1/orders/{orderId}/cancel, which cannot be undone through the API.
  • Ask the customer to request a quote for the missing units.
Info:

Check availability again after a conflict or a short result: the cache you used for the page is by now older than the stock.

WhatWhy
Your booking idThe root of both idempotency keys.
The Filmo order id, as soon as the create returnsRetries skip the create and go straight to the reserve, even after the 24 hours of the key.
The outcome: reserved, short, invalid or retry laterWhat the customer was told, and what the team must follow up.
requestId of failed callsWhat you quote to support.
ResponseWhat to do
429 rate_limited on availabilityDo not retry for the visitor. Fall back to the quote. RateLimit-* says which quota ran out.
429 rate_limited on create or reserveWait Retry-After (also retryAfter in the problem body) and repeat with the same key.
400 validation_errorRead errors[].path: contactId or lines[0].itemId with not_found means an id that does not belong to the account; endDate with must_be_after means the dates are the wrong way round. Nothing was written.
409 insufficient_stockStep 7.
409 conflictRead errors[0].code. in_progress and lock_timeout: wait and repeat with the same key; do not send a second request in parallel. invalid_state: do not retry; read the order with GET /api/v1/orders/{orderId} to see its status.
422 idempotency_key_reusedThe same booking id was sent with different content. Use a new booking id for a changed booking.
403 insufficient_scope, 403 forbiddenConfiguration of the key (requiredScope names the missing scope) or the key's owner lacks the permission; fix it in the app. Queue the booking and alert.
500 internal_errorRepeat with the same key and growing waits. If the answer carries Idempotent-Replay: true, the stored error is being replayed: stop repeating and look the order up with GET /api/v1/orders before creating another.
503 service_unavailableNo operation of this tutorial documents it. If it arrives, Filmo controls the switch behind it: do not retry it as a transient failure. Queue the booking and alert with the requestId.
  • Guides

    Rate limits

    Each API key has 120 requests per minute, and the three availability checks share their own quota of 30 per minute. Past a limit the API answers 429 with Retry-After.

  • Guides

    Idempotency and safe retries

    Send an Idempotency-Key header with every write and reuse the same value when you retry that operation: Filmo returns the stored result of the first attempt instead of writing twice.

  • Tutorials

    Turn a web form into an opportunity

    Your server receives the form, finds or creates the contact by email, and opens an opportunity in the pipeline, with a key derived from the submission so that no retry creates a second lead.

  • Guides

    Errors

    Errors are RFC 9457 problem documents. Branch on the stable code, never on the text, and use the retry table to decide whether to try again.

  • Guides

    Going live checklist

    Go through these eight checks before your integration runs against production data. Each one points to the guide that explains it.