.dev
REST API

Inventory

Stock objects, availability, movements, reservations, warehouses and SKU aliases — the project-scoped stock engine behind orders.

The inventory module tracks physical stock for a project (one or more stores sharing store.projectId). Everything lives under /v1/inventory; all endpoints require a bearer token except the public storefront availability check.

  • Permissions. The store owner can do everything. Members need inventory.read for lists and inventory.write for mutations. The inventory module must be enabled for the store (billing entitlement — the public endpoint returns 402 when it isn't).
  • Lists accept page, limit, search and the filterator; batch endpoints return the partial-success envelope.

Concepts

Inventory objects are stock buckets keyed by (namespace, sku, batch) inside a project. Each object tracks quantityOnHand and quantityReserved (available = on hand − reserved), plus a storageClass (HOT, WARM, COLD, QUARANTINE), optional warehouseId, priority, expiresAt and free-form metadata (receipt stock-in stores unit_cost there).

Products map to inventory by scoped SKU. The product's sku is the root scope; variant options and addons may carry their own SKUs, conventionally nested as paths:

SKUMeaning
tshirtProduct root scope
tshirt/red/mVariant path (option names along variantPath)
tshirt/addon/gift-wrapProduct addon

When an order line resolves its SKU, the leaf variant option wins, then parent options, then the product root. Availability lookups accept a trailing * wildcard — tshirt* aggregates the whole scope subtree, tshirt matches only the exact SKU. Aliases map external codes (supplier refs, barcodes) onto a target SKU.

How orders touch inventory

Orders drive reservations automatically via the order-inventory bridge. Defaults per store (overridable in store.configs.inventory_integration):

HookDefault order statusesEffect
reserve_onpending, review, accepted, followup, processingCreates a reservation (holderRef = order id) and bumps quantityReserved; records reason: "reserve" movements
unreserve_oncancelled, draftReleases the hold; records reason: "release" movements
consume_oncompletedConverts the hold into a stock decrement (consume)

Additional knobs: missing_bucket_policy (ignore skips untracked SKUs — the default — while reject fails the order), allow_backorder (default true: reservations may exceed on-hand, availability goes negative), and lifecycle *_rules for conditional matching. Every stock change — receive, reserve, release, consume, adjustment, receipt void — is an immutable movement with delta, reason, correlationRef and balanceAfter, so GET /v1/inventory/movements is the full audit trail. Order events themselves are also pushed via webhooks.

At a glance

Projects & public availability

MethodPathDescriptionAuth
POST/v1/inventory/public/availabilityStorefront availability: { storeId, skus } → map of SKU to quantity (max 250 SKUs, cached ~15 s)Public
GET/v1/inventory/projectsList projects linked to your storesBearer
POST/v1/inventory/projectsCreate a projectBearer

Objects

MethodPathDescriptionAuth
GET/v1/inventory/objectsList stock buckets (namespace, sku, storageClass, search filters)Bearer
POST/v1/inventory/objectsReceive stock into a bucket (creates or tops up)Bearer
GET/v1/inventory/objects/{id}Get oneBearer
PUT/v1/inventory/objects/{id}Update namespace/batch/priority/storageClass/warehouse/expiresAt/metadataBearer
DELETE/v1/inventory/objects/{id}Delete — fails if the bucket has active reservationsBearer
POST/v1/inventory/objects/apply-deltasAdjust quantities: { deltas: [{ objectId, quantityDelta }], reason }Bearer
POST/v1/inventory/objects:batchDeleteDelete manyBearer
POST/v1/inventory/objects:batchUpdateUpdate many with updateMask + shared fieldsBearer

Availability & movements

MethodPathDescriptionAuth
GET/v1/inventory/availabilityskus (comma-separated, * wildcard) → available quantities; pass namespace for per-namespace breakdownBearer
GET/v1/inventory/movementsAudit trail (objectId, correlationRef, reason filters)Bearer

Reservations

MethodPathDescriptionAuth
GET/v1/inventory/reservationsList holds (state, holderRef filters)Bearer
GET/v1/inventory/reservations/{id}Get one with lines + objectsBearer
GET/v1/inventory/reservations/holder/{holderRef}Holds for a holder (e.g. an order id)Bearer
POST/v1/inventory/reservations/{id}:releaseRelease a hold (projectId query param required)Bearer
POST/v1/inventory/reservations:batchReleaseRelease manyBearer

Reservations are created by the system (order flow or receipt posting) — there is no create endpoint. Release is the only mutation.

Aliases & warehouses

MethodPathDescriptionAuth
GET/v1/inventory/aliasesList SKU aliases (targetSku, search filters)Bearer
POST/v1/inventory/aliasesCreate { alias, targetSku }Bearer
PUT/v1/inventory/aliases/{alias}Repoint an alias (projectId query param)Bearer
DELETE/v1/inventory/aliases/{alias}Delete (projectId query param)Bearer
POST/v1/inventory/aliases:batchDeleteDelete many (names are alias strings)Bearer
GET/v1/inventory/warehousesList warehousesBearer
POST/v1/inventory/warehousesCreate (name, code, optional namespacePrefix)Bearer
PUT/v1/inventory/warehouses/{id}UpdateBearer
DELETE/v1/inventory/warehouses/{id}DeleteBearer
POST/v1/inventory/warehouses:batchDeleteDelete manyBearer

Reset

MethodPathDescriptionAuth
POST/v1/inventory/resetWipe project inventory data — owner only, body confirm: "RESET"Bearer

Examples

Receive stock into a batch bucket:

curl -X POST "https://api.feeef.org/v1/inventory/objects" \
  -H "Authorization: Bearer $FEEEF_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "projectId": "PROJECT_ID",
    "sku": "tshirt/red/m",
    "batch": "B-2026-08",
    "quantity": 100
  }'

