active のクーポンに対して、1–500 個のランダムなプロモーションコードを一度に生成します。同じバッチのコードは同じ制限と batchId を共有し、レスポンスでまとめて返されます。たとえば使い切りコードの CSV としてそのままエクスポートできます。
POST /v1/actions/promotion-code/generate-promotion-codes
必ず
X-Idempotency-Key を送信し、同じキーでリトライしてください。 コードはランダムに生成されるため、キーなしでリトライすると 2 つ目のバッチが生成されます。リクエストボディ
| フィールド | 型 | 必須 | 説明 |
|---|---|---|---|
couponId | string | はい | クーポン ID(Short ID 形式 CPN_xxx)。クーポンは active である必要があります |
count | integer | はい | 生成数、1–500 |
prefix | string | いいえ | コードの接頭辞。英字・数字・- の 1–12 文字で、大文字に変換されます |
length | integer | いいえ | ランダム部分の長さ、8–16。デフォルト 10。大文字の英字と数字で構成され、0、O、1、I、L は使用されません |
restrictions | object | いいえ | すべてのコードに適用。フィールドはプロモーションコードの作成と同じです。使い切りコードには maxRedemptions: 1 を設定してください |
prefix とランダム部分で構成されます(例:VIP-7KQ2MZ9H3X)。生成されたコードは、同じストア・同じ環境の有効なコードと重複しません。
リクエスト例
const { batchId, promotionCodes } = await client.promotionCodes.generate(
{
couponId: "CPN_5dXBtmF2HLlHfbPNm0Wcnz",
count: 100,
prefix: "vip-",
length: 10,
restrictions: { maxRedemptions: 1 },
},
{ idempotencyKey: "MER_2aUyqjCzEIiEcYMKj7TZtw-7c41d0b2-93ae-4f68-b5d1-6a2e9f03c8d4" },
);
console.log(promotionCodes.length, promotionCodes[0].code); // => 100 "VIP-7KQ2MZ9H3X"
MERCHANT_ID="MER_2aUyqjCzEIiEcYMKj7TZtw"
IDEMPOTENCY_KEY="MER_2aUyqjCzEIiEcYMKj7TZtw-7c41d0b2-93ae-4f68-b5d1-6a2e9f03c8d4"
TIMESTAMP=$(date +%s)
BODY='{"couponId":"CPN_5dXBtmF2HLlHfbPNm0Wcnz","count":100,"prefix":"vip-","length":10,"restrictions":{"maxRedemptions":1}}'
BODY_HASH=$(echo -n "$BODY" | openssl dgst -sha256 -binary | base64 | tr -d '\n')
CANONICAL_REQUEST="POST
/v1/actions/promotion-code/generate-promotion-codes
$TIMESTAMP
$BODY_HASH"
SIGNATURE=$(echo -n "$CANONICAL_REQUEST" | openssl dgst -sha256 -sign private_key.pem | base64 | tr -d '\n')
curl -X POST "https://api.waffo.ai/v1/actions/promotion-code/generate-promotion-codes" \
-H "Content-Type: application/json" \
-H "X-Merchant-Id: $MERCHANT_ID" \
-H "X-Timestamp: $TIMESTAMP" \
-H "X-Signature: $SIGNATURE" \
-H "X-Idempotency-Key: $IDEMPOTENCY_KEY" \
-d "$BODY"
import hashlib, time, base64, json, requests
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding
merchant_id = "MER_2aUyqjCzEIiEcYMKj7TZtw"
timestamp = str(int(time.time()))
body = {
"couponId": "CPN_5dXBtmF2HLlHfbPNm0Wcnz",
"count": 100,
"prefix": "vip-",
"length": 10,
"restrictions": {"maxRedemptions": 1},
}
body_json = json.dumps(body, separators=(",", ":"))
body_hash = base64.b64encode(hashlib.sha256(body_json.encode()).digest()).decode()
path = "/v1/actions/promotion-code/generate-promotion-codes"
canonical = f"POST\n{path}\n{timestamp}\n{body_hash}"
with open("private_key.pem", "rb") as f:
private_key = serialization.load_pem_private_key(f.read(), password=None)
signature = base64.b64encode(
private_key.sign(canonical.encode(), padding.PKCS1v15(), hashes.SHA256())
).decode()
resp = requests.post(
f"https://api.waffo.ai{path}",
headers={
"Content-Type": "application/json",
"X-Merchant-Id": merchant_id,
"X-Timestamp": timestamp,
"X-Signature": signature,
"X-Idempotency-Key": "MER_2aUyqjCzEIiEcYMKj7TZtw-7c41d0b2-93ae-4f68-b5d1-6a2e9f03c8d4",
},
data=body_json,
)
print(resp.json())
成功レスポンス (200)
{
"data": {
"batchId": "9b2f6c1e-3d4a-4f5b-8c7d-1e2f3a4b5c6d",
"promotionCodes": [
{
"id": "PMC_7fZDvoH4JNnJhdRPobYepB",
"couponId": "CPN_5dXBtmF2HLlHfbPNm0Wcnz",
"code": "VIP-7KQ2MZ9H3X",
"status": "active",
"restrictions": {
"customerEmails": [],
"perCustomerLimit": null,
"maxRedemptions": 1,
"expiresAt": "2026-12-01T00:00:00.000Z",
"minimumAmount": null
},
"batchId": "9b2f6c1e-3d4a-4f5b-8c7d-1e2f3a4b5c6d",
"createdAt": "2026-11-03T08:00:00.000Z"
}
]
}
}
レスポンスフィールド
| フィールド | 型 | 説明 |
|---|---|---|
batchId | string | この呼び出しで生成されたすべてのコードが共有するバッチ ID。GraphQL promotionCodes をこの値で絞り込むと、バッチを再取得できます |
promotionCodes | array | 生成されたプロモーションコード。各要素のフィールドはプロモーションコードの作成のレスポンスと同じです |
エラー
リトライポリシー:4xx は一切リトライしない — リクエストを修正してから再送信。5xx は指数バックオフでリトライ(5s 開始、最大 3 回)。リトライ時は必ず同じ X-Idempotency-Key を使用。
| ステータス | errors[0].message | 意味 | 推奨処理 |
|---|---|---|---|
| 400 | Missing header: x-context-merchant-id | マーチャントコンテキスト header が転送されていない | SDK 設定を修正 |
| 400 | Missing or invalid header: x-context-environment | 環境 header が欠落、または test / prod ではない | X-Environment を test または prod に設定 |
| 400 | Invalid JSON body | ボディが正しい JSON ではない | ボディを修正して再送信 |
| 400 | Missing required field: couponId / Missing required field: count | 必須フィールド欠落 | フィールドを追加して再送信 |
| 400 | Expected format: CPN_xxx, got "X" | couponId が正しい Short ID ではない | CPN_ 接頭辞付きの Short ID を使用 |
| 400 | count must be an integer / count must be between 1 and 500 | count が範囲外 | 1–500 の整数を使用。それ以上は複数回に分けて生成 |
| 400 | prefix must be 1-12 characters of letters, digits or '-' | prefix に使用できない文字が含まれる、または長さが範囲外 | prefix を修正して再送信 |
| 400 | length must be an integer / length must be between 8 and 16 | length が範囲外 | 8–16 の整数を使用 |
| 400 | restrictions.maxRedemptions must not exceed the coupon's maxRedemptions (X) | 上限がクーポンの上限より大きい | maxRedemptions をクーポンの値以下にする |
| 400 | restrictions.expiresAt must not be later than the coupon's redeemBy (X) | 有効期限がクーポンの有効期限より後 | クーポンの redeemBy 以前の時刻を使用 |
| 400 | restrictions.expiresAt must be more than 60 seconds in the future | 指定した expiresAt が現在から 60 秒未満、またはすでに過ぎている | 現在から 60 秒より先の expiresAt を指定 |
| 400 | restrictions.customerEmails[N] must be a valid email address / restrictions.customerEmails must not exceed 100 entries | customerEmails の N 番目(0 始まり)が有効なメールアドレスではない、またはリストが 100 件を超える | リストを修正して再送信 |
| 400 | restrictions.firstTimeOnly is not supported / restrictions.dailyRedemptionLimit is not supported | restrictions に firstTimeOnly または dailyRedemptionLimit を指定した(null / false を含む任意の値)。これらのフィールドはサポートされていない | フィールドを削除 |
| 400 | restrictions.X must be an integer / restrictions.X must be at least 1 / restrictions.X must not exceed 2147483647 | perCustomerLimit または maxRedemptions が正の整数ではない、または大きすぎる。X は該当フィールド(例: restrictions.perCustomerLimit must be at least 1) | 1〜2147483647 の整数、または null を使用 |
| 400 | restrictions.expiresAt must be an ISO 8601 datetime | expiresAt の形式が不正 | タイムゾーンオフセット(例:Z)付きの ISO 8601 時刻を指定 |
| 400 | X: Invalid input: expected Y, received Z | フィールド X の JSON 型が正しくない。X はフィールドの完全なパス(例: length: Invalid input: expected number, received string、restrictions.maxRedemptions: Invalid input: expected number, received string) | 上記の型でフィールドを指定 |
| 400 | restrictions.minimumAmount: At least one currency is required | minimumAmount が空のオブジェクト | 通貨を指定するか null を送信 |
| 400 | restrictions.minimumAmount: Invalid currency code: "X". Must be 3 uppercase letters (e.g., "USD", "EUR", "JPY") | minimumAmount のキーが ISO 4217 コードに準拠していない | 3 文字の大文字を使用 |
| 400 | restrictions.minimumAmount: Invalid amount for X: "Y". Must be a positive number string (e.g., "5.00", "500") | minimumAmount の値が正の数値文字列ではない | 正の十進数文字列を使用 |
| 400 | restrictions.minimumAmount: Invalid amount for X: "Y". X allows at most N decimal places / restrictions.minimumAmount: Invalid amount for X: "Y". X does not allow decimal places | minimumAmount の金額が通貨の小数桁数を超えている(例:USD "0.001"、JPY "5.5")。四捨五入は行わない | 通貨の小数桁数に合わせる(USD 2 桁、JPY 0 桁、BHD 3 桁) |
| 400 | restrictions.minimumAmount: Invalid amount for X: "Y". Must be at least Z X | minimumAmount の金額が通貨の最小単位 1 つ分に満たない | 少なくとも通貨の最小単位を指定(例:USD "0.01") |
| 401 | Authentication required | 認証失敗 | API Key、タイムスタンプ、署名を確認 |
| 404 | Coupon not found | クーポンが存在しない、他のマーチャントに属する、または別の環境にある | couponId と API Key の環境を確認 |
| 409 | Coupon is not active (status: X) / Coupon is not active | クーポンが pending(作成処理中)、failed または inactive | 有効なクーポンを使用 |
| 409 | Coupon has expired (redeemBy: X); new promotion codes cannot be added | クーポンが期限切れ(redeemBy まで 60 秒未満も期限切れとみなす)。expiresAt の指定有無を問わない | 期限切れのクーポンにはプロモーションコードを追加できない。新しいクーポンを作成 |
| 409 | Generated codes kept colliding with existing codes; try a longer length or another prefix | ランダムなコードが既存の有効なコードと繰り返し重複した。コードは 1 つも作成されていない | length を長くするか prefix を変えてリトライ |
| 409 | Request is being processed | 同じ X-Idempotency-Key のリクエストがまだ処理中 | 数秒待ってから同じキーでリトライ |
| 500 | Internal server error | サーバ側の予期しない障害 | 指数バックオフでリトライ(5s 開始、最大 3 回)。冪等キーは同じものを使用 |