> ## Documentation Index
> Fetch the complete documentation index at: https://docs.stora.co/llms.txt
> Use this file to discover all available pages before exploring further.

# Building a booking flow

> Create a custom booking experience using the Stora API — from browsing availability to completing an order.

The Stora API lets you build a custom browsing and checkout experience for a self-storage operator. Your application handles everything from displaying available units to assembling an order. When the customer is ready to pay, you redirect them to a hosted checkout page where their payment details are collected. After payment, Stora handles customer onboarding and creates the tenancy, subscription, and invoices automatically. Your application is notified via [webhooks](/2025-09/guides/webhooks).

## Before you start

This guide assumes you have:

* An access token or OAuth 2.0 credentials — see [Authentication](/2025-09/guides/authentication)
* Familiarity with Stora's domain model — see [Core concepts](/2025-09/guides/core-concepts)
* A webhook endpoint configured to receive events — see [Webhooks](/2025-09/guides/webhooks)

Your token needs the following [scopes](/2025-09/guides/authorization):

| Scope                          | Used for                                   |
| ------------------------------ | ------------------------------------------ |
| `public.site:read`             | Browsing sites                             |
| `public.unit_type:read`        | Browsing unit types and pricing            |
| `public.unit:read`             | Checking unit availability                 |
| `public.contact:write`         | Creating customers                         |
| `public.order:write`           | Creating and finalizing orders             |
| `public.order:read`            | Reading order status                       |
| `public.coupon:read`           | Looking up coupons (if applicable)         |
| `public.protection_level:read` | Listing protection options (if applicable) |
| `public.product:read`          | Listing add-on products (if applicable)    |

## How it works

Your application controls the experience up to payment. After that, Stora takes over. Choose the order creation flow that matches your checkout.

<Tabs>
  <Tab title="Multi-step order">
    ```mermaid actions={false} theme={null}
    %%{init: {'theme': 'base', 'themeVariables': {'primaryColor': '#e8ebf4', 'primaryTextColor': '#05195A', 'primaryBorderColor': '#e8ebf4', 'lineColor': '#FF7237', 'secondaryColor': '#e8ebf4', 'tertiaryColor': '#e8ebf4'}}}%%
    sequenceDiagram
        participant App as Your application
        participant API as Stora API
        participant Pay as Hosted checkout
        participant Portal as Customer portal

        App->>API: Browse sites and unit types
        API-->>App: Available storage and pricing

        App->>API: Create contact
        API-->>App: Contact

        App->>API: Create order (draft)
        API-->>App: Draft order

        App->>API: Validate order
        API-->>App: Validation result

        App->>API: Finalize order
        API-->>App: Finalized order with payment URL

        App->>Pay: Redirect customer
        Pay->>Portal: After payment, customer sets up account

        API-->>App: Webhooks (order.completed, tenancy.created)

    ```
  </Tab>

  <Tab title="Single-request order">
    ```mermaid actions={false} theme={null}
    %%{init: {'theme': 'base', 'themeVariables': {'primaryColor': '#e8ebf4', 'primaryTextColor': '#05195A', 'primaryBorderColor': '#e8ebf4', 'lineColor': '#FF7237', 'secondaryColor': '#e8ebf4', 'tertiaryColor': '#e8ebf4'}}}%%
    sequenceDiagram
        participant App as Your application
        participant API as Stora API
        participant Pay as Hosted checkout
        participant Portal as Customer portal

        App->>API: Browse sites and unit types
        API-->>App: Available storage and pricing

        App->>API: Create order with contact and finalize
        API-->>App: Finalized order with payment URL

        App->>Pay: Redirect customer
        Pay->>Portal: After payment, customer sets up account

        API-->>App: Webhooks (order.completed, tenancy.created)
    ```
  </Tab>
</Tabs>

After payment, the customer is directed to set up their account and access the operator's customer portal — a white-label experience managed by Stora on the operator's behalf. From the portal, customers can manage payment methods, view allocated units and subscriptions, sign contracts (if required), and complete identity verification (if enabled). This requires no integration work on your part.