Check availability for a SKU scope (wildcard aggregates variants):

curl -H "Authorization: Bearer $FEEEF_TOKEN" \
  "https://api.feeef.org/v1/inventory/availability?projectId=PROJECT_ID&skus=tshirt*,mug"

Adjust quantities (stock count correction) — on hand can never go negative or below reserved:

curl -X POST "https://api.feeef.org/v1/inventory/objects/apply-deltas" \
  -H "Authorization: Bearer $FEEEF_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "projectId": "PROJECT_ID",
    "deltas": [{ "objectId": "OBJECT_ID", "quantityDelta": -3 }],
    "reason": "damaged"
  }'

Inspect an order's hold and release it manually:

curl -H "Authorization: Bearer $FEEEF_TOKEN" \
  "https://api.feeef.org/v1/inventory/reservations/holder/ORDER_ID?projectId=PROJECT_ID"

curl -X POST -H "Authorization: Bearer $FEEEF_TOKEN" \
  "https://api.feeef.org/v1/inventory/reservations/RESERVATION_ID:release?projectId=PROJECT_ID"

Storefront availability (no token — safe to call from the browser):

curl -X POST "https://api.feeef.org/v1/inventory/public/availability" \
  -H "Content-Type: application/json" \
  -d '{ "storeId": "STORE_ID", "skus": ["tshirt*", "mug"] }'

Notes

  • Receiving via POST /v1/inventory/objects tops up an existing (namespace, sku, batch) bucket instead of duplicating it. Purchase receipts are the finance-side way to stock in — they also enforce a per-batch unit cost.
  • Batch updates use updateMask to whitelist fields: { projectId, names, updateMask: ["storageClass"], storageClass: "QUARANTINE" }.
  • Availability responses are cached (~15 s) and invalidated on stock mutations; the public endpoint additionally caps requests at 250 SKUs.
  • Movements and reservations are immutable audit records — there are no update or delete endpoints for them.

On this page