# 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": [] }
```

| Field | Rules |
|---|---|
| `updates` | 1 to 500 entries. |
| `updates[].sku` | The listing's SKU, matched case-insensitively after trimming. Exactly one of `sku` or `productId` per row. |
| `updates[].productId` | The listing's id in your supplier catalogue, a UUID. |
| `updates[].quantity` | The new level, an integer of 0 or more. This is an absolute value, not an adjustment. |
| `updates[].lowStockThreshold` | Optional. The level at which the listing is flagged as low. Integer of 0 or more. |
| `skipUnmatched` | Optional, default `false`. See [Unknown SKUs](#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](/supplier-api/guides/reliability#rate-limits).

## 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

- Know when a store sends you an order: [Webhooks](/supplier-api/guides/webhooks).
- The request and response schema: [Supplier API Reference](/supplier-api/reference).
