Validate Coupon Code
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.
Authorizations
Request Body required
POST /api/billing/coupons/validate.
object
Responses
200
Successful Response
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.
422
Validation Error