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.
Before you start
This guide assumes you have:
- An access token or OAuth 2.0 credentials — see Authentication
- Familiarity with Stora’s domain model — see Core concepts
- A webhook endpoint configured to receive events — see Webhooks
Your token needs the following scopes:
| 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.
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 (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 rather than fetching them on every page load.
Fetch sites
curl -X GET https://public-api.stora.co/2025-09/sites \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
{
"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.”
curl -X GET "https://public-api.stora.co/2025-09/unit_types?site_id=site_14b419f1096013f1" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
{
"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"],
"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:
prices — an array with an entry per billing period. All amounts are in the smallest currency unit (pence for GBP, cents for USD). Use the formatted field for display.
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.
selling_points — operator-defined features you can display in your UI.
Check availability
Check whether a unit type has available stock by querying its units filtered by status:
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.
Fetch protection levels and products
If your checkout lets customers add protection or products, fetch the available options:
# 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 |
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.
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.
Step 3: Create the order
Create an order in draft status with at least one unit_type line item:
The response includes the order with status: "draft" and calculated totals you can use to build an order summary 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.
line_items — at least one unit_type line item is required.
Use ?expand=line_items to include full line item objects in the response instead of just IDs. You can also expand contact and site.
Add more line items
While the order is in draft status, you can add protection, products, and security deposits:
# 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" }
}'
# 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, configure email notifications, and attach metadata.
Step 4: Finalize the order
Finalizing locks the order and generates a hosted checkout page. Optionally validate first to catch any issues:
curl -X GET https://public-api.stora.co/2025-09/orders/ord_9ef07151f2470754/validate \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Then finalize:
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 response includes a payment_url. Redirect the customer there to collect their payment details.
cancel_redirect_url (required) — where the customer lands if they abandon checkout.
payment_url — redirect the customer here.
Once finalized, the order is locked. If the customer needs to make changes, create a new order.
Create and finalize in one step
For single-page checkouts, include the finalize field in the create request:
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",
"line_items": [
{
"type": "unit_type",
"quantity": 1,
"price": { "amount": 9500 },
"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"
}
}'
Step 5: Handle completion
After payment, Stora processes the booking automatically. Everything is communicated via webhooks.
What Stora creates
Tenancy
The storage agreement — linking the contact, site, and unit type with start and end dates.
Subscription
The billing agreement — recurring charges, billing period, and payment method. See invoicing for when each charge type is billed. Unit allocation
If the operator has auto-reservation enabled, Stora reserves an available unit automatically. Otherwise, the operator assigns one manually. Your application can also handle this — listen for tenancy.created and use the reserve endpoint to allocate a specific unit. Contract (if configured)
If a contract template was specified on the order, Stora generates a contract for the customer to sign.
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 tenancy/subscription are created |
tenancy.created | A tenancy is created for the completed order |
subscription.created | A subscription is created for the completed order |
unit.reserved | A unit has been reserved |
order.completed is the primary signal that the booking is done.
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.
See 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. The price.amount should come from the relevant resource’s prices array for the selected billing period.
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.
Coupons
You can apply one coupon per order by including the coupon field:
{
"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.
- Only one coupon per order.
- Fixed-amount coupons can only apply at the subscription level.
- One-off products and security deposits are never discounted.
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. Disable either per order:
{
"emails": {
"booking_confirmation": false,
"move_in_day": false
}
}
Attach up to 20 key-value pairs for your own references:
{
"metadata": {
"external_booking_id": "BK-20260330-001",
"channel": "website"
}
}
See 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.