> ## Documentation Index
> Fetch the complete documentation index at: https://docs.gameball.co/llms.txt
> Use this file to discover all available pages before exploring further.

# 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.

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.

<Info>
  To validate a single coupon code, use [Validate Single Coupon](/api-reference/coupons/validate-single-coupon).
</Info>

<Info>
  **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](/tutorials/experiences/more/omni-channel).
</Info>

<Info>
  **Security**: Requires **apikey** and **secretkey** headers.
</Info>

## 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](#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](#eligibility-checks). The fields that describe a cart fall into two groups, and the group decides how the restriction behaves.

| Group | Fields | Behaviour |
| :- | :- | :- |
| **Cart-level** | `merchantId`, `branchId`, `totalPurchaseAmount` | An order is placed with one merchant at one branch, so these gate the whole basket. They have no per-item form. |
| **Item-level — flat** | `collectionId`, `collectionsIds`, `categories`, `productsIds` | What the basket contains, without saying which item contains what. |
| **Item-level — lines** | `lineItems[]` | The same cart described item by item. |

<Warning>
  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.
</Warning>

<Note>
  `variantId` on a line item is stored and echoed back on the coupon, but is **never evaluated** — there is no variant restriction.
</Note>

### 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

```bash cURL theme={null}
curl --request POST \
  --url 'https://api.gameball.co/api/v4.0/integrations/coupons/validate' \
  --header 'APIKey: <your-api-key>' \
  --header 'secretkey: <your-secret-key>' \
  --header 'Content-Type: application/json' \
  --data '{
    "customerId": "sara-1180",
    "coupons": ["WINTER25", "FREESHIP"],
    "lock": false,
    "merchantId": "cairo-downtown",
    "branchId": "branch-14",
    "totalPurchaseAmount": 640.0,
    "lineItems": [
      {
        "productId": "p-parka-navy",
        "variantId": "v-m",
        "categories": ["Outerwear"],
        "collectionsIds": ["winter-24"]
      }
    ]
  }'
```

## Example response

```json theme={null}
{
  "valid": true,
  "coupons": [
    {
      "code": "winter25",
      "type": "percentage",
      "value": 25.0,
      "usageLimit": -1,
      "limitPerCustomer": 1,
      "startDate": null,
      "expiryDate": "2026-12-31T23:59:59Z",
      "capping": null,
      "minReward": null,
      "minOrderValue": null,
      "entitledProductIds": null,
      "entitledVariantIds": null,
      "entitledCollectionIds": ["winter-24"],
      "entitledMerchantIds": null,
      "entitledBranchIds": null,
      "entitledCategoryIds": ["Outerwear"],
      "couponRulesLogicalOperator": null,
      "combinesWith": {
        "orderDiscounts": true,
        "productDiscounts": true,
        "shippingDiscounts": true
      }
    },
    {
      "code": "freeship",
      "type": "shipping",
      "value": 50.0,
      "usageLimit": 1,
      "limitPerCustomer": 1,
      "startDate": null,
      "expiryDate": null,
      "capping": null,
      "minReward": null,
      "minOrderValue": 500.0,
      "entitledProductIds": null,
      "entitledVariantIds": null,
      "entitledCollectionIds": null,
      "entitledMerchantIds": null,
      "entitledBranchIds": null,
      "entitledCategoryIds": null,
      "couponRulesLogicalOperator": null,
      "combinesWith": {
        "orderDiscounts": false,
        "productDiscounts": false,
        "shippingDiscounts": true
      }
    }
  ],
  "lockReference": null,
  "dateToExpire": null
}
```

## 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.

<Steps>
  <Step title="Is the customer real and active in your Gameball program?">
    Resolved from `customerId`, `email` or `mobile`.
  </Step>

  <Step title="If you referenced an existing lock, is it still usable by this customer?">
    Resolved from `lockReference`.
  </Step>

  <Step title="For each coupon: does it exist, does it apply to this cart, and may this customer use it?">
    Runs the ordered checks below.
  </Step>
</Steps>

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.

| Order | Check | Error |
| :- | :- | :- |
| 1 | The coupon code exists for your account | `404`, code `4004` |
| 2 | The coupon's restrictions match the cart | See [Restriction errors](#restriction-errors) |
| 3 | `totalPurchaseAmount` is present and at or above the coupon's `minOrderValue` | `9017` |
| 4 | The coupon is active | `9014` |
| 5 | The coupon's start date has passed | `9011` |
| 6 | The coupon has not expired | `9013` |
| 7 | The customer is in the coupon's audience, and the coupon is not assigned to a different customer | `9011` |
| 8 | The customer has not hit the coupon's per-customer limit | `9012` |
| 9 | The coupon has not hit its total usage limit | `9012` |
| 10 | The coupon may be combined with the other coupons in the same call | `9011` |

