update_coupon
Change one or more fields of a coupon code. Partial by design — anything omitted keeps its stored value. Emits coupon.updated.
Purpose
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.
Required ability
project-coupon:update
Input schema
{
"type": "object",
"required": ["coupon_id"],
"properties": {
"coupon_id": {
"type": "string",
"description": "UUID of the coupon to change."
},
"code": {
"type": "string",
"description": "New code subscribers type at checkout. Letters, numbers and dashes only, 3 to 64 characters. Stored uppercase; must not already exist in the project."
},
"name": {
"type": "string",
"description": "Internal label, 3 to 255 characters."
},
"discount_type": {
"type": "string",
"description": "Either \"percentage\" or \"fixed\". Switching to fixed needs a currency_id unless the code already has one."
},
"discount_value": {
"type": "number",
"description": "For percentage, 1..100. For fixed, an amount greater than zero in the coupon's currency."
},
"currency_id": {
"type": "string",
"description": "Currency UUID for a fixed discount. Ignored and cleared for a percentage."
},
"max_redemptions": {
"type": "integer",
"minimum": 1,
"description": "Total uses allowed across everyone. Pass null for unlimited."
},
"max_redemptions_per_user": {
"type": "integer",
"minimum": 1,
"description": "How many times one subscriber may use the code."
},
"minimum_amount": {
"type": "number",
"description": "Only apply the code when the plan costs at least this much. Pass null for no minimum."
},
"starts_at": {
"type": "string",
"description": "ISO 8601 instant the code becomes usable. Pass null to start immediately."
},
"expires_at": {
"type": "string",
"description": "ISO 8601 instant the code stops working. Pass null for no end date. Must fall after the start."
},
"active": {
"type": "boolean",
"description": "Whether the code is usable."
},
"plan_ids": {
"type": "array",
"items": { "type": "string" },
"description": "Plan UUIDs to restrict the code to. An empty list covers every plan in the project, including ones added later."
}
}
}Output shape
{
"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.
Example prompts
"Extend coupon
4b9d3e08-a1f6-4275-9c83-7e01d6a2f594to 8 December and raise the cap to 750 uses."
"Restrict the WELCOME10 code to the Starter plan only — everything else about it stays."
"Remove the expiry from the launch code so it runs indefinitely."
Failure modes
TEAM_TIER_REQUIRED— the project owner's tier no longer includes coupons.error.context.reasonsays what lapsed.VALIDATION_FAILED— one entry per offending field inerror.context:codeoutside 3–64 characters or containing anything but letters, numbers and dashes, or already taken in the project (compared uppercase);nameoutside 3–255 characters;discount_typenotpercentageorfixed;discount_valuezero, negative, or above 100 on a percentage;currency_idmissing or unknown when the effective type isfixed;max_redemptionsbelow 1;max_redemptions_per_userbelow 1 or null;minimum_amountzero or negative;starts_at/expires_atnot parseable, or an end that does not fall after the effective start;plan_idsnot a list, or naming a plan that belongs to another project. Acouponentry means the caller's role lacks the team's coupon-update permission.RESOURCE_NOT_FOUND— unknowncoupon_id, a coupon on another team's project, one outside the token'sscope:project:allow-list, or a coupon that has been deleted.AUTHENTICATION_REQUIRED— no authenticated user on the request.TOKEN_MISSING_ABILITY— token lacksproject-coupon:update.
Related
get_coupon— read the current values first.activate_coupon/deactivate_coupon— flipactivealone, with their own events.create_coupon— the same rules, on a new code.list_plans— resolve plan UUIDs forplan_ids.- Coupons API — the REST equivalent.
- Coupon Codes — the feature walkthrough.
How is this guide?