Skip to main content

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 TypeNilo API TypeEndpoint
Direct Bonus (same product)BONUS_INDIVIDUALPUT /promotion/bonus/individual
Crossed Bonus (different product)BONUS_CROSSEDPUT /promotion/bonus/crossed
Group Discount (product combination)DISCOUNT_MULTIPLEPUT /promotion/discount/multiple
Tiered Simple DiscountDISCOUNT_SIMPLEPUT /promotion/discount/simple
Combos with discountDISCOUNT_MULTIPLEPUT /promotion/discount/multiple
Combos with bonusBONUS_CROSSEDPUT /promotion/bonus/crossed

Important Considerations​

Mutually Exclusive Fields

The API defines certain fields as mutually exclusive:

  • Quantity vs Amount condition: Don't send discountTiersData/bonusTierData and discountTiersDataCash/bonusTierDataCash simultaneously
  • Discount type: Specify discountPercentage OR discountAmount, not both

Sending both fields will result in an error.

  1. 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)
  2. 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
  3. Image Management:

    • The image field 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
  4. 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
  5. Discount application mode (discountApplicationMode):

    • Optional. Applies to PUT /promotion/discount/simple, PUT /promotion/discount/multiple, and nested items in POST /batch/promotion (upsert.discount.simple[] / multiple[])
    • ALL_QUALIFYING (default): the discount applies to all units that meet the tier
    • COMPLETE_MULTIPLES: the discount applies only to complete multiples of the tier min. 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

Single Promotion Operations​

Get Promotion Details​

GET/promotion/{code}

Retrieve detailed information about a specific promotion.

Path Parameters​

ParameterTypeRequiredDescription
codestringYesInternal 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​

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));

List All Promotions​

GET/promotion

Retrieve 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​

ParameterTypeRequiredDescription
takenumberNoNumber of items per page (default: 50, max: 50)
pagestringNoPage number (default: 1)
cursorstringNoCursor for pagination. First call should pass 0, subsequent calls use the cursor from the last item of the previous response
enabledbooleanNoFilter by enabled status
fromstringNoStart date filter (YYYY-MM-DD)
tostringNoEnd date filter (YYYY-MM-DD)

Create/Update Simple Discount Promotion​

PUT/promotion/discount/simple

Create or update a simple discount promotion for a single product with tiered discounts.

Request Body Parameters​

ParameterTypeRequiredDescription
namestringYesName of the promotion
internalCodestringYesUnique identifier for the promotion (same as in ERP)
startstringYesStart date (YYYY-MM-DD)
endstringYesEnd date (YYYY-MM-DD)
productsobjectYesProduct included in the promotion
discountTiersDataarrayNo*Quantity-based discount tiers (min, max, discountPercentage OR discountAmount)
discountTiersDataCasharrayNo*Amount-based discount tiers (cashMinAmount, cashMaxAmount, discountPercentage OR discountAmount)
enabledbooleanYesWhether the promotion is active
promotionGrouperCodesarrayNoArray of promotion grouper codes
recommendationThresholdnumberNoThreshold value for the promotion to be suggested to users
discountApplicationModestringNoALL_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):

FieldTypeRequiredDescription
minintegerYesMinimum quantity to qualify
maxintegerNoMaximum quantity for this tier
discountPercentagenumberNo*Percentage discount (e.g., 10.5 for 10.5%)
discountAmountnumberNo*Fixed amount discount

discountTiersDataCash (amount-based):

FieldTypeRequiredDescription
cashMinAmountnumberYesMinimum purchase amount to qualify
cashMaxAmountnumberNoMaximum amount for this tier
discountPercentagenumberNo*Percentage discount
discountAmountnumberNo*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​

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));

Create/Update Multiple Discount Promotion​

PUT/promotion/discount/multiple

Create or update a promotion with multiple discount tiers.

Request Body Parameters​

ParameterTypeRequiredDescription
namestringYesName of the promotion
internalCodestringYesUnique identifier for the promotion
titlestringNoDisplay title for the promotion
descriptionstringNoDetailed description
imagestringNoURL of promotion image
startstringYesStart date (YYYY-MM-DD)
endstringYesEnd date (YYYY-MM-DD)
productsarrayYesArray of products in promotion
discountTiersDataarrayYesDiscount tiers configuration
enabledbooleanYesWhether the promotion is active
discountApplicationModestringNoALL_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​

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));

Create/Update Individual Bonus Promotion​

PUT/promotion/bonus/individual

Create or update a buy X get Y promotion for the same product.

Request Body Parameters​

