Coupon Tools

A coupon is one code many buyers can redeem for money off at checkout, the multi-use counterpart of an access code.

A coupon is one code many buyers can redeem for money off at checkout, the multi-use counterpart of an access code. These tools create coupons, switch them on and off, list redemptions and retire them.

Tools

activate_coupon

DESTRUCTIVE

Switch a coupon code on so subscribers can redeem it again, subject to its own dates and cap. Emits coupon.activated.

Turn a coupon back on. Switching on does not override the code's own rules: it still honours its starts_at, expires_at and redemption cap, so an activated code can come back with redeemable: false when it is outside its window or exhausted. Read that field, not active, to tell a subscriber whether the code will work.

This is the counterpart of deactivate_coupon and the narrow alternative to update_coupon with active: true: it announces coupon.activated rather than a generic coupon.updated, so an automation that cares about codes going live does not have to diff payloads to notice.

Entitlement is checked on the way back on

Requires the Coupons Addon or a Growth plan on the project owner. A creator whose tier lost coupons is told why the code stays off with TEAM_TIER_REQUIRED; the code is left as it was.

Re-calling on a code that is already on leaves it unchanged. The event emits on every successful call, so key a consumer on the coupon's state rather than on counting events.

Requires ability

The token behind the MCP session must hold it, or the call is refused with TOKEN_MISSING_ABILITY.

The REST endpoint and this tool share one action, so validation, permissions and events are identical.

Fires one event

Delivered to every endpoint subscribed to it once the change is made.

Annotations

DestructiveIdempotent

A client that honours annotations asks a person before running it. Sending the same arguments twice changes nothing the second time.

Arguments

coupon_id*string

UUID of the coupon to switch on.

What it returns

{  "data": {    "id": "4b9d3e08-a1f6-4275-9c83-7e01d6a2f594",    "project_id": "7f3d1c92-8b45-4e6a-9d21-5c8e0a4b6f13",    "code": "BLACKFRIDAY",    "name": "Black Friday 2026",    "discount_type": "percentage",    "discount_value": "25.0000",    "currency": null,    "duration": "once",    "max_redemptions": 500,    "max_redemptions_per_user": 1,    "minimum_amount": null,    "redemptions": {      "count": 137,      "remaining": 363,      "exhausted": false    },    "starts_at": "2026-11-27T00:00:00+00:00",    "expires_at": "2026-12-01T00:00:00+00:00",    "plan_ids": [],    "active": true,    "redeemable": true,    "created_at": "2026-11-20T09:14:02+00:00"  }}

The full get_coupon row, with active: true. redeemable is the field that says whether the code will apply at checkout right now.

How it fails

TEAM_TIER_REQUIRED

the project owner's tier no longer includes coupons. error.context.reason

VALIDATION_FAILED

the caller is a team member whose role lacks the team's coupon-update

RESOURCE_NOT_FOUND

unknown coupon_id, a coupon on another team's project, one outside the

AUTHENTICATION_REQUIRED

no authenticated user on the request.

TOKEN_MISSING_ABILITY

token lacks project-coupon:update.

create_coupon

DESTRUCTIVE

Create a coupon code any number of subscribers can redeem for money off. Returns the coupon immediately — no job to poll.

Author a coupon code: one string many subscribers redeem for a discount at checkout. Distinct from an access code, which is one code for one person and grants access outright without a payment.

Synchronous — the coupon is returned in the response, and coupon.created emits once.

Discounts apply to the first payment only. Renewals charge list price, so an agent should not describe a coupon as changing a subscriber's ongoing rate.

Requires the Coupons Addon or a Growth plan. Without it the call fails with TEAM_TIER_REQUIRED rather than creating anything. Entitlement is checked again when a subscriber redeems, so a coupon created today stops applying if the addon later lapses.

Requires ability

The token behind the MCP session must hold it, or the call is refused with TOKEN_MISSING_ABILITY.

Runs the same action as

The REST endpoint and this tool share one action, so validation, permissions and events are identical.

Fires one event

Delivered to every endpoint subscribed to it once the change is made.

Annotations

DestructiveOpen world

A client that honours annotations asks a person before running it. It reaches beyond Subscriby: a connector, a provider or a member.

Arguments

project_id*string

UUID of the project the code belongs to.

code*string

The code subscribers type at checkout. Letters, numbers and dashes only, 3 to 64 characters. Stored uppercase.

