Skip to main content
POST
cURL
This API validates several coupon codes for one customer in a single call. You can also describe the cart, so every coupon is checked against what the customer is actually buying, and set lock to true to reserve them all under one lock session. All coupons must pass. The first failure ends the call, and no lock is created — this endpoint never returns a partial result. Available on v4.0 and v4.1. Requires the Gameball Coupon Engine feature on your plan — accounts without it are rejected before any validation runs.
To validate a single coupon code, use Validate Single Coupon.
Channel Merging Available
If your system uses different customer IDs across multiple channels (e.g., online and offline), Gameball’s channel merging feature helps unify customer profiles. By including the customer’s mobile number or email (based on your merging configuration) with each request, Gameball will combine activities into a single profile.
For more information, head to the Omni-Channel Handling Guide.
Security: Requires apikey and secretkey headers.

Describing the cart

The same cart is applied to every code in coupons, and the codes are additionally checked against one another — see Combining coupons. Duplicate codes are rejected outright, so send each code once. A coupon’s restrictions are only enforced when the request describes a cart, and whether it does is decided separately for each coupon from its own restrictions — see Eligibility checks. The fields that describe a cart fall into two groups, and the group decides how the restriction behaves.
Send lineItems or the flat lists, not both. When lineItems is present, the top-level collectionId, collectionsIds, categories and productsIds are ignored for matching — put every category, collection and product on the lines instead. The cart-level fields are always read from the top of the body.
variantId on a line item is stored and echoed back on the coupon, but is never evaluated — there is no variant restriction.

Size limits

lineItems accepts at most 500 entries, and any single ID list at most 200 values. A request over either limit is rejected with 400.

Example request

cURL

Example response

How validation works

A validate call answers three questions in order. The first failure ends the call — you get one error, never a list, and it does not say which code in coupons caused it beyond what the message names.
1

Is the customer real and active in your Gameball program?

Resolved from customerId, email or mobile.
2

If you referenced an existing lock, is it still usable by this customer?

Resolved from lockReference.
3

For each coupon: does it exist, does it apply to this cart, and may this customer use it?

Runs the ordered checks below.
The third question runs these checks in this exact order. The order matters: a coupon that is both expired and wrong for the cart reports the cart problem, because restrictions are checked first.

Combining coupons

Each coupon carries a combinesWith object saying which other discount types it accepts alongside it: orderDiscounts, productDiscounts and shippingDiscounts. When you send several codes in one call, they are checked against one another, and a pair that may not be combined is rejected with 9011. Duplicate codes within coupons are rejected outright — send each code once.

How restrictions are matched

A coupon can carry restrictions in five dimensions, plus a minimum order value. The group a restriction belongs to decides how it behaves. Every coupon in the list is matched against the same cart, each using its own restrictions and its own operator. couponRulesLogicalOperator is returned on each coupon object and tells you how that coupon’s item-level restrictions combine:
Merchant and branch are checked in every mode, including OR. An OR coupon restricted to one merchant still rejects a different merchant, no matter what else in the cart matches.

When lineItems is required

If a coupon has two or more item-level restrictions and couponRulesLogicalOperator is 1 (AND), the flat lists cannot answer the question — they show that the basket holds a Sony item and an audio item, without showing whether one item is both. The call is rejected with code 9032, and you resend the same cart as lineItems. Because one cart serves the whole list, a single AND coupon among the codes is enough to require lineItems for the call.

Matching rules

  • Coupon codes and restriction values are compared case-insensitively, and surrounding whitespace is trimmed.
  • Values within a single restriction are alternatives — matching any one of them is enough.
  • collectionId and collectionsIds are evaluated together as one list.
  • categories are free-text names, not IDs — whatever was typed when the coupon was configured, compared as plain strings.
  • variantId on a line item is stored and echoed back but never evaluated.

Eligibility checks

