タイトル: 署名認証の方法(PHPサンプル ItemSearch)
SEOタイトル: Amazon PA-API 5.0 の AWS V4 署名を PHP で生成(SDK 利用 / 自前 hash_hmac 両方)
| この記事の要点 |
|
PA-API 5.0 とは
Amazon Product Advertising API は商品検索・取得用 API。2020 年 3 月 9 日に旧 4.0(XML、ItemSearch オペレーション)の新規受付終了、その後完全停止しました。現行は PA-API 5.0(JSON、AWS V4 署名)です。本記事は 5.0 を前提に説明します。
必要なクレデンシャル
| 項目 | 取得元 | 例 |
|---|---|---|
| Access Key | Amazon アソシエイト管理画面 | AKIAxxxxxxxxxxxxxxxx |
| Secret Key | 同上(発行時のみ表示) | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx |
| Partner Tag | アソシエイト ID | myassoc-22 |
| Marketplace | 対象国 | 日本: www.amazon.co.jp |
| Host | API ホスト名 | 日本: webservices.amazon.co.jp |
| Region | AWS 風リージョン | 日本: us-west-2 (マーケット別に決まる) |
方法1: 公式 SDK を使う(推奨)
# Composer で導入
composer require amazon-paapi5/sdk-phpsetAccessKey('AKIAxxxxxxxxxxxxxxxx');
$config->setSecretKey('xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx');
$config->setHost('webservices.amazon.co.jp');
$config->setRegion('us-west-2');
$apiInstance = new DefaultApi(new GuzzleHttp\Client(), $config);
$req = new SearchItemsRequest();
$req->setPartnerTag('myassoc-22');
$req->setPartnerType(PartnerType::ASSOCIATES);
$req->setKeywords('ルアー');
$req->setSearchIndex('All');
$req->setItemCount(10);
$req->setResources([
SearchItemsResource::ITEM_INFOTITLE,
SearchItemsResource::OFFERSLISTINGSPRICE,
SearchItemsResource::IMAGESPRIMARYMEDIUM,
]);
try {
$resp = $apiInstance->searchItems($req);
foreach ($resp->getSearchResult()->getItems() as $item) {
echo $item->getASIN() . "\t"
. $item->getItemInfo()->getTitle()->getDisplayValue() . "\n";
}
} catch (ApiException $e) {
echo "Error: " . $e->getMessage() . "\n";
echo "Response: " . $e->getResponseBody() . "\n";
}
方法2: 自前で AWS V4 署名を生成
学習や軽量化のため自前実装する場合。AWS V4 署名の手順は次の 4 ステップです:
'ルアー',
'PartnerTag' => $partnerTag,
'PartnerType' => 'Associates',
'Marketplace' => 'www.amazon.co.jp',
'Resources' => [
'ItemInfo.Title',
'Offers.Listings.Price',
'Images.Primary.Medium',
],
]);
// ===== タイムスタンプ =====
$amzDate = gmdate('Ymd\THis\Z'); // 20260517T120000Z
$dateStamp = gmdate('Ymd'); // 20260517
// ===== Canonical Request =====
$canonicalUri = $path;
$canonicalQueryString = '';
$canonicalHeaders =
"content-encoding:amz-1.0\n" .
"content-type:application/json; charset=utf-8\n" .
"host:$host\n" .
"x-amz-date:$amzDate\n" .
"x-amz-target:$target\n";
$signedHeaders = 'content-encoding;content-type;host;x-amz-date;x-amz-target';
$payloadHash = hash('sha256', $payload);
$canonicalRequest =
"POST\n" .
"$canonicalUri\n" .
"$canonicalQueryString\n" .
"$canonicalHeaders\n" .
"$signedHeaders\n" .
"$payloadHash";
// ===== String to Sign =====
$algorithm = 'AWS4-HMAC-SHA256';
$credentialScope = "$dateStamp/$region/$service/aws4_request";
$stringToSign =
"$algorithm\n" .
"$amzDate\n" .
"$credentialScope\n" .
hash('sha256', $canonicalRequest);
// ===== Signing Key 派生 =====
$kDate = hash_hmac('sha256', $dateStamp, 'AWS4' . $secretKey, true);
$kRegion = hash_hmac('sha256', $region, $kDate, true);
$kService = hash_hmac('sha256', $service, $kRegion, true);
$kSigning = hash_hmac('sha256', 'aws4_request', $kService, true);
// ===== Signature =====
$signature = hash_hmac('sha256', $stringToSign, $kSigning);
// ===== Authorization ヘッダ =====
$authHeader =
"$algorithm Credential=$accessKey/$credentialScope, " .
"SignedHeaders=$signedHeaders, " .
"Signature=$signature";
// ===== HTTP リクエスト =====
$ch = curl_init("https://$host$path");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $payload,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: $authHeader",
"Content-Encoding: amz-1.0",
"Content-Type: application/json; charset=utf-8",
"Host: $host",
"X-Amz-Date: $amzDate",
"X-Amz-Target: $target",
],
]);
$body = curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
echo "HTTP $code\n";
$data = json_decode($body, true);
foreach (($data['SearchResult']['Items'] ?? []) as $it) {
echo $it['ASIN'] . "\t" . ($it['ItemInfo']['Title']['DisplayValue'] ?? '') . "\n";
}
署名計算の落とし穴
| ハマりどころ | 対処 |
|---|---|
| ヘッダ名は小文字でソート済が必須 | host, x-amz-date 等。アルファベット順 |
SignedHeaders と Canonical Headers の対象を一致 | セミコロン区切り、同じ並び |
| payload hash は正確な送信 bodyのハッシュ | json_encode の改行・空白に注意 |
| タイムスタンプは UTC | gmdate() 使用、ローカルタイムは NG |
5 分以上ずれると InvalidSignature | NTP で時刻同期 |
レスポンス例
{
"SearchResult": {
"Items": [
{
"ASIN": "B08XXXXX",
"DetailPageURL": "https://www.amazon.co.jp/dp/B08XXXXX?tag=myassoc-22",
"ItemInfo": {
"Title": { "DisplayValue": "...ルアー...", "Label": "Title", "Locale": "ja_JP" }
},
"Offers": {
"Listings": [
{ "Price": { "Amount": 1980, "Currency": "JPY", "DisplayAmount": "¥1,980" } }
]
}
}
]
}
}
FAQ
Q: 旧 ItemSearch (PA-API 4.0) サンプルはまだ使える?
A: 使えません。エンドポイントが廃止済。必ず 5.0 (/paapi5/searchitems) を使ってください。
Q: TooManyRequests エラー
A: 売上実績がないアカウントは 1 req/sec, 1 day 8640 req 制限。指数バックオフで再試行 + 売上連動の枠拡大。
Q: InvalidSignature が出る
A: Canonical Request の改行コード(LF 必須)、ヘッダの並びと小文字化、payload hash のずれを順に確認。