Promotion Management API
Overview
The Promotion Management API provides a comprehensive set of endpoints for managing promotions in the Nilo platform. This API allows you to create, update, and manage different types of promotions including simple discounts, multiple discounts, individual bonuses, and crossed bonuses.
Understanding Promotions
Promotion Structure
- Internal Code: Unique identifier for the promotion (same as in your ERP)
- Name: Descriptive name for the promotion
- Type: Type of promotion (DISCOUNT_SIMPLE, DISCOUNT_MULTIPLE, BONUS_INDIVIDUAL, BONUS_CROSSED)
- Status: Enabled/disabled state of the promotion
- Validity Period: Start and end dates for the promotion
- Tiers: Conditions for discounts or bonuses (quantity-based or amount-based)
- Products: Products included in the promotion
- Groupers: Promotion groupers that determine which stores see the promotion
- Discount application mode: For discount promotions, whether the discount applies to all qualifying units or only to complete multiples of the tier minimum
Promotion Types Mapping
| Promotion Type | Nilo API Type | Endpoint |
|---|---|---|
| Direct Bonus (same product) | BONUS_INDIVIDUAL | PUT /promotion/bonus/individual |
| Crossed Bonus (different product) | BONUS_CROSSED | PUT /promotion/bonus/crossed |
| Group Discount (product combination) | DISCOUNT_MULTIPLE | PUT /promotion/discount/multiple |
| Tiered Simple Discount | DISCOUNT_SIMPLE | PUT /promotion/discount/simple |
| Combos with discount | DISCOUNT_MULTIPLE | PUT /promotion/discount/multiple |
| Combos with bonus | BONUS_CROSSED | PUT /promotion/bonus/crossed |
Important Considerations
The API defines certain fields as mutually exclusive:
- Quantity vs Amount condition: Don't send
discountTiersData/bonusTierDataanddiscountTiersDataCash/bonusTierDataCashsimultaneously - Discount type: Specify
discountPercentageORdiscountAmount, not both
Sending both fields will result in an error.
-
Tier Types:
- Quantity-based (
discountTiersData/bonusTierData): Discount applies based on quantity purchased (e.g., buy 5+ units get 10% off) - Amount-based (
discountTiersDataCash/bonusTierDataCash): Discount applies based on purchase amount (e.g., spend $100+ get 10% off)
- Quantity-based (
-
Discount Configuration:
- Can be percentage-based (
discountPercentage) or fixed amount (discountAmount) - Cannot mix percentage and fixed amount in the same tier
- Multiple tiers can be configured with different thresholds
- Can be percentage-based (
-
Image Management:
- The
imagefield expects a public and accessible URL - Nilo will download, store, and distribute through CDN
- Use high-quality images - optimized versions are auto-generated for different devices
- The
-
Date Handling:
- All dates must be in YYYY-MM-DD format
- Both start and end dates are required
- End date must be after start date
-
Discount application mode (
discountApplicationMode):- Optional. Applies to
PUT /promotion/discount/simple,PUT /promotion/discount/multiple, and nested items inPOST /batch/promotion(upsert.discount.simple[]/multiple[]) ALL_QUALIFYING(default): the discount applies to all units that meet the tierCOMPLETE_MULTIPLES: the discount applies only to complete multiples of the tiermin. Example:min= 10 and the cart has 21 boxes → 20 boxes discounted, 1 at list price- If omitted on create, the supplier default is used (then
ALL_QUALIFYING). If omitted on update, the existing value is kept - Unknown values are rejected. Omitting the field is valid
- Optional. Applies to
Single Promotion Operations
Get Promotion Details
/promotion/{code}Retrieve detailed information about a specific promotion.
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| code | string | Yes | Internal code of the promotion |
Response Example
{
"name": "Summer Sale",
"internalCode": "SUMMER2023",
"title": "Summer Sale",
"enabled": true,
"description": "Summer Sale 20%",
"image": "https://image.jpg",
"start": "2023-06-01",
"end": "2023-08-31",
"type": "DISCOUNT_SIMPLE",
"promotionGrouperCodes": ["GROUP1", "GROUP2"],
"products": [
{
"internalCode": "PROD123",
"boxRatio": 1
}
],
"discountTiersData": [
{
"min": 1,
"max": 2,
"discountPercentage": 20
}
],
"discountApplicationMode": "ALL_QUALIFYING"
}
Example Usage
- Javascript
- Python
const headers = {
Authorization: "YOUR_AUTH_TOKEN",
"x-api-key": "YOUR_API_KEY",
};
fetch(
"https://tm0cs5kjs6.execute-api.us-east-1.amazonaws.com/dev/promotion/SUMMER2023",
{
method: "GET",
headers: headers,
}
)
.then((response) => response.json())
.then((result) => console.log(result))
.catch((error) => console.log("error", error));
import requests
url = "https://tm0cs5kjs6.execute-api.us-east-1.amazonaws.com/dev/promotion/SUMMER2023"
headers = {
'Authorization': 'YOUR_AUTH_TOKEN',
'x-api-key': 'YOUR_API_KEY'
}
response = requests.get(url, headers=headers)
print(response.text)
List All Promotions
/promotionRetrieve a list of all promotions with pagination and filtering support.
Both this endpoint and GET /promotion/{code} return promotionGrouperCodes,
containing the internal codes of the associated promotion groupers. The value is
an empty array when the promotion is not assigned to a grouper.
Discount promotions also return discountApplicationMode when it is set
(ALL_QUALIFYING or COMPLETE_MULTIPLES).
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| take | number | No | Number of items per page (default: 50, max: 50) |
| page | string | No | Page number (default: 1) |
| cursor | string | No | Cursor for pagination. First call should pass 0, subsequent calls use the cursor from the last item of the previous response |
| enabled | boolean | No | Filter by enabled status |
| from | string | No | Start date filter (YYYY-MM-DD) |
| to | string | No | End date filter (YYYY-MM-DD) |
Create/Update Simple Discount Promotion
/promotion/discount/simpleCreate or update a simple discount promotion for a single product with tiered discounts.
Request Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| name | string | Yes | Name of the promotion |
| internalCode | string | Yes | Unique identifier for the promotion (same as in ERP) |
| start | string | Yes | Start date (YYYY-MM-DD) |
| end | string | Yes | End date (YYYY-MM-DD) |
| products | object | Yes | Product included in the promotion |
| discountTiersData | array | No* | Quantity-based discount tiers (min, max, discountPercentage OR discountAmount) |
| discountTiersDataCash | array | No* | Amount-based discount tiers (cashMinAmount, cashMaxAmount, discountPercentage OR discountAmount) |
| enabled | boolean | Yes | Whether the promotion is active |
| promotionGrouperCodes | array | No | Array of promotion grouper codes |
| recommendationThreshold | number | No | Threshold value for the promotion to be suggested to users |
| discountApplicationMode | string | No | ALL_QUALIFYING (all qualifying units) or COMPLETE_MULTIPLES (only complete multiples of the tier min). See Discount application mode |
*Either discountTiersData OR discountTiersDataCash must be provided, not both.
discountTiersData (quantity-based):
| Field | Type | Required | Description |
|---|---|---|---|
| min | integer | Yes | Minimum quantity to qualify |
| max | integer | No | Maximum quantity for this tier |
| discountPercentage | number | No* | Percentage discount (e.g., 10.5 for 10.5%) |
| discountAmount | number | No* | Fixed amount discount |
discountTiersDataCash (amount-based):
| Field | Type | Required | Description |
|---|---|---|---|
| cashMinAmount | number | Yes | Minimum purchase amount to qualify |
| cashMaxAmount | number | No | Maximum amount for this tier |
| discountPercentage | number | No* | Percentage discount |
| discountAmount | number | No* | Fixed amount discount |
*Use discountPercentage OR discountAmount, not both.
Request Body Example (Quantity-based)
{
"name": "Summer Sale",
"internalCode": "SUMMER2023",
"start": "2025-06-01",
"end": "2025-08-31",
"products": {
"internalCode": "PROD123",
"boxRatio": 1
},
"discountTiersData": [
{
"min": 1,
"max": 5,
"discountPercentage": 10
},
{
"min": 6,
"max": 10,
"discountPercentage": 15
},
{
"min": 11,
"discountPercentage": 20
}
],
"enabled": true,
"promotionGrouperCodes": ["GROUP1", "GROUP2"],
"recommendationThreshold": 5,
"discountApplicationMode": "ALL_QUALIFYING"
}
Request Body Example (Complete multiples)
Use a single quantity tier (min = block size) and COMPLETE_MULTIPLES so only complete blocks receive the discount. Remainder units are charged at list price.
{
"name": "Discount by multiples",
"internalCode": "MULT10",
"start": "2025-06-01",
"end": "2025-08-31",
"products": {
"internalCode": "PROD123",
"boxRatio": 1
},
"discountTiersData": [
{
"min": 10,
"discountPercentage": 20
}
],
"enabled": true,
"discountApplicationMode": "COMPLETE_MULTIPLES"
}
Request Body Example (Amount-based)
{
"name": "Spend & Save",
"internalCode": "SPEND2025",
"start": "2025-06-01",
"end": "2025-08-31",
"products": {
"internalCode": "PROD123",
"boxRatio": 1
},
"discountTiersDataCash": [
{
"cashMinAmount": 100,
"cashMaxAmount": 500,
"discountPercentage": 5
},
{
"cashMinAmount": 500,
"discountPercentage": 10
}
],
"enabled": true,
"promotionGrouperCodes": ["GROUP1"]
}
Example Usage for Simple Discount Promotion
- Javascript
- Python
const headers = {
Authorization: "YOUR_AUTH_TOKEN",
"x-api-key": "YOUR_API_KEY",
"Content-Type": "application/json",
};
const data = {
name: "Summer Sale",
internalCode: "SUMMER2023",
start: "2023-06-01",
end: "2023-08-31",
products: {
internalCode: "PROD123",
boxRatio: 1,
},
discountTiersData: [
{
min: 1,
max: 2,
discountPercentage: 10.5,
},
],
enabled: true,
promotionGrouperCodes: ["GROUP1", "GROUP2"],
discountApplicationMode: "ALL_QUALIFYING",
};
fetch("https://api.nilo.com/promotion/discount/simple", {
method: "PUT",
headers: headers,
body: JSON.stringify(data),
})
.then((response) => response.json())
.then((result) => console.log(result))
.catch((error) => console.log("error", error));
import requests
url = "https://api.nilo.com/promotion/discount/simple"
headers = {
'Authorization': 'YOUR_AUTH_TOKEN',
'x-api-key': 'YOUR_API_KEY',
'Content-Type': 'application/json'
}
data = {
"name": "Summer Sale",
"internalCode": "SUMMER2023",
"start": "2023-06-01",
"end": "2023-08-31",
"products": {
"internalCode": "PROD123",
"boxRatio": 1
},
"discountTiersData": [
{
"min": 1,
"max": 2,
"discountPercentage": 10.5
}
],
"enabled": True,
"promotionGrouperCodes": ["GROUP1", "GROUP2"],
"discountApplicationMode": "ALL_QUALIFYING"
}
response = requests.put(url, headers=headers, json=data)
print(response.text)
Create/Update Multiple Discount Promotion
/promotion/discount/multipleCreate or update a promotion with multiple discount tiers.
Request Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| name | string | Yes | Name of the promotion |
| internalCode | string | Yes | Unique identifier for the promotion |
| title | string | No | Display title for the promotion |
| description | string | No | Detailed description |
| image | string | No | URL of promotion image |
| start | string | Yes | Start date (YYYY-MM-DD) |
| end | string | Yes | End date (YYYY-MM-DD) |
| products | array | Yes | Array of products in promotion |
| discountTiersData | array | Yes | Discount tiers configuration |
| enabled | boolean | Yes | Whether the promotion is active |
| discountApplicationMode | string | No | ALL_QUALIFYING or COMPLETE_MULTIPLES. Same meaning as simple discount |
Request Body Example
{
"name": "Bulk Purchase Discount",
"internalCode": "BULK2023",
"title": "Buy More, Save More",
"description": "Progressive discounts on bulk purchases",
"image": "https://example.com/promo.jpg",
"start": "2023-06-01",
"end": "2023-08-31",
"products": [
{
"internalCode": "PROD123",
"boxRatio": 1
},
{
"internalCode": "PROD124",
"boxRatio": 1
}
],
"discountTiersData": [
{
"min": 1,
"max": 5,
"discountPercentage": 10
},
{
"min": 6,
"max": 10,
"discountPercentage": 15
}
],
"enabled": true,
"discountApplicationMode": "ALL_QUALIFYING"
}
Example Usage for Multiple Discount Promotion
- Javascript
- Python
const headers = {
Authorization: "YOUR_AUTH_TOKEN",
"x-api-key": "YOUR_API_KEY",
"Content-Type": "application/json",
};
const data = {
name: "Bulk Purchase Discount",
internalCode: "BULK2023",
title: "Buy More, Save More",
description: "Progressive discounts on bulk purchases",
image: "https://example.com/promo.jpg",
start: "2023-06-01",
end: "2023-08-31",
products: [
{
internalCode: "PROD123",
boxRatio: 1,
},
{
internalCode: "PROD124",
boxRatio: 1,
},
],
discountTiersData: [
{
min: 1,
max: 5,
discountPercentage: 10,
},
{
min: 6,
max: 10,
discountPercentage: 15,
},
],
enabled: true,
discountApplicationMode: "ALL_QUALIFYING",
};
fetch("https://api.nilo.com/promotion/discount/multiple", {
method: "PUT",
headers: headers,
body: JSON.stringify(data),
})
.then((response) => response.json())
.then((result) => console.log(result))
.catch((error) => console.log("error", error));
import requests
url = "https://api.nilo.com/promotion/discount/multiple"
headers = {
'Authorization': 'YOUR_AUTH_TOKEN',
'x-api-key': 'YOUR_API_KEY',
'Content-Type': 'application/json'
}
data = {
"name": "Bulk Purchase Discount",
"internalCode": "BULK2023",
"title": "Buy More, Save More",
"description": "Progressive discounts on bulk purchases",
"image": "https://example.com/promo.jpg",
"start": "2023-06-01",
"end": "2023-08-31",
"products": [
{
"internalCode": "PROD123",
"boxRatio": 1
},
{
"internalCode": "PROD124",
"boxRatio": 1
}
],
"discountTiersData": [
{
"min": 1,
"max": 5,
"discountPercentage": 10
},
{
"min": 6,
"max": 10,
"discountPercentage": 15
}
],
"enabled": True,
"discountApplicationMode": "ALL_QUALIFYING"
}
response = requests.put(url, headers=headers, json=data)
print(response.text)
Create/Update Individual Bonus Promotion
/promotion/bonus/individualCreate or update a buy X get Y promotion for the same product.
Request Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| name | string | Yes | Name of the promotion |
| internalCode | string | Yes | Unique identifier for the promotion |
| start | string | Yes | Start date (YYYY-MM-DD) |
| end | string | Yes | End date (YYYY-MM-DD) |
| products | object | Yes | Product configuration |
| bonusTierData | object | Yes | Bonus configuration |
| enabled | boolean | Yes | Whether the promotion is active |
Request Body Example
{
"name": "Buy 2 Get 1 Free",
"internalCode": "B2G1FREE",
"start": "2023-06-01",
"end": "2023-08-31",
"products": {
"internalCode": "PROD123",
"boxRatio": 1
},
"bonusTierData": {
"quantityRequired": 2,
"quantityGiven": 1,
"units": 1
},
"enabled": true
}
Example Usage for Individual Bonus Promotion
- Javascript
- Python
const headers = {
Authorization: "YOUR_AUTH_TOKEN",
"x-api-key": "YOUR_API_KEY",
"Content-Type": "application/json",
};
const data = {
name: "Buy 2 Get 1 Free",
internalCode: "B2G1FREE",
start: "2023-06-01",
end: "2023-08-31",
products: {
internalCode: "PROD123",
boxRatio: 1,
},
bonusTierData: {
quantityRequired: 2,
quantityGiven: 1,
units: 1,
},
enabled: true,
};
fetch("https://api.nilo.com/promotion/bonus/individual", {
method: "PUT",
headers: headers,
body: JSON.stringify(data),
})
.then((response) => response.json())
.then((result) => console.log(result))
.catch((error) => console.log("error", error));
import requests
url = "https://api.nilo.com/promotion/bonus/individual"
headers = {
'Authorization': 'YOUR_AUTH_TOKEN',
'x-api-key': 'YOUR_API_KEY',
'Content-Type': 'application/json'
}
data = {
"name": "Buy 2 Get 1 Free",
"internalCode": "B2G1FREE",
"start": "2023-06-01",
"end": "2023-08-31",
"products": {
"internalCode": "PROD123",
"boxRatio": 1
},
"bonusTierData": {
"quantityRequired": 2,
"quantityGiven": 1,
"units": 1
},
"enabled": True
}
response = requests.put(url, headers=headers, json=data)
print(response.text)
Create/Update Crossed Bonus Promotion
/promotion/bonus/crossedCreate or update a promotion where buying one product gives a bonus of a different product.
Request Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| name | string | Yes | Name of the promotion |
| internalCode | string | Yes | Unique identifier for the promotion |
| title | string | No | Display title for the promotion |
| description | string | No | Detailed description |
| image | string | No | URL of promotion image |
| start | string | Yes | Start date (YYYY-MM-DD) |
| end | string | Yes | End date (YYYY-MM-DD) |
| products | array | Yes | Products that trigger the bonus |
| bonusTierData | object | Yes | Bonus product configuration |
| enabled | boolean | Yes | Whether the promotion is active |
Request Body Example
{
"name": "Buy X Get Y Free",
"internalCode": "CROSSPROMO",
"title": "Special Cross Promotion",
"description": "Buy Product A, Get Product B Free",
"image": "https://example.com/cross-promo.jpg",
"start": "2023-06-01",
"end": "2023-08-31",
"products": [
{
"internalCode": "PRODA",
"boxRatio": 1
}
],
"bonusTierData": {
"quantityRequired": 2,
"quantityGiven": 1,
"productInternalCode": "PRODB",
"units": 1
},
"enabled": true
}
Example Usage for Crossed Bonus Promotion
- Javascript
- Python
const headers = {
Authorization: "YOUR_AUTH_TOKEN",
"x-api-key": "YOUR_API_KEY",
"Content-Type": "application/json",
};
const data = {
name: "Buy X Get Y Free",
internalCode: "CROSSPROMO",
title: "Special Cross Promotion",
description: "Buy Product A, Get Product B Free",
image: "https://example.com/cross-promo.jpg",
start: "2023-06-01",
end: "2023-08-31",
products: [
{
internalCode: "PRODA",
boxRatio: 1,
},
],
bonusTierData: {
quantityRequired: 2,
quantityGiven: 1,
productInternalCode: "PRODB",
units: 1,
},
enabled: true,
};
fetch("https://api.nilo.com/promotion/bonus/crossed", {
method: "PUT",
headers: headers,
body: JSON.stringify(data),
})
.then((response) => response.json())
.then((result) => console.log(result))
.catch((error) => console.log("error", error));
import requests
url = "https://api.nilo.com/promotion/bonus/crossed"
headers = {
'Authorization': 'YOUR_AUTH_TOKEN',
'x-api-key': 'YOUR_API_KEY',
'Content-Type': 'application/json'
}
data = {
"name": "Buy X Get Y Free",
"internalCode": "CROSSPROMO",
"title": "Special Cross Promotion",
"description": "Buy Product A, Get Product B Free",
"image": "https://example.com/cross-promo.jpg",
"start": "2023-06-01",
"end": "2023-08-31",
"products": [
{
"internalCode": "PRODA",
"boxRatio": 1
}
],
"bonusTierData": {
"quantityRequired": 2,
"quantityGiven": 1,
"productInternalCode": "PRODB",
"units": 1
},
"enabled": True
}
response = requests.put(url, headers=headers, json=data)
print(response.text)
Change Promotion Status
/promotion/{code}/statusEnable or disable a specific promotion.
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| code | string | Yes | Internal code of the promotion |
Request Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| enabled | boolean | Yes | New status for the promotion |
Request Body Example
{
"enabled": true
}
Batch Operations
Change Promotion Status in Batch
/batch/promotion/statusEnables or disables multiple promotions asynchronously. Each item requires code and enabled. The response returns 202 Accepted with batchIds, which can be followed through the Batch Operations API.
{
"items": [
{ "code": "PROMOTION-001", "enabled": true },
{ "code": "PROMOTION-002", "enabled": false }
]
}
Required permission: supplier/promotion.bulkwrite.
Batch Update Promotions
/batch/promotionUpdate multiple promotions in a single operation.
Request Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| upsert | object | No | Contains promotions to create or update |
| remove | object | No | Contains promotions to remove |
Nested upsert.discount.simple[] and upsert.discount.multiple[] accept the same fields as the corresponding PUT endpoints, including optional discountApplicationMode.
Request Body Example
{
"upsert": {
"discount": {
"simple": [
{
"name": "Simple Discount 1",
"internalCode": "SD1",
"start": "2023-06-01",
"end": "2023-08-31",
"products": {
"internalCode": "PROD1",
"boxRatio": 1
},
"discountTiersData": [
{
"min": 1,
"discountPercentage": 10
}
],
"enabled": true,
"discountApplicationMode": "COMPLETE_MULTIPLES"
}
],
"multiple": []
},
"bonus": {
"individual": [],
"crossed": []
}
}
}
Example Usage for Batch Operations
- Javascript
- Python
const headers = {
Authorization: "YOUR_AUTH_TOKEN",
"x-api-key": "YOUR_API_KEY",
"Content-Type": "application/json",
};
const data = {
upsert: {
discount: {
simple: [
{
name: "Simple Discount 1",
internalCode: "SD1",
start: "2023-06-01",
end: "2023-08-31",
products: {
internalCode: "PROD1",
boxRatio: 1,
},
discountTiersData: [
{
min: 1,
discountPercentage: 10,
},
],
enabled: true,
discountApplicationMode: "COMPLETE_MULTIPLES",
},
],
multiple: [],
},
bonus: {
individual: [],
crossed: [],
},
},
};
fetch("https://api.nilo.com/batch/promotion", {
method: "POST",
headers: headers,
body: JSON.stringify(data),
})
.then((response) => response.json())
.then((result) => console.log(result))
.catch((error) => console.log("error", error));
import requests
url = "https://api.nilo.com/batch/promotion"
headers = {
'Authorization': 'YOUR_AUTH_TOKEN',
'x-api-key': 'YOUR_API_KEY',
'Content-Type': 'application/json'
}
data = {
"upsert": {
"discount": {
"simple": [
{
"name": "Simple Discount 1",
"internalCode": "SD1",
"start": "2023-06-01",
"end": "2023-08-31",
"products": {
"internalCode": "PROD1",
"boxRatio": 1
},
"discountTiersData": [
{
"min": 1,
"discountPercentage": 10
}
],
"enabled": True,
"discountApplicationMode": "COMPLETE_MULTIPLES"
}
],
"multiple": []
},
"bonus": {
"individual": [],
"crossed": []
}
}
}
response = requests.post(url, headers=headers, json=data)
print(response.text)
Batch Update Promotion Groupers
/batch/promotion/update/grouperAdd or remove promotion groupers from promotions in bulk. This is useful for changing which stores can see specific promotions.
Request Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| add | object | No | Contains groups to add to promotions |
| remove | object | No | Contains groups to remove from promotions |
groups array items:
| Parameter | Type | Required | Description |
|---|---|---|---|
| internalCode | string | Yes | Internal code of the promotion |
| groupInternalCode | string | Yes | Internal code of the grouper |
Request Body Example
{
"add": {
"groups": [
{
"internalCode": "PROMO123",
"groupInternalCode": "GROUP1"
},
{
"internalCode": "PROMO123",
"groupInternalCode": "GROUP2"
}
]
},
"remove": {
"groups": [
{
"internalCode": "PROMO456",
"groupInternalCode": "GROUP3"
}
]
}
}
Response Codes
| Code | Description |
|---|---|
| 202 | Request accepted, processing initiated |
| 402 | Bad request |
Error Codes
When an operation fails, the API returns a structured error response:
{
"code": 404,
"message": "PROMOTION_NOT_FOUND",
"object": {
"statusCode": 404,
"code": "PROMOTION_NOT_FOUND",
"message": "Promotion not found",
"entity": "promotion",
"entityCode": "PROMO-001",
"timestamp": "2026-05-18T15:16:47.000Z"
}
}
The object field provides additional context about the error:
| Field | Description |
|---|---|
statusCode | HTTP status code |
code | Error code (same as top-level message) |
message | Human-readable error description |
entity | Entity type that caused the error |
entityCode | The internalCode of the entity involved |
timestamp | When the error occurred (ISO 8601) |
404 — Not Found
| Error Code | Description |
|---|---|
PROMOTION_NOT_FOUND | The promotion does not exist for the given internal code |
409 — Conflict
| Error Code | Description |
|---|---|
PROMOTION_NAME_ALREADY_EXIST | A promotion with the same name already exists |
PROMOTION_INTERNAL_CODE_ALREADY_EXIST | A promotion with the same internal code already exists |
PROMOTION_NAME_AND_INTERNAL_CODE_ALREADY_EXIST | Both name and internal code are already in use |
PRODUCT_ALREADY_IN_PROMOTION | The product is already present in another active promotion |
PROMOTION_GROUPS_HAS_CONFLICTS | Products conflict with other promotions in the promotion group |
STORE_ALREADY_IN_PROMOTION | The store is already assigned to this promotion |
400 — Bad Request
| Error Code | Description |
|---|---|
PROMOTION_START_DATE_AFTER_END_DATE | Start date cannot be after end date |
PROMOTION_START_DATE_IS_IN_THE_PAST | Start date cannot be in the past |
PROMOTION_TIERS_OVERLAP | Tier unit ranges overlap each other |
PROMOTION_TIER_MAX_LOWER_THAN_MIN | Max units cannot be lower than min units in a tier |
PROMOTION_TIER_MIN_LESS_THAN_ONE | Tier min units must be greater than 0 |
PROMOTION_PRODUCTS_DATA_EMPTY | Promotion requires at least one product |
PROMOTION_DISCOUNT_DATA_EMPTY | Discount promotion requires discount data |
PROMOTION_BONUS_DATA_EMPTY | Bonus promotion requires bonus data |
PROMOTION_HAS_MULTIPLE_PRODUCTS | Simple discount/bonus promotions accept only one product |
MULTIPLE_PROMOTION_HAS_SINGLE_PRODUCT | Multiple discount promotions require more than one product |
BOX_RATIO_INVALID | The product has an invalid box ratio value |
422 — Business Rule Violation
| Error Code | Description |
|---|---|
PROMOTION_INTERNAL_CODE_NON_EDITABLE | The internal code of an existing promotion cannot be changed |
NEW_PRODUCT_IN_PROMOTION_WITH_STORES | Cannot add products to a promotion that already has stores assigned |
Security
Authentication
All endpoints require two types of authentication:
- API Key in header:
x-api-key - Authorization token in header:
Authorization
Required Permissions
- For reading operations:
supplier/promotion.read - For writing operations:
supplier/promotion.write - For batch operations:
supplier/promotion.bulkwrite