Inventory Levels — 在庫
在庫を Last Mile の在庫マスターに取り込みます。用途別に 2 エンドポイントに分かれます: 定期的な全量同期は snapshot、販売・入荷など変動イベントの都度反映は available を使います(外部倉庫の拠点で販売チャネル側の販売可能数を伝える channel-available もあります)。両方の併用も可能です(古い読み値の上書き防止により、古いバッチ読み値がリアルタイム更新済みの値を上書きすることはありません)。商品は barcode、拠点は location_id または外部識別子で解決します。
Base URL
https://api.lmile.io· 認証x-ingest-secretヘッダー(詳細) ·shopはリクエストボディに含めます(省略するとキーのアカウント)
エンドポイント
POST
/api/ingest/inventory-snapshot
1 拠点の現在庫を絶対値で一括投入(全在庫を上書き)
初回投入 / 1 日 1 回などの定期全量同期 / ズレ補正
POST
/api/ingest/inventory-available
販売可能在庫 (available_qty) のリアルタイム更新(absolute / delta)
販売・入荷・返品などの変動をその場で反映
POST
/api/ingest/channel-available
販売チャネル(Shopify など)での販売可能数を伝える
在庫数を外部倉庫が送ってくる拠点で、チャネルで売っていない商品を売れない数にする
取込により出荷元の合算在庫が変わると Source Inventory Webhook が発火します。
レート制限はありません。上限は 1 リクエストあたり
lines最大 10,000 行です(超過は400)。
商品(barcode)の指定
両エンドポイント共通。商品は行の barcode(JAN コード)で商品マスターと照合します。SKU や商品名では照合できません。未登録の barcode はリクエスト全体を失敗させず、HTTP 200 のまま該当行だけ反映されず unresolved[] に返ります(→ 行単位エラー)。事前に Products で商品(barcode)を登録してください。
拠点(location)の指定
両エンドポイント共通。location_id(内部 UUID)か location(外部識別子)のいずれかを指定します。
"location_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
// または
"location": { "identifier_type": "smaregi_store_id", "identifier_value": "4" }古い読み値の上書き防止
両エンドポイント共通。**「古い在庫の読み値で、新しい値を上書きしない」**ための仕組みです。
いつ関係するか — 在庫を複数の経路で送っている場合です。典型は「販売のたびに送るリアルタイム更新」と「数時間おきのバッチ全量同期」の併用。バッチは在庫を読み取ってから届くまでに時間差があるため、対策がないとバッチの古い在庫数が直近のリアルタイム更新を巻き戻してしまいます(売り切れた商品が「在庫あり」に戻る事故)。送信経路が 1 つだけなら、この節は読み飛ばして構いません(as_of 省略のままで正しく動きます)。
具体例 — 1 時間おきのバッチとリアルタイム更新を併用しているとき:
14:00
バッチが在庫システムから読み取り: barcode 1234567890123 = 5 個
14:10
店頭で 5 個売れ、リアルタイム更新が available_qty: 0 を送信 → 在庫 0(読み値の時刻 14:10 として反映)
14:30
14:00 に読んだバッチが届き、available_qty: 5 を as_of: 14:00 付きで送信
14:30 の行は「14:00 の読み値 < 反映済みの 14:10」なので適用されず、在庫は 0 のまま守られます。この仕組みが無ければ在庫が 5 に巻き戻り、実際には無い在庫が売れてしまいます。
判定ルール — 各行の as_of(省略時はリクエストの inventory_as_of、それも省略なら受信時刻)を、反映済みの時刻と比較します。
新しい / 同じ / 初回(既存行なし)
適用します(同時刻を許すので、同じリクエストの再送は成功します)
古い
その行だけ適用しません。HTTP 200 のまま stale_skipped[] に返ります
レスポンスの見方 — 上の 14:30 のリクエストにはこう返ります:
送信側がやること
バッチ連携では、各行の
as_ofに在庫システム側でその値を読み取った時刻を入れてください(例: スマレジ/stockのupdDateTime)。送信時刻ではありません。送信経路が 1 つだけなら
as_ofは省略できます(受信時刻が使われ、古い読み値として弾かれることはありません)。stale_skipped[]は「より新しい値が反映済み」の合図です。再送は不要です。恒常的に大量に返る場合だけ、as_ofに入れている時刻が正しいか確認してください。
POST /api/ingest/inventory-snapshot
「今この拠点にこの商品が何個あるか」を絶対値で送信します。送るたびに現在庫が送った値で丸ごと置き換わります(追記や加算ではありません)。当日の日次履歴と変更ログも同時に更新されます。
リクエスト
ボディパラメータ
shop
string
店舗ドメイン(省略するとキーのアカウント)
source_system
string
✓
取込元。ログの change_source に記録
location_id / location
string / object
✓
いずれか必須(上記参照)
lines
array
✓
在庫行の配列(最大 10,000 件)
idempotency_key
string
監査用の任意キー。このエンドポイントでは重複排除に使いません(再送の安全性は絶対値の置き換えで担保されます)
lines[].barcode
string
✓
商品 barcode
lines[].on_hand_qty
integer (≥0)
✓
物理在庫(拠点に実在する数)
lines[].available_qty
integer
販売可能在庫(省略時は on_hand_qty)
lines[].allocated_qty reserved_qty incoming_qty
integer
引当 / 予約 / 入荷予定(default: 0)
レスポンス
lines_processed
受け取った行数
lines_upserted
在庫に反映した行数
lines_unresolved
反映しなかった行数(内訳は unresolved[])
lines_stale_skipped
読み値が古く反映しなかった行数(内訳は stale_skipped[])
logs_written
変更ログを書いた行数(値が変わった行のみ)
unresolved[]
反映しなかった行と理由(→ 行単位エラー)
stale_skipped[]
読み値が古く反映しなかった行(→ 古い読み値の上書き防止)
挙動・冪等性
在庫は店舗 × 商品 × 拠点ごとに 1 行で管理されます。送信すると該当行が送った値で丸ごと置き換わり、無ければ新規作成されます(追記や加算はされません)。
同時に、その日の日次履歴も同じ値に更新されます(1 日 1 行。同じ日に何度送っても増えません)。
変更ログは値が変わった行だけ記録されます。同じ値の再送でログは増えません。
同じリクエストは何度再送しても安全です(冪等)。タイムアウト等で結果が不明なときは、そのまま再送してください。
未登録の
barcodeの行はその行だけ反映されずunresolved[]に返ります(→ 行単位エラー)。行の読み値が反映済みより古い場合はその行だけ反映されません(→ 古い読み値の上書き防止)。
POST /api/ingest/inventory-available
販売可能在庫 (available_qty) を即時更新します。1 リクエストで 1〜数件の (barcode × 拠点) を更新する用途です。
任意で committed_qty(注文済み・未出荷)と on_hand_qty(物理在庫)も同時に送れます。送った行だけ上書きされ、省略した値は現状維持です(allocated_qty / reserved_qty はこのエンドポイントでは変わりません)。
なぜ
committed_qtyも送るのか — Shopify では注文が入った瞬間、その分の在庫はavailableからcommitted(注文済み・未出荷)へ移ります。例: 在庫 1 個の商品が売れるとavailable: 0, committed: 1。このときavailable_qty: 0だけを送ると Last Mile からは「在庫なし」に見え、いま入ったその注文にすら引き当てられません。committed_qty: 1を併せて送ると、引き当て可能数はavailable + committed = 1と判定され、その注文のために確保済みの 1 個へ正しく引き当てられます。
available_qty の送り方は mode で選びます:
mode
送る値
こんなとき
再送したら
absolute
在庫数そのもの(available_qty: 3 = 3 個にする)
その時点の在庫数を知っている場合。可能ならこちら推奨
同じ値で上書きされるだけ。安全(冪等)
delta
増減量(available_delta: -2 = 2 個減らす)
販売・返品などの変動イベントしか取れない場合
二重に増減してしまうため、イベントごとに一意な idempotency_key を必ず付けてください(同一キーの 2 回目は適用されません)
delta の結果が負になる場合は 0 で止まり、clamped[] に返ります(エラーにはなりません)。committed_qty / on_hand_qty は mode に関わらず常に「在庫数そのもの」で送ります。
リクエスト
ボディパラメータ
shop
string
店舗ドメイン(省略するとキーのアカウント)
source_system
string
✓
取込元
mode
string
✓
absolute または delta
location_id / location
string / object
✓
いずれか必須
lines
array
✓
更新行の配列(最大 10,000 件)
idempotency_key
string
delta ではイベントごとに一意な値を必ず(同一キーの 2 回目は適用されません)。absolute では不要
lines[].barcode
string
✓
商品 barcode
lines[].available_qty
integer (≥0)
absolute 時 ✓
上書きする販売可能在庫
lines[].available_delta
integer
delta 時 ✓
変動量(負可)
lines[].committed_qty
integer (≥0)
注文済み・未出荷の数を上書き(省略時は現状維持)。→ 上の「なぜ committed_qty も送るのか」
lines[].on_hand_qty
integer (≥0)
物理在庫を上書き(省略時は現状維持)
レスポンス
lines_processed
受け取った行数
lines_updated
在庫に反映した行数
lines_unresolved
反映しなかった行数(内訳は unresolved[])
lines_clamped
delta の結果が負になり 0 に丸めた行数(内訳は clamped[])
lines_deduped
同一 idempotency_key の二重適用として skip した行数
lines_stale_skipped
読み値が古く反映しなかった行数(内訳は stale_skipped[])
unresolved[]
反映しなかった行と理由(→ 行単位エラー)
clamped[]
0 に丸めた行({ barcode, computed, set })
stale_skipped[]
読み値が古く反映しなかった行(→ 古い読み値の上書き防止)
挙動・冪等性
送った行の
available_qtyが更新されます。在庫行が無ければ新規作成されます(送らなかった数量は 0 で作成)。committed_qty/on_hand_qtyは送った行だけ上書きされ、省略した値は現状維持です(allocated_qty/reserved_qtyはこのエンドポイントでは変わりません)。変更ログは値が変わった行だけ記録されます。
日次履歴は更新しません(日次は snapshot エンドポイントの役割です)。
absoluteは同じ値の再送で上書きされるだけなので安全です(冪等)。deltaの重複排除は連続リトライを吸収する best-effort です。イベントごとに一意なidempotency_keyを必ず付けてください。行の読み値が反映済みより古い場合はその行だけ反映されません(→ 古い読み値の上書き防止)。
POST /api/ingest/channel-available
販売チャネル(Shopify など)で、ある拠点の商品がいくつ売れる状態かを伝えます。在庫の数そのものは外部倉庫など別の送り手が送ってくる拠点(外部倉庫の拠点)のためのエンドポイントです。
送った値が 0 以下の商品は、その拠点の販売可能在庫が 0 になります。倉庫に現物があってもチャネルで売っていない商品を、Last Mile でも「売れない」として扱うためです。1 以上を送ると、倉庫から届いた在庫数にもとづく通常の計算に戻ります。
例: 倉庫から「在庫 3」が届いている商品について
送った channel_available_qty
販売可能在庫
未送信
倉庫の数にもとづく通常の計算(例: 3)
0 または負(売り越し)
0
1 以上
通常の計算に戻る(例: 3。送った数そのものにはなりません)
倉庫から届いた在庫数は書き換えません。倉庫の数は従来どおり inventory-available で送ってください
受注の引き当ては止まりません。外部倉庫の拠点は在庫数に関わらず受注を引き受けます。0 で変わるのは販売可能在庫の数(お届け予定日の表示・入荷通知など)です
外部倉庫の拠点以外に送ると
422(location_not_derived)で拒否します
リクエスト
ボディパラメータ
shop
string
✓
店舗ドメイン
source_system
string
✓
送り元(変更ログに残ります)
location_id
string (UUID)
✓
外部倉庫の拠点の内部 UUID(外部識別子での指定はできません)
lines
array
✓
更新行の配列(最大 10,000 件)
inventory_as_of
ISO 8601
読み取り時刻の既定値。行の as_of 省略時に使われます(それも省略なら受信時刻)
lines[].barcode
string
✓
商品 barcode
lines[].channel_available_qty
integer
✓
チャネルでの販売可能数。負の値もそのまま送ってください(0 以下はすべて「売れない」)
lines[].as_of
ISO 8601
その行の値を読み取った時刻
レスポンス
lines_processed
受け取った行数
lines_updated
チャネルの販売可能数が前回から変わった行数
unresolved[]
反映しなかった行と理由(→ 行単位エラー)
stale_skipped[]
読み値が古く反映しなかった行({ barcode, as_of, current_as_of })
changed[]
その結果、販売可能在庫が変わった行({ locationId, barcode, qtyBefore, qtyAfter })
挙動・冪等性
同じ値を再送しても結果は変わりません(冪等)。そのまま再送して構いません。
受け取った時刻より新しい値がすでに届いている行は反映しません(
stale_skipped[])。順序が入れ替わって届いても、新しい値が勝ちます。在庫行がまだ無い商品(倉庫から一度も在庫数が届いていない商品)は、在庫 0 の行を作ってから反映します。例: 新商品に
channel_available_qty: 0を送ると、その拠点の販売可能在庫が 0 になります。あとから倉庫が在庫数を送ると、その数で通常どおり計算されます。販売可能在庫が変わると、Source Inventory Webhook の対象になります(取込と同じ扱い)。
エラー
エラーは 2 種類あります: リクエスト全体のエラー(HTTP 4xx で全行不採用)と、行単位エラー(HTTP 200 のまま該当行だけ不採用)。
リクエスト全体のエラー
エンドポイント固有:
422
ok:false(error_kind 付き)
{ "ok": false, "error_kind": "location_unresolved", "error": "no location for smaregi_store_id=999999" }
400
lines が 10,000 行を超過
{ "ok": false, "error": "max 10000 lines per request" }
error_kind: invalid_payload / location_unresolved / location_ambiguous。channel-available では location_not_found(shop にその拠点が無い)/ location_not_derived(外部倉庫の拠点ではない)も返ります。共通エラー(401 / 400 / 403 / 405 / 500 / 503)は Error Codes を参照。
行単位エラー(HTTP 200)
解決できない行があってもリクエスト全体はエラーになりません。HTTP 200・ok: true のまま該当行だけ在庫に反映されず、レスポンスの配列に理由付きで返ります(silent skip はしません)。「200 が返るのに在庫が変わらない」場合は、まず unresolved[] / stale_skipped[] を確認してください。接続テスト時はこれらが空であることの確認を推奨します。
配列
reason
意味
unresolved[]
missing_barcode
行に barcode が無い
unresolved[]
missing_on_hand_qty
snapshot: 行に on_hand_qty が無い
unresolved[]
missing_available_qty / missing_available_delta
available: mode に対応する値(absolute → available_qty / delta → available_delta)が行に無い
unresolved[]
invalid_as_of
行の as_of が日時として解釈できない
unresolved[]
invalid_channel_available_qty
channel-available: channel_available_qty が整数でない
データモデル
sot.inventory_levels— 現在庫マスター(shop + product_id + location_idで一意)。channel-available の値はchannel_available_qty/channel_available_as_ofsot.inventory_daily_snapshots— 日次履歴(+ snapshot_date)sot.inventory_update_logs— 変更監査ログ(change_type:snapshot/available_set/available_delta/committed_set/on_hand_set/channel_available_set)read は
agent_read.inventory_levels/inventory_daily_snapshots/inventory_update_logs(safe view)経由
Last updated
Was this helpful?