Whether a call is a cart question or an eligibility question is decided from each coupon’s own restrictions, not from which fields you happened to send — and it is decided per coupon, so one body can be a cart question for one code in the list and an eligibility question for another. The request describes a cart for a given coupon when it carries data for a dimension that coupon restricts on. When it does, every restriction that coupon holds is enforced — including ones the request said nothing about. When it does not, that coupon is answered as an eligibility question — “does this customer hold it, and is it live?” — and its restrictions are reported back instead, so you can discover what to send next. Every other check (active, dates, audience, limits, combinability) runs either way. A field a coupon does not restrict on is ignored as though it were never sent. Sending collectionsIds alongside a coupon with no collection restriction neither satisfies anything nor triggers anything for that coupon.
The call still fails as a whole on the first coupon that fails. A coupon answered as an eligibility question does not shield the others in the list from having their own restrictions enforced.
A restriction you send that a coupon does not carry is ignored for that coupon.Request data is matched to restrictions by type. collectionsIds is only ever checked against a collection restriction, merchantId against a merchant restriction, totalPurchaseAmount against minOrderValue, and so on. When a coupon holds no restriction of that type, the field is dropped before matching for that coupon — it cannot satisfy a different restriction, and it cannot cause a rejection.Because the list is judged coupon by coupon, the same field can be enforced for one code and discarded for another. Below, WINTER25 restricts on collection and FREESHIP carries no restrictions at all:
Request
Neither coupon restricts on merchant, branch or category, and neither carries a minOrderValue, so every cart field in that body is discarded. Both coupons are answered as eligibility questions and the call returns valid: true. Add collectionsIds and it becomes a cart question for WINTER25 only — FREESHIP still has nothing to check.
valid: true on an eligibility call means “this customer may use these coupons”, not “these coupons apply to this order”. Read entitledCollectionIds, entitledCategoryIds, entitledBranchIds, entitledMerchantIds, entitledProductIds, minOrderValue and couponRulesLogicalOperator off each coupon to see what the cart will have to satisfy, then validate again with those fields populated before you complete the order. That second call can reject a coupon that just came back valid.

The minimum order value

minOrderValue behaves like any other restriction. totalPurchaseAmount is matched against it and nothing else, so for a coupon with no minOrderValue the amount is ignored outright — it cannot make the call a cart question for that coupon. For one that has a minimum, the amount is enough to describe a cart on its own, and the minimum is then enforced whether or not you sent a value.
A cart that omits totalPurchaseAmount no longer slips past a coupon that has a minOrderValue. Both “you sent too little” and “you sent nothing” return 9017; read the message to tell them apart.

Locking coupons

Set lock to true to reserve the coupons when they all validate. The response carries one lockReference covering the whole list, plus a dateToExpire, and those coupons cannot be validated-and-locked by another order until the lock expires or is consumed. When you pass an existing lockReference it is checked before anything else, and must be unused, unexpired and belong to the same customer — otherwise the call fails with 9009 or 9010. A lock is only ever created after every coupon has passed validation, so a returned lockReference always means all of them were usable by that customer at that moment. If any one coupon fails, nothing is locked.

Locking without a cart

You can lock on an eligibility check. Send customerId and coupons with lock: true and no cart fields: coupons that carry restrictions still validate — their restrictions are reported back rather than enforced — and they are still locked against that customer. The lock is built from the coupon codes and the customer alone; the cart plays no part in it.
This is the intended way to reserve coupons before the basket is known. Read entitledCollectionIds, entitledCategoryIds, entitledBranchIds, entitledMerchantIds, entitledProductIds and couponRulesLogicalOperator off each coupon to see what the cart will have to satisfy.
A cart-less lock does not prove the coupons fit the eventual cart. Restrictions were never evaluated, and nothing re-evaluates them later — not when the lock is reused, and not when the order is placed. Validate again with the full cart, reusing the same lockReference, before you complete the order: that re-runs the restriction checks against the real basket and keeps the same lock.

Errors

Errors use the standard Gameball error envelope. The message names the specific values that failed, so it is the most useful field for diagnosing a rejection. It is returned in lower case.

By status

Customer and request errors

Duplicate codes are currently reported as the generic 4000 rather than a dedicated code. Read the message for the specific reason.

Coupon eligibility errors

All 422.

Restriction errors

All 422. A required code means the coupon restricts that dimension and your request described nothing for it. A not eligible code means you described it and the value was not on the coupon’s list. An OR coupon never returns a required code for a collection, category or product — a restriction with no cart data is skipped. It can still return 9018 or 9024, because merchant and branch are checked in every mode.