## 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.

| Group | Restrictions | Behaviour |
| :- | :- | :- |
| **Cart-level** | merchant, branch | An order is placed with one merchant at one branch, so these gate the whole basket. They must **always** match. |
| **Item-level** | collection, category, product | These vary from one product to the next, so they are the ones `couponRulesLogicalOperator` arbitrates over. |
| **Amount** | `minOrderValue` | Gates the whole basket like a cart-level restriction, but is set on the coupon's configuration rather than as a rule, so `couponRulesLogicalOperator` never applies to it. |

`couponRulesLogicalOperator` is returned on each coupon object and tells you how that coupon's **item-level** restrictions combine:

| Value | Mode | Meaning |
| :- | :- | :- |
| `null` | Flat | Every item-level restriction must be satisfied by something in the cart — not necessarily the same item. If the cart carries no data at all for one of them, the call is rejected. |
| `1` | AND | One **single item** must satisfy every item-level restriction. Use this for coupons like "25% off Sony audio", which must not be satisfied by a Sony television plus another brand's headphones. |
| `2` | OR | One satisfied restriction is enough. A restriction the cart carries no data for is skipped, never rejected. |

<Note>
  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.
</Note>

### 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.

| Coupon restricts on | Request carries | Outcome for that coupon |
| :- | :- | :- |
| Collection | `customerId` only | Eligibility — valid |
| Collection, no `minOrderValue` | `customerId`, `totalPurchaseAmount` | Eligibility — the amount matches no restriction, so it is ignored |
| Collection | `collectionsIds` | Cart — the collection restriction is enforced |
| Branch | `collectionsIds` | Eligibility — a collection matches no restriction on this coupon |
| Merchant, collection, product | `merchantId`, `collectionsIds` | Cart — all three are enforced, so the absent product is rejected with `9030` |
| Collection, plus `minOrderValue` | `totalPurchaseAmount` | Cart — the amount speaks to the minimum, so the collection restriction is enforced too |

<Note>
  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.
</Note>

<Note>
  **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:

  ```json Request theme={null}
  {
    "customerId": "sara-1180",
    "coupons": ["WINTER25", "FREESHIP"],
    "merchantId": "cairo-downtown",
    "branchId": "branch-14",
    "categories": ["Outerwear"],
    "totalPurchaseAmount": 1450.0
  }
  ```

  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.
</Note>

