Stock

The stock resource is Fulkruma's inventory ledger: a per-variant, per-warehouse quantity table plus an append-only movement log and a reservation queue. Every change to on-hand quantity goes through one of three primitives:

  • A movement records a delta (positive or negative) with a reason code. It's the canonical audit row — nothing else moves the level.
  • A reservation soft-locks quantity for an in-flight shipment or pending checkout without decrementing the level. Reservations expire if not consumed.
  • A level is the current on-hand quantity for a (variantId, warehouseId) pair. It's a derived view but materialised for query speed.

All requests on this page must be signed — see Authentication. For the workspace's stock dashboard view, see Portal → Products & stock.

Endpoints

Method Path Purpose
GET /api/v1/stock/levels List per-warehouse stock levels
GET /api/v1/stock/movements List stock movement audit trail
GET /api/v1/stock/reservations List active and historic reservations
POST /api/v1/stock/adjust Apply a delta to a level

There is no POST /reservations — reservations are created internally by the shipment and checkout flows. There is no movement update or delete; the table is append-only.

List stock levels

GET /api/v1/stock/levels

Returns up to 200 stock-level rows for the workspace, newest-updated first.

Query parameters

Param Type Description
variant_id string (var_…) Filter to a single variant. Returns one row per warehouse where the variant has ever moved.

Response — 200 OK

{
  "data": {
    "stock": [
      {
        "variantId": "var_01HXAB7K3M9N2P5QRS8TVWXY3Z",
        "warehouseId": "wh_01HXAB7K3M9N2P5QRS8TVWXY3Z",
        "quantity": 42,
        "updatedAt": "2026-05-12T10:42:00.123Z",
        "warehouse": {
          "id": "wh_01HX...",
          "name": "Main warehouse",
          "isDefault": true
        }
      }
    ]
  },
  "error": null,
  "meta": { "requestId": "req_01HX...", "timestamp": "..." }
}
// Node
const { stock } = await fk.stock.levels({ variant_id: 'var_01HX...' });
fulkruma_curl GET '/api/v1/stock/levels?variant_id=var_01HXAB7K3M9N2P5QRS8TVWXY3Z'

List stock movements

GET /api/v1/stock/movements

Returns up to 200 movement rows for the workspace, newest first. Each row is an append-only audit of one delta — created via /adjust or as a side-effect of shipment fulfilment.

Query parameters

Param Type Description
variant_id string (var_…) Filter to a single variant.

Response shape

{
  "data": {
    "movements": [
      {
        "id": "mv_01HXAB7K3M9N2P5QRS8TVWXY3Z",
        "variantId": "var_01HX...",
        "warehouseId": "wh_01HX...",
        "delta": -3,
        "reason": "manual_adjust",
        "note": "Damaged stock written off after audit.",
        "createdBy": "usr_01HX...",
        "createdAt": "2026-05-12T10:42:00.123Z"
      }
    ]
  },
  "error": null,
  "meta": { ... }
}

List reservations

GET /api/v1/stock/reservations

Returns up to 200 active or recently-historic reservations for the workspace. A reservation is active if consumedAt and releasedAt are both null and expiresAt is in the future; consumed if the linked shipment shipped; released if it was cancelled before shipping; expired if it timed out.

{
  "data": {
    "reservations": [
      {
        "id": "rsv_01HXAB7K3M9N2P5QRS8TVWXY3Z",
        "variantId": "var_01HX...",
        "warehouseId": "wh_01HX...",
        "quantity": 2,
        "shipmentId": "ship_01HX...",
        "checkoutSessionId": "cs_01HX...",
        "expiresAt": "2026-05-12T11:42:00.000Z",
        "consumedAt": null,
        "releasedAt": null,
        "createdAt": "2026-05-12T10:42:00.123Z"
      }
    ]
  },
  ...
}

Adjust stock

POST /api/v1/stock/adjust

Applies a signed delta to the (variantId, warehouseId) level inside a transaction, writing a StockMovement row and emitting a fulkruma.stock.adjusted.v1 event.

This is the only endpoint that mutates stock directly. Shipment fulfilment and refund-restock paths call into the same primitive internally.

Request body

Field Type Required Description
variantId string (var_…) yes The variant whose level you're moving.
warehouseId string (wh_…) yes The warehouse holding the stock. Must be in the calling workspace; cross-workspace IDs return 404.
delta integer yes Signed delta. Positive to add, negative to subtract. Cannot move the level below 0 — see STOCK_INSUFFICIENT.
reason enum yes One of: manual_adjust, initial_stock, transfer_in, transfer_out, damaged, returned_to_supplier, refund_restock, import. Free-form note lets you add context; the enum is for filterable reporting.
note string no Free-form audit note.