namestringoptional

Internal label to tell your codes apart, e.g. "Black Friday 2026". Defaults to the code itself.

discount_type*string

Either "percentage" (works on plans in any currency) or "fixed" (requires currency_id, and only applies to plans priced in it).

discount_value*number

For percentage, 1..99. For fixed, an amount greater than zero in the chosen currency.

currency_idstringoptional

Currency UUID. Required when discount_type is "fixed", ignored otherwise.

max_redemptionsintegeroptional

Total uses allowed across everyone. Omit for unlimited.

min1
max_redemptions_per_userintegeroptional

How many times one subscriber may use the code. Defaults to 1.

min1
minimum_amountnumberoptional

Only apply the code when the plan costs at least this much, in the plan's own currency.

starts_atstringoptional

ISO 8601 instant the code becomes usable. Omit to start immediately.

expires_atstringoptional

ISO 8601 instant the code stops working. Omit for no end date.

plan_idsstring[]optional

Plan UUIDs to restrict the code to. Leave empty or omit to cover every plan in the project, including ones added later.

activebooleanoptional

Whether the code is usable right away. Defaults to true.

What it returns

{  "data": {    "id": "4b9d3e08-a1f6-4275-9c83-7e01d6a2f594",    "project_id": "7f3d1c92-8b45-4e6a-9d21-5c8e0a4b6f13",    "code": "BLACKFRIDAY",    "name": "Black Friday 2026",    "discount_type": "percentage",    "discount_value": "25.0000",    "currency": null,    "duration": "once",    "max_redemptions": 500,    "max_redemptions_per_user": 1,    "minimum_amount": null,    "starts_at": "2026-11-27T00:00:00+00:00",    "expires_at": "2026-12-01T00:00:00+00:00",    "plan_ids": [],    "active": true,    "redeemable": true,    "created_at": "2026-11-20T09:14:02+00:00"  }}

code comes back uppercase regardless of what was sent. discount_value is a decimal string — parse it as a decimal, not a float.

How it fails

TEAM_TIER_REQUIRED

the creator has neither the Coupons Addon nor a Growth plan.

VALIDATION_FAILED

code already taken in this project (compared uppercase), code outside 3–64

RESOURCE_NOT_FOUND

unknown project_id, or the project is outside the token's scope.

AUTHENTICATION_REQUIRED

no authenticated user on the request.

TOKEN_MISSING_ABILITY

token lacks project-coupon:create.

Switch a coupon code off so no new subscriber can redeem it. A checkout that already applied it keeps its discount. Emits coupon.deactivated.

Stop a code right now. Switching off is the safe way to retire a coupon: no new subscriber can redeem it, while the redemptions already recorded stay exactly as they are and a buyer who applied the code before you called keeps the discount they were quoted. Nothing is deleted, so activate_coupon brings it back.

Why this, and not delete_coupon

delete_coupon refuses while a checkout holding the code is still in progress, because the discount has already reached a gateway. Deactivating never refuses for that reason — it is the call to make when a code has leaked or a campaign has to end this minute. Delete later if you want the row gone.

Announces coupon.deactivated rather than a generic coupon.updated, so an automation that reacts to codes being withdrawn does not have to diff payloads to notice. Re-calling on a code that is already off leaves it unchanged; the event emits on every successful call, so key a consumer on the coupon's state rather than on counting events.

Requires ability

The token behind the MCP session must hold it, or the call is refused with TOKEN_MISSING_ABILITY.

The REST endpoint and this tool share one action, so validation, permissions and events are identical.

Fires one event

Delivered to every endpoint subscribed to it once the change is made.

Annotations

DestructiveIdempotent

A client that honours annotations asks a person before running it. Sending the same arguments twice changes nothing the second time.

Arguments

coupon_id*string

UUID of the coupon to switch off.

What it returns

{  "data": {    "id": "4b9d3e08-a1f6-4275-9c83-7e01d6a2f594",    "project_id": "7f3d1c92-8b45-4e6a-9d21-5c8e0a4b6f13",    "code": "BLACKFRIDAY",    "name": "Black Friday 2026",    "discount_type": "percentage",    "discount_value": "25.0000",    "currency": null,    "duration": "once",    "max_redemptions": 500,    "max_redemptions_per_user": 1,    "minimum_amount": null,    "redemptions": {      "count": 137,      "remaining": 363,      "exhausted": false    },    "starts_at": "2026-11-27T00:00:00+00:00",    "expires_at": "2026-12-01T00:00:00+00:00",    "plan_ids": [],    "active": false,    "redeemable": false,    "created_at": "2026-11-20T09:14:02+00:00"  }}

