ClaudeでAmazon SP-APIのReportsを呼ぶ — 在庫・注文レポートから補充判断表へ
SP-APIのReports APIは、レポートの作成・状態確認・文書の取得という3回の呼び出しに分かれます。この非同期の流れをClaude Codeに書かせ、落とした在庫と注文のデータをExcelの補充判断表にする手順です。
Amazonで売る在庫の補充は、セラーセントラルの画面からレポートをダウンロードして、Excelに貼る作業になりがちです。SP-API(Selling Partner API)のReports APIを使えば、この手作業をスクリプトに置き換えられます。ただし呼び出しは1回で終わらず、作成、状態確認、文書の取得と3回に分かれます。
この非同期の流れをClaude Codeに任せるときは、どこで待ち、どこで止めるかを先に決めておくのが要点です。この記事では、在庫と注文のレポートを取得するスクリプトの書かせ方と、落としたデータをExcelの補充判断表にする手順を順に説明します。安全在庫や発注点そのものの計算は、安全在庫と発注点をExcelで計算する記事で扱っています。
Reports APIは3回の呼び出しで1本のレポートを返す
Reports APIは、レポートを同期的には返しません。注文を出して、できあがりを待ち、受け取る流れです。Amazonのモデル定義(Reports API 2021-06-30版)に沿うと、手順は次の3段になります。
レポート1本を取得するまでの流れ
- 1
createReportでレポートを依頼する
POST /reports/2021-06-30/reportsに、reportTypeとmarketplaceIdsを渡します。この2つが必須項目です。成功するとHTTP 202が返り、reportIdが手に入ります。 - 2
getReportで処理の終わりを待つ
reportIdを渡してGET /reports/2021-06-30/reports/{reportId}を繰り返し呼びます。processingStatusがDONE、CANCELLED、FATALのどれかになれば処理は終了です。DONEのときreportDocumentIdが入ります。 - 3
getReportDocumentで取得先を得る
GET /reports/2021-06-30/documents/{reportDocumentId}が、事前署名付きのurlを返します。圧縮されている場合はcompressionAlgorithmにGZIPが入ります。 - 4
urlからダウンロードする
署名付きURLはAmazonの認証なしで開けます。
GZIPが指定されていれば、展開してから読みます。
IN_QUEUE と IN_PROGRESS は「まだ終わっていない」状態です。IN_QUEUE は、別の IN_PROGRESS のレポートを待っている可能性があります。待ち時間の上限は決まっていないため、スクリプト側で打ち切りの条件を持たせます。
在庫と注文で選ぶレポートタイプ
reportType は文字列で指定します。在庫の補充判断に使いやすいのは、次の5つです。
| 目的 | reportType | 内容 |
|---|---|---|
| 出品中の価格と数量 | reportTypeGET_FLAT_FILE_OPEN_LISTINGS_DATA | 内容出品商品のSKUごとの価格と数量 |
| FBAの在庫(利用可能分) | reportTypeGET_AFN_INVENTORY_DATA | 内容FBA在庫。ほぼリアルタイムで更新 |
| FBAの在庫(詳細) | reportTypeGET_FBA_MYI_UNSUPPRESSED_INVENTORY_DATA | 内容アーカイブされていない在庫の状態・数量・容積 |
| 注文(注文日基準) | reportTypeGET_FLAT_FILE_ALL_ORDERS_DATA_BY_ORDER_DATE_GENERAL | 内容指定期間に入った注文 |
| 注文(更新日基準) | reportTypeGET_FLAT_FILE_ALL_ORDERS_DATA_BY_LAST_UPDATE_GENERAL | 内容指定期間に更新された注文 |
いずれもタブ区切りのフラットファイルです。補充判断の「売れる速さ」には注文日基準を、「いま手元にいくつあるか」にはFBA在庫のレポートを使うと、役割が分かれます。更新日基準は、キャンセルや返品で後から変わった注文も拾いたいときの選択肢です。
注文のレポートには dataStartTime と dataEndTime を渡せます。ただし、日付範囲を使わないレポートタイプもあります。在庫のスナップショットに範囲を渡しても意味はなく、指定しなくても現在の状態が返る想定で書くのが無難です。
Claude Codeに取得スクリプトを書かせる
非同期APIをClaudeに書かせるときの失敗は、だいたい「待つ処理」で起きます。待ちが無限ループになる、FATAL を成功扱いにする、URLを取っておいて期限切れで開けなくなる、といった形です。先に規約をCLAUDE.mdに置いておくと、書かせるたびに言い直さずに済みます。
## SP-API Reports 取得スクリプトの規約
- createReport → getReport → getReportDocument → ダウンロードの4段を、関数に分けて書く
- getReport の待機は最大回数を決め、超えたら例外にする(無限ループにしない)
- processingStatus が FATAL / CANCELLED のときは取得に進まず、状態を出力して止める
- getReportDocument で得た url はその場でダウンロードする(保存して後で使わない)
- compressionAlgorithm が GZIP のときだけ展開する
- アクセストークンは環境変数 SPAPI_ACCESS_TOKEN から読み、コードに直接書かない
- 本番のセラーアカウントには、読み取りのレポート取得以外のAPIを呼ばないこの規約を置いたうえで、依頼は次のように具体的にします。
FBA在庫(GET_AFN_INVENTORY_DATA)と、直近30日の注文
(GET_FLAT_FILE_ALL_ORDERS_DATA_BY_ORDER_DATE_GENERAL)を取得して、
data/ にTSVで保存するPythonスクリプトを書いてください。
実行前にreportTypeとmarketplaceIdsを表示して、私の確認を待ってください。最後の一文が効きます。マーケットプレイスIDを取り違えると、別の国のデータを取って判断することになるためです。書き上がったスクリプトの骨格は、例えば次のような形になります(Amazonのモデル定義に沿った形)。
import gzip, os, time
import requests
BASE = "https://sellingpartnerapi-na.amazon.com" # 地域に合わせて変える
HEADERS = {"x-amz-access-token": os.environ["SPAPI_ACCESS_TOKEN"]}
def create_report(report_type, marketplace_ids, start=None, end=None):
body = {"reportType": report_type, "marketplaceIds": marketplace_ids}
if start: body["dataStartTime"] = start
if end: body["dataEndTime"] = end
r = requests.post(f"{BASE}/reports/2021-06-30/reports", json=body, headers=HEADERS)
r.raise_for_status()
return r.json()["reportId"]
def wait_report(report_id, max_tries=40, interval=30):
for _ in range(max_tries):
r = requests.get(f"{BASE}/reports/2021-06-30/reports/{report_id}", headers=HEADERS)
r.raise_for_status()
rep = r.json()
if rep["processingStatus"] == "DONE":
return rep["reportDocumentId"]
if rep["processingStatus"] in ("FATAL", "CANCELLED"):
raise RuntimeError(f"report {report_id}: {rep['processingStatus']}")
time.sleep(interval)
raise TimeoutError(f"report {report_id} が終わらない")
def download(document_id):
r = requests.get(f"{BASE}/reports/2021-06-30/documents/{document_id}", headers=HEADERS)
r.raise_for_status()
doc = r.json()
data = requests.get(doc["url"]).content # 署名付きURLには認証ヘッダーを付けない
if doc.get("compressionAlgorithm") == "GZIP":
data = gzip.decompress(data)
return data認可の取り方(出品者からの承認とアクセストークンの取得)は、SP-APIの開発者ガイドに従います。上のコードは、取得済みのトークンを環境変数で受ける前提で書いています。認証ヘッダーの名前もガイドの記述と突き合わせてください。
落とし穴
FATALでも、文書が付いてくることがある
FATAL はレポートが致命的なエラーで打ち切られた状態ですが、reportDocumentId が付くことがあります。付いている場合、その文書にエラーの理由が書かれている可能性があります。Claudeに「FATALなら例外で止める」と書かせたうえで、止める前に文書の中身を表示させると、原因を追いやすくなります。
待ち方と回数の上限を数字で決める
スクリプトを定期実行にするなら、APIの呼び出し制限が設計を決めます。モデル定義にある既定の使用量は次のとおりです。
操作ごとの既定のレート(1秒あたりのリクエスト数)
createReport
0.0167
約1分に1件
getReport
2
状態確認は余裕がある
getReportDocument
0.0167
約1分に1件
依頼と文書取得は約1分に1件しか呼べないので、在庫と注文の2本を取るなら、依頼を続けて出して、状態確認をまとめて回す形が自然です。待ちのたびに依頼し直すと、制限に当たります。応答ヘッダーの x-amzn-RateLimit-Limit には、実際に適用されたレートが入ります。売上の大きいセラーでは、既定より高い値が返ることもあります。
もう1つの制約は保持期間です。getReports が返すのは、直近90日以内に作られたレポートだけで、createdSince を省くと90日前が起点になります。取得したファイルは、手元に残してください。
取得スクリプトで先に決めておく数字
待機の上限
wait_reportの最大回数と間隔を決めます。例では30秒を40回で、20分を超えたら例外です。署名付きURLの寿命
URLは5分で失効します。
getReportDocumentを呼んだら、その場でdownloadまで進めます。ページサイズ
getReportsのpageSizeは1〜100で、既定は10です。過去分を探すときはnextTokenを単独で渡して次ページに進みます。
nextToken には落とし穴があります。次のページを取るときは、nextToken だけを渡します。reportTypes などの絞り込みと一緒に渡すと、リクエストが失敗します。
毎朝の取得はスケジュールに任せる
補充判断を毎週回すなら、依頼を自分で出す代わりに、createReportSchedule でAmazon側に定期依頼させられます。period には PT5M から P1M までの18種類が選べ、毎日なら P1D、毎週なら P7D です。
注意点は、同じ reportType と marketplaceIds のスケジュールがすでにあると、それが取り消されて新しいものに置き換わることです。Claudeに「スケジュールを作り直して」と頼むと、前の設定が黙って消えます。作る前に getReportSchedules で既存を一覧させるよう、CLAUDE.mdに1行足しておくと安全です。
スケジュールで作られたレポートは、getReports に reportTypes を渡して拾います。スケジュールの周期と近い間隔で呼び、完了している頃合いを狙います。
Excelの補充判断表にする
落としたTSVを、補充判断の表にします。ここで列名の扱いに注意が要ります。レポートの列名と並びは、レポートタイプごとに違い、Amazonは新しい項目や値を追加することがあります。パーサーが列の追加に耐えるよう、位置ではなく列名で読むのが基本です。
Claudeには、まず列名を見せるところから頼みます。
data/ のTSVの1行目(列名)だけを表示してください。
そのあと、SKU・FBAの利用可能数・注文数量に当たる列を、
候補と理由つきで挙げてください。私が確認してから、
COLUMN_MAP に書き込みます。列名の対応は COLUMN_MAP に1か所だけ持たせます。レポートの形式が変わったとき、直す場所がここだけで済みます。
COLUMN_MAP = { # 実ファイルの1行目を見て、人が確定する
"sku": "<SKUの列名>",
"on_hand": "<FBA利用可能数の列名>",
"order_qty": "<注文数量の列名>",
"order_date": "<注文日の列名>",
}注文のレポートは、1行が1注文明細です。SKUごとの直近30日の販売数に集計し、在庫のレポートとSKUで結合します。そこから、Excelの表には次の列を置きます。
| 列 | 中身 | 式の例 |
|---|---|---|
| A | 中身SKU | 式の例在庫レポートから |
| B | 中身在庫数 | 式の例FBAの利用可能数 |
| C | 中身30日の販売数 | 式の例注文レポートをSKUで集計 |
| D | 中身日販 | 式の例=C2/30 |
| E | 中身在庫日数 | 式の例=IF(D2=0,"",B2/D2) |
| F | 中身リードタイム(日) | 式の例人が入力 |
| G | 中身安全日数 | 式の例人が入力 |
| H | 中身補充判断 | 式の例=IF(E2="","動き無し",IF(E2<F2+G2,"補充","様子見")) |
H列の考え方は単純です。在庫日数が、納品までの日数に余裕の日数を足した値を下回れば「補充」です。余裕の日数を需要のばらつきから決めたい場合は、前述の安全在庫の記事の計算に差し替えられます。売れ行きと在庫のバランスを売場別に見る方法は、交差比率と在庫回転率の記事にあります。
Excelの式をClaudeに書かせたあとは、検算を挟みます。1件のSKUについて、日販と在庫日数を手計算し、表の値と合わせてください。注文のレポートの期間が30日に満たないと、D列の日販が実際より低く出るためです。取得時の dataStartTime と、表の 30 が一致しているかを見ます。
落とし穴
販売数ゼロのSKUは「補充不要」ではない
C列が0のSKUは、在庫日数が計算できません。上の式では「動き無し」と表示して、補充の対象から外しています。ただし、欠品して売れなかったSKUも同じ0になります。B列も0なら、需要がないのではなく、売る在庫がなかった可能性があります。この組み合わせは、別に色を付けて人が見るようにします。長く動かない在庫の評価は、滞留在庫の評価損を仕分ける記事が扱っています。
3PLの在庫は別の経路で取る
FBAではなく、物流会社(3PL)に在庫を置いている場合は、Reports APIの在庫レポートには載りません。その場合の在庫確認は、物流側のAPIやコネクタを経由します。たとえばShipBobなら、ClaudeとShipBobを連携する記事に、在庫と注文の確認でできる範囲をまとめています。APIから表にするという型は同じで、取得元だけが変わります。
運用に入る前の確認
最後に、本番で回す前に確かめる点を挙げます。
- 依頼する
reportTypeが、現行のReport Type Valuesに載っている marketplaceIdsが、判断したい国と一致している- スクリプトが
FATALとCANCELLEDで止まる - 列名の対応(
COLUMN_MAP)を、実ファイルの1行目で確定した - 取得したTSVを、日付つきで残している(Amazon側の保持は90日)
- Excelの1件を手計算して、表と一致した
Reports APIは、1回の呼び出しで答えが返ってこない分、Claudeに任せる範囲が広がりやすいAPIです。待ちの打ち切り、状態の分岐、期限の短いURLという3点をCLAUDE.mdで固定しておけば、スクリプトの書き直しを頼むたびに品質がぶれることは減ります。