Skip to content

Validate Coupon Code

POST
/api/billing/coupons/validate
curl --request POST \
--url https://example.com/api/billing/coupons/validate \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '{ "billing_cycle": "monthly", "code": "example", "plan_code": "example" }'

Advisory “is this code good, and at what price?” — always 200.

Invalidity is data, not an HTTP error (§6.1): the console renders the reason string inline next to the input, and a 4xx would make every keystroke look like a failed request. Read-only — no redemption slot is consumed, so a debounced field can call this freely.

No org scope and no billing-owner gate: the answer is derived entirely from the public plan catalog and a code the caller already typed, so requiring an org would only stop a signed-in user from pricing an add-on before they have one. An unknown plan_code is a bad request rather than a coupon verdict, hence 422 (see coupons.UnknownPlanError).

Rate-limited per user (burst + daily) because the same properties that make it advisory make it an oracle: a valid code comes back with its discount_type and discount_value, so an unthrottled signed-in caller could sweep the coupon namespace and harvest every live code. See app.core.rate_limit’s coupon-validate block for why the buckets are keyed on the user rather than on the IP.

Media type application/json
CouponValidateRequest

POST /api/billing/coupons/validate.

object
billing_cycle
Billing Cycle
string
default: monthly
Allowed values: monthly annual
code
required
Code
string
>= 1 characters <= 64 characters
plan_code
required
Plan Code
string
>= 1 characters <= 64 characters

Successful Response

Media type application/json
CouponValidateResponse

POST /api/billing/coupons/validate — always 200; validity is data.

reason carries the §6.1 wire-level rejection strings verbatim (CouponRejectionReason values, e.g. “coupon_expired”) — the console maps them to copy, so they are API surface.

object
discount_type
Any of:
string
discount_value
Any of:
integer
final_unit_price_cents
Any of:
integer
reason
Any of:
string
valid
required
Valid
boolean
Example generated
{
"discount_type": "example",
"discount_value": 1,
"final_unit_price_cents": 1,
"reason": "example",
"valid": true
}

Validation Error

Media type application/json
HTTPValidationError
object
detail
Detail
Array<object>
ValidationError
object
ctx
Context
object
input
Input
loc
required
Location
Array
msg
required
Message
string
type
required
Error Type
string
Example generated
{
"detail": [
{
"ctx": {},
"input": "example",
"loc": [
"example"
],
"msg": "example",
"type": "example"
}
]
}