> 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/fulfillment-api/external-fulfillment.md).

# External Fulfillment — 外部フルフィルメント連携

出荷を**外部フルフィルメントサービス**（例: Amazon マルチチャネルフルフィルメント）が実行する出荷元向けの連携 API 群です。連携アプリが 1) 発注に必要な情報（配送先・明細・外部システム SKU）を取得し、2) 外部サービスへ発注し、3) 受理 / 失敗を書き戻し、4) 受理済み・未出荷の一覧をポーリングして、外部側の出荷完了時に追跡番号付きで出荷済みを報告します。外部システム SKU は **barcode との事前登録された明示マッピングのみ**で解決します（SKU 名・商品名からの自動マッピングは行いません）。

> **Base URL** `https://api.lmile.io` · **認証** `x-ingest-secret` ヘッダー（[詳細](/lmile-docs/getting-started/authentication.md)） · 読み取り（`GET`）は `shop` をクエリに、書き込み（`POST`）はリクエストボディに含めます（省略するとキーのアカウント）

> `POST /api/routing/shipment/fulfill-result` と `POST /api/routing/shipment/ship` は機械間 secret（`x-ingest-secret`）と倉庫スタッフの operator JWT のどちらでも呼べます（[詳細](https://lmile.gitbook.io/lmile-docs/fulfillment-api/pages/7cB70I8yeRnMpvcdLkNu#routing-write-の-2-系統認証)）。`ship` を operator JWT 経由で呼んだ場合は操作スタッフを記録します。

> `GET /api/routing/shipment/fulfill-job` は**配送先（氏名・住所・電話・メール）を返します**。外部発注以外の用途に使わないでください（ラベル生成向けの label 連携が宛先を返さないのと異なる契約のため、別エンドポイントになっています）。

## 連携フロー

```
引き当て確定（webhook / Apps イベント）
  │
  ├─ 1. GET  /api/routing/shipment/fulfill-job      … 配送先・明細・外部 SKU を取得
  ├─ 2.（外部サービスへ発注。例: createFulfillmentOrder）
  ├─ 3. POST /api/routing/shipment/fulfill-result   … 受理 / 失敗を書き戻し
  ├─ 4. GET  /api/routing/shipment/fulfill-pending  … 受理済み・未出荷をポーリング
  └─ 5. POST /api/routing/shipment/ship             … 出荷完了を追跡番号付きで報告
```

発注のトリガーには [Routing Allocation Webhook](/lmile-docs/webhooks/allocation.md) や [Apps](/lmile-docs/apps/overview.md) のイベント配信（`routing_allocation_decided`）を使えます。

## エンドポイント

| Method | Path                                    | 用途                           |
| ------ | --------------------------------------- | ---------------------------- |
| `GET`  | `/api/routing/shipment/fulfill-job`     | 外部発注に必要な情報（配送先・明細・外部 SKU）の取得 |
| `POST` | `/api/routing/shipment/fulfill-result`  | 発注結果（受理 / 失敗）の書き戻し           |
| `GET`  | `/api/routing/shipment/fulfill-pending` | 受理済み・未出荷の出荷一覧（追跡ポーリング用）      |
| `POST` | `/api/routing/shipment/ship`            | 1 出荷を出荷済みに報告（追跡番号の同時確定に対応）   |
| `GET`  | `/api/ingest/sku-mappings`              | 外部システム SKU ↔ barcode 対応表の取得  |

***

## `GET` /api/routing/shipment/fulfill-job

出荷（shipment）1 件について、外部フルフィルメントへの発注に必要な情報を取得します。発注できない状態のときは HTTP エラーではなく `200` + `generatable: false`（`reason` 付き）を返します（silent skip しません）。

### リクエスト

```http
GET https://api.lmile.io/api/routing/shipment/fulfill-job?shop=demo-store.myshopify.com&shipmentId=3f2a1b0c-...&system=amazon_mcf_us
x-ingest-secret: {INGEST_SECRET}
```

### クエリパラメータ

| パラメータ        |  必須 | 説明                                            |
| ------------ | :-: | --------------------------------------------- |
| `shop`       |     | 店舗ドメイン（省略するとキーのアカウント）                         |
| `shipmentId` |  ✓  | 対象の出荷（shipment）ID（uuid）                       |
| `system`     |     | 外部システム識別子（既定 `amazon_mcf_us`）。SKU マッピングの解決に使用 |

### レスポンス

```json
{
  "ok": true,
  "shipmentId": "3f2a1b0c-...",
  "generatable": true,
  "system": "amazon_mcf_us",
  "orderName": "BJP1001",
  "externalOrderRef": "1001",
  "status": "allocated",
  "fromLocationId": "a1b2c3d4-...",
  "fulfillmentSourceLocationId": "054bccb8-...",
  "alreadyAccepted": false,
  "externalFulfillmentId": null,
  "shipTo": {
    "name": "山田 太郎",
    "company": null,
    "phone": "090-1234-5678",
    "email": "taro@example.com",
    "country": "US",
    "postalCode": "90001",
    "prefecture": "CA",
    "city": "Los Angeles",
    "address1": "123 Main St",
    "address2": null
  },
  "lines": [
    { "barcode": "1234567890123", "sku": "SKU-001", "quantity": 2, "sellerSku": "AMZ-SKU-001" }
  ]
}
```

| フィールド                                            | 説明                                                                                                                       |
| ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------ |
| `generatable`                                    | `true` = 発注に必要な情報が揃っている / `false` = 発注対象外・発注不能（`reason` 付き）                                                              |
| `orderName` / `externalOrderRef`                 | 注文番号 / 受注元システムの注文参照（外部発注の displayable order id 等に使用）                                                                     |
| `status`                                         | 出荷の現在の状態（[Fulfillment](/lmile-docs/fulfillment-api/fulfillment.md) のライフサイクル）                                             |
| `fromLocationId` / `fulfillmentSourceLocationId` | 引き当て先在庫ロケーション / 出荷元                                                                                                      |
| `alreadyAccepted`                                | 発注受理済みマーカー。`true` なら [`fulfill-result`](#post-apiroutingshipmentfulfill-result) で受理済み = **再発注しない**（イベント再配信時の二重発注 skip 用） |
| `externalFulfillmentId`                          | 受理済みの外部発注 ID（未受理は `null`）                                                                                                |
| `shipTo`                                         | 配送先。注文の宛先住所を**そのまま**返すため、[住所修正](/lmile-docs/fulfillment-api/address-recovery.md)が即反映されます                                 |
| `lines[]`                                        | 有効な単位を `barcode × sku` で数量集約した明細。`sellerSku` = 対応表で解決した外部システム SKU                                                        |

### 発注できないとき（`generatable: false`）

| `reason`                        | 意味                                                         |
| ------------------------------- | ---------------------------------------------------------- |
| `shipment_cancelled`            | 出荷が取消済み                                                    |
| `already_shipped`               | 既に出荷済み                                                     |
| `order_not_found`               | 出荷が指す注文が見つからない                                             |
| `order_cancelled`               | 注文が取消済み                                                    |
| `fulfillment_source_unresolved` | 出荷元が解決できない                                                 |
| `no_active_lines`               | 有効な明細が無い（全単位が取消済み）                                         |
| `seller_sku_unmapped`           | 対応表に無い barcode がある（`unmappedBarcodes[]` 付き。先に対応表へ登録してください） |

### 挙動・冪等性

* **read 専用**です。何回呼んでも状態を変えません。
* 明細は取消済み単位を除いて `barcode × sku` 単位に数量集約します。
* 外部 SKU は `system` ごとの**事前登録された明示マッピング**（[`GET /api/ingest/sku-mappings`](#get-apiingestsku-mappings) と同じ対応表）だけで解決します。1 つでも未登録 barcode があれば `seller_sku_unmapped` で発注に進めません（部分発注しません）。
* 発注受理後に再度呼ぶと `alreadyAccepted: true` + `externalFulfillmentId` が返ります。呼び出し側はこれを見て再発注を skip してください。

***

## `POST` /api/routing/shipment/fulfill-result

外部フルフィルメントへの発注結果（受理 / 失敗）を書き戻します。

### リクエスト

```http
POST https://api.lmile.io/api/routing/shipment/fulfill-result
x-ingest-secret: {INGEST_SECRET}
Content-Type: application/json
```

```json
{
  "shop": "demo-store.myshopify.com",
  "shipmentId": "3f2a1b0c-...",
  "status": "accepted",
  "externalFulfillmentId": "LM-3f2a1b0c-...",
  "acceptedAt": "2026-07-28T02:00:00.000Z"
}
```

### ボディパラメータ

| パラメータ                   | 型             |  必須 | 説明                                                                                            |
| ----------------------- | ------------- | :-: | --------------------------------------------------------------------------------------------- |
| `shop`                  | string        |     | 店舗ドメイン（省略するとキーのアカウント）                                                                         |
| `shipmentId`            | string (uuid) |  ✓  | 対象の出荷（shipment）ID                                                                             |
| `status`                | string        |  ✓  | `accepted`（外部サービスが発注を受理）/ `failed`（発注失敗）                                                      |
| `externalFulfillmentId` | string        |  △  | 外部発注 ID（最大 200 文字）。**`status=accepted` のとき必須**。同一 shipment で常に同じ決定的な値を推奨（例 `LM-{shipmentId}`） |
| `acceptedAt`            | string        |     | 受理時刻（ISO 8601）。省略時は現在時刻                                                                       |
| `error`                 | string        |     | `status=failed` のときのエラー内容（最大 2000 文字。省略時は既定メッセージ）                                             |

### レスポンス

```json
{
  "ok": true,
  "shipmentId": "3f2a1b0c-...",
  "recorded": "accepted",
  "externalFulfillmentId": "LM-3f2a1b0c-..."
}
```

| フィールド      | 説明                            |
| ---------- | ----------------------------- |
| `recorded` | `accepted` / `failed`（記録した結果） |

### 挙動・冪等性

* **`accepted`**: 外部発注 ID と受理時刻を出荷に記録し、エラー記録（`last_label_error`）をクリアします。以後 [`fulfill-job`](#get-apiroutingshipmentfulfill-job) は `alreadyAccepted: true` を返すため、イベント再配信時の二重発注を skip できます。
* **`failed`**: エラー内容だけを `last_label_error` に記録します（受理マーカーは既存値を保持）。エラーは出荷詳細画面と[ラベル再発行](/lmile-docs/fulfillment-api/address-recovery.md)の対象条件に載るため、失敗が無言で消えることはありません。
* **出荷の状態（status）は変更しません**。確保済（`secured`）への遷移は [`POST /api/routing/fulfillment`](/lmile-docs/fulfillment-api/fulfillment.md) で別途報告してください。
* **冪等**: 同じ内容の再送は列の上書きで収束し安全です。`externalFulfillmentId` に決定的な値を使えば、リトライしても受理記録は 1 つに保たれます。

***

## `GET` /api/routing/shipment/fulfill-pending

外部フルフィルメントに**受理済みで、まだ出荷済みになっていない**出荷の一覧を返します。追跡ポーリングの走査対象取得用で、外部側の出荷完了を検知したら [`ship`](#post-apiroutingshipmentship) で出荷済みを報告してください（報告した出荷は次回から一覧に出ません）。

### リクエスト

```http
GET https://api.lmile.io/api/routing/shipment/fulfill-pending?shop=demo-store.myshopify.com&days=30
x-ingest-secret: {INGEST_SECRET}
```

### クエリパラメータ

| パラメータ  |  必須 | 説明                                    |
| ------ | :-: | ------------------------------------- |
| `shop` |     | 店舗ドメイン（省略するとキーのアカウント）                 |
| `days` |     | 走査窓（出荷の作成からの日数）。既定 30・最大 60。不正値は既定に丸め |

### レスポンス

```json
{
  "ok": true,
  "windowDays": 30,
  "count": 2,
  "shipments": [
    {
      "shipmentId": "3f2a1b0c-...",
      "routingOrderId": "0f1e2d3c-...",
      "status": "secured",
      "externalFulfillmentId": "LM-3f2a1b0c-...",
      "acceptedAt": "2026-07-28T02:00:00.000Z"
    }
  ]
}
```

### 挙動

* **read 専用**です。定期実行（cron 等）から毎回呼んで走査対象を取り直す使い方を想定しています。
* 対象は「受理済み（外部発注 ID あり）・未出荷・未取消」で、作成が古い順に最大 500 件です。窓（`days`）を超えた古い出荷は返しません。

***

## `POST` /api/routing/shipment/ship

1 出荷（shipment）を**出荷済み（shipped）にします。外部フルフィルメントのように追跡番号が出荷完了時に初めて確定する**ケースでは、`trackingNumber` / `carrierCode` を同時に渡せます。

### リクエスト

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

```json
{
  "shop": "demo-store.myshopify.com",
  "shipmentId": "3f2a1b0c-...",
  "notifyCustomer": true,
  "trackingNumber": "TBA123456789000",
  "carrierCode": "amazon_logistics"
}
```

### ボディパラメータ

| パラメータ                  | 型             |  必須 | 説明                                                                                                     |
| ---------------------- | ------------- | :-: | ------------------------------------------------------------------------------------------------------ |
| `shop`                 | string        |     | 店舗ドメイン（省略するとキーのアカウント）                                                                                  |
| `shipmentId`           | string (uuid) |  ✓  | 対象の出荷（shipment）ID                                                                                      |
| `notifyCustomer`       | boolean       |  ✓  | 顧客への出荷通知（販売チャネル側の発送通知メール）の要否。出荷ステータス通知の payload で運ばれます                                                 |
| `trackingNumber`       | string        |     | 追跡番号（最大 200 文字）。指定時は出荷済みへの遷移と**同一の書き込み**で保存し、通知にも載せます。未指定は保存済みの値（ラベル発行時に取得済み）を使用                       |
| `carrierCode`          | string        |     | キャリアコード（最大 100 文字）。`trackingNumber` と同様                                                                |
| `trackingCompany`      | string        |     | 配送業者の名前（最大 100 文字）。販売チャネル（Shopify）の業者一覧の名前をそのまま送ると、Shopify が追跡リンクを作ります。`trackingNumber` と一緒のときだけ指定できます |
| `trackingUrl`          | string        |     | 追跡ページの URL（`http://` / `https://`、最大 2000 文字）。業者一覧に無い業者のときに送ります。`trackingNumber` と一緒のときだけ指定できます        |
| `actorEmail`           | string        |     | 操作者のメール（操作ログ用・任意）                                                                                      |
| `suppressCustomerMail` | boolean       |     | `true` にすると、この出荷では発送完了メール（お届け状況の更新メール）を送りません。送る方向には指定できません（店舗の通知設定で送らない場合は、指定しなくても送りません）               |

### レスポンス

```json
{
  "ok": true,
  "shipmentId": "3f2a1b0c-...",
  "status": "shipped",
  "shippedAt": "2026-07-28T05:00:00.000Z"
}
```

| フィールド                    | 説明                                                                               |
| ------------------------ | -------------------------------------------------------------------------------- |
| `noop`                   | 既に出荷済みだった場合 `true`（更新・通知なし）                                                      |
| `customerMailSuppressed` | `suppressCustomerMail: true` を受け付けたか（`shipmentId` 指定時のみ返します）                     |
| `consolidationGroup`     | MFC 集約グループの一員を指定した場合のみ。グループ全体を一括出荷し `headShipmentId` / `memberShipmentIds` を返します |

### 挙動・冪等性

* **出荷済みにできるのは確保済（`secured` / `oversell_secured`）または梱包中（`packing`）から**です。それ以外（引き当てのみ・取消済み等）は `409 invalid_state` で拒否します。
* 出荷済みへの遷移で、出荷ヘッダと配下の単位（[Units](/lmile-docs/fulfillment-api/units.md)）を `shipped` にし、アクティブな[棚紐付け](/lmile-docs/shelf-api/link.md)を解除します。
* 遷移は [Fulfillment Status Webhook](/lmile-docs/webhooks/fulfillment-status.md) で外部へ通知されます（設定時のみ。`notifyCustomer` と追跡番号を含む）。
* 発送完了メール（お届け状況の更新メール）を送るかは、**店舗の通知設定が決めます**。リクエストで指定できるのは「この出荷では送らない」（`suppressCustomerMail: true`）だけです（旧項目 `sendShippedMail` は受け取っても無視します）。止まるのは発送完了の段だけで、配達完了のメールは店舗の通知設定どおり送られます。
* 提携倉庫から出る注文にお届け状況メールを送る設定にしている店舗では、**同じ出荷について販売チャネル側の発送通知は送りません**（`notifyCustomer: true` を送っても抑止します）。出荷の連絡が 2 通届くのを避けるためで、どちらか一方は必ず送られます。`suppressCustomerMail: true` のときは抑止せず、販売チャネル側の通知は `notifyCustomer` どおりになります。
* **冪等**: 既に出荷済みの出荷への再 POST は `200` + `noop: true` です（更新も通知の再発火もしない = 二重 fulfill 防止）。
* 複数店舗分割の 1 箱をまとめる **MFC 集約グループ**の一員を指定すると、グループ全体を一括で出荷済みにします。このとき `trackingNumber` を指定すると、グループ先頭の出荷に保存し、出荷ステータス通知にも載せます（配送ラベルを自動発行しない出荷元で、箱に追跡番号を付けるための入力です。どのメンバーを指定しても保存先は先頭の出荷です）。`carrierCode` だけを `trackingNumber` なしで指定することはできません（`400`）。
* 注文配下に倉庫間移動（出荷元変更）フロー中の unit があり、このヘッダに出荷できる unit が 1 つも無い場合は `409 transfer_required` で明示拒否します（[Transfer Batches](/lmile-docs/transfer-api/batches.md)）。

***

## `GET` /api/ingest/sku-mappings

外部システム SKU ↔ barcode の対応表（**事前登録された明示マッピング**の有効行）を返します。外部システムの在庫（外部 SKU キー）を barcode に解決して [Inventory Snapshot](/lmile-docs/ingestion-api/inventory-levels.md) へ流す、といった用途に使います。

### リクエスト

```http
GET https://api.lmile.io/api/ingest/sku-mappings?shop=demo-store.myshopify.com&system=amazon_mcf_us
x-ingest-secret: {INGEST_SECRET}
```

### クエリパラメータ

| パラメータ    |  必須 | 説明                            |
| -------- | :-: | ----------------------------- |
| `shop`   |     | 店舗ドメイン（省略するとキーのアカウント）         |
| `system` |     | 外部システム識別子（既定 `amazon_mcf_us`） |

### レスポンス

```json
{
  "ok": true,
  "shop": "demo-store.myshopify.com",
  "system": "amazon_mcf_us",
  "count": 2,
  "mappings": [
    { "externalSku": "AMZ-SKU-001", "barcode": "1234567890123", "sku": "BAG-001-BK" },
    { "externalSku": "AMZ-SKU-002", "barcode": "1234567890456", "sku": null }
  ]
}
```

| フィールド                    | 型              | 説明                                                  |
| ------------------------ | -------------- | --------------------------------------------------- |
| `mappings[].externalSku` | string         | 外部システムの SKU                                         |
| `mappings[].barcode`     | string         | 対応する barcode                                        |
| `mappings[].sku`         | string \| null | その barcode の商品マスター上の SKU。商品マスターに無い barcode は `null` |

### 挙動

* **read 専用**です。外部 SKU の昇順で最大 10,000 件を返します。
* 対応表は人間がレビューして登録した明示マッピングのみです（SKU 名・商品名からの自動生成は行いません）。登録の追加・変更の公開 API はありません。

***

## エラー

エンドポイント固有:

| Status | 条件                                                                                                                           | レスポンス例                                                                                                                             |
| :----: | ---------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
|  `400` | 必須クエリ欠落（`shop` / `shipmentId`）/ ボディのバリデーション失敗 / `status=accepted` で `externalFulfillmentId` 無し / `acceptedAt` が ISO 8601 でない | `{ "ok": false, "error_kind": "invalid_input", "error": "externalFulfillmentId required when status=accepted" }`                   |
|  `400` | `ship`: `trackingCompany` / `trackingUrl` を `trackingNumber` なしで指定                                                           | `{ "ok": false, "error_kind": "invalid_input", "error": "trackingCompany/trackingUrl require trackingNumber" }`                    |
|  `400` | `ship`: 集約グループの出荷に `carrierCode` を `trackingNumber` なしで指定                                                                    | `{ "ok": false, "error_kind": "invalid_input", "error": "carrierCode requires trackingNumber on a consolidation-group shipment" }` |
|  `404` | `shipmentId` に該当する shipment が無い（fulfill-job / fulfill-result / ship）                                                         | `{ "ok": false, "error_kind": "shipment_not_found", "error": "fulfillment shipment not found" }`                                   |
|  `409` | `ship`: 出荷済みにできない状態（確保済・梱包中以外）                                                                                               | `{ "ok": false, "error_kind": "invalid_state", "error": "shipment status 'allocated' cannot transition to shipped" }`              |
|  `409` | `ship`: 倉庫間移動フロー中で出荷可能な unit が無い                                                                                             | `{ "ok": false, "error_kind": "transfer_required", "units": [...] }`                                                               |

`fulfill-job` の発注対象外は HTTP エラーではなく `200` + `generatable: false`（`reason` 付き）で返します（[発注できないとき](#発注できないときgeneratable-false)）。

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

## データモデル

* `sot.lm_fulfillment_shipments` — 出荷ヘッダ。外部発注の受理マーカー `external_fulfillment_id` / `external_fulfillment_accepted_at`、追跡番号 `tracking_number` / `carrier_code`、出荷 `shipped_at`、失敗記録 `last_label_error`（[Fulfillment](/lmile-docs/fulfillment-api/fulfillment.md)）
* `sot.lm_external_sku_mappings` — 外部システム SKU ↔ barcode の明示マッピング（`UNIQUE (shop, system, external_sku)`・`is_active`。人間レビュー済みの登録のみ）
* `sot.lm_routing_orders` — 宛先住所（`ship_to_*`）の単一ソース。`fulfill-job` の `shipTo` はここを直接返す（[Address Recovery](/lmile-docs/fulfillment-api/address-recovery.md)）
* `sot.lm_fulfillment_units` — 明細の単位（`barcode` / `sku`。`fulfill-job` の `lines[]` は有効単位の集約）


---

# 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/fulfillment-api/external-fulfillment.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.
