楽天Web Service 2026年インフラ刷新を乗り切った実装記録
楽天Web Service 2026年インフラ刷新(app.rakuten.co.jp停止)への移行手順を実装記録として残します。エンドポイント変更・accessKey追加・undici.request()切り替え・GitHub Actions設定の4点を解説します。

2026年5月14日に楽天 Web Service の旧ドメイン(app.rakuten.co.jp)が停止する予定です。このブログでは商品カードのデータを静的ビルド時に楽天 API から取得しているため、対応しなければ daily-product-update.yml の cron ジョブが止まります。移行前後に詰まった 403 エラーの記録を残します。
移行に必要な変更は2点でした。エンドポイントの差し替えと、accessKey の追加です。ただし Node 22.x 環境で fetch() を使っていた場合は undici.request() への切り替えも判断することになりました。GitHub Actions で動かしている場合は Secrets への追加も必要になります。
この記事で扱う課題は次の3つです。
- 旧エンドポイント(
app.rakuten.co.jp)から新エンドポイント(openapi.rakuten.co.jp/ichibams/api)への移行手順 fetch()で Referer が期待通りに届かなかったときの原因と対処法- GitHub Actions の cron ジョブを止めないためのキャッシュ戦略の変更
まず3行でまとめる — 変更点は2つだが、1つ見落としがちだった
楽天 Web Service の新エンドポイントは openapi.rakuten.co.jp/ichibams/api になりました。旧 app.rakuten.co.jp は2026年5月14日に完全停止する予定です。
変更点は2つあります。ベース URL の差し替えと、accessKey の追加です。旧 API は applicationId だけで認証できましたが、新 API では accessKey も必須になりました。(参考: 楽天 Ichiba Item Search API 公式ドキュメント)
| 状況 | まずやること | | ----------------------------- | -------------------------------------------------------------------------------------- | | 移行が未完了 | ベース URL を openapi.rakuten.co.jp/ichibams/api に変更 + accessKey をヘッダで付与 | | 403 が返る | applicationId / accessKey / scope / 許可Webサイト / API バージョンを順番に確認 | | GitHub Actions で動かしている | Secrets に RAKUTEN_ACCESS_KEY を追加 + env: に明示 |
移行期間は2026年2月10日〜5月13日で設けられており、5月14日以降は旧ドメインへのリクエストが通らなくなります。(参考: 楽天 Web Service インフラ刷新告知)
なぜ fetch() で Referer が届かなかったのか
403 の原因を掘り下げていくと、accessKey 未設定の他に「Referer が届いていない」疑惑が浮上しました。この点について整理します。
WHATWG Fetch Standard の forbidden request-header
まず仕様の話から入ります。WHATWG Fetch Standard では Origin と Referer を forbidden request-header として定義しています。ブラウザ環境ではこれらをスクリプトから手動で設定できず、ユーザーエージェントの管理下に置かれます。(参考: WHATWG Fetch Standard)
undici は制限を解除している
undici(Node.js が採用している HTTP クライアント)は README の Specification Compliance セクションで、ブラウザ環境の forbidden header 制約を外し、ユーザーが制御できるようにしていると明記しています。(参考: nodejs/undici — GitHub)
つまり undici.request() では Referer や Origin を手動で送信できます。
このプロジェクトでの実装判断
ただし、Node.js 組み込みの fetch()(globalThis.fetch)と undici.request() は別物です。Node.js の fetch() は undici を内部で使いながらも、別のレイヤーで動作している部分があります。挙動は Node バージョンによって異なる可能性もあります。
このプロジェクトでは Node 22.x 環境での切り分けを踏まえ、undici.request() を採用しました。fetch() への依存をやめ、ヘッダをより細かく制御できる undici.request() に切り替えることで問題を解消しました。