<Warning>
  `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.
</Warning>

### 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.

| Coupon | Request describes a cart | `totalPurchaseAmount` | Result |
| :- | :- | :- | :- |
| `minOrderValue: 80` | Yes | `100` | Passes |
| `minOrderValue: 80` | Yes | `50` | `9017` |
| `minOrderValue: 80` | Yes | not sent | `9017` |
| `minOrderValue: 80` | No | not sent | Eligibility — not checked |
| `minOrderValue: null` | Either | anything | Not checked — there is no minimum to meet |

<Warning>
  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.
</Warning>

## 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.

<Note>
  A coupon that is already locked still returns `valid: true` when this request does not lock it. Omit `lock`, `lockReference`, and `lockDuration` — for example, do not send `"lock": true`, `"lockReference": "123"`, and `"lockDuration": 1440`. Read `isLocked` on [Get Customer Coupons](/api-reference/customers/management/get-customer-coupons) to see the current lock state.
</Note>

| Setting | Effect |
| :- | :- |
| `lock: false` (default) | Validation only. `lockReference` and `dateToExpire` come back `null`. |
| `lock: true`, no `lockReference` | A new lock is created for the whole list and its reference is returned. |
| `lock: true` with an existing `lockReference` | The existing lock is reused and these coupons are added to it. A coupon already in that lock is overwritten rather than rejected. |
| `lockDuration` | Overrides your account's default lock duration, in minutes. Must be greater than `0` and less than `21600` (15 days). |

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.

```json theme={null}
{
  "customerId": "sara-1180",
  "coupons": ["WINTER25", "FREESHIP"],
  "lock": true,
  "lockDuration": 30
}
```

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.

<Warning>
  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.
</Warning>

## Errors

Errors use the standard Gameball [error envelope](/api-reference/overview/status-error-codes). 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.

```json theme={null}
{
  "code": 9026,
  "type": "TRANSACTION_ERROR",
  "message": "this coupon requires a valid category to be applied. no category was provided.",
  "documentationUrl": "https://docs.gameball.co/api-reference/coupons/validate-multiple-coupons",
  "requestId": "3f9c21b8a4d7"
}
```

### By status

| Status | When |
| :- | :- |
| `400 Bad Request` | Invalid request payload, a missing required field, duplicate codes in `coupons`, or a cart over the size limits — more than 500 `lineItems`, or more than 200 values in any single ID list. |
| `401 Unauthorized` | Missing or invalid API key or secret key. |
| `404 Not Found` | The customer does not exist, a coupon code does not exist, or the `lockReference` does not exist. |
| `422 Unprocessable Entity` | A coupon exists but cannot be applied. |
| `429 Too Many Requests` | Rate limit exceeded. |
| `500 Internal Server Error` | An unexpected error occurred while validating. |

### Customer and request errors

| Code | Status | Meaning |
| :- | :- | :- |
| `7000` | 404 | The customer was not found for the provided ID, email or mobile. |
| `3009` | 422 | The customer exists but is not active in your Gameball program. |
| `3000` | 400 | A required field is missing, `coupons` is empty, or the cart exceeded the size limits. |
| `4004` | 404 | A coupon code or the `lockReference` does not exist. |
| `4000` | 400 | `coupons` contains the same code more than once. |

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

### Coupon eligibility errors

All `422`.

| Code | Meaning |
| :- | :- |
| `9011` | A coupon is not applicable — the customer is outside its audience, it is assigned to a different customer, it has not started yet, or it cannot be combined with the other coupons in the call. |
| `9012` | A usage limit was reached — either a coupon's total limit or this customer's per-customer limit. |
| `9013` | A coupon has expired. |
| `9014` | A coupon is inactive. |
| `9017` | `totalPurchaseAmount` is below a coupon's `minOrderValue`, or the request described a cart for that coupon and omitted it while the coupon has one. |

### 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.

| Code | Restriction | Raised in |
| :- | :- | :- |
| `9018` | Merchant — none provided | Every mode |
| `9019` | Merchant — not eligible | Every mode |
| `9024` | Branch — none provided | Every mode |
| `9025` | Branch — not eligible | Every mode |
| `9020` | Collection — none provided | Flat and AND |
| `9021` | Collection — not eligible | Flat, AND, and single-restriction OR |
| `9026` | Category — none provided | Flat and AND |
| `9027` | Category — not eligible | Flat, AND, and single-restriction OR |
| `9030` | Product — none provided | Flat and AND |
| `9031` | Product — not eligible | Flat, AND, and single-restriction OR |
| `9022` | Several restrictions were checked and none was satisfied | Multi-restriction OR only |
| `9032` | The coupon must be evaluated against individual cart items — resend with `lineItems` | AND only |
| `9033` | `lineItems` was sent, every restriction is met somewhere, but no single item meets them all | AND only |

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

| Code | Status | Meaning |
| :- | :- | :- |
| `9009` | 422 | The `lockReference` is invalid, has expired, or belongs to a different customer. |
| `9010` | 422 | The `lockReference` has already been used. |
| `9015` | 422 | `lockDuration` is negative. |
| `4000` | 400 | `lockDuration` is outside the permitted range — it must be greater than 0 and less than 21,600 minutes. |

<Note>
  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.
</Note>

## 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.

| Area | Change |
| :- | :- |
| Request | New `branchId`, `categories`, `productsIds` and `lineItems[]` |
| Response | New `entitledBranchIds`, `entitledCategoryIds` and `couponRulesLogicalOperator` on each coupon |
| Response | `entitledProductIds` is no longer limited to a single value |
| Errors | New `422` codes: `9024`–`9027` and `9030`–`9033` |

<AccordionGroup>
  <Accordion title="Behaviour changes on existing requests">
    These affect calls you are already making, with no change on your side.

    | What | Before | Now |
    | :- | :- | :- |
    | A coupon restricted by branch, category or product | The restriction was stored but not enforced | Enforced |
    | A coupon with two or more restrictions, where your cart described only one of them | Accepted | Rejected with the matching *required* code |
    | A coupon restricted by merchant, with `couponRulesLogicalOperator` = `2` (OR) | The wrong merchant could still pass if another restriction matched | The merchant must always match |
    | Sending `collectionId` **and** `collectionsIds` together | Only `collectionsIds` was evaluated | Both are evaluated as one list |
    | A validate call sending **no** cart fields at all | Rejected with `9018` | Returns `200` with each coupon's restrictions attached |
  </Accordion>

  <Accordion title="Restriction matching is now decided per coupon">
    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.

    | What | Before | Now |
    | :- | :- | :- |
    | A cart field a coupon does not restrict on | Switched on enforcement of that coupon's other restrictions | Ignored as though it were never sent |
    | `totalPurchaseAmount` alone, coupon has no `minOrderValue` | Enforced every restriction; a restricted coupon was rejected | Eligibility check — valid, with restrictions attached |
    | `totalPurchaseAmount` alone, coupon **has** a `minOrderValue` | Enforced every restriction | Unchanged — the amount speaks to the minimum, so it describes a cart |
    | A cart describing some of a coupon's restrictions | Enforced all of them | Unchanged — the missing ones still return their *required* code |
    | A cart omitting `totalPurchaseAmount`, coupon has a `minOrderValue` | Accepted — the minimum was skipped | Rejected with `9017` |
    | A list mixing restricted and unrestricted coupons | All were judged by the same cart-or-not decision | Each coupon is judged on its own restrictions |

    Error precedence, the `couponRulesLogicalOperator` modes and every error code are unchanged.
  </Accordion>

  <Accordion title="Migration notes">
    * **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.
  </Accordion>
</AccordionGroup>


## OpenAPI

````yaml POST /api/v4.0/integrations/coupons/validate
openapi: 3.1.0
info:
  title: Gameball API
  description: >-
    Gameball REST API v4.0 - Complete API reference for integrating loyalty,
    gamification, and customer engagement features
  version: 4.0.0
servers:
  - url: https://api.gameball.co
security:
  - bearerAuth: []
paths:
  /api/v4.0/integrations/coupons/validate:
    post:
      description: >-
        Validates a list of coupons and checks their eligibility for use by a
        customer. Every coupon must pass - the first failure ends the call and
        nothing is locked. Describe the cart as well, and each coupon is
        additionally matched against its merchant, branch, collection, category
        and product restrictions. Set the lock flag to True to reserve the
        coupons under one lock session. By default the lock flag is False,
        allowing only validation without reserving the coupons.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - customerId
                - coupons
              properties:
                customerId:
                  type: string
                  description: >-
                    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.
                email:
                  type: string
                  description: >-
                    Customer's email address. Required if your account uses
                    email-based channel merging.
                mobile:
                  type: string
                  description: >-
                    Customer's mobile number. Required if your account uses
                    mobile-based channel merging.
                coupons:
                  type: array
                  items:
                    type: string
                  description: A list of coupon codes to validate.
                lock:
                  type: boolean
                  description: >-
                    Indicates whether the request is intended to validate the
                    coupon or to lock it for a future redemption.
                lockReference:
                  type: string
                  description: >-
                    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:
                  type: integer
                  description: >-
                    Represents the number of minutes for which a coupon will be
                    locked if the lock flag is set to True.
                merchantId:
                  type: string
                  description: >-
                    Cart-level. The merchant the order is placed with. Required
                    if the coupon is restricted to specific merchants.
                  example: cairo-downtown
                branchId:
                  type: string
                  description: >-
                    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:
                  type: string
                  description: >-
                    Item-level. A collection the basket touches. Evaluated
                    together with collectionsIds as a single list. Ignored when
                    lineItems is present.
                collectionsIds:
                  type: array
                  items:
                    type: string
                  maxItems: 200
                  description: >-
                    Item-level. Every collection the basket touches. Maximum 200
                    values. Ignored when lineItems is present.
                categories:
                  type: array
                  items:
                    type: string
                  maxItems: 200
                  description: >-
                    Item-level. Every category the basket touches. These are
                    free-text category names, not IDs. Maximum 200 values.
                    Ignored when lineItems is present.
                  example:
                    - Audio
                    - TV
                productsIds:
                  type: array
                  items:
                    type: string
                  maxItems: 200
                  description: >-
                    Item-level. Every product ID in the basket. Maximum 200
                    values. Ignored when lineItems is present.
                lineItems:
                  type: array
                  maxItems: 500
                  description: >-
                    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.
                  items:
                    type: object
                    properties:
                      productId:
                        type: string
                        maxLength: 100
                        description: This line's product ID.
                      variantId:
                        type: string
                        maxLength: 100
                        description: >-
                          This line's variant ID. Stored and echoed back, but
                          never evaluated - there is no variant restriction.
                      categories:
                        type: array
                        items:
                          type: string
                        maxItems: 200
                        description: The categories this product belongs to.
                      collectionsIds:
                        type: array
                        items:
                          type: string
                        maxItems: 200
                        description: The collections this product belongs to.
                totalPurchaseAmount:
                  type: number
                  description: >-
                    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.
      responses:
        '200':
          description: Coupons validated successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  valid:
                    type: boolean
                    description: Indicates whether the coupons are valid.
                  coupons:
                    type: array
                    items:
                      $ref: '#/components/schemas/ValidatedCoupon'
                    description: >-
                      An array containing the details of the coupons that need
                      to be locked or validated in the request.
                  lockReference:
                    type: string
                    description: >-
                      The unique reference code associated with the coupon lock
                      session.
                  dateToExpire:
                    type: string
                    format: date-time
                    description: The exact date and time when the coupon lock will expire.
      security:
        - apiKey: []
          secretKey: []
components:
  schemas:
    ValidatedCoupon:
      type: object
      description: >-
        A coupon as returned by the validate endpoints, including the
        restrictions it carries and how they combine.
      properties:
        code:
          type: string
          description: The coupon code the customer uses. Returned in lower case.
        type:
          type: string
          enum:
            - shipping
            - fixed
            - percentage
            - product
            - percentage-fees
            - fixed-cashback
            - percentage-cashback
          description: >-
            Type of the coupon, such as fixed amount, percentage discount, free
            shipping, cashback or product-specific coupon.
        value:
          type: number
          description: >-
            The coupon's amount - a percentage for percentage types, a currency
            amount for fixed types.
        usageLimit:
          type: number
          description: >-
            The total number of times the coupon can be used across all
            customers. -1 means unlimited.
        limitPerCustomer:
          type: number
          description: >-
            The number of times a single customer can use the coupon. -1 means
            unlimited.
        startDate:
          type: string
          format: date-time
          description: When the coupon becomes usable. null means immediately.
        expiryDate:
          type: string
          format: date-time
          description: When the coupon stops being usable. null means never.
        capping:
          type: number
          description: >-
            The maximum discount the coupon can produce, applied on top of
            percentage calculations.
        minReward:
          type: number
          description: >-
            The minimum discount a customer is guaranteed to receive on a
            percentage-based coupon.
        minOrderValue:
          type: number
          description: >-
            The minimum order value the coupon requires, evaluated against
            totalPurchaseAmount.
        entitledProductIds:
          type: array
          items:
            type: string
          description: Products the coupon applies to. null means unrestricted.
        entitledVariantIds:
          type: array
          items:
            type: string
          description: >-
            Variants named on the coupon. Informational - variants are not
            matched against the cart.
        entitledCollectionIds:
          type: array
          items:
            type: string
          description: Collections the coupon applies to. null means unrestricted.
        entitledMerchantIds:
          type: array
          items:
            type: string
          description: >-
            Merchant external IDs the coupon may be used with. null means
            unrestricted.
        entitledBranchIds:
          type: array
          items:
            type: string
          description: Branches the coupon may be used at. null means unrestricted.
        entitledCategoryIds:
          type: array
          items:
            type: string
          description: >-
            Categories the coupon applies to. These are free-text names, despite
            the field name. null means unrestricted.
        couponRulesLogicalOperator:
          type: integer
          enum:
            - 1
            - 2
          description: >-
            How the coupon's item-level restrictions combine: 1 = AND (one
            single item must satisfy them all), 2 = OR (one satisfied
            restriction is enough), null = flat (each must be satisfied by
            something in the cart, not necessarily the same item).
        combinesWith:
          type: object
          description: Which other discount types this coupon may be combined with.
          properties:
            orderDiscounts:
              type: boolean
              description: >-
                Indicates if the coupon can be combined with order-level
                discounts.
            productDiscounts:
              type: boolean
              description: >-
                Indicates if the coupon can be combined with product-specific
                discounts.
            shippingDiscounts:
              type: boolean
              description: Indicates if the coupon can be combined with shipping discounts.
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
    apiKey:
      type: apiKey
      in: header
      name: apikey
    secretKey:
      type: apiKey
      in: header
      name: secretkey

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.