> 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/link.md).

# Shelf Link — 棚紐付け

倉庫スタッフが配送ラベルの**追跡番号**と**棚コード**を連続スキャンし、棚に出荷（shipment）を割り当てる API です。追跡番号で [Fulfillment](/lmile-docs/fulfillment-api/fulfillment.md) の出荷元（`sot.lm_fulfillment_shipments`）を特定し、`sot.lm_shelf_links` に棚コード ↔ shipment のアクティブ紐付けを作成します。出荷完了（`shipped`）・取消（`cancel`）で自動解除されます。

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

> 棚紐付けは **shipment 単位**（注文単位ではない）です。追跡番号が shipment（梱包単位）に発行されるため、棚も出荷元（from-location）に属します。1 棚に 1 shipment / 1 shipment に 1 棚（アクティブ紐付け）。フィールドは camelCase（routing 系と同じ）。

## エンドポイント

| Method | Path              | 用途                                 | テーブル                                          |
| ------ | ----------------- | ---------------------------------- | --------------------------------------------- |
| `POST` | `/api/shelf/link` | 追跡番号 + 棚コードのスキャンで棚に shipment を割り当て | `lm_shelf_links` / `lm_fulfillment_shipments` |

***

## `POST` /api/shelf/link

追跡番号で shipment を特定し、棚コードと紐付けます。同一棚 × 同一 shipment の再送は冪等（`200` + 既存 link 返却）です。

### リクエスト

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

```json
{
  "shop": "demo-store.myshopify.com",
  "trackingNumber": "4912345678901",
  "shelfCode": "A-7-WQ"
}
```

### ボディパラメータ

| パラメータ            | 型             |  必須 | 説明                                                                                                                               |
| ---------------- | ------------- | :-: | -------------------------------------------------------------------------------------------------------------------------------- |
| `shop`           | string        |  ✓  | 店舗ドメイン（API キーで呼ぶ場合は省略できます）                                                                                                       |
| `trackingNumber` | string        |  ✓  | 配送ラベルの追跡番号（最大 100 文字）。`sot.lm_fulfillment_shipments.tracking_number` と突合                                                         |
| `shelfCode`      | string        |  ✓  | 棚コード（最大 32 文字）。形式 `^[A-Z0-9]+(-[A-Z0-9]+)+$` かつ英字を 1 文字以上含む                                                                      |
| `locationId`     | string (uuid) |     | 出荷元ロケーション。省略時は shipment の `from_location_id` を採用。指定時は shipment の出荷元と一致が必須                                                        |
| `relink`         | boolean       |     | `true` のとき、shipment が既に**別棚**へ紐付け済みなら既存紐付けを `manual_release` で解除してから新しい棚へ付け替える。省略 / `false` は従来どおり `409 shipment_already_linked` |

### レスポンス

```json
{
  "ok": true,
  "link": {
    "id": "11111111-2222-3333-4444-555555555555",
    "shop": "demo-store.myshopify.com",
    "locationId": "b2c3d4e5-f6a7-8901-bcde-f23456789012",
    "shelfCode": "A-7-WQ",
    "fulfillmentShipmentId": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
    "linkedBy": null,
    "linkedAt": "2026-06-07T09:00:00.000Z",
    "releasedAt": null,
    "releasedReason": null
  }
}
```

| フィールド                                     | 説明                                                                |
| ----------------------------------------- | ----------------------------------------------------------------- |
| `link.id`                                 | 棚紐付け id（`sot.lm_shelf_links.id`）                                  |
| `link.shelfCode`                          | 紐付いた棚コード                                                          |
| `link.fulfillmentShipmentId`              | 紐付いた出荷元（shipment）id                                               |
| `link.linkedBy`                           | 操作者（operator JWT 経由のみ非 null = `auth.users.id`。機械間 ingest は null）  |
| `link.linkedAt`                           | 紐付け日時                                                             |
| `link.releasedAt` / `link.releasedReason` | 解除日時 / 理由（アクティブな間は null）                                          |
| `idempotent`                              | 同一棚 × 同一 shipment の再送で `true`（フィールド自体は冪等時のみ）                      |
| `relinked` / `previousShelfCode`          | `relink: true` で既存紐付けを解除して付け替えた場合のみ `relinked: true` と解除前の棚コードを返す |

### 紐付け可能な shipment 状態

shipment の `status` が **`secured` 以降 `shipped` 未満**（売越しサブ状態を含む）のときのみ紐付けできます。範囲外は `409 shipment_status_not_linkable`。