コンテキスト設計教本:AIエージェントの「忘却」を防ぐリポジトリ・アーキテクチャ
AIエージェントはなぜ「忘れる」のか。コンテキストの3層構造、CLAUDE.md・AGENTS.mdの指示ファイル設計、ADRによる再提案防止、Docs-as-Code+CI/CDでの鮮度管理まで、4つの実践手法を体系的に解説します。
403 の原因はヘッダだけではない
実際に 403 が返るケースは複数あります。ヘッダの問題に飛びつく前に次の順番で確認してください。
applicationIdが正しいかaccessKeyが設定されているか(新 API からの必須要件)- 許可Webサイトに対象ドメインが登録されているか
- API バージョンが正しいか
- 楽天デベロッパーコンソールの利用規約への同意が完了しているか(筆者の切り分けポイント。公式の 403 原因一覧としては未確認)
コードを書く前に curl で疎通確認する
実装に入る前に、まず curl で認証情報と接続が正しく機能するか確認しておくと、原因の切り分けが楽になります。以下は現行バージョン(20260401)を使った確認用コマンドです。
curl -G "https://openapi.rakuten.co.jp/ichibams/api/IchibaItem/Search/20260401" \
-H "accessKey: ${RAKUTEN_ACCESS_KEY}" \
-H "Origin: https://your-site.example" \
-H "Referer: https://your-site.example" \
--data-urlencode "applicationId=${RAKUTEN_APPLICATION_ID}" \
--data-urlencode "format=json" \
--data-urlencode "keyword=テスト" \
--data-urlencode "hits=1"
Origin の値はご自身のサイトドメインに変更してください。楽天デベロッパーコンソールの「許可Webサイト」に登録したドメインと一致している必要があります。
${RAKUTEN_ACCESS_KEY} と ${RAKUTEN_APPLICATION_ID} はシェルの環境変数から読み込まれます。事前に export RAKUTEN_ACCESS_KEY=... で設定するか、コマンドの先頭に RAKUTEN_ACCESS_KEY=... RAKUTEN_APPLICATION_ID=... を付けて実行してください。
レスポンスに "Items" 配列が返れば接続成功です。403 が返る場合は「なぜ fetch() で Referer が届かなかったのか」のセクションにある原因リストを参照してください。
実際のコードで見る移行前後の diff
エンドポイントとヘッダ設定の変更
src/lib/rakuten-api.ts を例に、変更箇所を示します。
変更前(fetch 版):
// ❌ 旧実装: fetch() + 旧エンドポイント
const endpoint = 'https://app.rakuten.co.jp/services/api/IchibaItem/Search/20170706'
const params = new URLSearchParams({
applicationId: process.env.RAKUTEN_APPLICATION_ID || '',
keyword: keyword.trim(),
format: 'json',
hits: '1',
})
const url = `${endpoint}?${params.toString()}`
const response = await fetch(url)
const data = (await response.json()) as RakutenSearchResponse
変更後(undici 版):
// ✅ 新実装: undici.request() + 新エンドポイント
import { request as undiciRequest } from 'undici'
// 認証情報は環境変数から取得(コードへの直書き厳禁)
const applicationId = process.env.RAKUTEN_APPLICATION_ID
if (!applicationId) {
throw new Error('RAKUTEN_APPLICATION_ID is not configured')
}
const accessKey = process.env.RAKUTEN_ACCESS_KEY
if (!accessKey) {
throw new Error('RAKUTEN_ACCESS_KEY is not configured')
}
const endpoint = 'https://openapi.rakuten.co.jp/ichibams/api/IchibaItem/Search/20220601'
const params = new URLSearchParams({
applicationId: applicationId,
keyword: keyword.trim(),
format: 'json',
hits: '1',
// httpReferer はクエリパラメータにも追加(保守的な運用判断。公式必須要件ではない)
httpReferer: 'https://your-site.example',
})
const url = `${endpoint}?${params.toString()}`
const { statusCode, body } = await undiciRequest(url, {
method: 'GET',
headers: {
'User-Agent': 'your-app/1.0',
Origin: 'https://your-site.example',
Referer: 'https://your-site.example',
accessKey: accessKey,
},
})
if (statusCode < 200 || statusCode >= 300) {
const errorText = await body.text()
throw new Error(`Rakuten API error: ${statusCode} - ${errorText}`)
}
const data = (await body.json()) as RakutenSearchResponse
変更箇所は3か所になります。エンドポイント URL、accessKey ヘッダの追加、そして fetch() から undici.request() への切り替えです。
本実装では undici を直接依存として package.json に追加しました。未導入の場合は依存追加を検討してください。
API バージョンについて
現行実装では 20220601 を使用しています。楽天公式の現行バージョンは 20260401(2026年4月1日版)です。(参考: 楽天 Ichiba Item Search API 公式ドキュメント)
20220601 が2026年5月14日の廃止対象かという点も確認しました。廃止対象バージョンは 20170706 以前に限定されており、20220601 はそのリストに含まれていません。(参考: 楽天市場APIの旧バージョン廃止のお知らせ)公式の旧バージョン一覧にも引き続き掲載されており、廃止告知はありません(2026年5月10日確認)。(参考: Rakuten Ichiba Item Search API version:2022-06-01)
リスクを最小化したい場合は 20260401 への移行を検討してください。20220601 のまま継続することを妨げる公式告知は現時点では存在しません。
ここまでのポイント
- ベース URL を
openapi.rakuten.co.jp/ichibams/apiに変更します accessKeyをヘッダ(accessKey: <値>)で付与します- Node 22.x 環境では
fetch()からundici.request()に切り替える判断をしました undiciが未導入の場合は依存追加を検討します
GitHub Actions の cron を止めないための設定
このブログでは商品データの自動更新を daily-product-update.yml の cron ジョブで動かしています。CI/CD 側の設定変更も2点ありました。
Secrets の追加
まず RAKUTEN_ACCESS_KEY を GitHub Secrets に追加します。RAKUTEN_APPLICATION_ID は既存の Secrets にありますが、新 API から accessKey が必須になったため追加が必要です。
GitHub リポジトリの Settings → Secrets and variables → Actions → New repository secret から追加します。
Name: RAKUTEN_ACCESS_KEY
Value: (楽天デベロッパーコンソールで取得した accessKey)
ワークフローの env: にも追記します。
- name: Update product data
env:
RAKUTEN_APPLICATION_ID: ${{ secrets.RAKUTEN_APPLICATION_ID }}
RAKUTEN_ACCESS_KEY: ${{ secrets.RAKUTEN_ACCESS_KEY }} # ← 追加
RAKUTEN_AFFILIATE_ID: ${{ secrets.RAKUTEN_AFFILIATE_ID }}
Cloudflare Workers 環境変数にも同様に RAKUTEN_ACCESS_KEY を追加しておくと、将来的なランタイム実行時にも対応できます。
キャッシュ戦略の変更
以前の構成には「キャッシュを削除してから更新する」ステップがありました。これが問題になることがあります。API 障害中にキャッシュ削除が先に走ると、既存の商品データが消えた後に更新も失敗するという最悪の状況になります。
actions/cache/restore と actions/cache/save を分離する方式に変更し、last-known-good のデータを保護するようにしました。
- name: Restore product cache
uses: actions/cache/restore@v5
with:
path: src/data/product-cache.json
key: product-cache-${{ github.run_id }}-${{ github.run_attempt }}
restore-keys: |
product-cache-
- name: Update product data
env:
RAKUTEN_ACCESS_KEY: ${{ secrets.RAKUTEN_ACCESS_KEY }}
# ...
- name: Save product cache
if: always() && hashFiles('src/data/product-cache.json') != ''
uses: actions/cache/save@v5
with:
path: src/data/product-cache.json
key: product-cache-${{ github.run_id }}-${{ github.run_attempt }}
if: always() を付けることで、更新ステップが失敗してもキャッシュの保存を試みます。hashFiles() はパターンに一致するファイルが存在しない場合に空文字を返すため、ファイルがない場合の無駄な保存を防ぐことができます(ファイルが存在するが中身が空・壊れている場合は別途チェックが必要です)。
restore-keys: product-cache- の prefix 指定により、直近のキャッシュから復元するフォールバックも機能します。API が一時的に落ちているときでも、前回の商品データでビルドを継続できます。

