> For the complete documentation index, see [llms.txt](https://lmile.gitbook.io/lmile-docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://lmile.gitbook.io/lmile-docs/shelf-api/inventory.md).

# Shelf Inventory — 棚在庫

棚（シェルフ）に置かれた商品の在庫を、棚コード × barcode 単位で増減する API です。入庫（`receive`）で qty を増やし、ピック（`pick`）で減らします。棚から棚へ動かす・barcode を付け替える（`move`）、数えた数に合わせて 1 セルを直す（`adjust`）もあります。`sot.lm_shelf_inventory` の qty 集約行を更新し、同一トランザクションで増減ログ（`sot.lm_shelf_inventory_logs`）を追記します。商品参照は **barcode のみ**（Shopify 非依存）です。

> **Base URL** `https://api.lmile.io` · **認証** `x-ingest-secret` ヘッダー または operator JWT（`Authorization: Bearer`）（[詳細](/lmile-docs/getting-started/authentication.md)） · `shop` はリクエストボディに含めます（API キーで呼ぶ場合は省略できます）

> 棚在庫は **qty 集約モデル**（1 行 = `(shop, location, shelfCode, barcode)` の合計数量）です。同一棚に複数 barcode は不可（先に入庫した barcode が棚を占有）、同一 barcode を複数棚に分散させるのは可。フィールドは camelCase（routing 系と同じ）。

## エンドポイント

| Method | Path                                  | 用途                                | テーブル                                                                          |
| ------ | ------------------------------------- | --------------------------------- | ----------------------------------------------------------------------------- |
| `POST` | `/api/shelf/inventory/receive`        | 棚への入庫（qty 増）                      | `lm_shelf_inventory` / `lm_shelf_inventory_logs`                              |
| `POST` | `/api/shelf/inventory/pick`           | 棚からのピック（qty 減）                    | `lm_shelf_inventory` / `lm_shelf_inventory_logs`                              |
| `POST` | `/api/shelf/inventory/move`           | 同じ拠点内で棚→棚へ動かす / barcode を付け替える    | `lm_shelf_inventory` / `lm_shelf_inventory_logs`                              |
| `POST` | `/api/shelf/inventory/adjust`         | 1 セルの数量を絶対値で直す（`0` で棚から削除）        | `lm_shelf_inventory` / `lm_shelf_inventory_logs`                              |
| `POST` | `/api/shelf/inventory/condition-move` | 同じ棚・同じ商品の数を、商品状態（良品 ⇄ 良品以外）の間で動かす | `lm_shelf_inventory` / `lm_shelf_condition_stock` / `lm_shelf_inventory_logs` |

***

## `POST` /api/shelf/inventory/receive

棚コード × barcode の在庫を `qty` だけ増やします。対象行が無ければ新規作成します。

### リクエスト

```http
POST https://api.lmile.io/api/shelf/inventory/receive
x-ingest-secret: {INGEST_SECRET}
Content-Type: application/json
```

```json
{
  "shop": "demo-store.myshopify.com",
  "locationId": "b2c3d4e5-f6a7-8901-bcde-f23456789012",
  "shelfCode": "SA-A-13",
  "barcode": "4912345678901",
  "qty": 3
}
```

## `POST` /api/shelf/inventory/pick

棚コード × barcode の在庫を `qty` だけ減らします。在庫が不足する操作（qty が負になる）は `409 insufficient_shelf_qty` で reject します（新規行は作りません）。

### リクエスト

```http
POST https://api.lmile.io/api/shelf/inventory/pick
x-ingest-secret: {INGEST_SECRET}
Content-Type: application/json
```

```json
{
  "shop": "demo-store.myshopify.com",
  "locationId": "b2c3d4e5-f6a7-8901-bcde-f23456789012",
  "shelfCode": "SA-A-13",
  "barcode": "4912345678901",
  "qty": 1
}
```

### ボディパラメータ

`receive` / `pick` 共通です。

| パラメータ        | 型             |  必須 | 説明                                                                               |
| ------------ | ------------- | :-: | -------------------------------------------------------------------------------- |
| `shop`       | string        |  ✓  | 店舗ドメイン（API キーで呼ぶ場合は省略できます）                                                       |
| `locationId` | string (uuid) |  ✓  | 出荷元ロケーション（shop に実在が必須）                                                           |
| `shelfCode`  | string        |  ✓  | 棚コード（最大 32 文字）。形式 `^[A-Z0-9]+(-[A-Z0-9]+)+$` かつ英字を 1 文字以上含む。**大文字へ正規化してから**照合します |
| `barcode`    | string        |  ✓  | 商品 barcode（`sot.products.barcode`）                                               |
| `qty`        | number (int)  |  ✓  | 増減数量（正の整数）。`receive` は `+qty`、`pick` は `-qty`                                    |

### レスポンス

```json
{
  "ok": true,
  "shop": "demo-store.myshopify.com",
  "locationId": "b2c3d4e5-f6a7-8901-bcde-f23456789012",
  "shelfCode": "SA-A-13",
  "barcode": "4912345678901",
  "reason": "receive",
  "delta": 3,
  "qtyBefore": 0,
  "qtyAfter": 3,
  "rowId": "99999999-8888-7777-6666-555555555555"
}
```

| フィールド                    | 説明                                               |
| ------------------------ | ------------------------------------------------ |
| `reason`                 | `receive` / `pick`                               |
| `delta`                  | 適用した符号付き増減（`receive` は `+qty` / `pick` は `-qty`） |
| `qtyBefore` / `qtyAfter` | 適用前 / 適用後の棚在庫数量                                  |
| `rowId`                  | 在庫行 id（`sot.lm_shelf_inventory.id`）              |

### 挙動・冪等性

* **原子性**: qty 更新と増減ログ追記は write RPC `lm_shelf_inventory_apply` が同一トランザクション・行ロック下で実行します（部分適用なし）。
* **MFC の拠点だけが対象**: 棚ごとの在庫を持つのは MFC（`location_type` が `mfc`、または `mfc_enabled` の拠点）だけです。それ以外の拠点（店舗など）に棚の行を**新しく作る**操作（`receive` と、[Inventory Levels](/lmile-docs/ingestion-api/inventory-levels.md) 以外の棚への書き込み全般）は `422 not_self_inventory_location` で reject します。既にある行を減らす `pick` は止めません。店舗の在庫は取り込み元（POS など）の数をそのまま使います。
* **同一棚 複数商品の禁止**: 同一棚に qty > 0 の**別 barcode** 行が既にあると `409 shelf_occupied_by_different_barcode`（占有中の barcode を返却）。
* **在庫不足**: `pick` で qty が負になる操作は `409 insufficient_shelf_qty`（`available` / `requested` を返却）。対象行が無い `pick` は在庫 0 とみなして同じく不足扱い。
* **引き当て済みの数を下回る `pick`**: 減らした結果、その商品の棚の合計が注文に引き当て済みの数を上回る状態から下回る状態に変わる `pick` は `409 shelf_qty_below_active` です（何も変わりません）。現場アプリの集荷・確保は「その 1 点」を引き当て済みから除いて判定するので止まりません。すでに下回っている在庫を減らす操作は止めません。
* **非冪等**: 増減は自然冪等ではありません。冪等キーは持たないため、リトライ時はクライアント側で適用済みかを確認してください（既存 routing write の secure / oversell と同方針）。
* 増減はすべて `sot.lm_shelf_inventory_logs` に append-only で記録されます（`delta` / `qty_after` / `reason` / `operator`）。

***

## `POST` /api/shelf/inventory/move

同じ拠点の中で、棚コード × barcode の在庫を `qty` だけ別のセルへ動かします。棚を変える（棚移動）か、barcode を変える（付け替え）かのどちらかに使います。移動元 −qty と移動先 +qty を同じトランザクションで反映し、増減ログを 2 行（`reason: relocate`）追記します。

### リクエスト

```http
POST https://api.lmile.io/api/shelf/inventory/move
x-ingest-secret: {INGEST_SECRET}
Content-Type: application/json
```

```json
{
  "shop": "demo-store.myshopify.com",
  "locationId": "b2c3d4e5-f6a7-8901-bcde-f23456789012",
  "fromShelfCode": "SA-A-13",
  "fromBarcode": "4912345678901",
  "toShelfCode": "SA-A-14",
  "toBarcode": "4912345678901",
  "qty": 2
}
```

### ボディパラメータ

| パラメータ                           | 型             |  必須 | 説明                                        |
| ------------------------------- | ------------- | :-: | ----------------------------------------- |
| `shop`                          | string        |  ✓  | 店舗ドメイン（API キーで呼ぶ場合は省略できます）                |
| `locationId`                    | string (uuid) |  ✓  | 拠点（shop に実在が必須）。移動元と移動先は同じ拠点              |
| `fromShelfCode` / `toShelfCode` | string        |  ✓  | 移動元 / 移動先の棚コード（形式は `receive` と同じ。大文字へ正規化） |
| `fromBarcode` / `toBarcode`     | string        |  ✓  | 移動元 / 移動先の barcode。同じなら棚移動、違えば付け替え        |
| `qty`                           | number (int)  |  ✓  | 動かす数量（正の整数）                               |

### レスポンス

```json
{
  "ok": true,
  "shop": "demo-store.myshopify.com",
  "locationId": "b2c3d4e5-f6a7-8901-bcde-f23456789012",
  "qty": 2,
  "from": { "shelfCode": "SA-A-13", "barcode": "4912345678901", "qtyBefore": 5, "qtyAfter": 3 },
  "to":   { "shelfCode": "SA-A-14", "barcode": "4912345678901", "qtyBefore": 0, "qtyAfter": 2 }
}
```

### 挙動・冪等性

* 移動元の数量が足りないと `409 insufficient_shelf_qty` です（何も変わりません）。
* **付け替え（barcode が違う）で、移動元の商品の棚の合計が注文に引き当て済みの数を下回る**と `409 shelf_qty_below_active` です（何も変わりません）。例: 棚に 3 個あり 2 個が注文に引き当て済みのとき、2 個以上を別の barcode へ付け替えることはできません。同じ barcode の棚移動は合計が変わらないので対象外です。すでに下回っている在庫を減らす操作は止めません。
* 移動元が 0 になった行は残しません（履歴はログに残ります）。
* **非冪等（差分）**: 同じリクエストを再送すると二重に動きます。リトライ時は適用済みかを確認してください。
* 反映のあとに販売可能数を再計算し、[出荷元在庫の通知](/lmile-docs/webhooks/source-inventory.md)を送ります（同じ barcode の棚移動は合計が変わらないので通知は出ません）。

***

## `POST` /api/shelf/inventory/adjust

1 セル（棚コード × barcode）の数量を、数えた数（絶対値）に合わせます。`qty` を `0` にすると棚から削除します。増減があったときだけ増減ログ（`reason: stocktake`）を追記します。

### リクエスト

```http
POST https://api.lmile.io/api/shelf/inventory/adjust
x-ingest-secret: {INGEST_SECRET}
Content-Type: application/json
```

```json
{
  "shop": "demo-store.myshopify.com",
  "locationId": "b2c3d4e5-f6a7-8901-bcde-f23456789012",
  "shelfCode": "SA-A-13",
  "barcode": "4912345678901",
  "qty": 4
}
```

### ボディパラメータ

| パラメータ        | 型             |  必須 | 説明                              |
| ------------ | ------------- | :-: | ------------------------------- |
| `shop`       | string        |  ✓  | 店舗ドメイン（API キーで呼ぶ場合は省略できます）      |
| `locationId` | string (uuid) |  ✓  | 拠点（shop に実在が必須）                 |
| `shelfCode`  | string        |  ✓  | 棚コード（形式は `receive` と同じ。大文字へ正規化） |
| `barcode`    | string        |  ✓  | 商品 barcode                      |
| `qty`        | number (int)  |  ✓  | 新しい数量（0 以上の整数）。`0` で棚から削除       |

### レスポンス

```json
{
  "ok": true,
  "shop": "demo-store.myshopify.com",
  "locationId": "b2c3d4e5-f6a7-8901-bcde-f23456789012",
  "shelfCode": "SA-A-13",
  "barcode": "4912345678901",
  "qtyBefore": 5,
  "qtyAfter": 4,
  "delta": -1
}
```

### 挙動・冪等性

* **減らした結果、その商品の棚の合計が注文に引き当て済みの数を下回る**と `409 shelf_qty_below_active` です（何も変わりません）。例: 棚に 3 個あり 2 個が注文に引き当て済みのとき、`qty` を 1 にはできません。増やす操作は対象外です。すでに下回っている在庫を実数に直す操作は止めません（再計算と監査が拾います）。
* **冪等（絶対値）**: 同じ値の再送は `delta: 0` で何もしません（ログも書きません）。
* `qty: 0` は行を残しません。無い行に `0` を送っても行は作りません。
* 反映のあとに販売可能数を再計算し、[出荷元在庫の通知](/lmile-docs/webhooks/source-inventory.md)を送ります。

***

## `POST` /api/shelf/inventory/condition-move

同じ棚・同じ barcode の数を、商品状態の間で動かします。良品（shop の既定の状態。通常は `A`）は棚在庫そのもの、良品以外（アウトレット・展示用・不良品・検品中など）は状態ごとの数として別に持ちます。**良品以外の数は販売可能数に入りません**（注文の引き当て対象外）。良品を減らす・増やすと、同じトランザクションで販売可能数を再計算します。

状態の一覧は `agent_read.inventory_condition_codes` にあります（shop ごと。`is_default` が良品）。

### リクエスト

```http
POST https://api.lmile.io/api/shelf/inventory/condition-move
x-ingest-secret: {INGEST_SECRET}
Content-Type: application/json
```

```json
{
  "shop": "demo-store.myshopify.com",
  "locationId": "b2c3d4e5-f6a7-8901-bcde-f23456789012",
  "shelfCode": "SA-A-13",
  "barcode": "4912345678901",
  "qty": 2,
  "toCondition": "C"
}
```

### ボディパラメータ

| パラメータ           | 型             |  必須 | 説明                                         |
| --------------- | ------------- | :-: | ------------------------------------------ |
| `shop`          | string        |  ✓  | 店舗ドメイン（API キーで呼ぶ場合は省略できます）                 |
| `locationId`    | string (uuid) |  ✓  | 拠点（shop に実在が必須。MFC のみ）                     |
| `shelfCode`     | string        |  ✓  | 棚コード（形式は `receive` と同じ。大文字へ正規化）            |
| `barcode`       | string        |  ✓  | 商品 barcode                                 |
| `qty`           | number (int)  |  ✓  | 動かす数量（正の整数）                                |
| `fromCondition` | string        |     | 動かす元の状態コード。省略すると良品                         |
| `toCondition`   | string        |     | 動かす先の状態コード。省略すると良品。`fromCondition` と同じ値は不可 |

### レスポンス

```json
{
  "ok": true,
  "shop": "demo-store.myshopify.com",
  "locationId": "b2c3d4e5-f6a7-8901-bcde-f23456789012",
  "shelfCode": "SA-A-13",
  "barcode": "4912345678901",
  "qty": 2,
  "from": { "condition": "A", "isDefault": true,  "qtyBefore": 5, "qtyAfter": 3 },
  "to":   { "condition": "C", "isDefault": false, "qtyBefore": 0, "qtyAfter": 2 },
  "changed": [{ "locationId": "b2c3d4e5-…", "barcode": "4912345678901", "before": 4, "after": 2 }]
}
```

| フィールド         | 説明                                            |
| ------------- | --------------------------------------------- |
| `from` / `to` | 動かす元 / 先の状態と、その棚での前後の数量。`isDefault: true` が良品 |
| `changed`     | 販売可能数が変わった商品（良品が動いたときだけ。良品以外の間の移動は空）          |

### 挙動・冪等性

* **良品を減らすとき**、その商品の棚の合計が注文に引き当て済みの数を下回る移動は `409 shelf_qty_below_active` です（何も変わりません）。例: 棚に 3 個あり 2 個が引き当て済みのとき、2 個を不良品にはできません。すでに下回っている在庫を減らす操作は止めません。
* 元の数が足りないと `409 insufficient_shelf_qty`（良品）/ `409 insufficient_condition_qty`（良品以外）です。
* 0 になった行は残しません。良品以外の行は増減ログ（`reason: condition_move`）に状態コード付きで残ります。
* 棚の行は MFC の拠点にだけ作れます（`422 not_self_inventory_location`）。
* **非冪等（差分）**: 同じリクエストを再送すると二重に動きます。
* 良品が動いたときは、販売可能数の再計算のあとに[出荷元在庫の通知](/lmile-docs/webhooks/source-inventory.md)を送ります。

## エラー

エンドポイント固有:

| Status | 条件                                              | レスポンス例                                                                                                                                                                  |
| :----: | ----------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|  `422` | 棚コード形式不正                                        | `{ "ok": false, "error_kind": "invalid_shelf_code", "error": "shelfCode must match ...", "shelfCode": "SA_A_13" }`                                                      |
|  `422` | `locationId` が shop に存在しない                      | `{ "ok": false, "error_kind": "location_unresolved", "error": "location_id not found for shop" }`                                                                       |
|  `422` | `locationId` が MFC ではない拠点に棚の行を作ろうとした            | `{ "ok": false, "error_kind": "not_self_inventory_location", "error": "not_self_inventory_location: shelf inventory is only kept at MFC locations (location=...)" }`    |
|  `409` | 同一棚に別 barcode が占有中                              | `{ "ok": false, "error_kind": "shelf_occupied_by_different_barcode", "error": "...", "occupyingBarcodes": [{ "barcode": "...", "qty": 2 }] }`                           |
|  `409` | `pick` で在庫不足                                    | `{ "ok": false, "error_kind": "insufficient_shelf_qty", "error": "...", "available": 1, "requested": 3 }`                                                               |
|  `409` | `pick` で、減らした結果が注文に引き当て済みの数を下回る                 | `{ "ok": false, "error_kind": "shelf_qty_below_active", "error": "...", "shelfTotal": 3, "shelfTotalAfter": 1, "active": 2 }`                                           |
|  `409` | `move` で移動元の数量が不足                               | `{ "ok": false, "error_kind": "insufficient_shelf_qty", "error": "...", "shelfCode": "SA-A-13", "barcode": "...", "available": 1, "requested": 3 }`                     |
|  `409` | `adjust` / `move`（付け替え）で、減らした結果が注文に引き当て済みの数を下回る | `{ "ok": false, "error_kind": "shelf_qty_below_active", "error": "...", "shelfCode": "SA-A-13", "barcode": "...", "shelfTotal": 3, "shelfTotalAfter": 1, "active": 2 }` |
|  `409` | `condition-move` で良品以外の元の数が不足                   | `{ "ok": false, "error_kind": "insufficient_condition_qty", "error": "...", "condition": "C", "available": 1, "requested": 3 }`                                         |
|  `409` | `condition-move` で良品を減らした結果が注文に引き当て済みの数を下回る     | `{ "ok": false, "error_kind": "shelf_qty_below_active", "error": "...", "shelfTotal": 3, "shelfTotalAfter": 1, "active": 2 }`                                           |
|  `422` | `condition-move` の状態コードが shop に無い / 無効          | `{ "ok": false, "error_kind": "condition_unresolved", "error": "...", "condition": "ZZZ" }`                                                                             |

共通エラー（`401` / `400` / `403` / `405` / `500` / `503`）は [Error Codes](/lmile-docs/reference/error-codes.md) を参照。

## データモデル

* `sot.lm_shelf_condition_stock` — 棚 × barcode × 商品状態の数（良品以外だけ）。`UNIQUE (shop, location_id, shelf_code, barcode, condition_code)` / `qty > 0`。販売可能数に入りません。読み取り view: `agent_read.shelf_stock`（良品と良品以外を 1 つにしたもの。`is_default_condition` で区別）
* `sot.lm_shelf_inventory` — 棚在庫の qty 集約行。`UNIQUE (shop, location_id, shelf_code, barcode)` / `qty >= 0`。商品参照は barcode のみ。`location_id` → `sot.locations`。読み取り view: `public.lm_v_shelf_inventory`（内部 service / 管理用途。`agent_read.*` には露出しません）
* `sot.lm_shelf_inventory_logs` — 棚在庫の増減ログ（append-only）。`delta`（`<> 0`）/ `qty_after`（`>= 0`）/ `reason`（`receive` / `pick` / `adjust` / `oversell_receive` / `stocktake` / `relocate`）/ `operator`（operator JWT 経由のみ非 null）。本 API が出すのは `receive` / `pick` / `relocate`（`move`）/ `stocktake`（`adjust`）


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://lmile.gitbook.io/lmile-docs/shelf-api/inventory.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
