Guidelines
This guide provides modelling, formatting and operational guidance for partners integrating with the Promotions API.
For endpoint details, authentication, request examples and the full upload workflow, see the Promotions API Integration Guide.
Promotion Model
Each promotion represents one partner-defined promotion with one mechanic, one condition model and one reward model.
At a high level:
promotion_typedefines the promotion mechanicconditiondefines what the customer basket must satisfyrewarddefines what the customer receives
Example:
{
"promotion_id": "GROCER-PCT-OFF-PASTA-2026Q2",
"promotion_type": "PERCENTAGE_OFF_ON_ITEMS",
"name": "20% off selected pasta",
"user_target": "LOYALTY_CUSTOMER",
"sites": [
"grocer-gb-london-001"
],
"fulfillment_method": "DELIVERY",
"start_at": "2026-06-01T00:00:00Z",
"end_at": "2026-06-30T23:59:59Z",
"condition": {
"items": [
"5000169816246",
"5000169816247"
]
},
"reward": {
"percentage": 20
}
}Promotion Fields
promotion_id
Partner-defined identifier for the promotion.
The promotion_id is used for reconciliation, update and synchronisation operations. Partners must provide a promotion_id for every promotion.
Each promotion_id must be unique within a file. One promotion_id identifies exactly one promotion with one mechanic, one condition model and one reward model.
Recommendations:
- Use stable identifiers for promotions you expect to reconcile or update over time
- Keep IDs human-readable where possible
- Use a format that helps identify the campaign, mechanic and time period
- Do not reuse a
promotion_idwhile a previous promotion with the same identifier is still processing or active - Use a new
promotion_idfor a different mechanic, reward value or non-contiguous promotion window - If some items need one reward value and other items need a different reward value, create separate promotions with separate
promotion_idvalues - Ensure each
promotion_idappears only once in a file
Example: GROCER-PCT-OFF-PASTA-2026Q2
Maximum length: 128 characters.
Allowed characters: letters, numbers, underscore (_), hyphen (-) and period (.).
promotion_type
Defines the promotion mechanic.
Supported promotion_type values:
| Value | Description |
|---|---|
PERCENTAGE_OFF_ON_ITEMS | Percentage discount on selected items |
AMOUNT_OFF_ON_ITEMS | Fixed amount discount on selected items |
PERCENTAGE_OFF_ON_SECOND_ITEM | Percentage discount on the second qualifying item |
PERCENTAGE_OFF_ON_SINGLE_ITEM_MULTIBUY | Percentage discount when buying multiple units of the same item |
FIXED_PRICE_ON_SINGLE_ITEM_MULTIBUY | Fixed price when buying multiple units of the same item |
PERCENTAGE_OFF_ON_MULTIPLE_ITEM_MULTIBUY | Percentage discount when buying a bundle of different items |
FIXED_PRICE_ON_MULTIPLE_ITEM_MULTIBUY | Fixed price when buying a bundle of different items |
PERCENTAGE_OFF_ON_BASKET | Percentage discount on basket total |
FREE_DELIVERY | Free delivery promotion |
BUY_X_FOR_Y | Buy X qualifying items and receive reward items |
BUY_X_PLUS_SAVE_Y_PERCENT | Buy X or more qualifying items and save Y% |
Recommendations:
- Use item-level promotion types when the promotion applies to specific catalogue items
- Use basket-level promotion types when the promotion applies to the customer basket
- Use
FREE_DELIVERYonly for delivery-fee promotions - Use multibuy promotion types when the customer must purchase a qualifying quantity
- Use fixed-price multibuy promotion types when the customer pays a fixed final price for the qualifying items
- Use future promotion types only once enabled for your integration
name
Human-readable promotion name.
Recommendations:
- Use a clear name that describes the campaign or reward
- Keep names useful for operational support and reconciliation
- Do not rely on
nameas the unique identifier; usepromotion_idfor matching and updates
Example: 20% off selected pasta
sites
List of Deliveroo site identifiers where the promotion applies.
Recommendations:
- Ensure all sites are already synced with Deliveroo
- Remove inactive, closed or invalid sites before upload
- Keep
sitesconsistent across related promotions where the same campaign should apply to the same estate
Unknown or inactive sites may fail during processing with a SITE_NOT_FOUND error.
user_target
Defines the customer segment eligible for the promotion.
Supported user_target values:
| Value | Description | Notes |
|---|---|---|
ALL_CUSTOMERS | All customers | Default if user_target is omitted |
NEW_CUSTOMER | First-time orderers on the store | Use when the promotion should only apply to customers placing their first order with the store |
LAPSED_CUSTOMER | Customers who have not ordered on the store for the past 365 days | Use for reactivation campaigns |
LOYALTY_CUSTOMER | Loyalty programme members | Use only for partners with a configured loyalty programme on Deliveroo. Please consult your Account Manager before integrating loyalty promotions. |
PLUS_SUBSCRIBER | Deliveroo Plus subscribers | Do not use with FREE_DELIVERY, because Plus already includes delivery benefits |
STUDENT | Verified students | Use for student-targeted campaigns |
NEW_TO_BRAND | Customers who have never ordered from this brand before | Use for brand acquisition campaigns |
Use ALL_CUSTOMERS, an empty value or omit user_target for all-customer promotions.
Recommendations:
- Keep customer targeting consistent across related promotions
- Avoid creating overlapping targeted and non-targeted versions of the same promotion unless this has been agreed during onboarding
- Use
LOYALTY_CUSTOMERonly for partners with a configured loyalty programme on Deliveroo - Do not use unsupported customer segments unless agreed during onboarding
fulfillment_method
Restricts the promotion to a specific fulfilment channel.
Supported values:
ANYDELIVERYCOLLECTION
If omitted, fulfillment_method defaults to ANY.
Recommendations:
- Use
DELIVERYfor delivery-only promotions - Use
COLLECTIONfor collection-only promotions - Use
ANYonly where the promotion should apply across supported fulfilment channels - Keep
fulfillment_methodconsistent across related promotions where the same campaign should apply to the same channel
start_at and end_at
Defines the promotion schedule.
Accepted format: ISO 8601 timestamp with UTC or an explicit offset.
Example: 2026-06-01T00:00:00Z
Both start_at and end_at are inclusive. The promotion is active at start_at and remains active until and including end_at.
Recommendations:
- Use UTC timestamps or explicit offsets
- Ensure
end_atis greater thanstart_atfor newly scheduled promotions - Do not create overlapping promotion windows for the same item and site
- Use a different
promotion_idfor non-contiguous promotion windows - To end an active promotion early, update the promotion with a new
end_atvalue - If the promotion should end immediately, set
end_atto a timestamp in the past, then poll the returnedjob_idif confirmation is needed
Invalid schedules may fail with a SCHEDULE_INVALID error. Overlapping promotions may fail with a PROMOTION_OVERLAP error.
Condition Fields
The condition object defines what the customer basket must satisfy before the promotion can apply.
Use condition fields for qualifying items, required quantities, basket thresholds and alcohol eligibility.
Example:
"condition": {
"items": [
"5000169816246",
"5000169816247"
],
"quantity": 3
}condition.items
Array of partner catalogue item identifiers.
Required for item-level promotion types.
Do not include condition.items for basket-level or delivery-fee promotions.
Recommendations:
- Use the same IDs provided through catalogue synchronisation
- Ensure items already exist in Deliveroo’s catalogue before upload
- Remove inactive or deleted items before upload
- Validate item coverage before submitting large uploads
- Keep each
promotion_idto a maximum of 2,000 items
Unknown items may fail during processing with an ITEM_NOT_FOUND error.
condition.quantity
Number of qualifying items the customer must purchase before the promotion applies.
Required for multibuy and buy-X promotion types.
Recommendations:
- Use integer values
- Use
condition.quantityfor the qualifying purchase quantity - For single-item multibuy promotions, use the quantity required for the same item
- For multiple-item multibuy promotions, use the quantity required across the qualifying item set
- Do not use
condition.quantityfor simple percentage-off or amount-off item promotions unless the promotion type requires it
Example:
"condition": {
"items": [
"5000169816246"
],
"quantity": 3
}condition.min_order_value
Minimum basket subtotal required before the promotion applies, in minor currency units.
Example: 2000
For GBP, 2000 means £20.00.
Recommendations:
- Use integer minor-unit values
- Use only with promotion types that support basket thresholds, such as
PERCENTAGE_OFF_ON_BASKETorFREE_DELIVERY - Ensure the value reflects the intended basket subtotal threshold before discounts
Applicable promotion types include:
PERCENTAGE_OFF_ON_BASKETFREE_DELIVERY
condition.alcohol_allowed
Defines whether a basket or delivery promotion may apply to baskets containing alcoholic items.
If omitted, condition.alcohol_allowed defaults to false.
Recommendations:
- Use only where relevant for basket-level or delivery-fee promotions
- Set deliberately based on the partner’s policy and applicable market rules
- Do not rely on this field for item catalogue classification
Applicable promotion types include:
PERCENTAGE_OFF_ON_BASKETFREE_DELIVERY
Reward Fields
The reward object defines what the customer receives when the promotion condition is met.
Use reward fields for percentage discounts, amount-off discounts, fixed prices, reward quantities and discount caps.
Example:
"reward": {
"percentage": 20
}reward.percentage
Percentage discount value.
Example: 20
Recommendations:
- Use integer percentage values
- Use
reward.percentagewith percentage-based promotion types - Validate discount values before upload
Applicable promotion types include:
PERCENTAGE_OFF_ON_ITEMSPERCENTAGE_OFF_ON_SECOND_ITEMPERCENTAGE_OFF_ON_SINGLE_ITEM_MULTIBUYPERCENTAGE_OFF_ON_MULTIPLE_ITEM_MULTIBUYPERCENTAGE_OFF_ON_BASKETBUY_X_PLUS_SAVE_Y_PERCENT
reward.amount_off
Fixed discount value in minor currency units.
Example: 100
For GBP, 100 means £1.00.
Currency is derived from the brand configuration. Each brand_id is associated with a single market during onboarding.
Recommendations:
- Use integer minor-unit values
- Use
reward.amount_offwith amount-off promotion types - Ensure the value represents the discount amount, not the final customer price
Applicable promotion types include:
AMOUNT_OFF_ON_ITEMS
reward.fixed_price
Fixed final price for a qualifying multibuy promotion, in minor currency units.
Example: 400
For GBP, 400 means £4.00.
Recommendations:
- Use integer minor-unit values
- Use
reward.fixed_priceonly with fixed-price promotion types - Ensure the value represents the final customer price for the qualifying items, not the discount amount
Applicable promotion types include:
FIXED_PRICE_ON_SINGLE_ITEM_MULTIBUYFIXED_PRICE_ON_MULTIPLE_ITEM_MULTIBUY
reward.quantity
Reward quantity, where applicable.
Use reward.quantity for the quantity the customer receives as the reward, not the qualifying purchase quantity.
For buy-X-get-Y mechanics:
condition.quantitydefines X, the number of qualifying items the customer must buyreward.quantitydefines Y, the number of reward items the customer receives
Example:
"condition": {
"items": [
"5000169816301"
],
"quantity": 3
},
"reward": {
"quantity": 1
}Applicable promotion types include:
BUY_X_FOR_Y
reward.max_discount
Maximum discount amount, in minor currency units.
Example: 500
For GBP, 500 means £5.00.
Recommendations:
- Use integer minor-unit values
- Use only where the promotion type supports a maximum discount cap
- Use to limit the maximum customer discount for percentage-based basket promotions
Applicable promotion types include:
PERCENTAGE_OFF_ON_BASKET
Multibuy Promotions
The Promotions API supports single-item and multiple-item multibuy promotions.
Multibuy promotions use:
condition.itemsto define qualifying itemscondition.quantityto define the required purchase quantityreward.percentageorreward.fixed_priceto define the customer reward
Single-item multibuy
Triggered when customers purchase multiple units of the same item.
Example: buy 2 sandwiches for £2.
Use single-item multibuy promotion types when the same item must be purchased multiple times.
Applicable promotion types include:
PERCENTAGE_OFF_ON_SINGLE_ITEM_MULTIBUYFIXED_PRICE_ON_SINGLE_ITEM_MULTIBUY
Example:
{
"promotion_type": "FIXED_PRICE_ON_SINGLE_ITEM_MULTIBUY",
"condition": {
"items": [
"5000169880001"
],
"quantity": 2
},
"reward": {
"fixed_price": 200
}
}Multiple-item multibuy
Triggered when customers purchase qualifying quantities across multiple items in condition.items.
Example: any 3 snacks, 25% off.
Use multiple-item multibuy promotion types when customers can build a bundle from a set of different items.
Applicable promotion types include:
PERCENTAGE_OFF_ON_MULTIPLE_ITEM_MULTIBUYFIXED_PRICE_ON_MULTIPLE_ITEM_MULTIBUY
Example:
{
"promotion_type": "PERCENTAGE_OFF_ON_MULTIPLE_ITEM_MULTIBUY",
"condition": {
"items": [
"5000169816101",
"5000169816102",
"5000169816103"
],
"quantity": 3
},
"reward": {
"percentage": 25
}
}BUY_X_FOR_Y
Triggered when customers buy a required quantity of qualifying items and receive a reward quantity.
Example: buy 2, get 1 free.
Use:
condition.itemsfor the qualifying itemscondition.quantityfor the number of items the customer must buyreward.quantityfor the number of reward items the customer receives
Example:
{
"promotion_type": "BUY_X_FOR_Y",
"condition": {
"items": [
"5000169816301"
],
"quantity": 2
},
"reward": {
"quantity": 1
}
}BUY_X_PLUS_SAVE_Y_PERCENT
Triggered when customers buy at least a required quantity of qualifying items and receive a percentage discount.
Example: buy 3 or more, save 15%.
Use:
condition.itemsfor the qualifying itemscondition.quantityfor the minimum number of items the customer must buyreward.percentagefor the percentage discount
Example:
{
"promotion_type": "BUY_X_PLUS_SAVE_Y_PERCENT",
"condition": {
"items": [
"5000169816401",
"5000169816402"
],
"quantity": 3
},
"reward": {
"percentage": 15
}
}Basket and Delivery Promotions
Basket promotions
Basket promotions apply to the customer basket rather than specific items.
Applicable promotion types include:
PERCENTAGE_OFF_ON_BASKET
Basket promotions do not require condition.items.
Common fields include:
condition.min_order_valuecondition.alcohol_allowedreward.percentagereward.max_discount
Example:
{
"promotion_type": "PERCENTAGE_OFF_ON_BASKET",
"condition": {
"min_order_value": 2000,
"alcohol_allowed": false
},
"reward": {
"percentage": 10,
"max_discount": 500
}
}Free delivery promotions
Free delivery promotions apply to the delivery fee.
Applicable promotion types include:
FREE_DELIVERY
Free delivery promotions do not require condition.items.
Common fields include:
condition.min_order_valuecondition.alcohol_allowed
Example:
{
"promotion_type": "FREE_DELIVERY",
"condition": {
"min_order_value": 1500,
"alcohol_allowed": false
},
"reward": {}
}Promotion Synchronisation
Promotion uploads use a synchronisation model. Each successful upload represents the desired promotion state for the brand.
When a new file is processed:
- new promotions are created
- changed promotions are updated
- unchanged promotions are left as they are
- promotions missing from the file are removed
Partners should submit a complete file for the brand unless they intentionally want omitted promotions to be removed.
To remove all promotions for a brand, submit an empty promotions array if this behaviour has been enabled for your integration.
Example:
{
"promotions": []
}Overlapping Promotions
Overlapping promotions for the same items, sites and time windows may fail during processing with a PROMOTION_OVERLAP error.
Recommendations:
- Avoid overlapping active promotions for the same item and site
- Check existing campaign calendars before uploading new promotions
- Use distinct
promotion_idvalues for separate campaigns, mechanics, reward values or non-contiguous windows - Avoid creating overlapping targeted and non-targeted versions of the same promotion unless agreed during onboarding
Upload Size and Structure
Promotion uploads support files up to 50 MB.
Each promotion_id can contain up to 2,000 items.
Large promotions with many sites or many items are split internally during processing. Partners do not need to manually split promotions by site or item unless required operationally.
Recommendations:
- Group related promotions into a single upload where practical
- Avoid unnecessary duplication across promotion objects
- Validate JSON locally before upload
- Keep each upload focused on the intended promotion state for the relevant brand
- Remove obsolete or invalid promotion objects before submitting the file
- Ensure each
promotion_idappears only once in the file - Keep each
promotion_idwithin the 2,000 item limit
Files above the maximum size may fail with a FILE_TOO_LARGE error. Malformed JSON may fail with an INVALID_JSON error. Duplicate promotion identifiers may fail with a DUPLICATE_PROMOTION_ID error.
Async Processing and Polling
Promotion uploads and updates are processed asynchronously.
Each async operation returns a job_id. Partners can use this job_id to poll the job-status endpoint until the operation reaches a terminal state.
Recommended polling pattern:
- Start with a short initial delay
- Use exponential backoff
- Cap polling intervals for long-running jobs
- Stop polling once a terminal status is reached
- Avoid continuous polling loops
Terminal statuses include:
completed: All promotions were processed successfullypartial_success: Some promotions succeeded and others failed. Partners should inspect counters and errors to reconcile the outcomefailed: The upload or update did not complete successfully. Inspect the returned errors before retrying
Updating or Ending Promotions
Use PUT /brands/{brand_id}/promotions/{promotion_id} to update an existing promotion.
The request body should contain the full updated promotion object. The promotion_id in the URL path must match the promotion_id in the request body.
Common use cases:
- Correcting promotion details during the day
- Updating price, discount, items, sites, timing or targeting
- Ending an active promotion by updating its end time
Recommendations:
- Send the full promotion object when updating a promotion
- Use the Integration Guide for endpoint request and response examples
- Poll the returned
job_idif your integration needs confirmation - Avoid retrying update operations without first checking job state
- If a bulk upload is processing the same
promotion_id, retry after the bulk job reaches a terminal state - Re-upload promotions for broad campaign changes across many promotions
Errors
The Promotions API can return errors in two ways:
- Synchronous API errors returned immediately by an endpoint
- Asynchronous processing errors returned through job status after upload or update processing
Standard Error Response
Synchronous API errors use the following structure:
{
"error_code": "INVALID_REQUEST",
"error_message": "Field 'brand_id' is required.",
"details": [
{
"field": "brand_id",
"message": "must be provided in the URL path"
}
]
}The request correlation ID is returned in the X-Request-Id HTTP response header. Log this value alongside errors for operational support.
Synchronous API Errors
| Code | Meaning | Recommended action |
|---|---|---|
INVALID_REQUEST | Request is malformed or missing required fields | Fix the request and retry |
UNAUTHENTICATED | Bearer token is missing or expired | Refresh the token and retry |
FORBIDDEN | Client is not authorised for the brand or scope | Contact Deliveroo onboarding |
NOT_FOUND | Unknown job_id or promotion_id | Check the identifier |
CONFLICT | Resource is in an incompatible state | Re-fetch state before retrying |
RATE_LIMITED | Rate limit exceeded | Back off before retrying |
INTERNAL_ERROR | Temporary platform error | Retry with exponential backoff |
Recommendations:
- Log request correlation IDs for operational support
- Retry
5xxresponses using exponential backoff - Respect rate-limit wait headers on
429responses - Check job state before retrying conflicting operations
S3 Upload Errors
| Status | Meaning | Recommended action |
|---|---|---|
200 OK | File accepted by S3 | No action required |
400 Bad Request | Malformed request to S3 | Check method, headers and payload |
403 Forbidden | Presigned URL expired or signature mismatch | Request a new upload URL |
Processing Errors
| Code | Meaning | Recommended action |
|---|---|---|
ITEM_NOT_FOUND | Item identifier did not resolve to a catalogue item for this brand | Verify the item exists in the Deliveroo catalogue and re-upload corrected items |
SITE_NOT_FOUND | Site is not registered or active for the brand | Confirm site synchronisation and active status |
PROMOTION_OVERLAP | Another active promotion already exists for an overlapping item, site and time window | Adjust the promotion window or remove the duplicate promotion |
HFSS_VIOLATION | Item is subject to HFSS promotion restrictions | Confirm affected items during onboarding |
SCHEDULE_INVALID | Promotion timing is invalid | Fix timestamps and re-upload |
FILE_TOO_LARGE | Upload exceeds the maximum file size | Split into multiple uploads |
INVALID_PROMOTION_TYPE | promotion_type is not supported | Use a supported promotion_type |
INVALID_CONDITION | The condition object is missing, malformed or incompatible with the selected promotion_type | Fix the condition fields and re-upload |
INVALID_REWARD | The reward object is missing, malformed or incompatible with the selected promotion_type | Fix the reward fields and re-upload |
INVALID_JSON | File could not be parsed as JSON | Validate JSON locally before upload |
DUPLICATE_FILE | Uploaded file matches the most recent successful upload for the brand, so promotions were not reprocessed | Treat as a no-op unless the duplicate upload was unintended |
DUPLICATE_PROMOTION_ID | The same promotion_id appears on more than one promotion object in the file | Give each promotion object a unique promotion_id |
Rate Limits
Rate limits are configured per brand during onboarding.
Default rate limits:
| Endpoint | Default limit |
|---|---|
POST /brands/{brand_id}/promotions/upload_url | 6 per brand per hour |
PUT /brands/{brand_id}/promotions/{promotion_id} | 60 per brand per minute |
GET /brands/{brand_id}/promotions/jobs/{job_id}/status | 60 per brand per minute per job |
Rate-limited responses may include:
X-Deliveroo-RateLimit-LimitX-Deliveroo-RateLimit-RemainingX-Deliveroo-RateLimit-Wait-Time-Seconds
On a 429, wait at least X-Deliveroo-RateLimit-Wait-Time-Seconds before retrying.
Retry and Duplicate Upload Detection
The bulk upload flow detects duplicate file submissions by comparing each uploaded file with the most recent successful upload for the brand.
If the uploaded file matches the most recent successful upload, the job ends with DUPLICATE_FILE and promotions are not reprocessed.
Single-promotion update requests do not have built-in duplicate detection. Partners should maintain their own request tracking if they require at-most-once behaviour for updates.
Recommendations:
- Retry upload URL requests where appropriate, especially after transient
429or5xxresponses - Use exponential backoff for transient failures such as
429and5xxresponses - Check the latest job status before retrying update operations
- Maintain partner-side request tracking if you require at-most-once behaviour for updates
Avoid blindly retrying update requests, as the operation may already be processing asynchronously.
Reconciliation
Partners may choose to reconcile uploads after they reach a terminal status.
Use the job-status response to review:
- Upload status
- Promotion counters
- Promotion item counters
- Aggregated errors
A partial_success status means some promotions succeeded while others failed. Treat this as an actionable outcome and use the returned counters and errors to identify which promotions need correction or re-upload.
Retain job_id and request correlation IDs for operational support.
The errors array is grouped by promotion_id. Each promotion-level entry contains one or more error types and the related item identifiers, where applicable. Successful promotions do not appear in the errors list.