There are two ways to create an order: build it up incrementally as a draft (useful for multi-step checkouts), or [create and finalize in a single request](#create-and-finalize-in-one-step) (simpler for single-page checkouts). This guide covers the multi-step approach first.

## Step 1: Display available storage

Start by fetching the operator's sites, then the unit types and pricing at each site. If you're serving this data to many customers, consider [caching these resources locally](/2025-09/guides/data-synchronisation) rather than fetching them on every page load.

### Fetch sites

```bash theme={null}
curl -X GET https://public-api.stora.co/2025-09/sites \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
```

```json theme={null}
{
  "sites": [
    {
      "id": "site_14b419f1096013f1",
      "name": "Manchester City Centre",
      "description": "24/7 access, CCTV monitored.",
      "phone": "0161 123 4567",
      "address": {
        "line_1": "42 Deansgate",
        "city": "Manchester",
        "postal_code": "M3 2EG"
      },
      "access_hours": {
        "monday": { "status": "set_hours", "open": "06:00", "close": "22:00" },
        "tuesday": { "status": "set_hours", "open": "06:00", "close": "22:00" }
      }
    }
  ]
}
```

If the operator has multiple sites, use `address`, `access_hours`, and `description` to build a site selection UI.

### Fetch unit types at a site

Unit types represent the categories of storage available — for example "50 sq ft indoor" or "20 ft container."

```bash theme={null}
curl -X GET "https://public-api.stora.co/2025-09/unit_types?site_id=site_14b419f1096013f1" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
```

```json theme={null}
{
  "unit_types": [
    {
      "id": "utype_3b3aed5cca33b11d",
      "name": "Medium unit",
      "status": "bookable",
      "size_description": "10x12 ft",
      "dimensions": {
        "width": 10.0,
        "length": 12.0,
        "height": 11.0,
        "measurement_unit": "ft"
      },
      "selling_points": ["Drive-up access", "Ground floor"],
      "live_prices": [
        {
          "billing_period": "monthly",
          "price": { "amount": 9000, "currency": "GBP", "formatted": "£90.00" }
        }
      ],
      "prices": [
        {
          "billing_period": "monthly",
          "price": { "amount": 9500, "currency": "GBP", "formatted": "£95.00" }
        }
      ],
      "require_insurance_coverage": true,
      "require_security_deposit": true,
      "security_deposit": { "amount": 5000, "currency": "GBP", "formatted": "£50.00" },
      "site": { "id": "site_14b419f1096013f1" }
    }
  ]
}
```

Key fields:

* **`live_prices`** — the current Unit Type prices to show customers and use for `unit_type` order line items. Each entry is scoped to a `billing_period`. Use the matching entry for the customer's selected billing period.
* **`prices`** — the base configured prices for the Unit Type. These are useful for reference, but your booking flow should use `live_prices` when displaying or submitting Unit Type pricing.
* **`status`** — should be `bookable` for unit types you display.
* **`require_insurance_coverage`** / **`require_security_deposit`** — indicate what the operator expects. The API doesn't enforce these, but your checkout should prompt for them when `true`. See [operator expectations](#operator-expectations).
* **`selling_points`** — operator-defined features you can display in your UI.

<Warning>
  Do not calculate or guess Unit Type prices yourself. Use the matching `live_prices` entry for the selected billing period. If no matching `live_prices` entry exists, refresh the Unit Type and ask the customer to choose an available billing period before creating the order.
</Warning>

### Check availability

Check whether a unit type has available stock by querying its units filtered by status:

```bash theme={null}
curl -X GET "https://public-api.stora.co/2025-09/units?unit_type_id=utype_3b3aed5cca33b11d&status=available&limit=1" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
```

If the response contains units, that unit type is available. You don't need to select a specific unit — Stora handles unit allocation when the order completes.

Customers book Unit Types, not individual units. Use Unit Types for customer-facing browsing, pricing, and order creation. Every order must include at least one `unit_type` line item. Specific units are allocated or reserved later through Stora's completion and allocation flow.

### Fetch protection levels and products

If your checkout lets customers add protection or products, fetch the available options:

```bash theme={null}
# Protection levels (goods coverage tiers)
curl -X GET https://public-api.stora.co/2025-09/protection_levels \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"

# Products (add-on items and services, filtered by site)
curl -X GET "https://public-api.stora.co/2025-09/products?site_id=site_14b419f1096013f1" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
```

Protection levels return a `cover_level` (the coverage amount) and `prices` per billing period. Products return a `charge_type` (`one_time` or `recurring`) and `prices`. Both are referenced by ID when adding line items to an order.

## Step 2: Capture customer details

Every order needs a contact — the person renting the storage.

| Approach                                                        | Best for                                                      |
| --------------------------------------------------------------- | ------------------------------------------------------------- |
| **Create upfront** — `POST /contacts` before creating the order | Multi-step checkouts where you want to capture the lead early |
| **Create inline** — embed contact fields in the order request   | Single-page checkouts where everything is submitted at once   |

```bash theme={null}
curl -X POST https://public-api.stora.co/2025-09/contacts \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "jane.smith@example.com",
    "full_name": "Jane Smith",
    "phone_number": "+447700900123",
    "type": "domestic",
    "source": "booking",
    "use_case": "moving_home",
    "address": {
      "line_1": "15 Park Road",
      "city": "Manchester",
      "postal_code": "M14 5RQ",
      "country_alpha2": "GB"
    }
  }'
```

Only `email` is required. All other fields are optional but recommended — the operator will see this information in their back office.

<Warning>
  Email addresses must be unique per operator. If a contact with the same email already exists, the request will fail with a validation error. Use `GET /contacts?email=` to check for an existing contact first, and reference it by `id` on the order if found.
</Warning>

## Step 3: Create the order

Create an order in `draft` status with at least one `unit_type` line item:

<Tabs>
  <Tab title="With existing contact">
    ```bash theme={null}
    curl -X POST https://public-api.stora.co/2025-09/orders \
      -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
      -H "Content-Type: application/json" \
      -H "Idempotency-Key: booking-jane-smith-2026-03-30" \
      -d '{
        "site": { "id": "site_14b419f1096013f1" },
        "contact": { "id": "con_0ac0514ed0711462" },
        "billing_period": "monthly",
        "payment_method": "card",
        "starts_at": "2026-04-15T00:00:00Z",
        "coupon": { "id": "cpn_9f4a1d7b2c8e3456" },
        "email_notifications": {
          "payment_details": true
        },
        "line_items": [
          {
            "type": "unit_type",
            "quantity": 1,
            "price": { "amount": 9000 },
            "item": { "id": "utype_3b3aed5cca33b11d" }
          }
        ]
      }'
    ```
  </Tab>

  <Tab title="With inline contact">
    ```bash theme={null}
    curl -X POST https://public-api.stora.co/2025-09/orders \
      -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
      -H "Content-Type: application/json" \
      -H "Idempotency-Key: booking-jane-smith-2026-03-30" \
      -d '{
        "site": { "id": "site_14b419f1096013f1" },
        "contact": {
          "email": "jane.smith@example.com",
          "full_name": "Jane Smith",
          "phone_number": "+447700900123"
        },
        "billing_period": "monthly",
        "payment_method": "card",
        "starts_at": "2026-04-15T00:00:00Z",
        "coupon": { "id": "cpn_9f4a1d7b2c8e3456" },
        "email_notifications": {
          "payment_details": true
        },
        "line_items": [
          {
            "type": "unit_type",
            "quantity": 1,
            "price": { "amount": 9000 },
            "item": { "id": "utype_3b3aed5cca33b11d" }
          }
        ]
      }'
    ```
  </Tab>
</Tabs>

The response includes the order with `status: "draft"` and calculated totals you can use to build an [order summary](#order-summary-fields) for the customer.

Key request fields:

* **`site.id`** (required) — the site the customer is booking at.
* **`contact`** — either an `id` referencing an existing contact, or inline contact fields.
* **`billing_period`** — `weekly`, `monthly`, `every_four_weeks`, `every_three_months`, `every_six_months`, or `yearly`.
* **`payment_method`** — `card`, `bacs_debit`, or `sepa_debit`.
* **`starts_at`** — when the tenancy begins. ISO 8601 date-time or `"now"` for immediate move-in.
* **`coupon.id`** — optional coupon to apply to the order. For automation or AI-created orders, use the selected `unit_type.promotion` when present.
* **`email_notifications.payment_details`** — set to `true` to email the customer a payment link when the order is finalized.
* **`line_items`** — at least one `unit_type` [line item](#line-items) is required.

<Tip>
  Use `?expand=line_items` to include full line item objects in the response instead of just IDs. You can also expand `contact` and `site`.
</Tip>

### Add more line items

While the order is in `draft` status, you can add protection, products, and security deposits:

```bash theme={null}
# Add protection
curl -X POST https://public-api.stora.co/2025-09/orders/ord_9ef07151f2470754/line_items \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "protection",
    "quantity": 1,
    "price": { "amount": 999 },
    "item": { "id": "plvl_e8f3ad0a07e47566" }
  }'
```

```bash theme={null}
# Add a security deposit
curl -X POST https://public-api.stora.co/2025-09/orders/ord_9ef07151f2470754/line_items \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "security_deposit",
    "quantity": 1,
    "price": { "amount": 5000 },
    "item": { "id": "utype_3b3aed5cca33b11d" }
  }'
```

You can also apply a [coupon](#coupons), configure [email notifications](#email-notifications), and attach [metadata](#metadata).

## Step 4: Finalize the order

Finalizing locks the order and generates a hosted checkout page. Optionally validate first to catch any issues:

```bash theme={null}
curl -X GET https://public-api.stora.co/2025-09/orders/ord_9ef07151f2470754/validate \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
```

Then finalize:

```bash theme={null}
curl -X POST https://public-api.stora.co/2025-09/orders/ord_9ef07151f2470754/finalize \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "finalize": {
      "cancel_redirect_url": "https://your-app.com/booking/cancelled"
    }
  }'
```

The `finalize.cancel_redirect_url` request field is required and controls where the customer lands if they abandon checkout. Use `"cancel_redirect_url": "default"` if you do not have your own URL to redirect to. The finalized order response includes a `payment_url`. See [Handling payment](#handling-payment) for when to redirect the customer or send the link by email.

<Warning>
  Once finalized, the order is locked. If the customer needs to make changes, create a new order.
</Warning>

### Handling payment

Finalizing an order generates a hosted `payment_url`. In an interactive booking flow, redirect the customer to this URL so they can enter their payment details.

If the order is created by automation, AI, or another flow where you cannot redirect the customer immediately, set `email_notifications.payment_details` to `true`. Stora emails the customer a payment link when the order is finalized.

Use one payment path per order: redirect the customer to `payment_url`, send the payment details email, or do both only when that is intentional for your customer experience.

### Create and finalize in one step

For single-page checkouts, include the `finalize` field in the create request. You can reference an existing contact or create the contact inline in the same request.

<Tabs>
  <Tab title="With existing contact">
    ```bash theme={null}
    curl -X POST https://public-api.stora.co/2025-09/orders \
      -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
      -H "Content-Type: application/json" \
      -H "Idempotency-Key: booking-jane-smith-2026-03-30" \
      -d '{
        "site": { "id": "site_14b419f1096013f1" },
        "contact": { "id": "con_0ac0514ed0711462" },
        "billing_period": "monthly",
        "payment_method": "card",
        "starts_at": "2026-04-15T00:00:00Z",
        "coupon": { "id": "cpn_9f4a1d7b2c8e3456" },
        "email_notifications": {
          "payment_details": true
        },
        "line_items": [
          {
            "type": "unit_type",
            "quantity": 1,
            "price": { "amount": 9000 },
            "item": { "id": "utype_3b3aed5cca33b11d" }
          },
          {
            "type": "protection",
            "quantity": 1,
            "price": { "amount": 999 },
            "item": { "id": "plvl_e8f3ad0a07e47566" }
          }
        ],
        "finalize": {
          "cancel_redirect_url": "https://your-app.com/booking/cancelled"
        }
      }'
    ```
  </Tab>

  <Tab title="With inline contact">
    ```bash theme={null}
    curl -X POST https://public-api.stora.co/2025-09/orders \
      -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
      -H "Content-Type: application/json" \
      -H "Idempotency-Key: booking-jane-smith-2026-03-30" \
      -d '{
        "site": { "id": "site_14b419f1096013f1" },
        "contact": {
          "email": "jane.smith@example.com",
          "full_name": "Jane Smith",
          "phone_number": "+447700900123",
          "type": "domestic",
          "source": "booking",
          "use_case": "moving_home",
          "address": {
            "line_1": "15 Park Road",
            "city": "Manchester",
            "postal_code": "M14 5RQ",
            "country_alpha2": "GB"
          }
        },
        "billing_period": "monthly",
        "payment_method": "card",
        "starts_at": "2026-04-15T00:00:00Z",
        "coupon": { "id": "cpn_9f4a1d7b2c8e3456" },
        "email_notifications": {
          "payment_details": true
        },
        "line_items": [
          {
            "type": "unit_type",
            "quantity": 1,
            "price": { "amount": 9000 },
            "item": { "id": "utype_3b3aed5cca33b11d" }
          },
          {
            "type": "protection",
            "quantity": 1,
            "price": { "amount": 999 },
            "item": { "id": "plvl_e8f3ad0a07e47566" }
          }
        ],
        "finalize": {
          "cancel_redirect_url": "https://your-app.com/booking/cancelled"
        }
      }'
    ```
  </Tab>
</Tabs>

Using an inline contact here creates the contact as part of order creation. The response is a finalized order with a `payment_url`. If finalization fails, the order is not stored.

## Step 5: Handle completion

After payment, Stora emits `order.completed` to confirm that the booking is complete. It then creates the tenancy and subscription and attempts auto-reservation when the operator has enabled it. Everything is communicated via webhooks.

### What Stora creates

<Steps>
  <Step title="Tenancy">
    The storage agreement — linking the contact, site, and unit type with start and end dates.
  </Step>

  <Step title="Subscription">
    The billing agreement — recurring charges, billing period, and payment method. See [invoicing](#invoicing) for when each charge type is billed.
  </Step>

  <Step title="Unit allocation">
    For storefront and Public API orders, Stora attempts to reserve the requested units automatically when the operator has enabled auto-reservation. Use the dedicated `tenancy.auto_reservation.*` events to determine the outcome. For partial or failed attempts, use the [reserve endpoint](/2025-09/api-reference/units/reserve-a-unit) to allocate unreserved units manually.
  </Step>

  <Step title="Contract (if configured)">
    If a contract template was specified on the order, Stora generates a contract for the customer to sign.
  </Step>
</Steps>

### Webhook events

| Event                                          | When it fires                                                  |
| ---------------------------------------------- | -------------------------------------------------------------- |
| `order.created`                                | The order is created                                           |
| `order.finalized`                              | The order is finalized and the payment link is generated       |
| `order.completed`                              | Payment is complete and the booking is ready for processing    |
| `tenancy.created`                              | A tenancy is created for the completed order                   |
| `tenancy.auto_reservation.succeeded`           | All requested units were reserved automatically                |
| `tenancy.auto_reservation.partially_succeeded` | Some, but not all, requested units were reserved automatically |
| `tenancy.auto_reservation.failed`              | No requested units were reserved automatically                 |
| `subscription.created`                         | A subscription is created for the completed order              |
| `unit.reserved`                                | An individual unit has been reserved                           |

`order.completed` is the primary signal that the booking is done. Stora triggers it before tenancy creation and auto-reservation processing, but webhook delivery order is not guaranteed. It may arrive after later tenancy or auto-reservation events. Use the `tenancy.auto_reservation.*` events to determine the complete reservation outcome. Each successfully reserved unit also emits `unit.reserved`. For partial or failed attempts, allocate the unreserved units manually.

<Tip>
  Always use webhooks to detect completion — not redirects. The customer is redirected to Stora's account setup flow after payment, not back to your application.
</Tip>

See [Webhooks](/2025-09/guides/webhooks) for setup, payload structure, and signature verification.

## Understanding orders in detail

### Line items

There are four line item types:

| Type               | Description                                                                          |
| ------------------ | ------------------------------------------------------------------------------------ |
| `unit_type`        | The storage unit rental. References a unit type ID. At least one required per order. |
| `protection`       | Goods protection / insurance coverage. References a protection level ID.             |
| `product`          | An add-on product or service (e.g. padlock, admin fee). References a product ID.     |
| `security_deposit` | A one-off refundable deposit. References the unit type ID.                           |

Every line item needs a `type`, `quantity`, `price.amount`, and `item.id`. For `unit_type` line items, set `price.amount` from the matching `unit_type.live_prices` entry for the selected billing period. For `protection` and `product` line items, use the relevant resource's `prices` array.

The API requires at least one `unit_type` line item but does not enforce protection or security deposit inclusion — your application controls the checkout experience.

### Operator expectations

The unit type's `require_insurance_coverage` and `require_security_deposit` flags indicate what the operator expects. When `true`, your checkout should prompt the customer accordingly. The API won't reject an order missing these.

### Tax

Tax is calculated automatically based on the operator's configuration at the site level — you don't set it yourself. Each line item type can have its own tax rate. Security deposits are never taxed.

Each line item returns `tax` and `total_excluding_tax` fields, and the order has aggregate tax fields across all line items.

### Promotions

Unit types can include a `promotion` when a promotional coupon is associated with them. Expand or inspect the selected Unit Type before creating the order so you can apply that promotion consistently.

When an order is created by automation or AI, use `unit_type.promotion` as the order `coupon` when it is present:

```json theme={null}
{
  "coupon": { "id": "cpn_9f4a1d7b2c8e3456" }
}
```

This keeps automated bookings aligned with the promotion **the customer would see in a normal booking flow**. If you also let customers enter coupon codes manually, decide whether the entered code or `unit_type.promotion` takes precedence before creating the order.

### Coupons

You can apply one coupon per order by including the `coupon` field:

```json theme={null}
{
  "coupon": { "id": "cpn_a26d0d4c582740c1" }
}
```

To let customers enter a coupon code, fetch available coupons with `GET /coupons` and match by the `code` field.

Coupons fall into two categories based on their `auto_apply_to` configuration:

* **Subscription-level** (`auto_apply_to.subscriptions: true`) — apply to the subscription as a whole.
* **Line-item level** — apply individually to matching line items. The `auto_apply_to` object controls which types: `unit_types`, `protections`, and/or `products`.

Stora applies the coupon to the relevant line items automatically based on this configuration. The order's `total_discount` field reflects the result.

<Note>
  * Only one coupon per order.
  * Fixed-amount coupons can only apply at the subscription level.
  * One-off products and security deposits are never discounted.
</Note>

### Invoicing

The hosted checkout page collects the customer's payment method — not an immediate payment. After checkout, charges are split across separate invoices:

| Line item type        | When invoiced          | Invoice type                          |
| --------------------- | ---------------------- | ------------------------------------- |
| `unit_type`           | First billing cycle    | Subscription (recurring)              |
| `protection`          | First billing cycle    | Subscription (recurring)              |
| `product` (recurring) | First billing cycle    | Subscription (recurring)              |
| `product` (one-time)  | Shortly after checkout | Separate invoice, charged immediately |
| `security_deposit`    | Shortly after checkout | Separate invoice, charged immediately |

### Order summary fields

Every order response includes calculated totals that update as line items change:

| Field            | What it represents                                                   |
| ---------------- | -------------------------------------------------------------------- |
| `subtotal`       | Recurring charges before tax                                         |
| `tax`            | Total tax across all line items                                      |
| `total`          | Recurring charges including tax and discounts                        |
| `total_discount` | Total coupon discount applied                                        |
| `one_time_total` | One-off charges (security deposits, one-time products) including tax |

Each line item also returns `price`, `tax`, `total`, `total_excluding_tax`, and `discount_total` for per-item breakdowns. Use the `formatted` field on any money object for display-ready strings.

### Email notifications

Stora sends the customer a booking confirmation and move-in day reminder by default. The content is customised by the operator in the BackOffice.

Use `email_notifications` to control these emails per order. Set `payment_details` to `true` when Stora should email the customer a payment link after the order is finalized:

```json theme={null}
{
  "email_notifications": {
    "booking_confirmation": false,
    "move_in_day": false,
    "payment_details": true
  }
}
```

### Metadata

Attach up to 20 key-value pairs for your own references:

```json theme={null}
{
  "metadata": {
    "external_booking_id": "BK-20260330-001",
    "channel": "website"
  }
}
```

See [Metadata](/2025-09/guides/metadata) for details.

## Edge cases

### Availability races

Stora does not hold units during checkout. If a unit type sells out between browsing and payment, the order still completes but no unit is allocated. The operator handles this from the back office. Refresh availability data at your order summary page to reduce the risk.

### Abandoned orders

Orders that are finalized but never completed stay in `finalized` status. Track `order.finalized` events and flag orders that don't receive `order.completed` within a reasonable timeframe.

### Validation errors

Common issues:

* **"Line items must include at least one unit type line item"** — every order needs at least one storage unit.
* **"Billing period must exist"** — ensure the billing period matches one available on the unit type.

### Idempotency

Use the `Idempotency-Key` header on `POST` requests to safely retry on network errors. See [Idempotent requests](/2025-09/guides/requests#idempotent-requests).