Response — 200 OK

{
  "data": {
    "stock": {
      "variantId": "var_01HX...",
      "warehouseId": "wh_01HX...",
      "quantity": 39,
      "updatedAt": "2026-05-12T10:42:00.123Z"
    },
    "movement": {
      "id": "mv_01HX...",
      "variantId": "var_01HX...",
      "warehouseId": "wh_01HX...",
      "delta": -3,
      "reason": "damaged",
      "note": null,
      "createdAt": "2026-05-12T10:42:00.123Z"
    }
  },
  "error": null,
  "meta": { ... }
}

Errors specific to this endpoint {#errors}

Status error.code When
400 VALIDATION Missing fields, reason not in the enum, or delta not an integer.
404 NOT_FOUND Warehouse doesn't exist in this workspace. (No 404 for a non-existent variant — the upsert creates a fresh 0 level on the first positive delta and a missing variant is caught at use-time.)
500 NEGATIVE_STOCK Resulting quantity would go below 0. Currently surfaced as 500; the next iteration will reclassify this as 409 STOCK_INSUFFICIENT.
// Node
const { stock, movement } = await fk.stock.adjust({
  variantId: 'var_01HX...',
  warehouseId: 'wh_01HX...',
  delta: -3,
  reason: 'damaged',
  note: 'Box dropped during transit.',
});
# Python
res = fk.stock.adjust(
    variant_id='var_01HX...',
    warehouse_id='wh_01HX...',
    delta=-3,
    reason='damaged',
    note='Box dropped during transit.',
)
fulkruma_curl POST '/api/v1/stock/adjust' \
  '{"variantId":"var_01HX...","warehouseId":"wh_01HX...","delta":-3,"reason":"damaged"}'

Movements are append-only. There is no DELETE /api/v1/stock/movements/:id. To correct a bad adjustment, post a compensating delta with the same magnitude and opposite sign, and reference the original movement ID in the note. The history stays intact and auditable.

The level object

Field Type Description
variantId string (var_…) The variant.
warehouseId string (wh_…) The warehouse.
quantity integer On-hand quantity. Always ≥ 0. Excludes reservations — "available to ship" is quantity - sum(active reservations.quantity).
updatedAt string (ISO 8601 UTC) Last movement timestamp.
warehouse object Inlined warehouse summary (id, name, isDefault).

The movement object

Field Type Description
id string (mv_…) The movement ID.
variantId string The variant.
warehouseId string The warehouse.
delta integer The signed change.
reason enum The reason code.
note string | null Free-form note.
createdBy string | null The user ID (usr_…) of the actor, or null for system movements.
createdAt string (ISO 8601 UTC) Creation timestamp.

The reservation object

Field Type Description
id string (rsv_…) The reservation ID.
variantId string The variant being held.
warehouseId string The warehouse holding the stock.
quantity integer Reserved quantity.
shipmentId string | null The shipment this is held for, if any.
checkoutSessionId string | null The originating Plugipay checkout session, if any.
expiresAt string (ISO 8601 UTC) When the reservation auto-releases if not consumed. Default 60 minutes from creation.
consumedAt string | null When the linked shipment shipped (and the reservation became a real movement).
releasedAt string | null When the reservation was manually released.
createdAt string (ISO 8601 UTC) Creation timestamp.

Events

Event type Fires on Notes
fulkruma.stock.adjusted.v1 POST /api/v1/stock/adjust succeeds (every reason, refund_restock included). Payload includes quantityAfter so consumers can sync state without a separate read.
fulkruma.stock.low.v1 An adjustment takes a level from at or above the variant's lowStockThreshold to below it. Once per crossing, per warehouse; never for a variant without a threshold. See the event.

See Webhooks for the event envelope and signature recipe.

Common patterns

Initial seeding

When you first onboard, post initial_stock movements for every variant + warehouse pair you carry on-hand. These show up as the first row in the movement log and give the audit trail a clean origin.

fulkruma_curl POST '/api/v1/stock/adjust' \
  '{"variantId":"var_...","warehouseId":"wh_...","delta":100,"reason":"initial_stock"}'

Inter-warehouse transfer

A transfer is two movements: transfer_out from the source, transfer_in to the destination. Post both. Reference the other movement's ID in each note for traceability. Atomic transfer is on the roadmap.

Refund restock

When you process a refund through Plugipay against an order that was already shipped, post refund_restock with the same quantity the shipment consumed. The Plugipay-webhook handler does this automatically for orders Fulkruma routed; you only call it manually for refunds outside that flow.

Next

  • Products — create the variants that stock attaches to.
  • Shipments — consume reservations and decrement levels.
  • Warehouses — create the locations stock sits in.
  • Authentication — HMAC signing recipe.