> ## Documentation Index
> Fetch the complete documentation index at: https://docs.waffo.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# プロモーションコードの一括生成

> 1 回の呼び出しでクーポンに 1–500 個のランダムなプロモーションコードを生成する

`active` のクーポンに対して、1–500 個のランダムなプロモーションコードを一度に生成します。同じバッチのコードは同じ制限と `batchId` を共有し、レスポンスでまとめて返されます。たとえば使い切りコードの CSV としてそのままエクスポートできます。

```
POST /v1/actions/promotion-code/generate-promotion-codes
```

**認証：** API Key

> [!WARNING]
>
> **必ず `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 | いいえ | すべてのコードに適用。フィールドは[プロモーションコードの作成](https://docs.waffo.ai/ja/api-reference/endpoints/promotion-codes/create-promotion-code.md)と同じです。使い切りコードには `maxRedemptions: 1` を設定してください |

各コードは `prefix` とランダム部分で構成されます（例：`VIP-7KQ2MZ9H3X`）。生成されたコードは、同じストア・同じ環境の有効なコードと重複しません。

## リクエスト例

```typescript SDK theme={"system"}
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"
```

```bash cURL theme={"system"}
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"
```

```python Python theme={"system"}
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)

```json theme={"system"}
{
  "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 | 生成されたプロモーションコード。各要素のフィールドは[プロモーションコードの作成のレスポンス](https://docs.waffo.ai/ja/api-reference/endpoints/promotion-codes/create-promotion-code.md)と同じです |

## エラー

> **リトライポリシー**：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 回）。冪等キーは同じものを使用 |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.