Products

A product is something you sell — with one or more variants under it that carry SKU, price, and stock-level distinctness. Fulkruma's Product is a fulfilment-oriented record: it doesn't replace your storefront catalog (that belongs to Storlaunch or your own commerce platform), it gives Fulkruma enough to ship, deliver, or license-key the thing.

Products come in three types:

  • physical — needs warehousing, stock tracking, and a shipment to deliver.
  • digital — no stock; fulfilled via a delivery grant.
  • license — no stock; fulfilled by issuing a license key at checkout.

A product can have many variants. Every product gets an auto-created Default variant on creation; you only need to add more if you sell size/color/SKU permutations.

All requests on this page must be signed — see Authentication. The response envelope and error codes are described in the API overview.

Endpoints

Method Path Purpose
GET /api/v1/products List products
GET /api/v1/products/:id Retrieve one product
POST /api/v1/products Create a product
PATCH /api/v1/products/:id Update a product
DELETE /api/v1/products/:id Soft-archive a product
POST /api/v1/products/:id/variants Add a variant
PATCH /api/v1/products/:id/variants/:variantId Update a variant
DELETE /api/v1/products/:id/variants/:variantId Soft-archive a variant

List products

GET /api/v1/products

Returns up to 200 products in the workspace, newest first, with their variants expanded inline.

Query parameters

Param Type Default Description
archived true | false false If true, includes archived products. Default omits them.

Response — 200 OK

{
  "data": {
    "products": [
      {
        "id": "prod_01HXAB7K3M9N2P5QRS8TVWXY3Z",
        "accountId": "acc_01HXxxxxxxxxxxxxxxxxxxxxxx",
        "name": "Espresso Beans 250g",
        "sku": "BEANS-250",
        "description": "Single-origin from Java.",
        "type": "physical",
        "weight": 280,
        "length": 12,
        "width": 8,
        "height": 4,
        "licenseEnabled": false,
        "maxActivations": null,
        "archived": false,
        "externalSource": null,
        "externalRef": null,
        "createdAt": "2026-05-12T10:42:00.123Z",
        "updatedAt": "2026-05-12T10:42:00.123Z",
        "variants": [
          {
            "id": "var_01HXAB7K3M9N2P5QRS8TVWXY3Z",
            "productId": "prod_01HX...",
            "sku": "BEANS-250-LIGHT",
            "name": "Light roast",
            "priceCents": 12000000,
            "costCents": 6500000,
            "lowStockThreshold": 5,
            "weight": 280,
            "isDefault": true,
            "archived": false
          }
        ]
      }
    ]
  },
  "error": null,
  "meta": { "requestId": "req_01HX...", "timestamp": "2026-05-12T10:42:00.124Z" }
}
// Node
const { products } = await fk.products.list({ archived: false });
# curl
fulkruma_curl GET '/api/v1/products?archived=false'

Retrieve a product

GET /api/v1/products/:id

Returns the product with the given id, with its variants inlined.

Path parameters

Param Type Description
id string (prod_…) The product ID.

Errors

Status error.code When
404 NOT_FOUND The product doesn't exist, or exists in a different workspace. Fulkruma returns 404 (not 403) for cross-workspace IDs to avoid leaking their existence.
fulkruma_curl GET '/api/v1/products/prod_01HXAB7K3M9N2P5QRS8TVWXY3Z'

Create a product

POST /api/v1/products

Creates a product in the calling workspace. A Default variant is automatically created in the same transaction so the product is immediately usable for shipments and stock movements. Emits a fulkruma.product.created.v1 event.

Request body

Field Type Required Description
name string (1–200) yes Display name.
sku string no Product-level SKU. Variants can override.
description string no Free-form description.
type physical | digital | license no Default physical. Determines fulfilment path at checkout: physical → shipment, digital → delivery grant, license → license key issue. Immutable after create — create a new product instead.
weight integer no Grams. Used for shipping weight when no variant override is set.
length integer no Centimetres. Used for dimensional shipping calcs.
width integer no Centimetres.
height integer no Centimetres.
licenseEnabled boolean no For type: license products, whether license issuance is on.
maxActivations integer no Default maxActivations for licenses issued against this product.

Response — 201 Created. The full product object with the auto-created Default variant inlined.

Errors

Status error.code When
400 VALIDATION Field shape wrong, missing name, or invalid type.
403 NO_ACCOUNT Token authenticated but has no accountId claim.
// Node
const { product } = await fk.products.create({
  name: 'Espresso Beans 250g',
  sku: 'BEANS-250',
  type: 'physical',
  weight: 280,
});
# Python
product = fk.products.create(
    name='Espresso Beans 250g',
    sku='BEANS-250',
    type='physical',
    weight=280,
)['product']
# curl
fulkruma_curl POST '/api/v1/products' \
  '{"name":"Espresso Beans 250g","sku":"BEANS-250","type":"physical","weight":280}'

Update a product

PATCH /api/v1/products/:id

Partial update — send only the fields you want to change. type cannot be changed once set; create a new product if you need to switch fulfilment path.

Errors