The full get_coupon row, with active: false. redeemable is always false on a deactivated code; redemptions is untouched.

How it fails

VALIDATION_FAILED

the caller is a team member whose role lacks the team's coupon-update

RESOURCE_NOT_FOUND

unknown coupon_id, a coupon on another team's project, one outside the

AUTHENTICATION_REQUIRED

no authenticated user on the request.

TOKEN_MISSING_ABILITY

token lacks project-coupon:update.

delete_coupon

DESTRUCTIVE

Soft-delete a coupon code so nobody can redeem it again. Past redemptions are kept. Refused while a checkout holds the code.

Retire a coupon code permanently. The row is soft-deleted: the code disappears from every list and can never be redeemed again, while the redemptions already recorded and the discounts already applied stay in the history. Emits coupon.deleted with a snapshot taken before the row goes, because a consumer reacting to a deletion has no way left to look the coupon up.

Confirm the target with a human

Deletion cannot be undone from the API. The tool is annotated destructive so a client can prompt for confirmation; read the code back with get_coupon and confirm the coupon_id before calling.

Refused while a checkout is using the code

If a buyer has applied the code and their checkout has not settled, the discount has already been quoted to a gateway. The service refuses with VALIDATION_FAILED rather than pull the code out from under them. Call deactivate_coupon instead: it stops new redemptions at once and never refuses for a checkout in flight. Delete later, once the hold has cleared.

Idempotent: a second call on the same id finds nothing and answers RESOURCE_NOT_FOUND, exactly as an unknown or out-of-scope id does.

Requires ability

The token behind the MCP session must hold it, or the call is refused with TOKEN_MISSING_ABILITY.

The REST endpoint and this tool share one action, so validation, permissions and events are identical.

Fires one event

Delivered to every endpoint subscribed to it once the change is made.

Annotations

DestructiveIdempotent

A client that honours annotations asks a person before running it. Sending the same arguments twice changes nothing the second time.

Arguments

coupon_id*string

UUID of the coupon to delete.

What it returns

{  "data": {    "id": "4b9d3e08-a1f6-4275-9c83-7e01d6a2f594",    "deleted": true  }}

id echoes the argument as sent.

How it fails

VALIDATION_FAILED