GitHub公式MCPサーバをDocker MCP Toolkitで簡単セットアップ!初心者向け完全ガイド
対象読者:プログラミング初心者〜中級者、MCP初心者、バイブコーディング初心者 「GitHub上での作業、いつも同じことの繰り返しで疲れませんか?」 複数のリポジトリを管理していると、イシューの作成、プルリクエストのレビ [...]
移行完了チェックリスト
コード側
- [ ] ベース URL を
openapi.rakuten.co.jp/ichibams/apiに変更しました - [ ]
applicationIdをクエリに含め続けています - [ ]
accessKeyをヘッダ(accessKey: <値>)で付与しています - [ ]
undiciを依存に追加しundici.request()に切り替えました(または Node バージョン挙動を確認しました) - [ ] 旧ドメイン(
app.rakuten.co.jp)への参照が残っていないか grep しました
grep -r "app.rakuten.co.jp" src/
CI/CD 側
- [ ] GitHub Secrets に
RAKUTEN_ACCESS_KEYを登録しました - [ ]
daily-product-update.ymlのenv:にRAKUTEN_ACCESS_KEYを追記しました - [ ] キャッシュ戦略を
restore/save分離方式に変更しました
動作確認
- [ ] 403 が解消したことを CI ログまたはローカル実行で確認しました
- [ ] 商品データが正しく取得・更新されていることを確認しました
APIキーの管理(GitHub Secrets の運用・ローテーション・最小権限設計)については次の記事で詳しく解説しています。

OpenClawをMac miniに安全に導入する手順【2026年版】
CVE-2026-25253やClawHubマルウェア820件超の実態を踏まえ、loopback bind・SecretRef・最小権限構成でOpenClawをmacOSに安全導入する手順を実機検証データ付きで解説。導入前後チェックリスト付き。
関連記事

OpenClawをMac miniに安全に導入する手順【2026年版】
CVE-2026-25253やClawHubマルウェア820件超の実態を踏まえ、loopback bind・SecretRef・最小権限構成でOpenClawをmacOSに安全導入する手順を実機検証データ付きで解説。導入前後チェックリスト付き。

コンテキスト設計教本:AIエージェントの「忘却」を防ぐリポジトリ・アーキテクチャ
AIエージェントはなぜ「忘れる」のか。コンテキストの3層構造、CLAUDE.md・AGENTS.mdの指示ファイル設計、ADRによる再提案防止、Docs-as-Code+CI/CDでの鮮度管理まで、4つの実践手法を体系的に解説します。

GitHub公式MCPサーバをDocker MCP Toolkitで簡単セットアップ!初心者向け完全ガイド
対象読者:プログラミング初心者〜中級者、MCP初心者、バイブコーディング初心者 「GitHub上での作業、いつも同じことの繰り返しで疲れませんか?」 複数のリポジトリを管理していると、イシューの作成、プルリクエストのレビ [...]