# Update stock counts

> Set counted stock or add and remove units by SKU, safely retried with an idempotency key. Reaches linked marketplace listings.

Stock is the units **on hand** (on the shelf). **Available** is on hand minus what open orders hold: what can still be sold.

- Connector: `preview_stock_change`, then `apply_stock_change` (permission `stock:write`).
- API: `POST /stock`.

Stock changes only through this call, never through the product update.

## Count or adjust

Each row sends **one** of:

- `value`: the units now on the shelf, from a count. 0 or more.
- `delta`: units to add (positive) or remove (negative), for example `-3` for damaged goods. Not 0.

Optional per row:

- `expected_from` (with `value` only): the on-hand you read before. If it has changed since, the row is refused instead of overwritten. Use it when time passes between reading and writing.
- `reason`: up to 200 characters, shown in the shop's stock history, e.g. `monthly count`.

## Request

```json
{
  "idempotency_key": "count-2026-10-25-sheet1",
  "dry_run": true,
  "items": [
    { "sku": "TM-001", "value": 40, "expected_from": 37, "reason": "monthly count" },
    { "sku": "TM-002", "delta": -3, "reason": "damaged in transit" }
  ]
}
```

## Retrying safely

Give each new change its own `idempotency_key` (8–100 characters: letters, digits and `. _ : -`). If the call times out, send the **same request with the same key**: rows already applied come back `already_applied` and are not applied twice. Never reuse a key for a different change: a row with different numbers under a reused key is applied.

## Result

| Status | Meaning |
|---|---|
| `would_update` | dry run: `from`, `to` and `available` show what would happen |
| `updated` | applied: `from` (on hand before), `to` (on hand after), `available` |
| `already_applied` | this row was already applied under this idempotency key |
| `error` | not applied; `error` says why |

A dry run checks each row's shape, that the SKU is on your SellerSideKick store and `expected_from`. It does **not** check holds by open orders, a `delta` on a SKU that was never counted, or a replay, so the real call can still answer some rows with `error` or `already_applied`.

- A `delta` can't take on hand below what open orders hold. A counted `value` is recorded as counted, even below that.
- Only SKUs on your SellerSideKick store: a SKU that is only on a marketplace is an error row and is never sent anywhere.

## What reaches the marketplaces

Stock changes are recorded in the shop's stock history and sent to the SKU's linked Shopee, Lazada and TikTok listings, usually within about 10 minutes.
