Product Catalog · Push API

Stock: set and adjust

Every stock write chooses, per row, between set (the quantity becomes the sent value) and adjust (the sent value is a signed delta added to the quantity on hand). Set is the default, so an existing caller is unchanged.

Use PATCH inventory for live stock
A stock set or adjust inside a sync job is staged with the job and applies at close, not on arrival; a staged set re-bases on-hand stock at that moment. PATCH /v1/catalog/inventory is synchronous: the quantity changes before it answers.

PATCH /v1/catalog/inventory

The body is { rows: [...] }, 1 to 1,000 rows, at most 1 MiB. A row names a sku and can carry quantity (an integer), quantityMode (set or adjust), priceMinor, salePriceMinor (null clears it), currency and availability.

Adjust one SKU by +12
curl -X PATCH https://catalog.usethrottle.dev/v1/catalog/inventory \
  -H "Authorization: Bearer $CATALOG_ADMIN_KEY" \
  -H "Idempotency-Key: delivery-2026-11-01-a" \
  -H "Content-Type: application/json" \
  -d '{ "rows": [ { "sku": "HR-GUJI-250", "quantity": 12, "quantityMode": "adjust" } ] }'
Set and adjust (SDK)
import { createCatalogAdmin } from '@usethrottle/catalog/admin';

const admin = createCatalogAdmin({ key: process.env.CATALOG_ADMIN_KEY ?? '' });

// Set: the quantity becomes the sent value.
const set = await admin.inventory.set([{ sku: 'HR-GUJI-250', quantity: 40 }]);

// Adjust: a signed delta on the stored quantity (+12 after a delivery, -3
// after an offline sale). The SDK makes an Idempotency-Key when you pass none.
const adjusted = await admin.inventory.adjust(
  [
    { sku: 'HR-GUJI-250', quantity: 12 },
    { sku: 'HR-KAA-250', quantity: -3 },
  ],
  { idempotencyKey: 'delivery-2026-11-01-a' },
);

// One result per row, in order, with the quantity the row left.
for (const result of [...set.results, ...adjusted.results]) {
  console.log(result.sku, result.status, result.quantity, result.code);
}

Adjust rules

  • Atomic. Two adjusts that arrive together both apply (5 + 3 + 2 = 10); the last writer never wins.
  • An Idempotency-Key is required. A request, a job batch or a vendor single batch with an adjust row needs an Idempotency-Key of 1 to 200 characters, or the answer is 400 IDEMPOTENCY_KEY_REQUIRED before anything is written. A repeated key answers the first answer again and never applies the delta twice. Choose one key per delivery event, not per attempt. Reusing a key with a different body is 409 IDEMPOTENCY_CONFLICT. The SDK generates a key when you pass none.
  • On-hand only. An adjust changes the stored quantity. Stock reserved by open carts is not touched, and availability keeps being computed from reservations as before.
  • Below zero. An adjust may take the quantity below zero only when the variant's backorders setting is allow. Otherwise the row fails with QUANTITY_BELOW_ZERO and the quantity stays. The check uses the backorders value stored before the row writes: a row that also turns backorders on is still judged against the old value. Nothing is silently clamped to zero.
  • One mode and one row per SKU per request. The same SKU twice, or set and adjusted together, fails every row involved (DUPLICATE_SKU_IN_BATCH, STOCK_MODE_CONFLICT).

Results per row

The answer is { results: [...] }, one entry per row, in order:

FieldMeaning
skuThe row’s SKU (null if it had none).
statusupdated, unchanged, not_found (an unknown SKU, or one outside a vendor key’s reach, never SKU_CONFLICT) or invalid.
quantityThe quantity the row left on hand. Reconcile against this, not your own arithmetic.
codeWhy a row is invalid: QUANTITY_BELOW_ZERO, QUANTITY_OUT_OF_RANGE, QUANTITY_MODE_WITHOUT_QUANTITY, STOCK_MODE_CONFLICT or DUPLICATE_SKU_IN_BATCH.
sourceWinsfalse when the caller’s value does not win the quantity (the store key under a value the merchant set by hand).

A failed row never fails the others. In a job or a vendor single batch, the quantity each stock-writing row left is recorded and paged from GET …/stock (see reports).