a checkout using the code is still in progress (error.context.coupon

RESOURCE_NOT_FOUND

unknown coupon_id, a coupon on another team's project, one outside the

AUTHENTICATION_REQUIRED

no authenticated user on the request.

TOKEN_MISSING_ABILITY

token lacks project-coupon:delete.

Fetch one coupon by UUID — discount, limits, redemption tally, plan restriction and whether it is redeemable right now.

Read one coupon code in full. The row is the same one list_coupons emits, so an agent that spotted a code in the list reads exactly the same fields back here, and the row a write tool returns after a change is comparable field for field.

Reads go through the coupon service, so the tenant scope and the token's scope:project: allow-list both apply. An id the token cannot see — unknown, another team's, or on a project outside the allow-list — answers RESOURCE_NOT_FOUND, indistinguishable from an id that never existed.

Call it before update_coupon or delete_coupon to read the current state rather than assuming it.

Requires ability

The token behind the MCP session must hold it, or the call is refused with TOKEN_MISSING_ABILITY.

The REST endpoint and this tool share one action, so validation, permissions and events are identical.

Annotations

Read-only

It reads and never changes anything.

Arguments

coupon_id*string

UUID of the coupon to fetch.

What it returns

{  "data": {    "id": "4b9d3e08-a1f6-4275-9c83-7e01d6a2f594",    "project_id": "7f3d1c92-8b45-4e6a-9d21-5c8e0a4b6f13",    "code": "BLACKFRIDAY",    "name": "Black Friday 2026",    "discount_type": "percentage",    "discount_value": "25.0000",    "currency": null,    "duration": "once",    "max_redemptions": 500,    "max_redemptions_per_user": 1,    "minimum_amount": null,    "redemptions": {      "count": 137,      "remaining": 363,      "exhausted": false    },    "starts_at": "2026-11-27T00:00:00+00:00",    "expires_at": "2026-12-01T00:00:00+00:00",    "plan_ids": [],    "active": true,    "redeemable": true,    "created_at": "2026-11-20T09:14:02+00:00"  }}

`plan_ids: []` means every plan, not no plans

An empty array means the code applies to every plan in the project, including plans added later. Do not summarise it as "not applied to any plan".

active is not redeemable. A code can be switched on and still not apply — before starts_at, after expires_at, or with the cap reached. redeemable is the combined answer for the coupon as a whole; per-subscriber caps are only resolved at checkout.

Prefer redemptions.remaining over your own subtraction. It is derived from settled redemptions plus reservations whose hold has not expired, so an in-flight checkout is already counted. discount_value is a decimal string — parse it as a decimal, not a float. currency is an ISO code, and null on a percentage coupon.

How it fails

RESOURCE_NOT_FOUND

unknown coupon_id, a coupon on another team's project, one outside the

AUTHENTICATION_REQUIRED

no authenticated user on the request.

TOKEN_MISSING_ABILITY

token lacks project-coupon:view.

List coupon codes with derived remaining-redemption counts. Read-only. Paginated, optionally narrowed to one project or filtered by on/off state.

Discover existing coupon codes — to report on a campaign, to check whether a code already exists before calling create_coupon, or to find the id of a code the creator described by name.

Read-only. Never mutates anything.

Requires ability

The token behind the MCP session must hold it, or the call is refused with TOKEN_MISSING_ABILITY.

Runs the same action as

The REST endpoint and this tool share one action, so validation, permissions and events are identical.

Annotations

Read-only

It reads and never changes anything.

Arguments

project_idstringoptional

Optional project UUID to narrow the list to a single project.

activebooleanoptional

Optional filter: true for codes that are switched on, false for those switched off.

limitintegeroptional

Maximum coupons to return per page (1..100).

min1max100
pageintegeroptional

1-indexed page number.

min1

What it returns

{  "data": [    {      "id": "4b9d3e08-a1f6-4275-9c83-7e01d6a2f594",      "project_id": "7f3d1c92-8b45-4e6a-9d21-5c8e0a4b6f13",      "code": "BLACKFRIDAY",      "name": "Black Friday 2026",      "discount_type": "percentage",      "discount_value": "25.0000",      "currency": null,      "duration": "once",      "max_redemptions": 500,      "max_redemptions_per_user": 1,      "minimum_amount": null,      "redemptions": {        "count": 137,        "remaining": 363,        "exhausted": false      },      "starts_at": "2026-11-27T00:00:00+00:00",      "expires_at": "2026-12-01T00:00:00+00:00",      "plan_ids": [],      "active": true,      "redeemable": true,      "created_at": "2026-11-20T09:14:02+00:00"    }  ],  "meta": {    "page": 1,    "limit": 25,    "total": 4,    "has_more": false  }}

How it fails

RESOURCE_NOT_FOUND

unknown project_id, or the project is outside the token's scope.

AUTHENTICATION_REQUIRED

no authenticated user on the request.

TOKEN_MISSING_ABILITY

token lacks project-coupon:view-any.

Three fields worth reading carefully

`plan_ids: []` means every plan, not no plans

An empty array means the code applies to every plan in the project, including plans added later. An agent summarising a coupon must not report it as "not applied to any plan".

Prefer redemptions.remaining over your own subtraction. It is derived, not stored: the quota counts settled redemptions plus reservations whose hold has not expired, so an in-flight checkout is already accounted for and an abandoned one returns its slot. max_redemptions - count will disagree.

active is not redeemable. A code can be switched on and still not apply — outside its window, fully claimed, or capped for a particular subscriber. redeemable is the combined answer for the coupon as a whole; per-subscriber caps can only be resolved at checkout.

update_coupon

DESTRUCTIVE

Change one or more fields of a coupon code. Partial by design — anything omitted keeps its stored value. Emits coupon.updated.

Change an existing coupon without restating it. Only the arguments present in the call are written, the way the REST PATCH behaves, so extending an expiry is one field, not a re-creation. Every field is checked the way create_coupon checks it, and cross-field rules are checked against the coupon's effective values: a percentage cap is judged against the type the coupon will have after the call, an end date against the start it will have.

Synchronous — the row after the change comes back in the response, and coupon.updated emits once with the fields that moved. A call that carries only coupon_id returns the current row and emits nothing.

Omit to keep, send null to clear

Leaving a field out leaves it alone. To remove a limit or a date, send the key with null: max_redemptions (unlimited), minimum_amount (no minimum), starts_at (usable now) and expires_at (no end date) all accept it. max_redemptions_per_user does not — it is always at least 1.

`plan_ids: []` widens the code to every plan

Omitting plan_ids leaves the restriction as it is. Sending an empty list deliberately covers every plan in the project, including ones added later. To narrow a code to "the Premium plan", pass that plan's UUID; resolve it with list_plans first.

Requires the Coupons Addon or a Growth plan on the project owner. When the tier has lapsed the call fails with TEAM_TIER_REQUIRED and nothing is written. The discount still applies to the first payment only; nothing here changes a subscriber's renewal price.

Switching discount_type has a side effect worth knowing: moving to fixed needs a currency_id unless the coupon already stores one, and moving to percentage clears the stored currency.

Requires ability

The token behind the MCP session must hold it, or the call is refused with TOKEN_MISSING_ABILITY.

The REST endpoint and this tool share one action, so validation, permissions and events are identical.

Fires one event

Delivered to every endpoint subscribed to it once the change is made.

Annotations

DestructiveIdempotentOpen world

A client that honours annotations asks a person before running it. Sending the same arguments twice changes nothing the second time. It reaches beyond Subscriby: a connector, a provider or a member.

Arguments

coupon_id*string

UUID of the coupon to change.

codestringoptional

New code subscribers type at checkout. Letters, numbers and dashes only, 3 to 64 characters. Stored uppercase; must not already exist in the project.

namestringoptional

Internal label, 3 to 255 characters.

discount_typestringoptional

Either "percentage" or "fixed". Switching to fixed needs a currency_id unless the code already has one.

discount_valuenumberoptional

For percentage, 1..100. For fixed, an amount greater than zero in the coupon's currency.

currency_idstringoptional

Currency UUID for a fixed discount. Ignored and cleared for a percentage.

max_redemptionsintegeroptional

Total uses allowed across everyone. Pass null for unlimited.

min1
max_redemptions_per_userintegeroptional

How many times one subscriber may use the code.

min1
minimum_amountnumberoptional

Only apply the code when the plan costs at least this much. Pass null for no minimum.

starts_atstringoptional

ISO 8601 instant the code becomes usable. Pass null to start immediately.

expires_atstringoptional

ISO 8601 instant the code stops working. Pass null for no end date. Must fall after the start.

activebooleanoptional

Whether the code is usable.

plan_idsstring[]optional

Plan UUIDs to restrict the code to. An empty list covers every plan in the project, including ones added later.

What it returns

{  "data": {    "id": "4b9d3e08-a1f6-4275-9c83-7e01d6a2f594",    "project_id": "7f3d1c92-8b45-4e6a-9d21-5c8e0a4b6f13",    "code": "BLACKFRIDAY",    "name": "Black Friday 2026 (extended)",    "discount_type": "percentage",    "discount_value": "25.0000",    "currency": null,    "duration": "once",    "max_redemptions": 750,    "max_redemptions_per_user": 1,    "minimum_amount": null,    "redemptions": {      "count": 137,      "remaining": 613,      "exhausted": false    },    "starts_at": "2026-11-27T00:00:00+00:00",    "expires_at": "2026-12-08T00:00:00+00:00",    "plan_ids": [],    "active": true,    "redeemable": true,    "created_at": "2026-11-20T09:14:02+00:00"  }}

The whole row comes back, not only the fields sent, so there is no need for a follow-up get_coupon. code is uppercase whatever was sent; discount_value is a decimal string. redemptions.remaining is recomputed against the new cap.

How it fails

TEAM_TIER_REQUIRED

the project owner's tier no longer includes coupons. error.context.reason

VALIDATION_FAILED

one entry per offending field in error.context: code outside 3–64

RESOURCE_NOT_FOUND

unknown coupon_id, a coupon on another team's project, one outside the

AUTHENTICATION_REQUIRED

no authenticated user on the request.

TOKEN_MISSING_ABILITY

token lacks project-coupon:update.

How is this guide?

On this page

Subscriby is a product designed by you — for you.No boardroom full of executives deciding what we ships next. Our roadmap always shaped by you with your feedback.

Share feedback or a request