A coupon is the discount itself (percent off or flat amount off, with eligibility rules). A code is what a customer types at checkout. Each coupon has either one shared code (kind: "promo", whose value is the coupon’s normalized name) or many minted codes (kind: "generated").

The nine coupon tools cover the full lifecycle. All accept the same identifiers and field shapes as the REST /v1/coupons endpoints.

All money fields are in cents — both inbound (request) and outbound (response). amount, minimum_amount, and max_discount_amount on coupons, and amount on validate_coupon, match the cents convention used everywhere else in the EPD API. A $25 discount is 2500.

Tools

create_coupon

Create a coupon. Exactly one of percentage or amount must be provided. For kind: "promo", the trimmed/uppercased name becomes the redeemable code and must match ^[A-Z0-9-]{4,50}$. For kind: "generated", pass an inline codes block to mint a random batch at create time, or call generate_coupon_codes later.

readOnlydestructiveidempotentopenWorld
falsefalsetruefalse

Pass idempotency_key on retryable client paths.

list_coupons

List coupons with optional filters (active, kind, archived), sort, and cursor pagination. Returns the standard { data, has_more, cursors } list envelope.

readOnlydestructiveidempotentopenWorld
truefalsetruefalse

retrieve_coupon

Get one coupon by id.

readOnlydestructiveidempotentopenWorld
truefalsetruefalse

update_coupon

Patch one or more fields. After the first redemption, 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 — attempts to change a locked field return field_locked. name (on generated coupons), active, description, expires_at, minimum_amount, max_redemptions, and max_redemptions_per_customer stay editable. starts_at locks once the activation moment has passed.

readOnlydestructiveidempotentopenWorld
falsefalsetruefalse

archive_coupon

Soft-archive a coupon. Existing redemptions are preserved; the coupon stops accepting new redemptions and drops out of the default list. Naturally idempotent.

readOnlydestructiveidempotentopenWorld
falsetruetruefalse

Archiving is a soft state — codes already in customers’ hands stop working, but the coupon row is retained for redemption history. Use unarchive_coupon to restore.

unarchive_coupon

Remove the archive flag on a coupon. The coupon returns to the default list but stays paused (active: false) — archiving sets active: false and unarchive does not flip it back. Call update_coupon with { active: true } to actually resume redemptions. Naturally idempotent.

readOnlydestructiveidempotentopenWorld
falsefalsetruefalse

generate_coupon_codes

Mint a batch of codes under an existing coupon (rejected for kind: "promo" — which already has its single shared code). Provide either count (server-generated, optionally shaped by prefix/length) or codes (caller-supplied strings) — never both. Up to 500 per call.

readOnlydestructiveidempotentopenWorld
falsefalsetruefalse

When prefix is set, length must leave ≥ 4 random characters after the prefix (so prefix: "SUMMER" requires length ≥ 10).

list_coupon_codes

List the codes minted under one coupon, with cursor pagination and an optional redeemed filter.

readOnlydestructiveidempotentopenWorld
truefalsetruefalse

validate_coupon

Test whether a code is currently redeemable in a given context (plan / product / customer / order amount). Pure read — does not create a redemption. Redemption happens server-side when an order or subscription references the code.

readOnlydestructiveidempotentopenWorld
truefalsetruefalse

Always returns success (the tool never errors on ineligibility). Either { valid: true, ...terms } with amount and max_discount_amount in cents, or { valid: false, reason }. reason covers the full set of rejection causes: coupon_inactive, coupon_not_yet_active, coupon_expired, code_expired, coupon_exhausted, code_exhausted, code_not_found, minimum_amount_not_met, currency_mismatch, plan_not_eligible, product_not_eligible, customer_limit_reached, not_first_time_customer.

Pass the order amount (in cents) and currency to test minimum_amount and currency eligibility. Pass customer_id for per-customer caps and first_time_customer_only checks.