Skip to content

Stock updates

Keep the stock levels on your listings in step with your warehouse. One endpoint sets absolute levels for up to 500 listings at a time, and the new levels flow through to every store that lists your products, the same way a change made in the supplier dashboard does.

The call

POST /api/supplier-api/v1/stock/bulk needs the supplier_api:stock:write scope. Address each row by SKU, which is what a warehouse system knows, or by Orbit product id if you have one.

HTTP
POST /api/supplier-api/v1/stock/bulk HTTP/1.1
Host: api.myorbitcommerce.net
Authorization: Bearer sup_sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Content-Type: application/json

{
  "skipUnmatched": true,
  "updates": [
    { "sku": "BOLT-M10-60", "quantity": 340 },
    { "sku": "hinge-brass-75", "quantity": 0, "lowStockThreshold": 25 },
    { "productId": "6f2a1c3e-9d4b-4a7f-8e2c-1b3f5a7c9d0e", "quantity": 12 }
  ]
}
JSON
{ "updated": 2, "matched": 3, "unmatched": [] }
FieldRules
updates1 to 500 entries.
updates[].skuThe listing's SKU, matched case-insensitively after trimming. Exactly one of sku or productId per row.
updates[].productIdThe listing's id in your supplier catalogue, a UUID.
updates[].quantityThe new level, an integer of 0 or more. This is an absolute value, not an adjustment.
updates[].lowStockThresholdOptional. The level at which the listing is flagged as low. Integer of 0 or more.
skipUnmatchedOptional, default false. See Unknown SKUs.

How a SKU is matched

  • Products and variants. A SKU is looked up against both your product SKUs and your variant SKUs. When a variant matches, the variant's stock is set and the parent is left alone, because stock for a product with variants lives on each variant. A product-level match on a product that has variants is refused with a 400 that tells you to send the variant SKUs instead.
  • Every match is updated. SKUs are not required to be unique across your listings; the same SKU can appear on two listings on purpose. A row updates all of them, the same way a stock feed does.
  • Last row wins. If two rows address the same listing, by two spellings of the SKU or by SKU and id, the later row is the one applied and only one change event fires.

What the response means

  • updated counts the listings whose stored values actually changed, not the rows you sent. Pushing the same levels twice returns "updated": 0 the second time, so a full nightly resync tells you how much really moved. Only changed listings raise a stock-changed event downstream.
  • matched counts the listings addressed, products plus variants. It can exceed the number of rows when a SKU is shared.
  • unmatched lists the identifiers, exactly as you sent them, that matched nothing.

Unknown SKUs

By default any identifier that matches nothing rejects the whole call with 400 and nothing is written:

JSON
{
  "statusCode": 400,
  "timestamp": "2026-09-07T09:11:40.203Z",
  "path": "/api/supplier-api/v1/stock/bulk",
  "method": "POST",
  "message": "No product or variant matched 2 identifier(s) for this supplier: \"OLD-SKU-1\", \"OLD-SKU-2\""
}

Warehouse exports routinely contain discontinued or not-yet-listed SKUs, so most integrations send "skipUnmatched": true: the rows that match are applied in one transaction and the rest come back in unmatched for you to log. Either way the write is all or nothing for the rows that are applied.

Batching against the rate limit

Writes are limited to 300 per minute per key, and each call carries up to 500 rows, so a single key can push 150,000 levels a minute before the limit matters. A warehouse of a few thousand SKUs fits in a handful of calls. Send the batches sequentially and honour Retry-After if you ever see a 429. Details in Idempotency, rate limits and errors.

Pull instead of push

If your warehouse system cannot make outbound calls, the supplier dashboard's Automation section can pull stock for you on a schedule from a CSV upload, a URL, an FTP or SFTP folder, a Google Sheet or a REST endpoint you expose. A stock feed and this endpoint write the same levels through the same path, so you can start with a feed and move to the API later without changing anything downstream.

Next steps