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.

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

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

Response200 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_stock is reserved but not emitted. Earlier docs mentioned an inventory.low_stock event 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