Inventory
Per (variant × location) stock with 6 distinct states, TTL'd reservations, and an immutable movement ledger.
Stock states
on_hand physical units in the warehouse
- committed units reserved for paid orders awaiting fulfillment
- reserved units held by active checkouts (TTL'd)
- damaged units flagged unsellable
- safety_stock manual buffer (e.g. show as out-of-stock at <10)
= available GENERATED ALWAYS column the storefront sees
+ incoming on-the-way from supplier (info only)
available = on_hand - committed - reserved - damaged - safety_stock
is a Postgres GENERATED ALWAYS column — Postgres recomputes it on
every write. The storefront filters on available to decide what
to show as in-stock.
Locations
A location is a place stock can sit: warehouse, retail, POS, fulfillment center, dropship.
curl -X POST "$API/locations.json" \
-H "Authorization: Bearer $NEVIOS_KEY" -H "Content-Type: application/json" \
-d '{
"handle": "praha-warehouse",
"name": "Praha sklad",
"type": "warehouse",
"address1": "Plzeňská 8",
"city": "Praha",
"country_code": "CZ",
"is_default": true,
"fulfillment_priority": 50
}'
A location can have served_market_ids — if set, it only fulfills
orders for those markets. Useful for region-bound warehouses.
Adjust stock
curl -X PUT "$API/inventory/{variant_id}/{location_id}/adjust.json" \
-H "Authorization: Bearer $NEVIOS_KEY" -H "Content-Type: application/json" \
-d '{
"state": "on_hand",
"delta": 50,
"type": "received",
"reason_code": "PO-1234",
"reason_text": "Purchase order received"
}'
Every adjustment writes to the immutable inventory_movements
ledger (10 movement types: reserved | released | fulfilled | received | adjusted | transferred_in/out | damaged | restocked | quality_control). The ledger is append-only — UPDATE/DELETE blocked
by trigger.
Reservations
When a checkout is created (default commerce.checkout.reserve_at = checkout_enter), each line tries to reserve units at the
highest-priority active location with stock:
{
"inventory_reservation": {
"id": "308...",
"variant_id": "...",
"location_id": "...",
"quantity": 2,
"owner_type": "checkout",
"owner_id": "<checkout_id>",
"status": "active",
"expires_at": "2026-05-01T11:30:00Z",
"reserved_at": "2026-05-01T10:30:00Z"
}
}
Reservation lifecycle:
active → consumed (checkout completed → committed → fulfilled)
→ released (checkout abandoned / explicit release)
→ expired (TTL passed; cleanup cron sweeps these)
Configure reservation timing per account:
| Setting value | Behavior |
|---|---|
cart_add | Reserve when item is added to cart (longest hold; max guarantee) |
checkout_enter (default) | Reserve when cart converts to checkout |
payment | Reserve only when start_payment is called (shortest hold; risk of last-second oversells) |
Endpoints
| Verb | Path |
|---|---|
| POST | /{handle}/locations.json |
| GET | /{handle}/locations.json |
| PUT | /{handle}/inventory/{variant_id}/{location_id}/adjust.json |
| GET | /{handle}/inventory/{variant_id}/{location_id}.json |
| GET | /{handle}/variants/{variant_id}/inventory.json |
| POST | /{handle}/inventory/reservations.json |
| DELETE | /{handle}/inventory/reservations/{reservation_id}.json |
Next: Customers.