Coupons are discount campaigns redeemed at checkout. They come in two kinds:

  • generated — codes are minted in batches via POST /coupons/{id}/codes. Each code is typically unique per customer.
  • promo — a single shared code whose value equals the coupon's normalized name (e.g. BLACKFRIDAY). The code is auto-created at coupon-create time; POST /coupons/{id}/codes is rejected with 422 for these coupons.

To retrieve the code value for either kind, call GET /v1/coupons/{id}/codes — for promo, the list contains the single auto-minted row; for generated, the full minted batch.

To apply a coupon at checkout, send the code as coupon_code on POST /v1/orders (one-time purchase) or on POST /v1/subscriptions (recurring — the discount is applied to the first cycle and persists through later cycles per the coupon's duration). Renewal billing orders themselves are server-generated and do not accept a coupon_code; downstream cycles read the snapshot captured at the first redemption.

Discount Terms

A coupon is either percentage-off (percentage, 1–100) or amount-off (amount in cents) — exactly one of the two must be set.

  • max_discount_amount caps the discount a percentage coupon can apply per redemption (in cents). Rejected on amount-off coupons (the fixed amount already plays the role of a cap). Cap value is snapshotted at redemption time — subsequent edits to the live coupon do not retroactively change historical orders.
  • currency is part of the redemption snapshot for amount-off coupons. Percentage coupons are currency-agnostic; sending a non-USD currency for a percentage coupon is rejected.

Duration

Subscription semantics — how many billing cycles the discount applies for:

  • once — the discount applies to a single charge.
  • repeating — the discount applies for duration_in_cycles consecutive cycles (required when repeating).
  • forever — the discount applies to every cycle for the life of the subscription.

Immutability After First Redemption

Once a coupon has been redeemed at least once, certain fields become immutable — changing them would retroactively alter what existing redeemers were promised. PATCH /coupons/{id} rejects locked fields with the field_locked error code.

Locked on first redemption: percentage, amount, max_discount_amount, currency, duration, duration_in_cycles, first_time_customer_only, max_redemptions_per_code, product_scope, plan_scope, plan_ids, product_ids. Promo coupons additionally lock name (because the name is the code). starts_at locks once the activation moment has passed.

Always editable: name (generated coupons), description, active, expires_at, minimum_amount, max_redemptions, max_redemptions_per_customer. max_redemptions cannot be set below the current redemption count.

Validation (Hosted Checkout)

POST /coupons/validate previews whether a code is redeemable for a given plan/product/customer without consuming a redemption. The final redemption happens transactionally as part of order/subscription creation. Inputs (code, customer ID, cart amount) travel in the JSON body rather than the query string so they don't end up in proxy or access logs.

Permissions

Endpoint Required permission
POST /v1/coupons, PATCH /v1/coupons/{id}, DELETE /v1/coupons/{id}, POST /v1/coupons/{id}/archive, POST /v1/coupons/{id}/codes coupons:write
GET /v1/coupons, GET /v1/coupons/{id}, GET /v1/coupons/{id}/codes, POST /v1/coupons/validate coupons:read

Existing API keys do not automatically gain coupon access — rotate or update the key with the new permission to use these endpoints.

POST /coupons

Create a coupon

Creates a new coupon. Requires the coupons:write permission and an idempotency key.

Coupon Kinds

  • kind: generated (default) — no codes are minted at create time. Call POST /coupons/{id}/codes later, or pass a codes block on this request to mint a random batch inline.
  • kind: promo — a single shared code is auto-created using the trimmed/uppercased name as its value. POST /coupons/{id}/codes is rejected for promo coupons.

Discount Shape

Exactly one of percentage or amount must be provided. max_discount_amount is percentage-only and is rejected when paired with amount.

Idempotency

Requires an X-EPD-Idempotency-Key header. Replaying the same key within 24 hours returns the original response without creating a duplicate coupon.

Header parameters

NameTypeDescription
EPD-Version
string
API version override (format `YYYY-MM-DD`). If omitted, your account's pinned version or the latest version is used.
e.g. "2026-02-11"
X-EPD-Idempotency-Keyrequired
string (uuid)
UUID v4 idempotency key. Same key with same request returns cached response. Keys expire after 24 hours.
e.g. "550e8400-e29b-41d4-a716-446655440000"

Request body required

FieldTypeDescription
kind
enum
`generated` — code(s) are minted under the coupon (one or many). `promo` — one shared code derived from the trimmed/uppercased `name`. Immutable after create.
generatedpromo
namerequired
string
Display name (1–200 chars). For `kind: promo`, the trimmed/uppercased name **is** the redeemable code — normalization is trim+uppercase only (no internal whitespace stripping), so the input must already match `^[A-Z0-9-]{4,50}$` after upper-casing. `"BLACKFRIDAY2026"` ✓, `"blackfriday2026"` ✓, `"Black Friday 2026"` ✗ (space rejected). For `kind: generated`, `name` is a free-form label — the regex doesn't apply.
e.g. "BLACKFRIDAY2026"
description
string
Free-form merchant note. Whitespace-only inputs read back as `null`.
e.g. "15% off first order — use in welcome email."
percentage
number
Percent discount (0–100, up to 2 decimal places). Provide **exactly one** of `percentage` or `amount`.
e.g. 15
amount
integer
Amount discount in cents. Provide **exactly one** of `percentage` or `amount`.
e.g. 1000
currency
string
ISO-4217 alpha-3 code. Only meaningful for amount-off coupons. Case-insensitive on input; responses return it lowercased (e.g. `usd`). Non-`USD` values are rejected when paired with `percentage`.
duration
enum
`once` — single application (most common). `repeating` — applies for `duration_in_cycles` billing cycles on a subscription. `forever` — applies on every subscription renewal.
oncerepeatingforever
duration_in_cycles
integer
Required when `duration` is `repeating`; rejected otherwise.
minimum_amount
integer
Minimum eligible cart total in cents.
max_discount_amount
integer
Per-redemption discount cap in cents. **Percentage-only** — rejected when paired with `amount`. Snapshotted at redemption time.
first_time_customer_only
boolean
When `true`, only customers who have **never had a successful order** on this account can redeem. Validated at every redemption attempt.
max_redemptions
integer
Total redemption cap across the entire coupon (all codes, all customers combined). Omit for unlimited.
max_redemptions_per_code
integer
Cap on redemptions per individual minted code. Defaults to `1` for `kind: generated` (each code single-use); rejected for `kind: promo` (use `max_redemptions` for the shared code).
max_redemptions_per_customer
integer
Per-customer redemption cap. Defaults to `1` for `kind: promo` and `null` (no cap) for `kind: generated`.
starts_at
string (date-time)
ISO-8601 with offset. When the coupon becomes redeemable. Omit for active-immediately. Must be earlier than `expires_at` if both are set.
expires_at
string (date-time)
ISO-8601 with offset. When the coupon stops accepting new redemptions. Omit for no expiry.
product_scope
enum
Applicability for products. At least one of (`product_scope`, `plan_scope`) must be non-`none`. When omitted, the server infers from `product_ids` — new integrations should send both scopes explicitly.
noneallspecific
plan_scope
enum
Applicability for subscription plans. Same semantics as `product_scope`.
noneallspecific
plan_ids
array[string]
Required when `plan_scope` is `specific`. Must be empty otherwise.
product_ids
array[string]
Required when `product_scope` is `specific`. Must be empty otherwise.
codes
object
Optional inline batch-mint (random codes only). Mints `count` codes in the same call and returns them on `codes` in the response. Rejected for `kind: promo`. For caller-supplied literals, use `POST /v1/coupons/{coupon_id}/codes` instead.
countrequired
integer
Number of codes to mint inline.
prefix
string
Optional prefix prepended to each minted code. Trimmed and uppercased server-side. Must leave at least 4 random characters after the prefix.
e.g. "SUMMER"
length
integer
Total length of each minted code (prefix + random suffix). The random suffix uses an unambiguous alphabet.
expires_atnullable
string (date-time)
Optional per-batch expiry, independent of the parent coupon's `expires_at`.

Responses

201 The created coupon. When the request included a `codes` block, the response also carries a `codes` array with the minted batch.
FieldTypeDescription
codes
array[CouponCode]
Codes minted as part of this create call. For larger batches, paginate via `GET /v1/coupons/{coupon_id}/codes`.
400 Bad Request — schema validation failed (`validation_error`). Common triggers: - `percentage` and `amount` both provided, or neither provided - `max_discount_amount` paired with `amount` - `duration: repeating` without `duration_in_cycles` - `starts_at >= expires_at` - Both `product_scope` and `plan_scope` set to `none` - `product_scope: specific` with empty `product_ids` (or the equivalent for plans) - Inline `codes` block on `kind: promo` - Promo `name` outside `^[A-Z0-9-]{4,50}$`
FieldTypeDescription
errorrequired
object
typerequired
enum
The type of error.
invalid_request_errorauthentication_errorauthorization_errorrate_limit_erroridempotency_errorprocessing_errorwebhook_error
coderequired
string
A short string identifying the specific error.
e.g. "validation_error"
messagerequired
string
A human-readable message providing details about the error.
e.g. "Request validation failed"
paramnullable
string
The parameter that caused the error, if applicable.
e.g. "email"
request_id
string
Unique request identifier for debugging.
e.g. "req_a1b2c3d4e5f67890abcdef0123456789"
field_errors
array[object]
Detailed field-level errors for validation failures.
401 Unauthorized — Authentication failed.
FieldTypeDescription
errorrequired
object
typerequired
enum
The type of error.
invalid_request_errorauthentication_errorauthorization_errorrate_limit_erroridempotency_errorprocessing_errorwebhook_error
coderequired
string
A short string identifying the specific error.
e.g. "validation_error"
messagerequired
string
A human-readable message providing details about the error.
e.g. "Request validation failed"
paramnullable
string
The parameter that caused the error, if applicable.
e.g. "email"
request_id
string
Unique request identifier for debugging.
e.g. "req_a1b2c3d4e5f67890abcdef0123456789"
field_errors
array[object]
Detailed field-level errors for validation failures.
403 Forbidden — The API key doesn't have permission.
FieldTypeDescription
errorrequired
object
typerequired
enum
The type of error.
invalid_request_errorauthentication_errorauthorization_errorrate_limit_erroridempotency_errorprocessing_errorwebhook_error
coderequired
string
A short string identifying the specific error.
e.g. "validation_error"
messagerequired
string
A human-readable message providing details about the error.
e.g. "Request validation failed"
paramnullable
string
The parameter that caused the error, if applicable.
e.g. "email"
request_id
string
Unique request identifier for debugging.
e.g. "req_a1b2c3d4e5f67890abcdef0123456789"
field_errors
array[object]
Detailed field-level errors for validation failures.
409 Conflict — The request conflicts with existing data.
FieldTypeDescription
errorrequired
object
typerequired
enum
The type of error.
invalid_request_errorauthentication_errorauthorization_errorrate_limit_erroridempotency_errorprocessing_errorwebhook_error
coderequired
string
A short string identifying the specific error.
e.g. "validation_error"
messagerequired
string
A human-readable message providing details about the error.
e.g. "Request validation failed"
paramnullable
string
The parameter that caused the error, if applicable.
e.g. "email"
request_id
string
Unique request identifier for debugging.
e.g. "req_a1b2c3d4e5f67890abcdef0123456789"
field_errors
array[object]
Detailed field-level errors for validation failures.
429 Too Many Requests — Rate limit exceeded.
FieldTypeDescription
errorrequired
object
typerequired
enum
The type of error.
invalid_request_errorauthentication_errorauthorization_errorrate_limit_erroridempotency_errorprocessing_errorwebhook_error
coderequired
string
A short string identifying the specific error.
e.g. "validation_error"
messagerequired
string
A human-readable message providing details about the error.
e.g. "Request validation failed"
paramnullable
string
The parameter that caused the error, if applicable.
e.g. "email"
request_id
string
Unique request identifier for debugging.
e.g. "req_a1b2c3d4e5f67890abcdef0123456789"
field_errors
array[object]
Detailed field-level errors for validation failures.
GET /coupons

List coupons

Returns a paginated list of coupons. Archived coupons are hidden by default — use archived=true to return only archived, or archived=all to include both.

Query parameters

NameTypeDescription
limit
integer
Number of items to return per page.
Default: 10
starting_after
string
Cursor for forward pagination. Returns items created after this ID.
e.g. "550e8400-e29b-41d4-a716-446655440000"
ending_before
string
Cursor for backward pagination. Returns items created before this ID.
e.g. "550e8400-e29b-41d4-a716-446655440001"
active
boolean
Filter by the `active` flag. `true` returns currently-active coupons; `false` returns paused ones. Archived coupons are always paused (`active: false`) since archiving forces a pause — combine with `archived=all` to include them.
kind
enum
Filter by coupon kind.
generatedpromo
archived
enum
`true` returns only archived; `false` (default) hides archived; `all` returns both.
truefalseall
sort
string
Sort order. Format `field[direction]` where `direction` is `asc` or `desc`, e.g. `name[asc]` or `created_at[desc]`. Shorthand `-field` is also accepted for descending (`-created_at` ≡ `created_at[desc]`). Sortable fields: `created_at` (default), `updated_at`, `name`, `percentage`, `amount`.
e.g. "created_at[desc]"

Header parameters

NameTypeDescription
EPD-Version
string
API version override (format `YYYY-MM-DD`). If omitted, your account's pinned version or the latest version is used.
e.g. "2026-02-11"

Responses

200 A list of coupons.
FieldTypeDescription
data
array[Coupon]
url
any
e.g. "/v1/coupons"
401 Unauthorized — Authentication failed.
FieldTypeDescription
errorrequired
object
typerequired
enum
The type of error.
invalid_request_errorauthentication_errorauthorization_errorrate_limit_erroridempotency_errorprocessing_errorwebhook_error
coderequired
string
A short string identifying the specific error.
e.g. "validation_error"
messagerequired
string
A human-readable message providing details about the error.
e.g. "Request validation failed"
paramnullable
string
The parameter that caused the error, if applicable.
e.g. "email"
request_id
string
Unique request identifier for debugging.
e.g. "req_a1b2c3d4e5f67890abcdef0123456789"
field_errors
array[object]
Detailed field-level errors for validation failures.
403 Forbidden — The API key doesn't have permission.
FieldTypeDescription
errorrequired
object
typerequired
enum
The type of error.
invalid_request_errorauthentication_errorauthorization_errorrate_limit_erroridempotency_errorprocessing_errorwebhook_error
coderequired
string
A short string identifying the specific error.
e.g. "validation_error"
messagerequired
string
A human-readable message providing details about the error.
e.g. "Request validation failed"
paramnullable
string
The parameter that caused the error, if applicable.
e.g. "email"
request_id
string
Unique request identifier for debugging.
e.g. "req_a1b2c3d4e5f67890abcdef0123456789"
field_errors
array[object]
Detailed field-level errors for validation failures.
POST /coupons/validate

Validate a coupon code

Previews whether a code is redeemable in the supplied context. Does not consume a redemption — the final redemption happens transactionally inside POST /v1/orders or POST /v1/subscriptions when you pass coupon_code. Renewal billing orders themselves are server-generated and do not accept a coupon_code; downstream subscription cycles read the snapshot captured at the first redemption.

Response

Returns either { valid: true, ... } with discount terms (including max_discount_amount so callers can preview the discount), or { valid: false, reason, code } with a machine-readable ineligibility reason.

Computing the Preview Discount

When valid: true, compute the line-item discount client-side from the returned terms:

  • Amount-off: discount = min(amount, cart_total) (in cents).
  • Percentage-off: discount = floor(cart_total * percentage / 100), then clamp with max_discount_amount if non-null: discount = min(discount, max_discount_amount).

Example: a 15% coupon with max_discount_amount: 2500 against a $200 cart returns discount = min(floor(20000 * 15 / 100), 2500) = min(3000, 2500) = 2500 cents — i.e. $25 off, capped.

Server-side redemption inside POST /v1/orders uses the same math, so this preview matches what the customer is actually charged.

Always 200 for Ineligibility

Unknown codes return 200 with valid: false, reason: "code_not_found" rather than 404. Inactive, expired, exhausted, currency-mismatched, and scope-ineligible failures all flow through the same 200 envelope so callers can render a single error path. Non-200 responses are reserved for transport or auth failures (400, 401/403, 429).

Why a JSON Body

Inputs travel in the request body so customer IDs and cart amounts don't land in URL parameters that proxies and access logs retain.

Throttling

Subject to the standard per-merchant rate limit (429 too_many_requests with the usual Retry-After, RateLimit-* headers). Debounce in your checkout UI — call this on onBlur or after a 300 ms input pause, not on every keystroke.

Header parameters

NameTypeDescription
EPD-Version
string
API version override (format `YYYY-MM-DD`). If omitted, your account's pinned version or the latest version is used.
e.g. "2026-02-11"

Request body required

FieldTypeDescription
coderequired
string
Coupon code. Trimmed and uppercased server-side.
e.g. "BLKFRDY-A7K9MZQ2"
plan_id
string
Plan UUID (with or without the `plan_` prefix). Required to evaluate plan scope.
product_id
string
Product UUID (with or without the `prod_` prefix). Required to evaluate product scope.
customer_id
string
Customer UUID. Required to evaluate `first_time_customer_only` and per-customer caps.
amount
integer
Cart/plan amount in cents. Required when `currency` is sent.
currency
string
ISO-4217 alpha-3. Only valid alongside `amount`.

Responses

200 Validation result. Inspect `valid` to determine eligibility.
400 Bad Request — The request was invalid or cannot be served.
FieldTypeDescription
errorrequired
object
typerequired
enum
The type of error.
invalid_request_errorauthentication_errorauthorization_errorrate_limit_erroridempotency_errorprocessing_errorwebhook_error
coderequired
string
A short string identifying the specific error.
e.g. "validation_error"
messagerequired
string
A human-readable message providing details about the error.
e.g. "Request validation failed"
paramnullable
string
The parameter that caused the error, if applicable.
e.g. "email"
request_id
string
Unique request identifier for debugging.
e.g. "req_a1b2c3d4e5f67890abcdef0123456789"
field_errors
array[object]
Detailed field-level errors for validation failures.
401 Unauthorized — Authentication failed.
FieldTypeDescription
errorrequired
object
typerequired
enum
The type of error.
invalid_request_errorauthentication_errorauthorization_errorrate_limit_erroridempotency_errorprocessing_errorwebhook_error
coderequired
string
A short string identifying the specific error.
e.g. "validation_error"
messagerequired
string
A human-readable message providing details about the error.
e.g. "Request validation failed"
paramnullable
string
The parameter that caused the error, if applicable.
e.g. "email"
request_id
string
Unique request identifier for debugging.
e.g. "req_a1b2c3d4e5f67890abcdef0123456789"
field_errors
array[object]
Detailed field-level errors for validation failures.
403 Forbidden — The API key doesn't have permission.
FieldTypeDescription
errorrequired
object
typerequired
enum
The type of error.
invalid_request_errorauthentication_errorauthorization_errorrate_limit_erroridempotency_errorprocessing_errorwebhook_error
coderequired
string
A short string identifying the specific error.
e.g. "validation_error"
messagerequired
string
A human-readable message providing details about the error.
e.g. "Request validation failed"
paramnullable
string
The parameter that caused the error, if applicable.
e.g. "email"
request_id
string
Unique request identifier for debugging.
e.g. "req_a1b2c3d4e5f67890abcdef0123456789"
field_errors
array[object]
Detailed field-level errors for validation failures.
GET /coupons/{id}

Retrieve a coupon

Path parameters

NameTypeDescription
idrequired
string (uuid)
Coupon UUID.
e.g. "6ba7b820-9dad-11d1-80b4-00c04fd430c8"

Header parameters

NameTypeDescription
EPD-Version
string
API version override (format `YYYY-MM-DD`). If omitted, your account's pinned version or the latest version is used.
e.g. "2026-02-11"

Responses

200 The coupon.
FieldTypeDescription
idrequired
string (uuid)
e.g. "6ba7b820-9dad-11d1-80b4-00c04fd430c8"
namerequired
string
Display name (1–200 chars). For `kind: promo`, the trimmed-and-uppercased name **is** the redeemable code customers type at checkout. Normalization is `.trim().toUpperCase()` only — it does **not** strip internal whitespace or punctuation, so the input must already match `^[A-Z0-9-]{4,50}$` after upper-casing. `"blackfriday2026"` → `BLACKFRIDAY2026` ✓; `"Black Friday 2026"` → `BLACK FRIDAY 2026` ✗ (space rejected). Retrieve the canonical code via `GET /v1/coupons/{id}/codes` (which returns the single auto-minted code row for promos) if you don't want to normalize client-side. For `kind: generated`, `name` is a free-form merchant label — the regex doesn't apply.
e.g. "BLACKFRIDAY2026"
descriptionnullable
string
Free-form merchant note. `null` when unset.
e.g. "15% off first order — use in welcome email."
kindrequired
enum
`generated` — one or many codes minted under the coupon (each customer gets a unique code, e.g. for influencer campaigns or single-use rewards). `promo` — one shared code derived from the coupon name (e.g. `BLACKFRIDAY`). Set at create and immutable.
generatedpromo
e.g. "generated"
percentagerequired
number
Percent discount (0–100, up to 2 decimal places) for percentage coupons. `null` for amount-off.
e.g. 15
amountrequired
integer
Amount discount in cents for amount-off coupons. `null` for percentage.
e.g. null
currencyrequired
string
ISO-4217 alpha-3 code, **returned lowercase** (e.g. `usd`). Snapshotted at redemption time for amount-off coupons. Percentage-off coupons default to `usd`.
e.g. "usd"
durationrequired
enum
`once` — applies a single time (most common for one-off purchases). `repeating` — applies for `duration_in_cycles` billing cycles on a subscription. `forever` — applies on every renewal for the lifetime of the subscription. For order-only coupons, leave as `once`.
oncerepeatingforever
e.g. "once"
duration_in_cyclesrequired
integer
Required when `duration` is `repeating`; `null` otherwise.
e.g. null
minimum_amountrequired
integer
Minimum order/cart amount required for the coupon to apply, in cents.
e.g. 5000
max_discount_amountrequired
integer
Per-redemption discount cap in cents. **Percentage-only** — always `null` for amount-off coupons. Snapshotted at redemption time.
e.g. 2500
first_time_customer_onlyrequired
boolean
When `true`, the coupon only validates for customers who have **never had a successful order** on this merchant account. Snapshotted at redemption.
e.g. false
max_redemptionsrequired
integer
Total redemption cap across the entire coupon (all codes, all customers). `null` means unlimited. Editable after first redemption, but you cannot lower it below the current `total_redemptions`.
e.g. 1000
max_redemptions_per_coderequired
integer
Cap on redemptions per individual minted code. Not applicable to `kind: promo` (which has a single shared code — use `max_redemptions` instead). Defaults to `1` for `kind: generated`, meaning each minted code is single-use.
e.g. 1
max_redemptions_per_customerrequired
integer
Per-customer redemption cap. Defaults to `1` for `kind: promo` (one redemption per customer on the shared code) and `null` (no cap) for `kind: generated`. Counted against the customer ID on the redeeming order.
e.g. 1
starts_atrequired
string (date-time)
When the coupon becomes redeemable. `null` means active immediately on creation. Validation returns `coupon_not_yet_active` before this moment.
e.g. "2026-11-25T00:00:00.000Z"
expires_atrequired
string (date-time)
When the coupon stops accepting new redemptions. `null` means no expiry. Existing redemptions are unaffected.
e.g. "2026-12-01T23:59:59.000Z"
activerequired
boolean
Pause toggle. `false` blocks new redemptions without archiving — useful for temporary pauses (e.g. promo on hold). Returns `coupon_inactive` on `validate_coupon`. Distinct from `archived_at`, which is the permanent end-of-life state.
e.g. true
archived_atrequired
string (date-time)
Soft-archive timestamp. Non-null means the coupon is retired — new redemptions are rejected and it drops out of default `GET /coupons` listings. Existing redemption history is preserved. Restore via `POST /coupons/{id}/archive` with `{ "archived": false }` — note that unarchive only clears this field; the coupon stays `active: false` until you separately PATCH it.
e.g. null
product_scoperequired
enum
Applicability for products. `none` never applies; `all` applies to every product; `specific` applies only to `product_ids`. At least one of `product_scope` or `plan_scope` must be non-`none`.
noneallspecific
e.g. "all"
plan_scoperequired
enum
Applicability for subscription plans. Same semantics as `product_scope`.
noneallspecific
e.g. "specific"
plan_idsrequired
array[string]
Plans the coupon is restricted to. Non-empty only when `plan_scope` is `specific`.
e.g. []
product_idsrequired
array[string]
Products the coupon is restricted to. Non-empty only when `product_scope` is `specific`.
e.g. []
total_redemptionsrequired
integer
Successful redemptions across all codes on this coupon. Increments atomically when an order containing the coupon is charged. `max_redemptions` cannot be lowered below this value.
e.g. 42
last_mint_prefixrequired
string
Prefix used on the most recent random-mint batch. Surfaced so dashboards can prefill the next batch. `null` when no random batch has been minted.
e.g. "SUMMER-"
last_mint_lengthrequired
integer
Total code length used on the most recent random-mint batch. `null` when no random batch has been minted.
e.g. 12
created_atrequired
string (date-time)
e.g. "2026-11-01T10:30:00.000Z"
updated_atrequired
string (date-time)
e.g. "2026-11-25T00:00:01.000Z"
401 Unauthorized — Authentication failed.
FieldTypeDescription
errorrequired
object
typerequired
enum
The type of error.
invalid_request_errorauthentication_errorauthorization_errorrate_limit_erroridempotency_errorprocessing_errorwebhook_error
coderequired
string
A short string identifying the specific error.
e.g. "validation_error"
messagerequired
string
A human-readable message providing details about the error.
e.g. "Request validation failed"
paramnullable
string
The parameter that caused the error, if applicable.
e.g. "email"
request_id
string
Unique request identifier for debugging.
e.g. "req_a1b2c3d4e5f67890abcdef0123456789"
field_errors
array[object]
Detailed field-level errors for validation failures.
403 Forbidden — The API key doesn't have permission.
FieldTypeDescription
errorrequired
object
typerequired
enum
The type of error.
invalid_request_errorauthentication_errorauthorization_errorrate_limit_erroridempotency_errorprocessing_errorwebhook_error
coderequired
string
A short string identifying the specific error.
e.g. "validation_error"
messagerequired
string
A human-readable message providing details about the error.
e.g. "Request validation failed"
paramnullable
string
The parameter that caused the error, if applicable.
e.g. "email"
request_id
string
Unique request identifier for debugging.
e.g. "req_a1b2c3d4e5f67890abcdef0123456789"
field_errors
array[object]
Detailed field-level errors for validation failures.
404 Not Found — The requested resource doesn't exist.
FieldTypeDescription
errorrequired
object
typerequired
enum
The type of error.
invalid_request_errorauthentication_errorauthorization_errorrate_limit_erroridempotency_errorprocessing_errorwebhook_error
coderequired
string
A short string identifying the specific error.
e.g. "validation_error"
messagerequired
string
A human-readable message providing details about the error.
e.g. "Request validation failed"
paramnullable
string
The parameter that caused the error, if applicable.
e.g. "email"
request_id
string
Unique request identifier for debugging.
e.g. "req_a1b2c3d4e5f67890abcdef0123456789"
field_errors
array[object]
Detailed field-level errors for validation failures.
PATCH /coupons/{id}

Update a coupon

Partial update — only the fields you send change.

Immutability After First Redemption

Once a coupon has been redeemed at least once, discount terms (percentage, amount, currency, duration, duration_in_cycles, max_discount_amount), eligibility flags (first_time_customer_only, max_redemptions_per_code), and scope (product_scope, plan_scope, plan_ids, product_ids) lock. Editing a locked field returns 422 with code field_locked. name (on generated coupons), active, description, expires_at, minimum_amount, max_redemptions, and max_redemptions_per_customer remain editable. starts_at locks once the activation moment has passed.

Path parameters

NameTypeDescription
idrequired
string (uuid)
Coupon UUID.
e.g. "6ba7b820-9dad-11d1-80b4-00c04fd430c8"

Header parameters

NameTypeDescription
EPD-Version
string
API version override (format `YYYY-MM-DD`). If omitted, your account's pinned version or the latest version is used.
e.g. "2026-02-11"
X-EPD-Idempotency-Keyrequired
string (uuid)
UUID v4 idempotency key. Same key with same request returns cached response. Keys expire after 24 hours.
e.g. "550e8400-e29b-41d4-a716-446655440000"

Request body required

FieldTypeDescription
name
string
For `kind: promo`, this also rewrites the redeemable code (the normalized name *is* the code) — and the rename is rejected with `field_locked` after the first redemption. Editable on `generated` coupons at any time.
active
boolean
Pause (`false`) or resume (`true`) without archiving. Validation rejects redemptions while paused. Always editable.
descriptionnullable
string
Send `null` (or an empty string) to clear; omit to leave unchanged.
percentagenullable
number
Percentage off (1–100, two decimals). Send `null` together with a non-null `amount` to convert a percentage-off coupon into an amount-off coupon on a draft. Locked after first redemption.
amountnullable
integer
Amount discount in cents. Send `null` together with a non-null `percentage` to convert an amount-off coupon into a percentage-off coupon on a draft. Locked after first redemption.
currency
string
duration
enum
oncerepeatingforever
duration_in_cyclesnullable
integer
minimum_amountnullable
integer
Minimum eligible cart total in cents.
max_discount_amountnullable
integer
Per-redemption discount cap in cents. Send `null` to clear (allowed only before the first redemption).
first_time_customer_only
boolean
Locked after first redemption.
max_redemptionsnullable
integer
Total redemption cap across the entire coupon. Send `null` to lift the cap. Always editable, but cannot be lowered below the current `total_redemptions`.
max_redemptions_per_codenullable
integer
Per-code cap. Locked after first redemption.
max_redemptions_per_customernullable
integer
Per-customer cap. Send `null` to lift. Always editable.
starts_atnullable
string (date-time)
Reschedule the activation moment. Locked once the original `starts_at` has already passed.
expires_atnullable
string (date-time)
Extend or shorten the expiry window, or send `null` to remove it. Always editable.
product_scope
enum
Locked once the coupon has been redeemed. Send alongside `product_ids` when narrowing or widening on a draft.
noneallspecific
plan_scope
enum
Locked once the coupon has been redeemed. Send alongside `plan_ids` when narrowing or widening on a draft.
noneallspecific
plan_ids
array[string]
product_ids
array[string]

Responses

200 The updated coupon.
FieldTypeDescription
idrequired
string (uuid)
e.g. "6ba7b820-9dad-11d1-80b4-00c04fd430c8"
namerequired
string
Display name (1–200 chars). For `kind: promo`, the trimmed-and-uppercased name **is** the redeemable code customers type at checkout. Normalization is `.trim().toUpperCase()` only — it does **not** strip internal whitespace or punctuation, so the input must already match `^[A-Z0-9-]{4,50}$` after upper-casing. `"blackfriday2026"` → `BLACKFRIDAY2026` ✓; `"Black Friday 2026"` → `BLACK FRIDAY 2026` ✗ (space rejected). Retrieve the canonical code via `GET /v1/coupons/{id}/codes` (which returns the single auto-minted code row for promos) if you don't want to normalize client-side. For `kind: generated`, `name` is a free-form merchant label — the regex doesn't apply.
e.g. "BLACKFRIDAY2026"
descriptionnullable
string
Free-form merchant note. `null` when unset.
e.g. "15% off first order — use in welcome email."
kindrequired
enum
`generated` — one or many codes minted under the coupon (each customer gets a unique code, e.g. for influencer campaigns or single-use rewards). `promo` — one shared code derived from the coupon name (e.g. `BLACKFRIDAY`). Set at create and immutable.
generatedpromo
e.g. "generated"
percentagerequired
number
Percent discount (0–100, up to 2 decimal places) for percentage coupons. `null` for amount-off.
e.g. 15
amountrequired
integer
Amount discount in cents for amount-off coupons. `null` for percentage.
e.g. null
currencyrequired
string
ISO-4217 alpha-3 code, **returned lowercase** (e.g. `usd`). Snapshotted at redemption time for amount-off coupons. Percentage-off coupons default to `usd`.
e.g. "usd"
durationrequired
enum
`once` — applies a single time (most common for one-off purchases). `repeating` — applies for `duration_in_cycles` billing cycles on a subscription. `forever` — applies on every renewal for the lifetime of the subscription. For order-only coupons, leave as `once`.
oncerepeatingforever
e.g. "once"
duration_in_cyclesrequired
integer
Required when `duration` is `repeating`; `null` otherwise.
e.g. null
minimum_amountrequired
integer
Minimum order/cart amount required for the coupon to apply, in cents.
e.g. 5000
max_discount_amountrequired
integer
Per-redemption discount cap in cents. **Percentage-only** — always `null` for amount-off coupons. Snapshotted at redemption time.
e.g. 2500
first_time_customer_onlyrequired
boolean
When `true`, the coupon only validates for customers who have **never had a successful order** on this merchant account. Snapshotted at redemption.
e.g. false
max_redemptionsrequired
integer
Total redemption cap across the entire coupon (all codes, all customers). `null` means unlimited. Editable after first redemption, but you cannot lower it below the current `total_redemptions`.
e.g. 1000
max_redemptions_per_coderequired
integer
Cap on redemptions per individual minted code. Not applicable to `kind: promo` (which has a single shared code — use `max_redemptions` instead). Defaults to `1` for `kind: generated`, meaning each minted code is single-use.
e.g. 1
max_redemptions_per_customerrequired
integer
Per-customer redemption cap. Defaults to `1` for `kind: promo` (one redemption per customer on the shared code) and `null` (no cap) for `kind: generated`. Counted against the customer ID on the redeeming order.
e.g. 1
starts_atrequired
string (date-time)
When the coupon becomes redeemable. `null` means active immediately on creation. Validation returns `coupon_not_yet_active` before this moment.
e.g. "2026-11-25T00:00:00.000Z"
expires_atrequired
string (date-time)
When the coupon stops accepting new redemptions. `null` means no expiry. Existing redemptions are unaffected.
e.g. "2026-12-01T23:59:59.000Z"
activerequired
boolean
Pause toggle. `false` blocks new redemptions without archiving — useful for temporary pauses (e.g. promo on hold). Returns `coupon_inactive` on `validate_coupon`. Distinct from `archived_at`, which is the permanent end-of-life state.
e.g. true
archived_atrequired
string (date-time)
Soft-archive timestamp. Non-null means the coupon is retired — new redemptions are rejected and it drops out of default `GET /coupons` listings. Existing redemption history is preserved. Restore via `POST /coupons/{id}/archive` with `{ "archived": false }` — note that unarchive only clears this field; the coupon stays `active: false` until you separately PATCH it.
e.g. null
product_scoperequired
enum
Applicability for products. `none` never applies; `all` applies to every product; `specific` applies only to `product_ids`. At least one of `product_scope` or `plan_scope` must be non-`none`.
noneallspecific
e.g. "all"
plan_scoperequired
enum
Applicability for subscription plans. Same semantics as `product_scope`.
noneallspecific
e.g. "specific"
plan_idsrequired
array[string]
Plans the coupon is restricted to. Non-empty only when `plan_scope` is `specific`.
e.g. []
product_idsrequired
array[string]
Products the coupon is restricted to. Non-empty only when `product_scope` is `specific`.
e.g. []
total_redemptionsrequired
integer
Successful redemptions across all codes on this coupon. Increments atomically when an order containing the coupon is charged. `max_redemptions` cannot be lowered below this value.
e.g. 42
last_mint_prefixrequired
string
Prefix used on the most recent random-mint batch. Surfaced so dashboards can prefill the next batch. `null` when no random batch has been minted.
e.g. "SUMMER-"
last_mint_lengthrequired
integer
Total code length used on the most recent random-mint batch. `null` when no random batch has been minted.
e.g. 12
created_atrequired
string (date-time)
e.g. "2026-11-01T10:30:00.000Z"
updated_atrequired
string (date-time)
e.g. "2026-11-25T00:00:01.000Z"
400 Bad Request — schema validation failed (`validation_error`). Body-level invariants such as the `percentage`/`amount` XOR, both scopes set to `none`, scope/ID-list inconsistency, or `starts_at >= expires_at`.
FieldTypeDescription
errorrequired
object
typerequired
enum
The type of error.
invalid_request_errorauthentication_errorauthorization_errorrate_limit_erroridempotency_errorprocessing_errorwebhook_error
coderequired
string
A short string identifying the specific error.
e.g. "validation_error"
messagerequired
string
A human-readable message providing details about the error.
e.g. "Request validation failed"
paramnullable
string
The parameter that caused the error, if applicable.
e.g. "email"
request_id
string
Unique request identifier for debugging.
e.g. "req_a1b2c3d4e5f67890abcdef0123456789"
field_errors
array[object]
Detailed field-level errors for validation failures.
401 Unauthorized — Authentication failed.
FieldTypeDescription
errorrequired
object
typerequired
enum
The type of error.
invalid_request_errorauthentication_errorauthorization_errorrate_limit_erroridempotency_errorprocessing_errorwebhook_error
coderequired
string
A short string identifying the specific error.
e.g. "validation_error"
messagerequired
string
A human-readable message providing details about the error.
e.g. "Request validation failed"
paramnullable
string
The parameter that caused the error, if applicable.
e.g. "email"
request_id
string
Unique request identifier for debugging.
e.g. "req_a1b2c3d4e5f67890abcdef0123456789"
field_errors
array[object]
Detailed field-level errors for validation failures.
403 Forbidden — The API key doesn't have permission.
FieldTypeDescription
errorrequired
object
typerequired
enum
The type of error.
invalid_request_errorauthentication_errorauthorization_errorrate_limit_erroridempotency_errorprocessing_errorwebhook_error
coderequired
string
A short string identifying the specific error.
e.g. "validation_error"
messagerequired
string
A human-readable message providing details about the error.
e.g. "Request validation failed"
paramnullable
string
The parameter that caused the error, if applicable.
e.g. "email"
request_id
string
Unique request identifier for debugging.
e.g. "req_a1b2c3d4e5f67890abcdef0123456789"
field_errors
array[object]
Detailed field-level errors for validation failures.
404 Not Found — The requested resource doesn't exist.
FieldTypeDescription
errorrequired
object
typerequired
enum
The type of error.
invalid_request_errorauthentication_errorauthorization_errorrate_limit_erroridempotency_errorprocessing_errorwebhook_error
coderequired
string
A short string identifying the specific error.
e.g. "validation_error"
messagerequired
string
A human-readable message providing details about the error.
e.g. "Request validation failed"
paramnullable
string
The parameter that caused the error, if applicable.
e.g. "email"
request_id
string
Unique request identifier for debugging.
e.g. "req_a1b2c3d4e5f67890abcdef0123456789"
field_errors
array[object]
Detailed field-level errors for validation failures.
422 Unprocessable Entity — well-formed request rejected by the coupon state machine. Most common code is `field_locked` (a discount-term field was edited after the first redemption). Also covers cross-row scope conflicts that depend on the current stored row.
FieldTypeDescription
errorrequired
object
typerequired
enum
The type of error.
invalid_request_errorauthentication_errorauthorization_errorrate_limit_erroridempotency_errorprocessing_errorwebhook_error
coderequired
string
A short string identifying the specific error.
e.g. "validation_error"
messagerequired
string
A human-readable message providing details about the error.
e.g. "Request validation failed"
paramnullable
string
The parameter that caused the error, if applicable.
e.g. "email"
request_id
string
Unique request identifier for debugging.
e.g. "req_a1b2c3d4e5f67890abcdef0123456789"
field_errors
array[object]
Detailed field-level errors for validation failures.
DELETE /coupons/{id}

Archive a coupon

Soft-archives a coupon. Coupons are never hard-deleted — DELETE is an alias for POST /coupons/{id}/archive with { "archived": true }. Redemption history is preserved. To restore, call POST /coupons/{id}/archive with { "archived": false }. Idempotent.

Path parameters

NameTypeDescription
idrequired
string (uuid)
Coupon UUID.
e.g. "6ba7b820-9dad-11d1-80b4-00c04fd430c8"

Header parameters

NameTypeDescription
EPD-Version
string
API version override (format `YYYY-MM-DD`). If omitted, your account's pinned version or the latest version is used.
e.g. "2026-02-11"
X-EPD-Idempotency-Key
string (uuid)
Optional UUID v4 idempotency key for retry safety.
e.g. "550e8400-e29b-41d4-a716-446655440000"

Responses

200 The archived coupon.
FieldTypeDescription
idrequired
string (uuid)
e.g. "6ba7b820-9dad-11d1-80b4-00c04fd430c8"
namerequired
string
Display name (1–200 chars). For `kind: promo`, the trimmed-and-uppercased name **is** the redeemable code customers type at checkout. Normalization is `.trim().toUpperCase()` only — it does **not** strip internal whitespace or punctuation, so the input must already match `^[A-Z0-9-]{4,50}$` after upper-casing. `"blackfriday2026"` → `BLACKFRIDAY2026` ✓; `"Black Friday 2026"` → `BLACK FRIDAY 2026` ✗ (space rejected). Retrieve the canonical code via `GET /v1/coupons/{id}/codes` (which returns the single auto-minted code row for promos) if you don't want to normalize client-side. For `kind: generated`, `name` is a free-form merchant label — the regex doesn't apply.
e.g. "BLACKFRIDAY2026"
descriptionnullable
string
Free-form merchant note. `null` when unset.
e.g. "15% off first order — use in welcome email."
kindrequired
enum
`generated` — one or many codes minted under the coupon (each customer gets a unique code, e.g. for influencer campaigns or single-use rewards). `promo` — one shared code derived from the coupon name (e.g. `BLACKFRIDAY`). Set at create and immutable.
generatedpromo
e.g. "generated"
percentagerequired
number
Percent discount (0–100, up to 2 decimal places) for percentage coupons. `null` for amount-off.
e.g. 15
amountrequired
integer
Amount discount in cents for amount-off coupons. `null` for percentage.
e.g. null
currencyrequired
string
ISO-4217 alpha-3 code, **returned lowercase** (e.g. `usd`). Snapshotted at redemption time for amount-off coupons. Percentage-off coupons default to `usd`.
e.g. "usd"
durationrequired
enum
`once` — applies a single time (most common for one-off purchases). `repeating` — applies for `duration_in_cycles` billing cycles on a subscription. `forever` — applies on every renewal for the lifetime of the subscription. For order-only coupons, leave as `once`.
oncerepeatingforever
e.g. "once"
duration_in_cyclesrequired
integer
Required when `duration` is `repeating`; `null` otherwise.
e.g. null
minimum_amountrequired
integer
Minimum order/cart amount required for the coupon to apply, in cents.
e.g. 5000
max_discount_amountrequired
integer
Per-redemption discount cap in cents. **Percentage-only** — always `null` for amount-off coupons. Snapshotted at redemption time.
e.g. 2500
first_time_customer_onlyrequired
boolean
When `true`, the coupon only validates for customers who have **never had a successful order** on this merchant account. Snapshotted at redemption.
e.g. false
max_redemptionsrequired
integer
Total redemption cap across the entire coupon (all codes, all customers). `null` means unlimited. Editable after first redemption, but you cannot lower it below the current `total_redemptions`.
e.g. 1000
max_redemptions_per_coderequired
integer
Cap on redemptions per individual minted code. Not applicable to `kind: promo` (which has a single shared code — use `max_redemptions` instead). Defaults to `1` for `kind: generated`, meaning each minted code is single-use.
e.g. 1
max_redemptions_per_customerrequired
integer
Per-customer redemption cap. Defaults to `1` for `kind: promo` (one redemption per customer on the shared code) and `null` (no cap) for `kind: generated`. Counted against the customer ID on the redeeming order.
e.g. 1
starts_atrequired
string (date-time)
When the coupon becomes redeemable. `null` means active immediately on creation. Validation returns `coupon_not_yet_active` before this moment.
e.g. "2026-11-25T00:00:00.000Z"
expires_atrequired
string (date-time)
When the coupon stops accepting new redemptions. `null` means no expiry. Existing redemptions are unaffected.
e.g. "2026-12-01T23:59:59.000Z"
activerequired
boolean
Pause toggle. `false` blocks new redemptions without archiving — useful for temporary pauses (e.g. promo on hold). Returns `coupon_inactive` on `validate_coupon`. Distinct from `archived_at`, which is the permanent end-of-life state.
e.g. true
archived_atrequired
string (date-time)
Soft-archive timestamp. Non-null means the coupon is retired — new redemptions are rejected and it drops out of default `GET /coupons` listings. Existing redemption history is preserved. Restore via `POST /coupons/{id}/archive` with `{ "archived": false }` — note that unarchive only clears this field; the coupon stays `active: false` until you separately PATCH it.
e.g. null
product_scoperequired
enum
Applicability for products. `none` never applies; `all` applies to every product; `specific` applies only to `product_ids`. At least one of `product_scope` or `plan_scope` must be non-`none`.
noneallspecific
e.g. "all"
plan_scoperequired
enum
Applicability for subscription plans. Same semantics as `product_scope`.
noneallspecific
e.g. "specific"
plan_idsrequired
array[string]
Plans the coupon is restricted to. Non-empty only when `plan_scope` is `specific`.
e.g. []
product_idsrequired
array[string]
Products the coupon is restricted to. Non-empty only when `product_scope` is `specific`.
e.g. []
total_redemptionsrequired
integer
Successful redemptions across all codes on this coupon. Increments atomically when an order containing the coupon is charged. `max_redemptions` cannot be lowered below this value.
e.g. 42
last_mint_prefixrequired
string
Prefix used on the most recent random-mint batch. Surfaced so dashboards can prefill the next batch. `null` when no random batch has been minted.
e.g. "SUMMER-"
last_mint_lengthrequired
integer
Total code length used on the most recent random-mint batch. `null` when no random batch has been minted.
e.g. 12
created_atrequired
string (date-time)
e.g. "2026-11-01T10:30:00.000Z"
updated_atrequired
string (date-time)
e.g. "2026-11-25T00:00:01.000Z"
401 Unauthorized — Authentication failed.
FieldTypeDescription
errorrequired
object
typerequired
enum
The type of error.
invalid_request_errorauthentication_errorauthorization_errorrate_limit_erroridempotency_errorprocessing_errorwebhook_error
coderequired
string
A short string identifying the specific error.
e.g. "validation_error"
messagerequired
string
A human-readable message providing details about the error.
e.g. "Request validation failed"
paramnullable
string
The parameter that caused the error, if applicable.
e.g. "email"
request_id
string
Unique request identifier for debugging.
e.g. "req_a1b2c3d4e5f67890abcdef0123456789"
field_errors
array[object]
Detailed field-level errors for validation failures.
403 Forbidden — The API key doesn't have permission.
FieldTypeDescription
errorrequired
object
typerequired
enum
The type of error.
invalid_request_errorauthentication_errorauthorization_errorrate_limit_erroridempotency_errorprocessing_errorwebhook_error
coderequired
string
A short string identifying the specific error.
e.g. "validation_error"
messagerequired
string
A human-readable message providing details about the error.
e.g. "Request validation failed"
paramnullable
string
The parameter that caused the error, if applicable.
e.g. "email"
request_id
string
Unique request identifier for debugging.
e.g. "req_a1b2c3d4e5f67890abcdef0123456789"
field_errors
array[object]
Detailed field-level errors for validation failures.
404 Not Found — The requested resource doesn't exist.
FieldTypeDescription
errorrequired
object
typerequired
enum
The type of error.
invalid_request_errorauthentication_errorauthorization_errorrate_limit_erroridempotency_errorprocessing_errorwebhook_error
coderequired
string
A short string identifying the specific error.
e.g. "validation_error"
messagerequired
string
A human-readable message providing details about the error.
e.g. "Request validation failed"
paramnullable
string
The parameter that caused the error, if applicable.
e.g. "email"
request_id
string
Unique request identifier for debugging.
e.g. "req_a1b2c3d4e5f67890abcdef0123456789"
field_errors
array[object]
Detailed field-level errors for validation failures.
POST /coupons/{id}/archive

Archive or unarchive a coupon

Soft-archive ({ "archived": true }) or restore ({ "archived": false }). Redemption history is preserved across both transitions. Idempotent.

Note on unarchive: archiving sets active: false so paused redemptions match the merchant's intent. Unarchiving only clears archived_at — it does not flip active back to true. The coupon returns to the default list but stays paused until you call PATCH /v1/coupons/{id} with { "active": true }.

Path parameters

NameTypeDescription
idrequired
string (uuid)
Coupon UUID.
e.g. "6ba7b820-9dad-11d1-80b4-00c04fd430c8"

Header parameters

NameTypeDescription
EPD-Version
string
API version override (format `YYYY-MM-DD`). If omitted, your account's pinned version or the latest version is used.
e.g. "2026-02-11"
X-EPD-Idempotency-Key
string (uuid)
Optional UUID v4 idempotency key for retry safety.
e.g. "550e8400-e29b-41d4-a716-446655440000"

Request body required

FieldTypeDescription
archivedrequired
boolean

Responses

200 The coupon after the transition.
FieldTypeDescription
idrequired
string (uuid)
e.g. "6ba7b820-9dad-11d1-80b4-00c04fd430c8"
namerequired
string
Display name (1–200 chars). For `kind: promo`, the trimmed-and-uppercased name **is** the redeemable code customers type at checkout. Normalization is `.trim().toUpperCase()` only — it does **not** strip internal whitespace or punctuation, so the input must already match `^[A-Z0-9-]{4,50}$` after upper-casing. `"blackfriday2026"` → `BLACKFRIDAY2026` ✓; `"Black Friday 2026"` → `BLACK FRIDAY 2026` ✗ (space rejected). Retrieve the canonical code via `GET /v1/coupons/{id}/codes` (which returns the single auto-minted code row for promos) if you don't want to normalize client-side. For `kind: generated`, `name` is a free-form merchant label — the regex doesn't apply.
e.g. "BLACKFRIDAY2026"
descriptionnullable
string
Free-form merchant note. `null` when unset.
e.g. "15% off first order — use in welcome email."
kindrequired
enum
`generated` — one or many codes minted under the coupon (each customer gets a unique code, e.g. for influencer campaigns or single-use rewards). `promo` — one shared code derived from the coupon name (e.g. `BLACKFRIDAY`). Set at create and immutable.
generatedpromo
e.g. "generated"
percentagerequired
number
Percent discount (0–100, up to 2 decimal places) for percentage coupons. `null` for amount-off.
e.g. 15
amountrequired
integer
Amount discount in cents for amount-off coupons. `null` for percentage.
e.g. null
currencyrequired
string
ISO-4217 alpha-3 code, **returned lowercase** (e.g. `usd`). Snapshotted at redemption time for amount-off coupons. Percentage-off coupons default to `usd`.
e.g. "usd"
durationrequired
enum
`once` — applies a single time (most common for one-off purchases). `repeating` — applies for `duration_in_cycles` billing cycles on a subscription. `forever` — applies on every renewal for the lifetime of the subscription. For order-only coupons, leave as `once`.
oncerepeatingforever
e.g. "once"
duration_in_cyclesrequired
integer
Required when `duration` is `repeating`; `null` otherwise.
e.g. null
minimum_amountrequired
integer
Minimum order/cart amount required for the coupon to apply, in cents.
e.g. 5000
max_discount_amountrequired
integer
Per-redemption discount cap in cents. **Percentage-only** — always `null` for amount-off coupons. Snapshotted at redemption time.
e.g. 2500
first_time_customer_onlyrequired
boolean
When `true`, the coupon only validates for customers who have **never had a successful order** on this merchant account. Snapshotted at redemption.
e.g. false
max_redemptionsrequired
integer
Total redemption cap across the entire coupon (all codes, all customers). `null` means unlimited. Editable after first redemption, but you cannot lower it below the current `total_redemptions`.
e.g. 1000
max_redemptions_per_coderequired
integer
Cap on redemptions per individual minted code. Not applicable to `kind: promo` (which has a single shared code — use `max_redemptions` instead). Defaults to `1` for `kind: generated`, meaning each minted code is single-use.
e.g. 1
max_redemptions_per_customerrequired
integer
Per-customer redemption cap. Defaults to `1` for `kind: promo` (one redemption per customer on the shared code) and `null` (no cap) for `kind: generated`. Counted against the customer ID on the redeeming order.
e.g. 1
starts_atrequired
string (date-time)
When the coupon becomes redeemable. `null` means active immediately on creation. Validation returns `coupon_not_yet_active` before this moment.
e.g. "2026-11-25T00:00:00.000Z"
expires_atrequired
string (date-time)
When the coupon stops accepting new redemptions. `null` means no expiry. Existing redemptions are unaffected.
e.g. "2026-12-01T23:59:59.000Z"
activerequired
boolean
Pause toggle. `false` blocks new redemptions without archiving — useful for temporary pauses (e.g. promo on hold). Returns `coupon_inactive` on `validate_coupon`. Distinct from `archived_at`, which is the permanent end-of-life state.
e.g. true
archived_atrequired
string (date-time)
Soft-archive timestamp. Non-null means the coupon is retired — new redemptions are rejected and it drops out of default `GET /coupons` listings. Existing redemption history is preserved. Restore via `POST /coupons/{id}/archive` with `{ "archived": false }` — note that unarchive only clears this field; the coupon stays `active: false` until you separately PATCH it.
e.g. null
product_scoperequired
enum
Applicability for products. `none` never applies; `all` applies to every product; `specific` applies only to `product_ids`. At least one of `product_scope` or `plan_scope` must be non-`none`.
noneallspecific
e.g. "all"
plan_scoperequired
enum
Applicability for subscription plans. Same semantics as `product_scope`.
noneallspecific
e.g. "specific"
plan_idsrequired
array[string]
Plans the coupon is restricted to. Non-empty only when `plan_scope` is `specific`.
e.g. []
product_idsrequired
array[string]
Products the coupon is restricted to. Non-empty only when `product_scope` is `specific`.
e.g. []
total_redemptionsrequired
integer
Successful redemptions across all codes on this coupon. Increments atomically when an order containing the coupon is charged. `max_redemptions` cannot be lowered below this value.
e.g. 42
last_mint_prefixrequired
string
Prefix used on the most recent random-mint batch. Surfaced so dashboards can prefill the next batch. `null` when no random batch has been minted.
e.g. "SUMMER-"
last_mint_lengthrequired
integer
Total code length used on the most recent random-mint batch. `null` when no random batch has been minted.
e.g. 12
created_atrequired
string (date-time)
e.g. "2026-11-01T10:30:00.000Z"
updated_atrequired
string (date-time)
e.g. "2026-11-25T00:00:01.000Z"
401 Unauthorized — Authentication failed.
FieldTypeDescription
errorrequired
object
typerequired
enum
The type of error.
invalid_request_errorauthentication_errorauthorization_errorrate_limit_erroridempotency_errorprocessing_errorwebhook_error
coderequired
string
A short string identifying the specific error.
e.g. "validation_error"
messagerequired
string
A human-readable message providing details about the error.
e.g. "Request validation failed"
paramnullable
string
The parameter that caused the error, if applicable.
e.g. "email"
request_id
string
Unique request identifier for debugging.
e.g. "req_a1b2c3d4e5f67890abcdef0123456789"
field_errors
array[object]
Detailed field-level errors for validation failures.
403 Forbidden — The API key doesn't have permission.
FieldTypeDescription
errorrequired
object
typerequired
enum
The type of error.
invalid_request_errorauthentication_errorauthorization_errorrate_limit_erroridempotency_errorprocessing_errorwebhook_error
coderequired
string
A short string identifying the specific error.
e.g. "validation_error"
messagerequired
string
A human-readable message providing details about the error.
e.g. "Request validation failed"
paramnullable
string
The parameter that caused the error, if applicable.
e.g. "email"
request_id
string
Unique request identifier for debugging.
e.g. "req_a1b2c3d4e5f67890abcdef0123456789"
field_errors
array[object]
Detailed field-level errors for validation failures.
404 Not Found — The requested resource doesn't exist.
FieldTypeDescription
errorrequired
object
typerequired
enum
The type of error.
invalid_request_errorauthentication_errorauthorization_errorrate_limit_erroridempotency_errorprocessing_errorwebhook_error
coderequired
string
A short string identifying the specific error.
e.g. "validation_error"
messagerequired
string
A human-readable message providing details about the error.
e.g. "Request validation failed"
paramnullable
string
The parameter that caused the error, if applicable.
e.g. "email"
request_id
string
Unique request identifier for debugging.
e.g. "req_a1b2c3d4e5f67890abcdef0123456789"
field_errors
array[object]
Detailed field-level errors for validation failures.
POST /coupons/{id}/codes

Mint codes for a coupon

Mints codes for a generated coupon.

  • Provide exactly one of count (random codes) or codes (caller-supplied literals matching ^[A-Z0-9-]{8,50}$).
  • Random suffixes use an unambiguous alphabet.
  • Up to 500 codes per call.
  • Rejected with 422 for kind: promo (those mint a single shared code at create time).

Idempotency

Requires an X-EPD-Idempotency-Key header. Replays with the same key return the originally-minted batch.

Path parameters

NameTypeDescription
idrequired
string (uuid)
Coupon UUID.
e.g. "6ba7b820-9dad-11d1-80b4-00c04fd430c8"

Header parameters

NameTypeDescription
EPD-Version
string
API version override (format `YYYY-MM-DD`). If omitted, your account's pinned version or the latest version is used.
e.g. "2026-02-11"
X-EPD-Idempotency-Keyrequired
string (uuid)
UUID v4 idempotency key. Same key with same request returns cached response. Keys expire after 24 hours.
e.g. "550e8400-e29b-41d4-a716-446655440000"

Request body required

FieldTypeDescription
count
integer
Number of random codes to mint. The random suffix uses an unambiguous alphabet.
e.g. 100
codes
array[string]
Custom codes to mint. Each entry is trimmed, uppercased, and must match `^[A-Z0-9-]{8,50}$`. Duplicates against the request or existing codes are rejected.
prefix
string
Optional prefix prepended to each minted code. Trimmed and uppercased server-side. Must leave at least 4 random characters after the prefix. Only valid with `count`.
e.g. "SUMMER"
length
integer
Total length of each minted code (prefix + random suffix). Only valid with `count`.
expires_atnullable
string (date-time)
Per-batch expiry applied to every code in this request. `null` or omitted inherits from the parent coupon.

Responses

201 The minted codes.
400 Bad Request — The request was invalid or cannot be served.
FieldTypeDescription
errorrequired
object
typerequired
enum
The type of error.
invalid_request_errorauthentication_errorauthorization_errorrate_limit_erroridempotency_errorprocessing_errorwebhook_error
coderequired
string
A short string identifying the specific error.
e.g. "validation_error"
messagerequired
string
A human-readable message providing details about the error.
e.g. "Request validation failed"
paramnullable
string
The parameter that caused the error, if applicable.
e.g. "email"
request_id
string
Unique request identifier for debugging.
e.g. "req_a1b2c3d4e5f67890abcdef0123456789"
field_errors
array[object]
Detailed field-level errors for validation failures.
401 Unauthorized — Authentication failed.
FieldTypeDescription
errorrequired
object
typerequired
enum
The type of error.
invalid_request_errorauthentication_errorauthorization_errorrate_limit_erroridempotency_errorprocessing_errorwebhook_error
coderequired
string
A short string identifying the specific error.
e.g. "validation_error"
messagerequired
string
A human-readable message providing details about the error.
e.g. "Request validation failed"
paramnullable
string
The parameter that caused the error, if applicable.
e.g. "email"
request_id
string
Unique request identifier for debugging.
e.g. "req_a1b2c3d4e5f67890abcdef0123456789"
field_errors
array[object]
Detailed field-level errors for validation failures.
403 Forbidden — The API key doesn't have permission.
FieldTypeDescription
errorrequired
object
typerequired
enum
The type of error.
invalid_request_errorauthentication_errorauthorization_errorrate_limit_erroridempotency_errorprocessing_errorwebhook_error
coderequired
string
A short string identifying the specific error.
e.g. "validation_error"
messagerequired
string
A human-readable message providing details about the error.
e.g. "Request validation failed"
paramnullable
string
The parameter that caused the error, if applicable.
e.g. "email"
request_id
string
Unique request identifier for debugging.
e.g. "req_a1b2c3d4e5f67890abcdef0123456789"
field_errors
array[object]
Detailed field-level errors for validation failures.
404 Not Found — The requested resource doesn't exist.
FieldTypeDescription
errorrequired
object
typerequired
enum
The type of error.
invalid_request_errorauthentication_errorauthorization_errorrate_limit_erroridempotency_errorprocessing_errorwebhook_error
coderequired
string
A short string identifying the specific error.
e.g. "validation_error"
messagerequired
string
A human-readable message providing details about the error.
e.g. "Request validation failed"
paramnullable
string
The parameter that caused the error, if applicable.
e.g. "email"
request_id
string
Unique request identifier for debugging.
e.g. "req_a1b2c3d4e5f67890abcdef0123456789"
field_errors
array[object]
Detailed field-level errors for validation failures.
409 Conflict — one or more `codes` collide with codes already minted on this coupon.
FieldTypeDescription
errorrequired
object
typerequired
enum
The type of error.
invalid_request_errorauthentication_errorauthorization_errorrate_limit_erroridempotency_errorprocessing_errorwebhook_error
coderequired
string
A short string identifying the specific error.
e.g. "validation_error"
messagerequired
string
A human-readable message providing details about the error.
e.g. "Request validation failed"
paramnullable
string
The parameter that caused the error, if applicable.
e.g. "email"
request_id
string
Unique request identifier for debugging.
e.g. "req_a1b2c3d4e5f67890abcdef0123456789"
field_errors
array[object]
Detailed field-level errors for validation failures.
422 Unprocessable Entity — coupon is `kind: promo`, or neither/both of `count`/`codes` were provided.
FieldTypeDescription
errorrequired
object
typerequired
enum
The type of error.
invalid_request_errorauthentication_errorauthorization_errorrate_limit_erroridempotency_errorprocessing_errorwebhook_error
coderequired
string
A short string identifying the specific error.
e.g. "validation_error"
messagerequired
string
A human-readable message providing details about the error.
e.g. "Request validation failed"
paramnullable
string
The parameter that caused the error, if applicable.
e.g. "email"
request_id
string
Unique request identifier for debugging.
e.g. "req_a1b2c3d4e5f67890abcdef0123456789"
field_errors
array[object]
Detailed field-level errors for validation failures.
GET /coupons/{id}/codes

List codes for a coupon

Path parameters

NameTypeDescription
idrequired
string (uuid)
Coupon UUID.
e.g. "6ba7b820-9dad-11d1-80b4-00c04fd430c8"

Query parameters

NameTypeDescription
limit
integer
Number of items to return per page.
Default: 10
starting_after
string
Cursor for forward pagination. Returns items created after this ID.
e.g. "550e8400-e29b-41d4-a716-446655440000"
ending_before
string
Cursor for backward pagination. Returns items created before this ID.
e.g. "550e8400-e29b-41d4-a716-446655440001"
redeemed
boolean
Filter by redemption status (`true` returns only codes with `redemption_count > 0`).
sort
string
Sort order, same format as `GET /coupons`: `field[direction]` (e.g. `created_at[desc]`) or `-field` shorthand for descending. Sortable fields: `created_at` (default), `updated_at`, `redemption_count`.
e.g. "created_at[desc]"

Header parameters

NameTypeDescription
EPD-Version
string
API version override (format `YYYY-MM-DD`). If omitted, your account's pinned version or the latest version is used.
e.g. "2026-02-11"

Responses

200 A list of coupon codes.
FieldTypeDescription
data
array[CouponCode]
url
any
e.g. "/v1/coupons/{id}/codes"
401 Unauthorized — Authentication failed.
FieldTypeDescription
errorrequired
object
typerequired
enum
The type of error.
invalid_request_errorauthentication_errorauthorization_errorrate_limit_erroridempotency_errorprocessing_errorwebhook_error
coderequired
string
A short string identifying the specific error.
e.g. "validation_error"
messagerequired
string
A human-readable message providing details about the error.
e.g. "Request validation failed"
paramnullable
string
The parameter that caused the error, if applicable.
e.g. "email"
request_id
string
Unique request identifier for debugging.
e.g. "req_a1b2c3d4e5f67890abcdef0123456789"
field_errors
array[object]
Detailed field-level errors for validation failures.
403 Forbidden — The API key doesn't have permission.
FieldTypeDescription
errorrequired
object
typerequired
enum
The type of error.
invalid_request_errorauthentication_errorauthorization_errorrate_limit_erroridempotency_errorprocessing_errorwebhook_error
coderequired
string
A short string identifying the specific error.
e.g. "validation_error"
messagerequired
string
A human-readable message providing details about the error.
e.g. "Request validation failed"
paramnullable
string
The parameter that caused the error, if applicable.
e.g. "email"
request_id
string
Unique request identifier for debugging.
e.g. "req_a1b2c3d4e5f67890abcdef0123456789"
field_errors
array[object]
Detailed field-level errors for validation failures.
404 Not Found — The requested resource doesn't exist.
FieldTypeDescription
errorrequired
object
typerequired
enum
The type of error.
invalid_request_errorauthentication_errorauthorization_errorrate_limit_erroridempotency_errorprocessing_errorwebhook_error
coderequired
string
A short string identifying the specific error.
e.g. "validation_error"
messagerequired
string
A human-readable message providing details about the error.
e.g. "Request validation failed"
paramnullable
string
The parameter that caused the error, if applicable.
e.g. "email"
request_id
string
Unique request identifier for debugging.
e.g. "req_a1b2c3d4e5f67890abcdef0123456789"
field_errors
array[object]
Detailed field-level errors for validation failures.