Status error.code When
400 VALIDATION Field shape wrong.
404 NOT_FOUND Product not in this workspace.
fulkruma_curl PATCH '/api/v1/products/prod_01HXAB7K3M9N2P5QRS8TVWXY3Z' \
  '{"description":"Single-origin from Aceh."}'

Archive a product

DELETE /api/v1/products/:id

Soft-archives the product. Stock history, shipments referencing it, and any issued licenses or deliveries remain intact and queryable. Archived products are hidden from the default list response — pass ?archived=true to include them.

There is no hard delete. Unarchive by PATCHing the row externally; the field is settable.

fulkruma_curl DELETE '/api/v1/products/prod_01HXAB7K3M9N2P5QRS8TVWXY3Z'

Variants

A variant is a unique sellable line under a product. Stock levels and reservations are keyed by (variantId, warehouseId), never by productId directly — so a product with only the auto-created Default variant still stocks against that variant ID.

Add a variant

POST /api/v1/products/:id/variants

Request body

Field Type Required Description
name string (1–160) yes Variant label (e.g. Light roast, Large / Blue).
sku string no Variant SKU. Unique within a product (workspace-scoped uniqueness is not enforced).
priceCents integer (≥0) no Price in the smallest currency unit (IDR cents, USD cents). Storlaunch uses this for product mirroring.
costCents integer (≥0) no Wholesale cost; used for margin reports.
lowStockThreshold integer (≥0) no A warehouse's level falling below this raises fulkruma.stock.low.v1.
weight integer (≥0) no Grams. Overrides the product-level weight at shipping time.
isDefault boolean no Promote this variant to the product's default. Demotes any existing default in the same transaction.
const { variant } = await fk.products.variants.create('prod_01HX...', {
  name: 'Light roast',
  sku: 'BEANS-250-LIGHT',
  priceCents: 12_000_000,
  weight: 280,
});

Update a variant

PATCH /api/v1/products/:id/variants/:variantId

Partial update with the same schema. Promoting one variant to isDefault: true demotes the previously-default sibling atomically.

Archive a variant

DELETE /api/v1/products/:id/variants/:variantId

Soft-archives the variant. Stock movement history and any open reservations against it remain. A product must have at least one non-archived variant to participate in new shipments.

The product object

Field Type Nullable Description
id string no Fulkruma ID. Always prod_ + 26-char ULID.
accountId string no The workspace this product belongs to.
name string no Display name.
sku string yes Product-level SKU.
description string yes Free-form description.
type string no physical, digital, or license. Immutable.
weight integer yes Default weight (g) at shipping.
length integer yes Default length (cm).
width integer yes Default width (cm).
height integer yes Default height (cm).
licenseEnabled boolean no For license products, whether issuance is on.
maxActivations integer yes Default max activations on issued licenses.
externalSource string yes Where the product was synced from. storlaunch for products mirrored via Storlaunch's product-sync webhook; null for products created directly in Fulkruma.
externalRef string yes The originating system's ID (e.g. Storlaunch's product_…). Combined with externalSource, lets you reverse-look up the source record.
archived boolean no Whether the product is soft-archived.
createdAt string (ISO 8601 UTC) no Creation timestamp.
updatedAt string (ISO 8601 UTC) no Last mutation timestamp.
variants array no Inlined on list and retrieve. See the variant object.

The variant object

Field Type Nullable Description
id string no Fulkruma ID. Always var_ + 26-char ULID.
productId string no The parent product.
name string no Variant label.
sku string yes Variant SKU.
priceCents integer yes Price in minor units.
costCents integer yes Wholesale cost in minor units.
lowStockThreshold integer yes Quantity below which a low-stock alert fires.
weight integer yes Per-variant override.
isDefault boolean no Whether this is the product's default variant.
archived boolean no Whether the variant is soft-archived.

Events

Event type Fires on Notes
fulkruma.product.created.v1 POST /api/v1/products succeeds. Emitted in the same transaction as the product insert, delivered at-least-once.
fulkruma.product.updated.v1 PATCH /api/v1/products/:id changes a field. data.changed lists the fields.
fulkruma.product.archived.v1 DELETE /api/v1/products/:id archives it. Once.
fulkruma.variant.created.v1 POST /api/v1/products/:id/variants succeeds.
fulkruma.variant.archived.v1 DELETE /api/v1/products/:id/variants/:variantId archives it. Once.

Updating a variant sends no webhook; the Audit log records it (variant.updated).

See Webhooks for the event envelope and signature recipe.

Pattern 2: Storlaunch product mirroring

When Storlaunch is your storefront, products are mirrored into Fulkruma via the inbound storlaunch.product.synced webhook. Those rows carry externalSource: "storlaunch" and externalRef: "prod_<storlaunchId>". Treat them as read-only on the Fulkruma side: edits in Storlaunch overwrite the mirror, and edits made directly in Fulkruma will be overwritten on the next sync.

If you need to enrich a Storlaunch-sourced product with Fulkruma-only data, store it in your own system and join by externalRef.

Next

  • Stock — per-variant per-warehouse quantity and the movement audit trail.
  • Shipments — how a physical product gets to the buyer.
  • Deliveries — how a digital product gets to the buyer.
  • Licenses — how a license product gets to the buyer.
  • Portal → Products — the dashboard walkthrough.