Validate Multiple Coupons
Check whether several coupons can be used together by a customer, optionally against the cart they are about to pay for, and reserve them while the order is completed.
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.
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.
Describing the cart
The same cart is applied to every code incoupons, 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.
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
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 incoupons caused it beyond what the message names.
Is the customer real and active in your Gameball program?
customerId, email or mobile.If you referenced an existing lock, is it still usable by this customer?
lockReference.For each coupon: does it exist, does it apply to this cart, and may this customer use it?
Combining coupons
Each coupon carries acombinesWith 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:
When lineItems is required
If a coupon has two or more item-level restrictions andcouponRulesLogicalOperator 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.
collectionIdandcollectionsIdsare evaluated together as one list.categoriesare free-text names, not IDs — whatever was typed when the coupon was configured, compared as plain strings.variantIdon 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. SendingcollectionsIds alongside a coupon with no collection restriction neither satisfies anything nor triggers anything for that coupon.
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: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.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.
Locking coupons
Setlock 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.
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. SendcustomerId 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.
entitledCollectionIds, entitledCategoryIds, entitledBranchIds, entitledMerchantIds, entitledProductIds and couponRulesLogicalOperator off each coupon to see what the cart will have to satisfy.
Errors
Errors use the standard Gameball error envelope. Themessage 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
4000 rather than a dedicated code. Read the message for the specific reason.Coupon eligibility errors
All422.
Restriction errors
All422. 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.
9018 or 9024, because merchant and branch are checked in every mode.
Lock errors
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.Behaviour changes on existing requests
Behaviour changes on existing requests
Restriction matching is now decided per coupon
Restriction matching is now decided per coupon
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.couponRulesLogicalOperator modes and every error code are unchanged.Migration notes
Migration notes
- Add
branchIdif your coupons are branch-restricted. There is no other way to satisfy a branch restriction. - Choose between
lineItemsand the flat lists. Sending both is not an error, but the flat lists are silently ignored. If you are unsure, sendlineItems— it answers every case the flat lists do, plus the AND case they cannot. - Handle
9032by retrying withlineItems. 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/validateaccepts onlymerchantIdandcollectionId, 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.
Body
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.
A list of coupon codes to validate.
Customer's email address. Required if your account uses email-based channel merging.
Customer's mobile number. Required if your account uses mobile-based channel merging.
Indicates whether the request is intended to validate the coupon or to lock it for a future redemption.
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.
Represents the number of minutes for which a coupon will be locked if the lock flag is set to True.
Cart-level. The merchant the order is placed with. Required if the coupon is restricted to specific merchants.
"cairo-downtown"
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.
"branch-14"
Item-level. A collection the basket touches. Evaluated together with collectionsIds as a single list. Ignored when lineItems is present.
Item-level. Every collection the basket touches. Maximum 200 values. Ignored when lineItems is present.
200Item-level. Every category the basket touches. These are free-text category names, not IDs. Maximum 200 values. Ignored when lineItems is present.
200Item-level. Every product ID in the basket. Maximum 200 values. Ignored when lineItems is present.
200The 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.
500Cart-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
Coupons validated successfully
Indicates whether the coupons are valid.
An array containing the details of the coupons that need to be locked or validated in the request.
The unique reference code associated with the coupon lock session.
The exact date and time when the coupon lock will expire.