| 紐付け可  | `secured` / `oversell_pending` / `oversell_secured` / `oversell_returning` / `oversell_warehouse_received` / `in_transit_to_mfc` / `packing` |
| ----- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| 紐付け不可 | `unallocated` / `allocated`（確保前） / `shipped`（出荷完了 = 終端） / `cancelled`                                                                        |

### 自動解除

棚紐付けは shipment のライフサイクルに連動して自動解除されます（`released_at` / `released_reason` を埋める。行は履歴として残る）。

* **出荷完了**: [Fulfillment](/lmile-docs/fulfillment-api/fulfillment.md) で shipment が `shipped` へ遷移したとき、`released_reason = "shipped"`。
* **取消**: [Cancel](/lmile-docs/routing-api/cancel-update.md) で注文配下の shipment（`shipped` 以外）が取消されたとき、`released_reason = "cancelled"`。
* **出荷元変更**: [Source Change](/lmile-docs/oversell-api/source-change.md) を `secured` の単位で起票し、旧出荷元の shipment にアクティブ unit が残らなくなったとき、`released_reason = "source_changed"`。

### 挙動・冪等性

* **解決キー**: 追跡番号 → shipment（1 件）。複数 shipment に当たると `409 ambiguous_tracking_number`、見つからないと `404 shipment_not_found`。
* **再送冪等**: shipment に既にアクティブ紐付けがあり、棚コード・ロケーションが同一なら `200` + `idempotent: true`。別棚への紐付けは自動で付け替えず `409 shipment_already_linked`（先に解除が必要）。
* **付け替え（relink）**: `relink: true` を指定すると、別棚への既存紐付けを `manual_release` で解除してから新しい棚へ紐付け直す（`200` + `relinked: true` + `previousShelfCode`）。解除行は履歴として残る。
* **誤スキャン防止**: 棚コードがハイフン除去で追跡番号（数字正規化を含む）と一致すると `422 shelf_code_similar_to_tracking`。
* **アクティブ一意性**: 同一棚に別 shipment が占有中なら `409 shelf_already_linked`。最終防衛は DB の partial unique index（`released_at IS NULL`）。

## エラー

エンドポイント固有:

| Status | 条件                               | レスポンス例                                                                                                                                    |
| :----: | -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
|  `422` | 棚コード形式不正                         | `{ "ok": false, "error_kind": "invalid_shelf_code", "error": "shelfCode must match ..." }`                                                |
|  `422` | 棚コードが追跡番号の類似値（誤スキャン）             | `{ "ok": false, "error_kind": "shelf_code_similar_to_tracking", "error": "shelfCode looks like the tracking number (possible misscan)" }` |
|  `422` | `locationId` が shop に存在しない       | `{ "ok": false, "error_kind": "location_unresolved", "error": "location not found for shop" }`                                            |
|  `422` | `locationId` が shipment の出荷元と不一致 | `{ "ok": false, "error_kind": "location_mismatch", "error": "locationId does not match the shipment's from-location" }`                   |
|  `404` | 追跡番号に該当する shipment が無い           | `{ "ok": false, "error_kind": "shipment_not_found", "error": "shipment not found for tracking number" }`                                  |
|  `409` | 追跡番号が複数 shipment に当たる            | `{ "ok": false, "error_kind": "ambiguous_tracking_number", "error": "...", "shipmentIds": ["..."] }`                                      |
|  `409` | shipment が紐付け可能範囲外               | `{ "ok": false, "error_kind": "shipment_status_not_linkable", "error": "...", "status": "allocated" }`                                    |
|  `409` | shipment が既に別棚へ紐付け済              | `{ "ok": false, "error_kind": "shipment_already_linked", "error": "...", "currentShelfCode": "E-8" }`                                     |
|  `409` | 棚が既に別 shipment に占有済              | `{ "ok": false, "error_kind": "shelf_already_linked", "error": "shelf is already linked to another shipment (release it first)" }`        |

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

## データモデル

* `sot.lm_shelf_links` — 1 行 = 1 紐付けイベント。解除は DELETE せず `released_at` / `released_reason`（`shipped` / `manual_release` / `cancelled` / `source_changed`）を埋める（行自体が履歴）。アクティブ一意性（同一棚に 1 shipment / 同一 shipment に 1 棚）は partial unique index（`released_at IS NULL`）。`location_id` → `sot.locations`、`fulfillment_shipment_id` → `sot.lm_fulfillment_shipments`。読み取り view: `public.lm_v_shelf_links`（内部 service / 管理用途。`agent_read.*` には露出しません）


---

# 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/link.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.
