Promo codes

Generates a batch of codes, each redeemable for one subscription. Use this when you do not know who the end user will be — codes to sell, bundle, or give away.

POST /v1/billing/partner/promo-batches API key

Request

Body
{
  "planSlug": "standoff_radar_high_30days",
  "count": 50,
  "idempotencyKey": "0c7d5e91-4a2b-4c6d-8e0f-1a2b3c4d5e6f"
}
Body fields
planSlug string required

Which product the codes unlock. Use a productSlug from your balances response.

count integer required

How many codes to generate, up to 1000 per request. Each one costs a unit of quota.

idempotencyKey string required

8 to 128 characters, chosen by you. Reuse it when retrying this same batch.

How many times a code can be redeemed and whether it expires are set by us, not per request, so there are no options to pass beyond the three above.

Response

200 OK
{
  "orderPublicId": "3f2a91c4-7b5e-4d18-9c60-1a2b3c4d5e6f",
  "batchId": "7e8f90a1-b2c3-4d5e-8f90-a1b2c3d4e5f6",
  "planSlug": "standoff_radar_high_30days",
  "codes": [
    { "code": "SM-4K7P-2QX9" },
    { "code": "SM-8B3N-5RT1" }
  ],
  "remainingBalance": 362,
  "createdUtc": 1754380800
}
Response fields
orderPublicId string (uuid)

Identifies this batch purchase.

batchId string (uuid)

Groups the codes together.

codes array

Objects with a single code property. This is the only place the plaintext codes appear.

remainingBalance integer

Your quota for this product after the charge.

createdUtc integer

Unix epoch seconds, UTC.

Store the codes immediately. We keep only hashes. The plaintext codes are recoverable for about 72 hours by retrying with the same idempotency key, and after that they are gone for good — while your quota stays spent. Write them to your database before you acknowledge the response.

Quota is charged up front

A batch of 50 costs 50 units the moment it is generated, whether or not anyone ever redeems a code. Unredeemed codes are not refunded.

Because the whole batch is charged as one, a balance of 30 will not partially fill a request for 50 — it is refused outright with Reseller.InsufficientQuota and nothing is consumed.

If generation fails

Generating a batch touches more than one system, so it has a failure mode the direct issue does not. If the codes cannot be produced you get:

Reseller.PromoGenerationFailed 500

No quota was consumed — the charge is reversed automatically. Safe to retry, and you may reuse the same idempotency key.

Do not treat this 500 as unknown state. It is the one 5xx here that carries a definite outcome: nothing was charged and nothing was issued. Code that lumps all 5xx responses into “might have succeeded” will get this wrong and leave a sale stuck.

If a request times out instead, retry with the same key. You will get back either the original codes, a Reseller.RequestInProgress telling you to wait a moment longer, or the failure above.