ParameterTypeRequiredDescription
namestringYesName of the promotion
internalCodestringYesUnique identifier for the promotion
startstringYesStart date (YYYY-MM-DD)
endstringYesEnd date (YYYY-MM-DD)
productsobjectYesProduct configuration
bonusTierDataobjectYesBonus configuration
enabledbooleanYesWhether 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​

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));

Create/Update Crossed Bonus Promotion​

PUT/promotion/bonus/crossed

Create or update a promotion where buying one product gives a bonus of a different product.

Request Body Parameters​

ParameterTypeRequiredDescription
namestringYesName of the promotion
internalCodestringYesUnique identifier for the promotion
titlestringNoDisplay title for the promotion
descriptionstringNoDetailed description
imagestringNoURL of promotion image
startstringYesStart date (YYYY-MM-DD)
endstringYesEnd date (YYYY-MM-DD)
productsarrayYesProducts that trigger the bonus
bonusTierDataobjectYesBonus product configuration
enabledbooleanYesWhether 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​

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));

Change Promotion Status​

PUT/promotion/{code}/status

Enable or disable a specific promotion.

Path Parameters​

ParameterTypeRequiredDescription
codestringYesInternal code of the promotion

Request Body Parameters​

ParameterTypeRequiredDescription
enabledbooleanYesNew status for the promotion

Request Body Example​

{
"enabled": true
}

Batch Operations​

Change Promotion Status in Batch​

POST/batch/promotion/status

Enables 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​

POST/batch/promotion

Update multiple promotions in a single operation.

Request Body Parameters​

ParameterTypeRequiredDescription
upsertobjectNoContains promotions to create or update
removeobjectNoContains 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​

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));

Batch Update Promotion Groupers​

POST/batch/promotion/update/grouper

Add or remove promotion groupers from promotions in bulk. This is useful for changing which stores can see specific promotions.

Request Body Parameters​

ParameterTypeRequiredDescription
addobjectNoContains groups to add to promotions
removeobjectNoContains groups to remove from promotions

groups array items:

ParameterTypeRequiredDescription
internalCodestringYesInternal code of the promotion
groupInternalCodestringYesInternal 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​

CodeDescription
202Request accepted, processing initiated
402Bad 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:

FieldDescription
statusCodeHTTP status code
codeError code (same as top-level message)
messageHuman-readable error description
entityEntity type that caused the error
entityCodeThe internalCode of the entity involved
timestampWhen the error occurred (ISO 8601)

404 — Not Found​

Error CodeDescription
PROMOTION_NOT_FOUNDThe promotion does not exist for the given internal code

409 — Conflict​

Error CodeDescription
PROMOTION_NAME_ALREADY_EXISTA promotion with the same name already exists
PROMOTION_INTERNAL_CODE_ALREADY_EXISTA promotion with the same internal code already exists
PROMOTION_NAME_AND_INTERNAL_CODE_ALREADY_EXISTBoth name and internal code are already in use
PRODUCT_ALREADY_IN_PROMOTIONThe product is already present in another active promotion
PROMOTION_GROUPS_HAS_CONFLICTSProducts conflict with other promotions in the promotion group
STORE_ALREADY_IN_PROMOTIONThe store is already assigned to this promotion

400 — Bad Request​

Error CodeDescription
PROMOTION_START_DATE_AFTER_END_DATEStart date cannot be after end date
PROMOTION_START_DATE_IS_IN_THE_PASTStart date cannot be in the past
PROMOTION_TIERS_OVERLAPTier unit ranges overlap each other
PROMOTION_TIER_MAX_LOWER_THAN_MINMax units cannot be lower than min units in a tier
PROMOTION_TIER_MIN_LESS_THAN_ONETier min units must be greater than 0
PROMOTION_PRODUCTS_DATA_EMPTYPromotion requires at least one product
PROMOTION_DISCOUNT_DATA_EMPTYDiscount promotion requires discount data
PROMOTION_BONUS_DATA_EMPTYBonus promotion requires bonus data
PROMOTION_HAS_MULTIPLE_PRODUCTSSimple discount/bonus promotions accept only one product
MULTIPLE_PROMOTION_HAS_SINGLE_PRODUCTMultiple discount promotions require more than one product
BOX_RATIO_INVALIDThe product has an invalid box ratio value

422 — Business Rule Violation​

Error CodeDescription
PROMOTION_INTERNAL_CODE_NON_EDITABLEThe internal code of an existing promotion cannot be changed
NEW_PRODUCT_IN_PROMOTION_WITH_STORESCannot 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
Tana

Tana

Ask me anything about Nilo's API — endpoints, authentication, integration guides, or code examples.

Best practice: validate against the official documentation for the most up-to-date details.