Inventory
The inventory API covers warehouses, product variants, stock levels, stock movements, and adjustments. The catalogue half (variants) is owned by Storlaunch; everything else (warehouses, stock, movements) is canonical in Fulkruma and proxied through these endpoints. From the integrator's perspective: one auth surface, one base URL.
This entire surface requires the fulfilment module to be enabled on the workspace. Without it, every call returns 409 fulfillment_module_disabled — flip the module on in Settings → Modules (/dashboard/modules) or via POST /v1/modules.
All endpoints require an sk_* key.
Endpoints
Warehouses
| Method | Path | Purpose |
|---|---|---|
GET |
/v1/inventory/warehouses |
List warehouses |
POST |
/v1/inventory/warehouses |
Create a warehouse |
PATCH |
/v1/inventory/warehouses/:id |
Update a warehouse |
DELETE |
/v1/inventory/warehouses/:id |
Archive a warehouse |
Variants
| Method | Path | Purpose |
|---|---|---|
POST |
/v1/inventory/variants |
Create a product variant |
PATCH |
/v1/inventory/variants/:id |
Update a variant |
DELETE |
/v1/inventory/variants/:id |
Archive a variant |
Stock
| Method | Path | Purpose |
|---|---|---|
GET |
/v1/inventory/stock?variantId=… |
Read stock per warehouse for a variant |
POST |
/v1/inventory/adjust |
Apply a manual stock adjustment |
GET |
/v1/inventory/movements |
List movements (audit log of stock changes) |
GET |
/v1/inventory/low-stock |
Variants under their low-stock threshold |
Create a warehouse
POST /v1/inventory/warehouses
| Field | Type | Required | Description |
|---|---|---|---|
name |
string (1–100) | yes | Human label. |
address |
string (≤500) | no | Free-form address. |
city / postal / phone |
string | no | Address parts. |
isDefault |
boolean | no | If true, becomes the new default; the previous default is demoted in the same transaction. |
Response — 201 Created. Returns the warehouse object.
curl -X POST https://storlaunch.forjio.com/api/v1/inventory/warehouses \
-H "Authorization: Bearer sk_live_xxx" \
-H "Content-Type: application/json" \
-d '{"name":"Bandung Main","city":"Bandung","postal":"40256","isDefault":true}'
List / update / archive warehouses
Standard CRUD. DELETE is a soft-archive — the row stays for history, but it's hidden from GET and ineligible for new stock adjustments.
| Status | error.code |
When |
|---|---|---|
409 |
fulfillment_module_disabled |
Module isn't on. |
404 |
NOT_FOUND |
Warehouse doesn't exist or is in another workspace. |
Create a variant
POST /v1/inventory/variants
A variant is the SKU-level child of a product. It carries its own SKU, price-delta, cost price, and low-stock threshold. Adding the first variant flips the parent product's hasVariants flag to true and emits a storlaunch.product.updated.v1 event.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
productId |
string | yes | Parent product. |
sku |
string (≤50) | no | Your inventory SKU. |
name |
string (1–200) | yes | Variant name (e.g. Medium / Black). |
priceDelta |
integer | no | Add to the parent product's price. Smallest-unit. Default 0. Negative allowed (a "discount" variant). |
costPrice |
integer | no | Cost in smallest unit. Used for margin reports. |
lowStockThreshold |
integer | no | Below this, the variant shows up in /low-stock. |
Response — 201 Created. Returns the variant object.
Events emitted
storlaunch.variant.created.v1(always)storlaunch.product.updated.v1(only if this was the first variant on the parent)
Update a variant
PATCH /v1/inventory/variants/:id
Partial update. Same field set as create, all optional except you cannot change productId. Emits storlaunch.variant.updated.v1.
Archive a variant
DELETE /v1/inventory/variants/:id
Soft-archive. Cannot delete the default variant — you'll get 409 INVALID_STATE. Emits storlaunch.variant.archived.v1.
Read stock
GET /v1/inventory/stock?variantId=…
Returns per-warehouse stock levels for one variant. variantId is required.
Response — 200 OK
{
"data": [
{
"warehouseId": "wh_01HX...",
"warehouseName": "Bandung Main",
"variantId": "var_01HX...",
"available": 47,
"reserved": 3,
"incoming": 0
}
],
"error": null,
"meta": { "requestId": "req_01HX...", "timestamp": "2026-05-13T10:42:00Z" }
}
available— physical stock minus reservations.reserved— held against in-flight checkouts.incoming— on-order from suppliers (when the supplier module is on).
Apply an adjustment
POST /v1/inventory/adjust
The audit-friendly way to change stock. Every adjustment writes a row to the movements log so you can answer "why did we lose 5 units of SKU-007 yesterday?" months later.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
variantId |
string | yes | |
warehouseId |
string | yes | |
delta |
integer | yes | Positive to add stock, negative to remove. |
reason |
enum | yes | One of manual_adjust, refund_restock, transfer_in, transfer_out, damaged, returned_to_supplier, initial_stock, import. |
note |
string (≤500) | no | Free text shown alongside the movement. |
Errors specific to this endpoint
| Status | error.code |
When |
|---|---|---|
409 |
INSUFFICIENT_STOCK |
Negative delta would drive available below zero. |
curl -X POST https://storlaunch.forjio.com/api/v1/inventory/adjust \
-H "Authorization: Bearer sk_live_xxx" \
-H "Content-Type: application/json" \
-d '{"variantId":"var_01HX...","warehouseId":"wh_01HX...","delta":50,"reason":"initial_stock","note":"Q2 restock"}'
Movements log
GET /v1/inventory/movements?variantId=…
Returns the chronological list of stock changes. Each row carries the delta, the resulting balance, the reason, the actor (operator user or webhook), and a reference (e.g. the originating order ID for a refund_restock).
Low stock
GET /v1/inventory/low-stock
Returns variants where available <= lowStockThreshold and lowStockThreshold is set. Empty array if your dashboard widget should hide.
Variant object
| Field | Type | Nullable | Description |
|---|---|---|---|
id |
string | no | var_ + 26-char ULID. |
productId |
string | no | Parent product. |
sku |
string | yes | Your SKU. |
name |
string | no | Variant label. |
priceDelta |
integer | no | Added to the parent's price. |
costPrice |
integer | yes | Cost in smallest unit. |
lowStockThreshold |
integer | yes | Trigger for /low-stock. |
isDefault |
boolean | no | The single default for the parent. |
archived |
boolean | no | Soft-delete state. |
createdAt / updatedAt |
string | no | Timestamps. |
Events
| Event type | Fires on |
|---|---|
storlaunch.variant.created.v1 |
First successful POST /v1/inventory/variants for the variant. |
storlaunch.variant.updated.v1 |
Every successful PATCH /v1/inventory/variants/:id. |
storlaunch.variant.archived.v1 |
Successful DELETE /v1/inventory/variants/:id. |
storlaunch.product.updated.v1 |
First variant added to a product (flips hasVariants). |
Stock-level changes (/adjust) do not currently emit a public webhook event — the movements log is the source of truth. If you need realtime stock streaming, poll /v1/inventory/stock or subscribe to Fulkruma's own stock-change events directly.
inventory.low_stockis reserved but not emitted. Earlier docs mentioned aninventory.low_stockevent triggered by adjustments dropping below threshold; this isn't wired up in the current outbox. Subscribe defensively if you want to handle it when it ships.
Next
- Products — the parent of every variant.
- Shipping — couriers and shipment lifecycle.
- Manual orders — reservations that affect stock.