Lock errors

A lockDuration outside the permitted range is currently reported as the generic 4000 rather than the dedicated 9016. Read the message for the specific reason.

What changed

This endpoint previously matched a coupon against two things only: the merchant and the collection. It now matches against four, and the request can describe the cart line by line.
These affect calls you are already making, with no change on your side.
Previously any cart field switched on enforcement of every restriction held by every coupon in the list. A body carrying nothing but customerId and totalPurchaseAmount was enough to have a collection-restricted coupon rejected with 9020, for a dimension the caller never meant to describe.Enforcement is now decided from each coupon’s own restrictions, coupon by coupon. These affect calls you are already making, with no change on your side.Error precedence, the couponRulesLogicalOperator modes and every error code are unchanged.
  • Add branchId if your coupons are branch-restricted. There is no other way to satisfy a branch restriction.
  • Choose between lineItems and the flat lists. Sending both is not an error, but the flat lists are silently ignored. If you are unsure, send lineItems — it answers every case the flat lists do, plus the AND case they cannot.
  • Handle 9032 by retrying with lineItems. It is not a permanent failure; it is a request for more detail about the same cart. One AND coupon in the list is enough to trigger it.
  • API v3 cannot satisfy the new restrictions. POST /api/v3.0/integrations/coupons/validate accepts only merchantId and collectionId, so a coupon restricted by branch, category or product is rejected with no field available to fix it. Move to v4 before configuring those restrictions.

Authorizations

apikey
string
header
required
secretkey
string
header
required

Body

application/json
customerId
string
required

Unique identifier for the customer that you can reference across the customer's whole lifetime. Could be a database ID, random string, email or anything that uniquely identifies the customer.

coupons
string[]
required

A list of coupon codes to validate.

email
string

Customer's email address. Required if your account uses email-based channel merging.

mobile
string

Customer's mobile number. Required if your account uses mobile-based channel merging.

lock
boolean

Indicates whether the request is intended to validate the coupon or to lock it for a future redemption.

lockReference
string

Required only if the lock flag is set to True and you need to validate and lock a new or updated list of coupons within an existing lock session.

lockDuration
integer

Represents the number of minutes for which a coupon will be locked if the lock flag is set to True.

merchantId
string

Cart-level. The merchant the order is placed with. Required if the coupon is restricted to specific merchants.

Example:

"cairo-downtown"

branchId
string

Cart-level. The branch or outlet the order is placed at. Required if the coupon is restricted to specific branches. There is no plural or per-item form.

Example:

"branch-14"

collectionId
string

Item-level. A collection the basket touches. Evaluated together with collectionsIds as a single list. Ignored when lineItems is present.

collectionsIds
string[]

Item-level. Every collection the basket touches. Maximum 200 values. Ignored when lineItems is present.

Maximum array length: 200
categories
string[]

Item-level. Every category the basket touches. These are free-text category names, not IDs. Maximum 200 values. Ignored when lineItems is present.

Maximum array length: 200
Example:
productsIds
string[]

Item-level. Every product ID in the basket. Maximum 200 values. Ignored when lineItems is present.

Maximum array length: 200
lineItems
object[]

The cart described item by item. Maximum 500 entries. Required for a coupon that must satisfy two or more item-level restrictions on the same item. When lineItems is present, the flat collectionId, collectionsIds, categories and productsIds fields are ignored for matching.

Maximum array length: 500
totalPurchaseAmount
number

Cart-level. The total value of the purchase the coupon will be applied to. Checked against the coupon's minOrderValue. Sending this field describes a cart only when the coupon carries a minOrderValue; for those coupons it switches on full restriction enforcement, and omitting it is rejected the same way a value below the minimum is.

Response

200 - application/json

Coupons validated successfully

valid
boolean

Indicates whether the coupons are valid.

coupons
object[]

An array containing the details of the coupons that need to be locked or validated in the request.

lockReference
string

The unique reference code associated with the coupon lock session.

dateToExpire
string<date-time>

The exact date and time when the coupon lock will expire.