Skip to main content

Validate Coupon

Check whether a coupon can be redeemed — and what discount it would produce — without consuming it. Use this at checkout to show a customer their saving before they commit.

Endpoint

POST /v1/coupons/validate

Headers

HeaderTypeDescriptionRequired
X-Client-IdStringYour client ID✅ Yes
X-Client-SecretStringYour client secret✅ Yes
Content-TypeStringapplication/json✅ Yes

Request Body

FieldTypeDescriptionRequired
codeStringThe code the customer presented✅ Yes
orderObjectThe order to evaluate against❌ No

Order Object

FieldTypeDescription
amountNumberOrder total
itemsArrayOrder line items
items[].productIdStringProduct identifier
items[].categoryIdStringCategory identifier
items[].quantityIntegerUnits of this line
items[].unitPriceNumberPrice per unit

With an order, applicability is evaluated and the discount is computed. Without one, only the coupon's own state is checked — active, not expired, usage remaining, campaign live. Omit the order when a customer applies a code before a basket exists.

Example Request

curl --location 'https://stage-platform-exlr8-incentives.exlr8now.com/v1/coupons/validate' \
--header 'X-Client-Id: YOUR_CLIENT_ID' \
--header 'X-Client-Secret: YOUR_CLIENT_SECRET' \
--header 'Content-Type: application/json' \
--data '{
"code": "SAVE20-K4M9XQ",
"order": {
"amount": 4500,
"items": [
{
"productId": "sku-9981",
"categoryId": "electronics",
"quantity": 1,
"unitPrice": 4500
}
]
}
}'

Response

Valid — 200 OK

{
"success": true,
"data": {
"couponId": "7c8d9e0f-1a2b-4c3d-9e4f-5a6b7c8d9e0f",
"valid": true,
"result": {
"discountAmount": 900,
"description": "20% off (capped at 2000)"
}
}
}

Not valid — also 200 OK

{
"success": true,
"data": {
"couponId": "7c8d9e0f-1a2b-4c3d-9e4f-5a6b7c8d9e0f",
"valid": false,
"reason": "coupon has expired",
"code": "CV_EXPIRED"
}
}

:::info A rejected coupon is not an error Validation answers a question, and "no" is a valid answer. The HTTP status stays 200 and success stays truebranch on data.valid, not on the status code. Only malformed requests and authentication failures produce non-2xx responses here. :::

Response Fields

FieldTypeDescription
couponIdStringThe coupon the code resolved to
validBooleanWhether redemption would currently succeed
reasonStringHuman-readable explanation when valid is false
codeStringMachine-readable error code when valid is false
resultObjectThe discount that redemption would produce — only when valid and an order was supplied
result.discountAmountNumberMoney off the order
result.freeShippingBooleanPresent for FREE_SHIPPING coupons
result.descriptionStringHuman-readable summary, suitable for display

Show reason to the customer and branch on code. The values it can take are the CV_* codes in Error Codes.

The discount is not a quote

result.discountAmount reflects the order you sent, at that moment. The basket can change before payment, and the coupon can be consumed by another session in between.

Always apply the discount returned by Redeem Coupon, never the one from validation. Redemption recomputes from the order you send it, and that figure is the one that was actually committed. Treat validation as a display aid.

Applicability

When a coupon is scoped to CATEGORIES or PRODUCTS, only matching order lines form the discount base. An order with no matching line is not valid, with code CV_NOT_APPLICABLE.

This is why line items matter: sending only an amount for a scoped coupon produces a spurious rejection, because no line can match. Send the real basket.

Error Responses

Almost every outcome is a 200 with valid: false — including a code that matches no coupon. Only these are true errors:

Error codeHTTPCause
BAD_REQUEST400Missing code, malformed order, or an unknown field
TEMPLATE_NOT_FOUND404An order was supplied but the coupon's template no longer exists
INTERNAL_SERVER_ERROR500Platform fault

Codes returned inside data.code

These accompany valid: false at HTTP 200:

CodeMeaning
CV_INVALIDNo coupon matches this code
CV_ALREADY_USEDThe coupon is not ACTIVE — used, or voided
CV_EXPIREDPast its expiry date
CV_USAGE_LIMIT_REACHEDThe usage limit is exhausted
CV_NOT_APPLICABLEThe discount could not be computed for this order — including an order below the coupon's minimum value
PROGRAM_INACTIVEThe campaign is not ACTIVE
PROGRAM_NOT_FOUNDThe campaign no longer exists

:::caution Validation does not check budgets A coupon can validate cleanly and still fail to redeem. Redemption charges the discount against the template's and the campaign's value budgets, and neither is evaluated here — so TEMPLATE_LIMIT_EXCEEDED and CAMPAIGN_LIMIT_EXCEEDED can only surface at redemption. Handle them there regardless of what validation said. :::