Inventory on the external API
Inventory is exposed on the Orbit Public API under /api/v1/ext/inventory/*, authenticated by API
key and authorised by scope.
1. The six scopes
| Scope | Grants |
|---|---|
inventory:product:read | Read products, variations and product tags |
inventory:product:write | Create / replace / delete products, plus bulk category and tag updates |
inventory:stock:read | Read the inventory transaction ledger |
inventory:stock:write | Record inventory transactions (ledger entries) |
inventory:config:read | Read categories, attributes, units, raw materials and adjustment reasons |
inventory:production:read | Read production orders and their BOM ingredients |
Products and stock. There is no inventory:config:write and no
inventory:production:write — the API cannot create a unit, a category, an attribute, a raw material
or a production order. Master data is authored in the app only.
2. The endpoints
Products — inventory:product:read / :write
| Method | Path |
|---|---|
GET | /ext/inventory/products |
GET | /ext/inventory/products/{id} |
POST | /ext/inventory/products |
PUT | /ext/inventory/products/{id} |
DELETE | /ext/inventory/products/{id} |
POST | /ext/inventory/products/bulk-update |
PUT is a full reconcile, not a patchThe scope description says so explicitly: "PUT is a full reconcile". Sending a product without its variations deletes those variations.
Always GET the whole product, modify the object, and PUT it back. A partial PUT is a
destructive operation that returns 200.
Stock ledger — inventory:stock:read / :write
| Method | Path |
|---|---|
GET | /ext/inventory/stock/ledger |
POST | /ext/inventory/stock/transactions |
This is the only supported way to get the ledger out in bulk — the in-app Export Data button does nothing. See The inventory ledger.
current_stockPOST /stock/transactions goes through the same service as the UI, so it writes a ledger row and
leaves current_stock untouched. An integration that posts receipts through the API and expects the
storefront's "Sold out" gate to follow will be disappointed.
To move the number the storefront reads, PUT the product with a new variation stock value.
Production orders — inventory:production:read
| Method | Path |
|---|---|
GET | /ext/inventory/production-orders |
GET | /ext/inventory/production-orders/{id} |
Read-only, including the BOM ingredients per order. Orders cannot be created or completed via the API.
Lookups — inventory:config:read
GET only: /categories · /categories/{id} · /attributes · /units ·
/adjustment-reasons · /product-tags · /raw-materials
/adjustment-reasons returns an empty array on every tenantThe master reason table is empty everywhere and has no write path in the app or the API. The endpoint works; there is simply nothing in it. See Stock adjustments.
3. Behaviour you get for free
| API key auth | Applied per route with that route's scopes |
| Idempotency | POSTs are automatically wrapped with shared idempotency handling |
| Tenant isolation | Enforced at the framework layer; a key cannot read another org's data |
4. Reading multilingual fields
Names and descriptions are JSONB keyed by language, and the keys are not consistent:
| Object | Keys seen |
|---|---|
| Products, variations | en |
| Raw materials | en, Hindi, Bengali |
The app resolves English first, then en, then the first available value. An integration should do
the same rather than assuming en exists.
The generators offer 23 target languages while the app's Language Context
shows only Bengali, English and Hindi. A record may hold Korean or French values that are
invisible in the UI and perfectly readable here.
5. What the API cannot do
| Use instead | |
|---|---|
| Create units, categories, attributes, raw materials | The app |
| Create or complete production orders | The app |
| Upload product images | The app, or Storefront bulk image upload |
| Import a CSV | The app's importer |
| Trigger AI generation | The app |
6. Troubleshooting
| Symptom | Cause |
|---|---|
403 on a read | The key lacks that scope — reads and writes are separate |
| Variations disappeared after an update | PUT is a full reconcile |
/adjustment-reasons returns [] | The table is empty on every tenant |
| A name field is missing | Wrong language key — try English, then en |
| Ledger posted but storefront still "Sold out" | Transactions do not write current_stock |
| Cannot create a production order | No write scope exists for production |