試してみたブログ

AI関連・iPhone/Pixelなどのガジェット・音声入力・サーマルプリンタなど興味をある事をどんどん試してみた際の記録

Amazon CreatorsAPIが使える様になったので試す

試してみた

  • Amazon Product Advertising API(PA-API)がCreators APIとして置き換わった
  • amazonアソシエイトに登録し、3件の注文を獲得して承認を受ける
  • 30日で10件以上の発送済みがある場合のみAPIが200を返す

仕様まとめ

Amazon Creators API 最低限の疎通確認とハマりどころ

前提:使えるようになるまで

必要なもの

  1. Amazon アソシエイト承認済みアカウント(日本:affiliate.amazon.co.jp)
  2. 過去30日で 10 件以上の適格販売(発送済み) ← Creators API の利用資格条件
  3. https://affiliate.amazon.co.jp/creatorsapi で発行する Credential ID / Secret
  4. 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 scopecreatorsapi::default 以外にしている
unsupported_grant_type grant_typeclient_credentials 以外

Content-Typeapplication/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 を必ず含めること。

GIBOARDを買ってみた

GIBOARDセットwww.gibbon.co.jp

  • 体感を日常的に鍛えたい
  • バランス感覚を養いたい

使ってみた感想

  • 思ったよりも場所が必要
    • 日常的においておくのはちょっと難しそう
    • 段ボールに定期的に戻している
  • 子どもと一緒に時間計ってチャレンジできて良い
  • 今の所片足で2分ぐらいいけた
  • いろんなトリックがあるので、チャレンジしていきたい

サーマルプリンタをraspberrypiからmacminiに移行した

背景

  • raspberrypiでサーマルプリンタの運用していた
  • 詰まり(python実行後に途中で切れたり、反応しない)事が増えて来た
  • 3回に1回は起こるようになってきた
  • ClaudeCodeを直で立ち上げて改善したかった

試してみた

  • Mac miniに移行(接続+ポートのIDをスキャン)
  • ベタ書きだったのを環境変数に変更
  • 上記をClaudeCodeに基本やらせる
  • 詰まりもなく出力がめちゃくちゃ早くなった(実行後即出力)

iOS+WatchOSのアプリ作成にチャレンジしてみた際の大変だった点

背景

  • iOSとWatchOSを作ってみたかった
  • Apple Watchのアプリが少なくやりたいと思ってた事を実現したかった

大変だったポイント

  • GUI側での操作が必要な所が多い
  • 毎回iPhoneエミュレーターとWatchエミュレーターを立ち上げるのが大変
  • iPhone側にファイルを渡すのが大変
    • 結局ファイル配下にバインドさせて渡した
  • Watch側と連携がうまくいかない事がある
  • 実機テストの際に、Apple Watch側にアプリのインストールがうまくいかない・・・
  • 思った以上にサクッとは出来ないががんばってみる

「あとで読む」、「ブックマーク」の概念が少し変わった

LLM WikiからAIがお届けします

概要

「あとで読む」「ブックマーク」は長らくURLを保存するだけの行為だった。LLM Wikiを始めてから、この概念が大きく変わった。まずObsidianのClippingsに本文ごと取り込み、その後Wiki化して自分の知識ネットワークに統合することで、保存が単なる積読ではなく「自分事化のスタートライン」になった。

詳細

従来の「あとで読む」「ブックマーク」の限界

  • ブラウザのブックマークやPocket、はてなブックマークは URLとタイトルしか残らない
  • 「保存した安心感」で満足してしまい、結局読み返さない(ブックマークの墓場)
  • リンク切れ、サービス終了、有料化でアクセスできなくなるリスク
  • 横断検索や引用ができず、自分の知識として再利用しにくい
  • 「気になる」という感情の記録だけが残り、内容は記憶に残らない

新しいワークフロー: Clip → Wiki化

  1. Obsidian Web Clipper で本文ごと Clippings/ に保存(URL保存ではなく本文取り込み)
  2. すぐに読まなくてもローカルに残るので、リンク切れに強い
  3. Claude Codeのスキル /wiki-from-clipping で、Clippingを テーマ別のWiki記事 に統合する
  4. Wiki記事は本単位・記事単位ではなく 知識テーマ単位 で編成される。複数のClippingが同じWiki記事に溶け込む
  5. 自分の言葉で要約・整理されることで、読んだ内容が 自分事化 される

「保存」と「読了」の間に「Wiki化」を挟む

  • 従来モデル: 保存 → 読む(or 読まない)
  • 新モデル: 保存 → Wiki化 → 必要に応じて参照
  • Wiki化のプロセスで本文に一度向き合うため、「読んだのに何も残らない」が起きない
  • 元のClippingはソースとして残り続けるので、後から原典に戻れる

ブックマークから知識ネットワークへ

  • 保存物は「リンクの集積」ではなく 「テーマで束ねられたWiki記事群」 になる
  • ひとつのClippingが複数のWiki記事に貢献するため、情報の再利用率が上がる
  • バックリンクを辿ると、自分が何にどう興味を持っていたかを後から再発見できる
  • 「あとで読む」の山は、未参照のClippings という見える化された資産に変わる

取り込みの心理的ハードルが下がる

  • 「あとでちゃんと読まなきゃ」というプレッシャーが消える
  • とりあえずClipしておけば、AIが要約や統合を支援してくれる
  • 結果として 取り込みが軽くなり、インプット量が増える

課題

  • スクリーンショット・動画・有料コンテンツなど、Clipしにくいメディアの集約方法
  • 未参照Clippingの溜まりすぎを防ぐための定期整理
  • Wiki化のタイミング(その場でやるか、まとめて処理するか)の使い分け

読書メーターから過去読んだ本と感想を抜き出すものを作った

背景

  • LLM Wikiの為に過去の自分が読んだ本のコメントからも追記したくなった
  • 読書メーターにはエクスポートが存在しなかったので、作成した

試してみた

https://github.com/m1104m/bookmeter-export-extention

  • 必要に応じて公開します
  • tsv形式でタイトルとコメントのみを取得するので、移行には不向きかも
  • LLM Wikiに喰わせてだいぶ過去の自分を形成している物がWikiに入ってきた

Obsidianを使ったLLM Wikiが面白い

tameshitemita.blog

  • これがともかくおもしろい
  • 音声入力で書きためた事、Kindleでハイライトした内容、Obsidian Web Clipperで貯めた内容などを元に、自分の興味分野毎のWikiを作成してくれている

  • 自分の中で興味を持ったこと、はてブ登録した物から自分毎化されたWikiとなっている
  • 今後ここにデータを蓄積する為の導線を改めて考えていきたい