# Promotions

The Rule Engine enables you to set up almost any kind of promotions to boost your sales, from the simplest to the most complex, using the proper [order rules](https://docs.commercelayer.io/rules-engine/core-api-integration/order-rules).&#x20;

To make the Rule Engine work in conjunction with promotions, you need to leverage Commerce Layer [flex promotions](https://app.gitbook.com/s/RWJeylueWkzLadK710XZ/flex_promotions). All you need to do is [create](https://app.gitbook.com/s/RWJeylueWkzLadK710XZ/flex_promotions/create) a new flex promotion and give it a meaningful name, specifying the activation and expiration dates as the values of the related attributes and the order rules that you want to be implemented in the `rules` object. The discount defined by the related actions will be automatically applied to the orders that match the given conditions.

### Example

The following request creates a flex promotion that applies a 10% discount to the whole order if it contains at least 2 units of a promotional product:

<pre class="language-sh"><code class="lang-sh">curl -g -X POST \
  'https://yourdomain.commercelayer.io/api/flex_promotions' \
  -H 'Accept: application/vnd.api+json' \
  -H 'Authorization: Bearer your-access-token' \
  -H 'Content-Type: application/vnd.api+json' \
  -d '{
  "data": {
    "type": "flex_promotions",
    "attributes": {
      "name": "Personal promotion",
      "starts_at": "2018-01-01T12:00:00.000Z",
      "expires_at": "2018-01-02T12:00:00.000Z",
      "rules": {
<strong>        "rules": [
</strong>          {
            "name": "10% Promo T-shirt x2",
<strong>            "conditions": [
</strong>              {
                "field": "order.line_items.sku.code",
                "matcher": "eq",
<strong>                "value": "PROMOTSHIRT",
</strong>                "nested": {
                  "conditions": [
                    {
                      "field": "order.line_items.quantity",
                      "matcher": "gteq",
<strong>                      "value": 2
</strong>                    }
                  ]
                }
              }
            ],
<strong>            "actions": [
</strong>              {
                "type": "percentage",
                "selector": "order",
<strong>                "value": "0.1"
</strong>              }
            ]
          }
        ]
      }
    }
  }
}'
</code></pre>

## Available actions

The following [action types](https://docs.commercelayer.io/rules-engine/actions/types) are enabled on the flex promotions endpoint:

{% content-ref url="../actions/types/percentage" %}
[percentage](https://docs.commercelayer.io/rules-engine/actions/types/percentage)
{% endcontent-ref %}

{% content-ref url="../actions/types/fixed-price" %}
[fixed-price](https://docs.commercelayer.io/rules-engine/actions/types/fixed-price)
{% endcontent-ref %}

{% content-ref url="../actions/types/fixed-amount" %}
[fixed-amount](https://docs.commercelayer.io/rules-engine/actions/types/fixed-amount)
{% endcontent-ref %}

{% content-ref url="../actions/types/free-gift" %}
[free-gift](https://docs.commercelayer.io/rules-engine/actions/types/free-gift)
{% endcontent-ref %}

{% content-ref url="../actions/types/buy-x-pay-y" %}
[buy-x-pay-y](https://docs.commercelayer.io/rules-engine/actions/types/buy-x-pay-y)
{% endcontent-ref %}

{% content-ref url="../actions/types/every-x-discount-y" %}
[every-x-discount-y](https://docs.commercelayer.io/rules-engine/actions/types/every-x-discount-y)
{% endcontent-ref %}

## Check and validation

Once you've created a flex promotion (and defined one or more order rules) you can validate it against a specific order to check the rules outcomes (e.g. if and how the related discount will be applied in the case in point). You just need the flex promotion and the order IDs so that you can send (using a [sales channel](https://app.gitbook.com/s/-LgByaSP8eKjad-MIuHE/api-credentials#sales-channel) or [integration](https://app.gitbook.com/s/-LgByaSP8eKjad-MIuHE/api-credentials#integration) access token) a `GET` request to the endpoint:

```http
https://{{your_domain}}.commercelayer.io/api/flex_promotions/{{promo_id}}/check/{{order_id}}
```

Where:

* &#x20;`{{your_domain}}` is the unique subdomain of a Commerce Layer organization for which the Rules Engine is enabled.
* `{{promo_id}}` is the ID of the flex promotion whose outcomes you want to check.
* `{{order_id}}` is the ID of the order against which you want to validate the flex promotion.

### Examples

#### Matching order

Let's assume you want to check the flex promotion identified by the ID `ZOqmZfjPOp` against the order identified by the ID `NZrQhpRpRZ`:

{% tabs %}
{% tab title="Promotion" %}
The following flex contains a rule according to which the orders with a total amount greater than 5000 cents will be discounted by 10%:

<pre class="language-json"><code class="lang-json">{
  "data": {
<strong>    "id": "ZOqmZfjPOp",
</strong><strong>    "type": "flex_promotions",
</strong>    "links": { ... },
    "attributes": {
      "name": "Discount 10% if total greater than 5000 cents",
      "type": "flex_promotions",
      "exclusive": false,
      "priority": null,
      "starts_at": "2025-01-14T14:24:14.975Z",
      "expires_at": "2025-12-14T14:10:51.660Z",
      "total_usage_limit": null,
      "total_usage_count": 0,
      "active": true,
      "status": "active",
<strong>      "rules": {
</strong><strong>        "rules": [
</strong><strong>          {
</strong><strong>            "id": "b0dd0bbf-7938-3d99-f556-14ba2b67c5fe",
</strong><strong>            "name": "Discount 10% if total greater than 5000 cents",
</strong><strong>            "conditions": [
</strong><strong>              {
</strong><strong>                "field": "order.total_amount_cents",
</strong><strong>                "group": "bc285595-bae4-53fd-7471-41ef3048c220",
</strong><strong>                "value": 5000,
</strong><strong>                "matcher": "gt"
</strong><strong>              }
</strong><strong>            ],            
</strong><strong>            "actions": [
</strong><strong>              {
</strong><strong>                "type": "percentage",
</strong><strong>                "value": 0.1,
</strong><strong>                "groups": [ "bc285595-bae4-53fd-7471-41ef3048c220" ],
</strong><strong>                "selector": "order"
</strong><strong>              }
</strong><strong>            ]
</strong><strong>          }
</strong><strong>        ]
</strong><strong>      },
</strong>      "disabled_at": null,
      "created_at": "2025-01-14T14:24:15.234Z",
      "updated_at": "2025-01-14T14:40:50.884Z",
      "reference": null,
      "reference_origin": null,
      "rule_outcomes": {},
      "resource_payload": {},
      "metadata": {}
    },
    "relationships": { ... },
    "meta": { ... }
  }
}
</code></pre>

{% endtab %}

{% tab title="Order" %}
The following order has an amount greater than 5000 cents, thus matches the rule's conditions:

<pre class="language-json"><code class="lang-json">{
  "data": {
<strong>    "id": "NZrQhpRpRZ",
</strong><strong>    "type": "orders",
</strong>    "links": { ... },
    "attributes": {
      "number": 2596335,
      "affiliate_code": null,
      "autorefresh": true,
      "place_async": false,
      "status": "pending",
      "payment_status": "unpaid",
      "fulfillment_status": "unfulfilled",
      "guest": true,
      "editable": true,
      "customer_email": "jane@example.com",
      "language_code": "en",
      "currency_code": "USD",
      "tax_included": true,
      "tax_rate": null,
      "freight_taxable": null,
      "payment_method_taxable": null,
      "adjustment_taxable": null,
      "gift_card_taxable": null,
      "requires_billing_info": false,
      "country_code": null,
      "shipping_country_code_lock": null,
      "coupon_code": null,
      "gift_card_code": null,
      "gift_card_or_coupon_code": null,
      "subtotal_amount_cents": 20100,
      "subtotal_amount_float": 201.0,
      "formatted_subtotal_amount": "$201.00",
      "shipping_amount_cents": 0,
      "shipping_amount_float": 0.0,
      "formatted_shipping_amount": "$0.00",
      "payment_method_amount_cents": 0,
      "payment_method_amount_float": 0.0,
      "formatted_payment_method_amount": "$0.00",
      "discount_amount_cents": -2010,
      "discount_amount_float": -20.1,
      "formatted_discount_amount": "-$20.10",
      "adjustment_amount_cents": 0,
      "adjustment_amount_float": 0.0,
      "formatted_adjustment_amount": "$0.00",
      "gift_card_amount_cents": 0,
      "gift_card_amount_float": 0.0,
      "formatted_gift_card_amount": "$0.00",
      "total_tax_amount_cents": 0,
      "total_tax_amount_float": 0.0,
      "formatted_total_tax_amount": "$0.00",
      "subtotal_tax_amount_cents": 0,
      "subtotal_tax_amount_float": 0.0,
      "formatted_subtotal_tax_amount": "$0.00",
      "shipping_tax_amount_cents": 0,
      "shipping_tax_amount_float": 0.0,
      "formatted_shipping_tax_amount": "$0.00",
      "payment_method_tax_amount_cents": 0,
      "payment_method_tax_amount_float": 0.0,
      "formatted_payment_method_tax_amount": "$0.00",
      "adjustment_tax_amount_cents": 0,
      "adjustment_tax_amount_float": 0.0,
      "formatted_adjustment_tax_amount": "$0.00",
<strong>      "total_amount_cents": 18090,
</strong><strong>      "total_amount_float": 180.9,
</strong><strong>      "formatted_total_amount": "$180.90",
</strong>      "total_taxable_amount_cents": 20100,
      "total_taxable_amount_float": 201.0,
      "formatted_total_taxable_amount": "$201.00",
      "subtotal_taxable_amount_cents": 20100,
      "subtotal_taxable_amount_float": 201.0,
      "formatted_subtotal_taxable_amount": "$201.00",
      "shipping_taxable_amount_cents": 0,
      "shipping_taxable_amount_float": 0.0,
      "formatted_shipping_taxable_amount": "$0.00",
      "payment_method_taxable_amount_cents": 0,
      "payment_method_taxable_amount_float": 0.0,
      "formatted_payment_method_taxable_amount": "$0.00",
      "adjustment_taxable_amount_cents": 0,
      "adjustment_taxable_amount_float": 0.0,
      "formatted_adjustment_taxable_amount": "$0.00",
      "total_amount_with_taxes_cents": 18090,
      "total_amount_with_taxes_float": 180.9,
      "formatted_total_amount_with_taxes": "$180.90",
      "fees_amount_cents": 0,
      "fees_amount_float": 0.0,
      "formatted_fees_amount": "$0.00",
      "duty_amount_cents": null,
      "duty_amount_float": null,
      "formatted_duty_amount": null,
      "place_total_amount_cents": null,
      "place_total_amount_float": null,
      "formatted_place_total_amount": null,
      "skus_count": 3,
      "line_item_options_count": 0,
      "shipments_count": 0,
      "tax_calculations_count": 0,
      "validations_count": 0,
      "errors_count": 0,
      "payment_source_details": null,
      "token": "123xy080e1d6b1340805e9a1c883d2b2",
      "cart_url": null,
      "return_url": null,
      "privacy_url": null,
      "checkout_url": null,
      "placed_at": null,
      "approved_at": null,
      "cancelled_at": null,
      "payment_updated_at": null,
      "fulfillment_updated_at": null,
      "refreshed_at": null,
      "archived_at": null,
      "subscription_created_at": null,
      "circuit_state": null,
      "circuit_failure_count": null,
      "created_at": "2025-01-14T14:42:08.059Z",
      "updated_at": "2025-01-14T14:45:33.019Z",
      "reference": null,
      "reference_origin": null,
      "metadata": {}
    },
    "relationships": { ... },
    "meta": { ... }
  }
}
</code></pre>

{% endtab %}
{% endtabs %}

Let's call the validation endpoint to check the rule's outcomes:

{% tabs %}
{% tab title="Request" %}
The following request validates the rule identified by the ID `ZOqmZfjPOp` against the order identified by the ID `NZrQhpRpRZ`:

```sh
curl -g -X GET \
  'http://yourdomain.commercelayer.io/api/flex_promotions/ZOqmZfjPOp/check/NZrQhpRpRZ' \
  -H 'Authorization: Bearer your-access-token' \
  -H 'Accept: application/vnd.api+json'
```

{% endtab %}

{% tab title="Response" %}
The outcomes payload clearly shows that the order matches the rule, listing the order ID `conditions.matches` array and displaying the action that will be applied on the matching order within the `actions.resources` array:

<pre class="language-json"><code class="lang-json">{
  "data": [
    {
      "id": "b0dd0bbf-7938-3d99-f556-14ba2b67c5fe",
      "name": "Discount 10% if total greater than 5000 cents",
      "priority": 0,
<strong>      "match": true,
</strong>      "conditions_logic": "and",
      "conditions": [
        {
          "field": "order.total_amount_cents",
          "group": "bc285595-bae4-53fd-7471-41ef3048c220",
          "value": 5000,
          "matcher": "gt",
<strong>          "match": true,
</strong>          "matches": [ 
            { 
<strong>              "order": "NZrQhpRpRZ",
</strong>              "group": "bc285595-bae4-53fd-7471-41ef3048c220"
            } 
          ],
          "scope": "any"
        }
      ],
      "actions": [
        {
          "resources": [
            {
              "resource_type": "orders",
              "id": "NZrQhpRpRZ",
              "group": "bc285595-bae4-53fd-7471-41ef3048c220",
              "quantity": null,
<strong>              "value": 0.1,
</strong><strong>              "action_type": "percentage"
</strong>            }
          ]
        }
      ]
    }
  ]
}
</code></pre>

{% endtab %}
{% endtabs %}

#### Non-matching order

Let's assume you want to check the same flex promotion against the order identified by the ID `PgojhLQLQR` instead:

{% tabs %}
{% tab title="Promotion" %}
The following flex contains a rule according to which the orders with a total amount greater than 5000 cents will be discounted by 10%:

<pre class="language-json"><code class="lang-json">{
  "data": {
<strong>    "id": "ZOqmZfjPOp",
</strong><strong>    "type": "flex_promotions",
</strong>    "links": { ... },
    "attributes": {
      "name": "Discount 10% if total greater than 5000 cents",
      "type": "flex_promotions",
      "exclusive": false,
      "priority": null,
      "starts_at": "2025-01-14T14:24:14.975Z",
      "expires_at": "2025-12-14T14:10:51.660Z",
      "total_usage_limit": null,
      "total_usage_count": 0,
      "active": true,
      "status": "active",
<strong>      "rules": {
</strong><strong>        "rules": [
</strong><strong>          {
</strong><strong>            "id": "b0dd0bbf-7938-3d99-f556-14ba2b67c5fe",
</strong><strong>            "name": "Discount 10% if total greater than 5000 cents",
</strong><strong>            "conditions": [
</strong><strong>              {
</strong><strong>                "field": "order.total_amount_cents",
</strong><strong>                "group": "bc285595-bae4-53fd-7471-41ef3048c220",
</strong><strong>                "value": 5000,
</strong><strong>                "matcher": "gt"
</strong><strong>              }
</strong><strong>            ],            
</strong><strong>            "actions": [
</strong><strong>              {
</strong><strong>                "type": "percentage",
</strong><strong>                "value": 0.1,
</strong><strong>                "groups": [ "bc285595-bae4-53fd-7471-41ef3048c220" ],
</strong><strong>                "selector": "order"
</strong><strong>              }
</strong><strong>            ]
</strong><strong>          }
</strong><strong>        ]
</strong><strong>      },
</strong>      "disabled_at": null,
      "created_at": "2025-01-14T14:24:15.234Z",
      "updated_at": "2025-01-14T14:40:50.884Z",
      "reference": null,
      "reference_origin": null,
      "rule_outcomes": {},
      "resource_payload": {},
      "metadata": {}
    },
    "relationships": { ... },
    "meta": { ... }
  }
}
</code></pre>

{% endtab %}

{% tab title="Order" %}
The following order has an amount lower than 5000 cents, thus doesn't match the rule's conditions:

<pre class="language-json"><code class="lang-json">{
  "data": {
<strong>    "id": "PgojhLQLQR",
</strong><strong>    "type": "orders",
</strong>    "links": { ... },
    "attributes": {
      "number": 2345678,
      "affiliate_code": null,
      "autorefresh": true,
      "place_async": false,
      "status": "pending",
      "payment_status": "unpaid",
      "fulfillment_status": "unfulfilled",
      "guest": true,
      "editable": true,
      "customer_email": "john@example.com",
      "language_code": "en",
      "currency_code": "USD",
      "tax_included": true,
      "tax_rate": null,
      "freight_taxable": null,
      "payment_method_taxable": null,
      "adjustment_taxable": null,
      "gift_card_taxable": null,
      "requires_billing_info": false,
      "country_code": null,
      "shipping_country_code_lock": null,
      "coupon_code": null,
      "gift_card_code": null,
      "gift_card_or_coupon_code": null,
      "subtotal_amount_cents": 3000,
      "subtotal_amount_float": 30.0,
      "formatted_subtotal_amount": "$30.00",
      "shipping_amount_cents": 0,
      "shipping_amount_float": 0.0,
      "formatted_shipping_amount": "$0.00",
      "payment_method_amount_cents": 0,
      "payment_method_amount_float": 0.0,
      "formatted_payment_method_amount": "$0.00",
      "discount_amount_cents": 0,
      "discount_amount_float": 0.0,
      "formatted_discount_amount": "$0.00",
      "adjustment_amount_cents": 0,
      "adjustment_amount_float": 0.0,
      "formatted_adjustment_amount": "$0.00",
      "gift_card_amount_cents": 0,
      "gift_card_amount_float": 0.0,
      "formatted_gift_card_amount": "$0.00",
      "total_tax_amount_cents": 0,
      "total_tax_amount_float": 0.0,
      "formatted_total_tax_amount": "$0.00",
      "subtotal_tax_amount_cents": 0,
      "subtotal_tax_amount_float": 0.0,
      "formatted_subtotal_tax_amount": "$0.00",
      "shipping_tax_amount_cents": 0,
      "shipping_tax_amount_float": 0.0,
      "formatted_shipping_tax_amount": "$0.00",
      "payment_method_tax_amount_cents": 0,
      "payment_method_tax_amount_float": 0.0,
      "formatted_payment_method_tax_amount": "$0.00",
      "adjustment_tax_amount_cents": 0,
      "adjustment_tax_amount_float": 0.0,
      "formatted_adjustment_tax_amount": "$0.00",
<strong>      "total_amount_cents": 3000,
</strong><strong>      "total_amount_float": 30.0,
</strong><strong>      "formatted_total_amount": "$30.00",
</strong><strong>      "total_taxable_amount_cents": 3000,
</strong>      "total_taxable_amount_float": 30.0,
      "formatted_total_taxable_amount": "$30.00",
      "subtotal_taxable_amount_cents": 3000,
      "subtotal_taxable_amount_float": 30.0,
      "formatted_subtotal_taxable_amount": "$30.00",
      "shipping_taxable_amount_cents": 0,
      "shipping_taxable_amount_float": 0.0,
      "formatted_shipping_taxable_amount": "$0.00",
      "payment_method_taxable_amount_cents": 0,
      "payment_method_taxable_amount_float": 0.0,
      "formatted_payment_method_taxable_amount": "$0.00",
      "adjustment_taxable_amount_cents": 0,
      "adjustment_taxable_amount_float": 0.0,
      "formatted_adjustment_taxable_amount": "$0.00",
      "total_amount_with_taxes_cents": 3000,
      "total_amount_with_taxes_float": 30.0,
      "formatted_total_amount_with_taxes": "$30.00",
      "fees_amount_cents": 0,
      "fees_amount_float": 0.0,
      "formatted_fees_amount": "$0.00",
      "duty_amount_cents": null,
      "duty_amount_float": null,
      "formatted_duty_amount": null,
      "place_total_amount_cents": null,
      "place_total_amount_float": null,
      "formatted_place_total_amount": null,
      "skus_count": 2,
      "line_item_options_count": 0,
      "shipments_count": 0,
      "tax_calculations_count": 0,
      "validations_count": 0,
      "errors_count": 0,
      "payment_source_details": null,
      "token": "a22478a9225aa3bdb5fbb9f9e21bbc6d",
      "cart_url": null,
      "return_url": null,
      "terms_url": null,
      "privacy_url": null,
      "checkout_url": null,
      "placed_at": null,
      "approved_at": null,
      "cancelled_at": null,
      "payment_updated_at": null,
      "fulfillment_updated_at": null,
      "refreshed_at": null,
      "archived_at": null,
      "subscription_created_at": null,
      "circuit_state": null,
      "circuit_failure_count": null,
      "created_at": "2025-01-14T14:43:26.947Z",
      "updated_at": "2025-01-14T14:44:45.220Z",
      "reference": null,
      "reference_origin": null,
      "metadata": {}
    },
    "relationships": { ... },
    "versions": { ... },
    "meta": { ... }
  }
}
</code></pre>

{% endtab %}
{% endtabs %}

Let's call the validation endpoint to check the rule's outcomes:

{% tabs %}
{% tab title="Request" %}
The following request validates the rule identified by the ID `ZOqmZfjPOp` against the order identified by the ID `PgojhLQLQR`:

```sh
curl -g -X GET \
  'http://yourdomain.commercelayer.io/api/flex_promotions/ZOqmZfjPOp/check/PgojhLQLQR' \
  -H 'Authorization: Bearer your-access-token' \
  -H 'Accept: application/vnd.api+json'
```

{% endtab %}

{% tab title="Response" %}
The outcomes payload clearly shows that the order doesn't match the rule, resulting in empty `conditions.matches` array — consequently, the `actions` array is also empty and the promotions won't be applied:

<pre class="language-json"><code class="lang-json">{
  "data": [
    {
      "id": "b0dd0bbf-7938-3d99-f556-14ba2b67c5fe",
      "name": "Discount 10% if total greater than 5000 cents",
      "priority": 0,
<strong>      "match": false,
</strong>      "conditions_logic": "and",
      "conditions": [
        {
          "field": "order.total_amount_cents",
          "group": "bc285595-bae4-53fd-7471-41ef3048c220",
          "value": 5000,
          "matcher": "gt",
<strong>          "match": false,
</strong><strong>          "matches": [],
</strong>          "scope": "any"
        }
      ],
<strong>      "actions": []
</strong>    }
  ]
}
</code></pre>

{% endtab %}
{% endtabs %}

## Use cases

If you need to check more examples of promotions based on the Rule Engine, feel free to explore the [related section](https://docs.commercelayer.io/rules-engine/use-cases/promotions) of this documentation:

{% content-ref url="../use-cases/promotions" %}
[promotions](https://docs.commercelayer.io/rules-engine/use-cases/promotions)
{% endcontent-ref %}
