Console API
Promos
8 endpoints.
/api/v1/admin/promos/couponsList coupons (platform admin)
Every coupon, newest first. `include_deleted` adds the ones already deleted at Stripe — their rows are kept so past redemptions still resolve to the terms that were granted.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
include_deleted | query | boolean | default false |
authorization | header | string | null |
Responses
200Successful Responseapplication/jsonSuccessResponse_CouponListData_| Field | Type | Description | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
request_idrequired | string | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
success | true | default true | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
datarequired | CouponListData | CouponListData fields
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
metadata | ResponseMetadata | null | ResponseMetadata fields
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
meta | ResponseMeta | null | ResponseMeta fields
|
400Bad request401Unauthorized403Forbidden404Not found422Validation error500Internal server error503Service unavailable
Error bodies: ErrorResponse. See Errors.
/api/v1/admin/promos/couponsCreate a coupon (platform admin)
Create the discount's terms. The console names the plans it applies to (`applies_to_plan_names`, from `GET /promos/plans`); payments resolves each to the active plan's Stripe Product, which is why a name that is not an active priced plan comes back as a 400 rather than a silent unrestricted coupon. The restriction is immutable afterwards — a different set of plans is a different coupon.
`idempotency_key` is the console's, generated once per form submission and passed to Stripe verbatim, so a retry after a lost response returns the first coupon instead of minting a second.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
authorization | header | string | null |
Request bodyrequired
application/jsonCouponCreateBody| Field | Type | Description |
|---|---|---|
namerequired | string | min length 1 · max length 256 |
percent_off | string | null | |
amount_off | integer | null | |
currency | string | null | |
durationrequired | string | pattern ^(once|repeating|forever)$ |
duration_in_months | integer | null | |
applies_to_plan_names | string[] | null | string[] | null fields |
max_redemptions | integer | null | |
redeem_by | string (date-time) | null | |
idempotency_keyrequired | string | min length 1 · max length 128 |
Responses
200Successful Responseapplication/jsonSuccessResponse_CouponData_| Field | Type | Description | ||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
request_idrequired | string | |||||||||||||||||||||||||||||||||||||||||||||||||
success | true | default true | ||||||||||||||||||||||||||||||||||||||||||||||||
datarequired | CouponData | CouponData fields
| ||||||||||||||||||||||||||||||||||||||||||||||||
metadata | ResponseMetadata | null | ResponseMetadata fieldsResponseMetadata, expanded above. | ||||||||||||||||||||||||||||||||||||||||||||||||
meta | ResponseMeta | null | ResponseMeta fieldsResponseMeta, expanded above. |
400Bad request401Unauthorized403Forbidden404Not found422Validation error500Internal server error503Service unavailable
Error bodies: ErrorResponse. See Errors.
/api/v1/admin/promos/coupons/{coupon_id}One coupon with its codes (platform admin)
The detail screen: the coupon plus its code strings, each with its allowlist. A targeted code is issued once per business under the same string, so the entries **are** the allowlist; a public code has a single entry with no business. Every entry's business is enriched with its `company_name` here — payments stores only ids.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
coupon_idrequired | path | string (uuid) | |
authorization | header | string | null |
Responses
200Successful Responseapplication/jsonSuccessResponse_CouponDetailData_| Field | Type | Description | |||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
request_idrequired | string | ||||||||||||||||||||||||||||||||||||||||||||||||||||
success | true | default true | |||||||||||||||||||||||||||||||||||||||||||||||||||
datarequired | CouponDetailData | CouponDetailData fields
| |||||||||||||||||||||||||||||||||||||||||||||||||||
metadata | ResponseMetadata | null | ResponseMetadata fieldsResponseMetadata, expanded above. | |||||||||||||||||||||||||||||||||||||||||||||||||||
meta | ResponseMeta | null | ResponseMeta fieldsResponseMeta, expanded above. |
400Bad request401Unauthorized403Forbidden404Not found422Validation error500Internal server error503Service unavailable
Error bodies: ErrorResponse. See Errors.
/api/v1/admin/promos/coupons/{coupon_id}Delete a coupon (platform admin)
Delete at Stripe. New redemptions stop and every code under it goes inactive, but **discounts already granted keep running to their duration** — Stripe copies the terms onto the subscription at redemption, so deleting the coupon does not claw anything back. The mirror row survives, flagged deleted, so past redemptions still name their terms.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
coupon_idrequired | path | string (uuid) | |
authorization | header | string | null |
Responses
200Successful Responseapplication/jsonSuccessResponse_CouponData_| Field | Type | Description |
|---|---|---|
request_idrequired | string | |
success | true | default true |
datarequired | CouponData | CouponData fieldsCouponData, expanded above. |
metadata | ResponseMetadata | null | ResponseMetadata fieldsResponseMetadata, expanded above. |
meta | ResponseMeta | null | ResponseMeta fieldsResponseMeta, expanded above. |
400Bad request401Unauthorized403Forbidden404Not found422Validation error500Internal server error503Service unavailable
Error bodies: ErrorResponse. See Errors.
/api/v1/admin/promos/coupons/{coupon_id}/promotion-codesIssue a code, or add businesses to one (platform admin)
No `businesses` mints one public code; with them, one customer-restricted code **per business** sharing the string — which is also how a business is added to an existing code's allowlist.
Unknown businesses are refused here, before payments is called: payments would create a Stripe Customer for the id to restrict the code to, and a Customer minted for a typo is permanent. The `company_name` sor holds is passed along to label that Customer, because payments has no other source for it.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
coupon_idrequired | path | string (uuid) | |
authorization | header | string | null |
Request bodyrequired
application/jsonPromotionCodesCreateBody| Field | Type | Description | ||||||
|---|---|---|---|---|---|---|---|---|
coderequired | string | min length 1 · max length 64 | ||||||
businesses | BusinessRefBody[] | default []BusinessRefBody[] fields
| ||||||
expires_at | string (date-time) | null | |||||||
max_redemptions | integer | null | |||||||
first_time_transaction | boolean | default false | ||||||
minimum_amount | integer | default 0 · min 0 | ||||||
listed_for_auto_apply | boolean | null | |||||||
idempotency_keyrequired | string | min length 1 · max length 128 |
Responses
200Successful Responseapplication/jsonSuccessResponse_PromotionCodesData_| Field | Type | Description | ||||||
|---|---|---|---|---|---|---|---|---|
request_idrequired | string | |||||||
success | true | default true | ||||||
datarequired | PromotionCodesData | PromotionCodesData fields
| ||||||
metadata | ResponseMetadata | null | ResponseMetadata fieldsResponseMetadata, expanded above. | ||||||
meta | ResponseMeta | null | ResponseMeta fieldsResponseMeta, expanded above. |
400Bad request401Unauthorized403Forbidden404Not found422Validation error500Internal server error503Service unavailable
Error bodies: ErrorResponse. See Errors.
/api/v1/admin/promos/plansPlans a coupon can be restricted to (platform admin)
The plan picker on the coupon form. This is payments' catalogue **without** the free plan: a plan with no Stripe price has no Product to carry a discount, so offering it would only produce a 400 on submit. The names here are exactly what `applies_to_plan_names` takes.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
authorization | header | string | null |
Responses
200Successful Responseapplication/jsonSuccessResponse_PromoPlansData_| Field | Type | Description | ||||||
|---|---|---|---|---|---|---|---|---|
request_idrequired | string | |||||||
success | true | default true | ||||||
datarequired | PromoPlansData | PromoPlansData fields
| ||||||
metadata | ResponseMetadata | null | ResponseMetadata fieldsResponseMetadata, expanded above. | ||||||
meta | ResponseMeta | null | ResponseMeta fieldsResponseMeta, expanded above. |
400Bad request401Unauthorized403Forbidden404Not found422Validation error500Internal server error503Service unavailable
Error bodies: ErrorResponse. See Errors.
/api/v1/admin/promos/promotion-codes/{promotion_code_id}Activate, deactivate, or list a promotion code (platform admin)
`active=false` is how a business leaves an allowlist — codes are never deleted at Stripe. `listed_for_auto_apply` controls whether the catalogue advertises the code in the customer's promo dropdown; an unlisted code still works when typed at Checkout.
Stripe refuses to reactivate a code that expired, hit its cap, or whose coupon was deleted, and that refusal reaches the admin verbatim rather than as a generic failure.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
promotion_code_idrequired | path | string (uuid) | |
authorization | header | string | null |
Request bodyrequired
application/jsonPromotionCodeUpdateBody| Field | Type | Description |
|---|---|---|
active | boolean | null | |
listed_for_auto_apply | boolean | null |
Responses
200Successful Responseapplication/jsonSuccessResponse_PromotionCodeData_| Field | Type | Description | |||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
request_idrequired | string | ||||||||||||||||||||||||||||||||||||||||
success | true | default true | |||||||||||||||||||||||||||||||||||||||
datarequired | PromotionCodeData | PromotionCodeData fields
| |||||||||||||||||||||||||||||||||||||||
metadata | ResponseMetadata | null | ResponseMetadata fieldsResponseMetadata, expanded above. | |||||||||||||||||||||||||||||||||||||||
meta | ResponseMeta | null | ResponseMeta fieldsResponseMeta, expanded above. |
400Bad request401Unauthorized403Forbidden404Not found422Validation error500Internal server error503Service unavailable
Error bodies: ErrorResponse. See Errors.
/api/v1/admin/promos/redemptionsWho redeemed what (platform admin)
Redemption reporting, newest first, filterable by business, coupon or code. A row is a Stripe Discount as payments mirrors it, so `ends_at` is null while the discount is still running. Each row's business is named here from sor's table.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
business_id | query | string (uuid) | null | |
coupon_id | query | string (uuid) | null | |
promotion_code_id | query | string (uuid) | null | |
limit | query | integer | default 200 |
authorization | header | string | null |
Responses
200Successful Responseapplication/jsonSuccessResponse_RedemptionsData_| Field | Type | Description | ||||||
|---|---|---|---|---|---|---|---|---|
request_idrequired | string | |||||||
success | true | default true | ||||||
datarequired | RedemptionsData | RedemptionsData fields
| ||||||
metadata | ResponseMetadata | null | ResponseMetadata fieldsResponseMetadata, expanded above. | ||||||
meta | ResponseMeta | null | ResponseMeta fieldsResponseMeta, expanded above. |
400Bad request401Unauthorized403Forbidden404Not found422Validation error500Internal server error503Service unavailable
Error bodies: ErrorResponse. See Errors.