試してみた
- Amazon Product Advertising API(PA-API)がCreators APIとして置き換わった
- amazonアソシエイトに登録し、3件の注文を獲得して承認を受ける
- 30日で10件以上の発送済みがある場合のみAPIが200を返す
仕様まとめ
Amazon Creators API 最低限の疎通確認とハマりどころ
前提:使えるようになるまで
必要なもの
- Amazon アソシエイト承認済みアカウント(日本:affiliate.amazon.co.jp)
- 過去30日で 10 件以上の適格販売(発送済み) ← Creators API の利用資格条件
- https://affiliate.amazon.co.jp/creatorsapi で発行する Credential ID / Secret
- Partner Tag
注意点
- アソシエイトアカウントが承認済みでも、Creators API の利用には 別途 過去30日10件のハードルがある
- Amazonデバイス / Amazon Music / Prime Video / ギフトカードは「適格販売」にカウントされない
- 売上が落ちて30日 0件状態になると 一時停止。発送が再開されると 約2日で自動復元
最低限の動作確認コード
.env を用意:
CREATORS_CLIENT_ID=amzn1.application-oa2-client.xxxxxxxx CREATORS_CLIENT_SECRET=amzn1.oa2-cs.v1.xxxxxxxx CREATORS_PARTNER_TAG=xxxxxx-22 CREATORS_MARKETPLACE=www.amazon.co.jp
疎通テストスクリプト:
import json, os, requests from dotenv import load_dotenv load_dotenv() TOKEN_URL = "https://api.amazon.co.jp/auth/o2/token" GET_ITEMS_URL = "https://creatorsapi.amazon/catalog/v1/getItems" # 1. LWA でアクセストークン取得(client_credentials) r = requests.post(TOKEN_URL, headers={"Content-Type": "application/json"}, json={ "grant_type": "client_credentials", "client_id": os.environ["CREATORS_CLIENT_ID"], "client_secret": os.environ["CREATORS_CLIENT_SECRET"], "scope": "creatorsapi::default", }, timeout=30) r.raise_for_status() token = r.json()["access_token"] print(f"token ok: {token[:24]}...") # 2. getItems で1件取得 r = requests.post(GET_ITEMS_URL, headers={ "Authorization": f"Bearer {token}", "Content-Type": "application/json", "x-marketplace": "www.amazon.co.jp", }, json={ "partnerTag": os.environ["CREATORS_PARTNER_TAG"], "itemIds": ["B0GGY53D74"], # 任意のASIN "resources": [ "images.primary.large", "itemInfo.title", "itemInfo.byLineInfo", "offersV2.listings.price", ], }, timeout=30) print(r.status_code) print(json.dumps(r.json(), ensure_ascii=False, indent=2))
成功時のレスポンス(getItems)
{ "itemsResult": { "items": [{ "asin": "B0GGY53D74", "detailPageURL": "https://www.amazon.co.jp/dp/B0GGY53D74?tag=...", "images": {"primary": {"large": {"url": "...", "width": 396, "height": 500}}}, "itemInfo": { "title": {"displayValue": "Animage (アニメージュ) 2026年 05月号 [雑誌]"}, "byLineInfo": { "brand": {"displayValue": "徳間書店"}, "contributors": [{"name": "Animage編集部", "roleType": "author"}] } }, "offersV2": {"listings": [{"price": {"money": {"amount": 856, "currency": "JPY"}}}]} }] } }
これが返ってくれば疎通 OK。
使えないときの典型エラー
1. AssociateNotEligible(HTTP 403)
{ "message": "Your account does not currently meet the eligibility requirements.", "reason": "AssociateNotEligible", "type": "AccessDeniedException" }
原因: 過去30日で適格販売が10件未満。 対処: - アソシエイトのレポートで「過去30日・確定済み・発送済み」件数を確認 - 適格でない商品(Amazonデバイス、Prime Video、ギフトカード等)が混じってないか確認 - partner_tag が複数ある場合、別タグに売上が立っていないか確認 - 復元には 発送完了から約2日のラグ あり
補足:トークン取得(
/auth/o2/token)は資格に関係なく通る。AssociateNotEligibleが出るのは getItems / searchItems 側。
2. ValidationException — resources のフィールド名間違い(HTTP 400)
{ "message": "...failed to satisfy constraint: Member must satisfy enum value set: [searchRefinements, itemInfo.externalIds, ...]", "reason": "FieldValidationFailed", "type": "ValidationException" }
よくある誤り:
- offersV2.listings.programEligibility ← 存在しない(KU 判定のために試したくなるが NG)
- 任意の typo
searchItems で使える resources(実機検証で判明):
searchRefinements parentASIN itemInfo.title itemInfo.byLineInfo itemInfo.externalIds itemInfo.classifications itemInfo.manufactureInfo itemInfo.productInfo itemInfo.contentInfo itemInfo.contentRating itemInfo.features itemInfo.technicalInfo itemInfo.tradeInInfo images.primary.small / medium / large / highRes images.variants.small / medium / large / highRes browseNodeInfo.browseNodes browseNodeInfo.browseNodes.ancestor browseNodeInfo.browseNodes.salesRank browseNodeInfo.websiteSalesRank offersV2.listings.price offersV2.listings.condition offersV2.listings.availability offersV2.listings.dealDetails offersV2.listings.isBuyBoxWinner offersV2.listings.loyaltyPoints offersV2.listings.merchantInfo offersV2.listings.type customerReviews.starRating customerReviews.count
3. ValidationException — SearchIndex の値が無効(HTTP 400)
{ "fieldList": [{ "name": "InvalidParameterValue", "message": "The value Magazines provided in the request for SearchIndex is invalid." }], "type": "ValidationException" }
原因: 日本のロケール(amazon.co.jp)では SearchIndex=Magazines は存在しない。
対処: 雑誌の検索でも SearchIndex=KindleStore(Kindle 版なら)or Books を使う。BrowseNodeId でカテゴリ絞り込みする。
4. ThrottleException(HTTP 429)
{ "message": "Request rate limit exceeded.", "type": "ThrottleException" }
原因: レート制限。Creators API は概ね 1 TPS 程度。
対処:
- 呼び出し間に 1.0〜1.5 秒のウェイトを入れる
- 429 を受けたら 3 秒スリープしてリトライ
- 大量バッチは getItems(10 ASIN/req)を使ってリクエスト数を圧縮
実装例:
import time, threading class CreatorsClient: def __init__(self, min_interval_sec=1.2): self._lock = threading.Lock() self._last_call_at = 0.0 self._min_interval = min_interval_sec def _throttle(self): with self._lock: elapsed = time.time() - self._last_call_at if elapsed < self._min_interval: time.sleep(self._min_interval - elapsed) self._last_call_at = time.time()
5. トークン取得自体が失敗する
| ステータス | 原因 |
|---|---|
invalid_client |
CLIENT_ID / SECRET の typo、改行混入 |
invalid_scope |
scope を creatorsapi::default 以外にしている |
unsupported_grant_type |
grant_type が client_credentials 以外 |
Content-Type は application/json で JSON ボディを送ること(application/x-www-form-urlencoded のサンプルもあるが、api.amazon.co.jp の v3.3 は JSON で動作確認済)。
チートシート
| 項目 | 値 |
|---|---|
| トークン URL | https://api.amazon.co.jp/auth/o2/token |
| getItems URL | https://creatorsapi.amazon/catalog/v1/getItems |
| searchItems URL | https://creatorsapi.amazon/catalog/v1/searchItems |
| scope | creatorsapi::default |
| token TTL | 3600 秒(1 時間) |
| marketplace ヘッダ | x-marketplace: www.amazon.co.jp |
| getItems 上限 | 1 リクエスト 10 ASIN |
| searchItems 上限 | 1 ページ 10 件、itemPage 1〜10 で最大 100 件 |
| レート制限 | 約 1 TPS |
| 利用資格 | 過去30日に 10 件以上の適格販売(発送済み) |
Kindle Unlimited 判定の Tips
- レスポンスに「KU 対象フラグ」は 存在しない
- 代わりに
browseNodeInfo.browseNodesの祖先を再帰的に辿り、id=3197885051(Kindle Unlimited:読み放題 ジャンル)が含まれるかでチェック
def is_kindle_unlimited(item): KU_NODE_ID = "3197885051" def has_ancestor(node, target): if node.get("id") == target: return True a = node.get("ancestor") return has_ancestor(a, target) if a else False for n in item.get("browseNodeInfo", {}).get("browseNodes", []): if has_ancestor(n, KU_NODE_ID): return True return False
resources に browseNodeInfo.browseNodes.ancestor を必ず含めること。
