Skip to main content

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​

ScopeGrants
inventory:product:readRead products, variations and product tags
inventory:product:writeCreate / replace / delete products, plus bulk category and tag updates
inventory:stock:readRead the inventory transaction ledger
inventory:stock:writeRecord inventory transactions (ledger entries)
inventory:config:readRead categories, attributes, units, raw materials and adjustment reasons
inventory:production:readRead production orders and their BOM ingredients
There are only two write scopes

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​

MethodPath
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 patch

The 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​

MethodPath
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.

A posted transaction does not move current_stock

POST /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​

MethodPath
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 tenant

The 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 authApplied per route with that route's scopes
IdempotencyPOSTs are automatically wrapped with shared idempotency handling
Tenant isolationEnforced 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:

ObjectKeys seen
Products, variationsen
Raw materialsen, 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.

AI-generated records can carry keys the app never displays

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 materialsThe app
Create or complete production ordersThe app
Upload product imagesThe app, or Storefront bulk image upload
Import a CSVThe app's importer
Trigger AI generationThe app

6. Troubleshooting​

SymptomCause
403 on a readThe key lacks that scope — reads and writes are separate
Variations disappeared after an updatePUT is a full reconcile
/adjustment-reasons returns []The table is empty on every tenant
A name field is missingWrong language key — try English, then en
Ledger posted but storefront still "Sold out"Transactions do not write current_stock
Cannot create a production orderNo write scope exists for production