Integration
This guide covers the core integration flow for the Promotions API, including authentication, upload workflows, promotion file format, status polling and single-promotion updates.
For promotion modelling, field-level guidance, formatting rules, error handling, rate limits and operational best practices, see the Promotions API Guidelines.
Prerequisites
Before integrating, the following must already be in place:
| Requirement | Description |
|---|---|
| OAuth2 client credentials | Issued during onboarding |
promotions scope | Enabled on the OAuth client |
| Brand allowlisting | Access granted for the target brand |
| Site synchronisation | All site_ids already registered with Deliveroo |
| Catalogue synchronisation | Promoted item identifiers already exist in Deliveroo’s catalogue |
The Promotions API only references sites and catalogue items that already exist on Deliveroo. It does not create catalogue items, branches or sites.
Authentication
The Promotions API uses OAuth2 client credentials authentication.
Authenticated Promotions API requests must include:
Authorization: Bearer <access_token>Requests with a JSON body must also include:
Content-Type: application/jsonDo not send OAuth headers when uploading the promotion file to the presigned S3 URL.
Obtain an Access Token
curl -X POST https://auth.developers.deliveroo.com/oauth2/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-u "<client_id>:<client_secret>" \
-d "grant_type=client_credentials" \
-d "scope=promotions"Example response:
{
"access_token": "eyJhbGciOi...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "promotions"
}Recommendations:
- Cache tokens and reuse them until shortly before expiry
- Do not request a new token for every API call
Base URLs
| Environment | Base URL |
|---|---|
| Production | https://api.developers.deliveroo.com |
| OAuth token endpoint | https://auth.developers.deliveroo.com/oauth2/token |
| Developer Portal | https://developers.deliveroo.com/ |
Promotions API resource paths are scoped under /brands/{brand_id}/....
Sandbox and staging access are provided during onboarding.
Upload Workflow
The Promotions API uses an asynchronous upload workflow.
A typical upload has three steps:
- Request a presigned upload URL
- Upload the promotion file directly to S3
- Poll the job-status endpoint when confirmation or reconciliation is required
Step 1 — Request an Upload URL
Request a presigned S3 upload URL.
curl -X POST https://api.developers.deliveroo.com/brands/{brand_id}/promotions/upload_url \
-H "Authorization: Bearer <access_token>"Response: 200 OK
{
"job_id": "01926fa0-3c4d-7d6e-9a02-b7b3d4f5e601",
"upload_url": "https://...",
"expires_at": "2026-05-22T08:00:00Z"
}The returned job_id is used to track upload processing. The upload_url must be used before expires_at.
Step 2 — Upload the File to S3
Upload the JSON payload directly to the presigned S3 URL.
curl -X PUT "<upload_url>" \
-H "Content-Type: application/json" \
--data-binary @promotions.jsonResponse: 200 OK
Recommendations:
- Do not send OAuth headers to S3
- Upload the file before
expires_at - Validate JSON locally before upload
The presigned URL already contains the authentication required by S3. Sending Authorization: Bearer <access_token> to S3 may cause the upload request to fail.
Step 3 — Check Job Status
Poll the job-status endpoint when your integration needs confirmation, reconciliation or error details. Stop polling once the job reaches a terminal state.
curl -X GET https://api.developers.deliveroo.com/brands/{brand_id}/promotions/jobs/{job_id}/status \
-H "Authorization: Bearer <access_token>"Example response:
{
"job_id": "01926fa0-3c4d-7d6e-9a02-b7b3d4f5e601",
"job_type": "bulk_promotion_upload",
"status": "partial_success",
"total_promotions": 11,
"live_promotions": 8,
"completed_promotions": 1,
"failed_promotions": 2,
"total_promotion_items": 23400,
"succeeded_promotion_items": 22950,
"failed_promotion_items": 450,
"errors": [
{
"promotion_id": "GROCER-PCT-OFF-PASTA-2026Q2",
"errors": [
{
"type": "ITEM_NOT_FOUND",
"item_ids": [
"5000169816247",
"5000169816300"
]
},
{
"type": "PROMOTION_OVERLAP",
"item_ids": [
"5000169830055"
]
}
]
},
{
"promotion_id": "GROCER-FREE-DELIVERY-WEEKEND-2026Q3",
"errors": [
{
"type": "INVALID_DATE_RANGE",
"item_ids": []
}
]
}
],
"created_at": "2026-05-21T09:00:00Z",
"updated_at": "2026-05-21T09:14:32Z"
}Job Statuses
| Status | Meaning |
|---|---|
pending_upload | Upload URL issued; file not yet received |
processing | File received and processing is in progress |
completed | Terminal: All promotions were processed successfully |
partial_success | Terminal: Some promotions succeeded and others failed |
failed | Terminal: The upload failed, or no promotions were processed successfully |
Recommendations:
- Use exponential backoff when polling
- Stop polling once a terminal state is reached
- Reconcile failed promotions using returned errors and counters
Promotion File Format
Uploads use a JSON object with a promotions array.
Each object in the promotions array represents one promotion with one promotion_id, one promotion_type, 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
Item-level promotions use condition.items.
Basket and delivery promotions do not require condition.items.
Detailed field-level rules are covered in the Promotions API Guidelines.
Example Promotion File
{
"promotions": [
{
"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",
"grocer-gb-london-002"
],
"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_id": "GROCER-AMT-OFF-DAIRY-2026Q3",
"promotion_type": "AMOUNT_OFF_ON_ITEMS",
"name": "£1 off butter",
"user_target": "ALL_CUSTOMERS",
"sites": [
"grocer-gb-london-001"
],
"fulfillment_method": "COLLECTION",
"start_at": "2026-07-01T00:00:00Z",
"end_at": "2026-07-31T23:59:59Z",
"condition": {
"items": [
"5000169821000"
]
},
"reward": {
"amount_off": 100
}
},
{
"promotion_id": "GROCER-SECOND-ITEM-2026Q2",
"promotion_type": "PERCENTAGE_OFF_ON_SECOND_ITEM",
"name": "50% off second item",
"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": [
"5000169830001",
"5000169830002"
],
"quantity": 2
},
"reward": {
"percentage": 50
}
},
{
"promotion_id": "GROCER-BUY3-PASTA-PCT-2026Q3",
"promotion_type": "PERCENTAGE_OFF_ON_SINGLE_ITEM_MULTIBUY",
"name": "Buy 3 pasta, 30% off",
"user_target": "ALL_CUSTOMERS",
"sites": [
"grocer-gb-london-001"
],
"fulfillment_method": "DELIVERY",
"start_at": "2026-07-01T00:00:00Z",
"end_at": "2026-07-31T23:59:59Z",
"condition": {
"items": [
"5000169816246"
],
"quantity": 3
},
"reward": {
"percentage": 30
}
},
{
"promotion_id": "GROCER-SANDWICH-2FOR2-2026Q2",
"promotion_type": "FIXED_PRICE_ON_SINGLE_ITEM_MULTIBUY",
"name": "2 sandwiches for £2",
"user_target": "ALL_CUSTOMERS",
"sites": [
"grocer-gb-london-001",
"grocer-gb-london-002"
],
"fulfillment_method": "DELIVERY",
"start_at": "2026-06-10T00:00:00Z",
"end_at": "2026-06-25T23:59:59Z",
"condition": {
"items": [
"5000169880001"
],
"quantity": 2
},
"reward": {
"fixed_price": 200
}
},
{
"promotion_id": "GROCER-MIX3-SNACKS-25PCT-2026Q3",
"promotion_type": "PERCENTAGE_OFF_ON_MULTIPLE_ITEM_MULTIBUY",
"name": "Any 3 snacks, 25% off",
"user_target": "ALL_CUSTOMERS",
"sites": [
"grocer-gb-london-001"
],
"fulfillment_method": "DELIVERY",
"start_at": "2026-07-01T00:00:00Z",
"end_at": "2026-07-31T23:59:59Z",
"condition": {
"items": [
"5000169816101",
"5000169816102",
"5000169816103"
],
"quantity": 3
},
"reward": {
"percentage": 25
}
},
{
"promotion_id": "GROCER-ANY2-DRINKS-4-2026Q3",
"promotion_type": "FIXED_PRICE_ON_MULTIPLE_ITEM_MULTIBUY",
"name": "Any 2 drinks for £4",
"user_target": "ALL_CUSTOMERS",
"sites": [
"grocer-gb-london-001"
],
"fulfillment_method": "DELIVERY",
"start_at": "2026-07-01T00:00:00Z",
"end_at": "2026-07-31T23:59:59Z",
"condition": {
"items": [
"5000169816201",
"5000169816202",
"5000169816203",
"5000169816204"
],
"quantity": 2
},
"reward": {
"fixed_price": 400
}
},
{
"promotion_id": "GROCER-BASKET-10PCT-2026Q3",
"promotion_type": "PERCENTAGE_OFF_ON_BASKET",
"name": "10% off £20 baskets",
"user_target": "ALL_CUSTOMERS",
"sites": [
"grocer-gb-london-001"
],
"fulfillment_method": "DELIVERY",
"start_at": "2026-07-01T00:00:00Z",
"end_at": "2026-07-31T23:59:59Z",
"condition": {
"min_order_value": 2000,
"alcohol_allowed": false
},
"reward": {
"percentage": 10,
"max_discount": 500
}
},
{
"promotion_id": "GROCER-FREE-DELIVERY-WEEKEND-2026Q3",
"promotion_type": "FREE_DELIVERY",
"name": "Free delivery weekend",
"user_target": "ALL_CUSTOMERS",
"sites": [
"grocer-gb-london-001"
],
"fulfillment_method": "DELIVERY",
"start_at": "2026-07-04T00:00:00Z",
"end_at": "2026-07-06T23:59:59Z",
"condition": {
"min_order_value": 1500,
"alcohol_allowed": false
},
"reward": {}
},
{
"promotion_id": "GROCER-BUY2-GET1-FREE-2026Q3",
"promotion_type": "BUY_X_FOR_Y",
"name": "Any 3 for 2",
"user_target": "ALL_CUSTOMERS",
"sites": [
"grocer-gb-london-001"
],
"fulfillment_method": "DELIVERY",
"start_at": "2026-07-01T00:00:00Z",
"end_at": "2026-07-31T23:59:59Z",
"condition": {
"items": [
"5000169816301"
],
"quantity": 3
},
"reward": {
"quantity": 1
}
},
{
"promotion_id": "GROCER-BUY3PLUS-SAVE15-2026Q3",
"promotion_type": "BUY_X_PLUS_SAVE_Y_PERCENT",
"name": "Buy 3 or more, save 15%",
"user_target": "ALL_CUSTOMERS",
"sites": [
"grocer-gb-london-001"
],
"fulfillment_method": "DELIVERY",
"start_at": "2026-07-01T00:00:00Z",
"end_at": "2026-07-31T23:59:59Z",
"condition": {
"items": [
"5000169816401",
"5000169816402"
],
"quantity": 3
},
"reward": {
"percentage": 15
}
}
]
}Single-Promotion Updates
Use PUT /brands/{brand_id}/promotions/{promotion_id} to update or end one promotion outside the regular bulk upload workflow.
The request body must 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 setting
end_atto a past timestamp
curl -X PUT https://api.developers.deliveroo.com/brands/{brand_id}/promotions/{promotion_id} \
-H "Authorization: Bearer <access_token>" \
-H "Content-Type: application/json" \
--data-binary @promotion.jsonExample request body:
{
"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"
]
},
"reward": {
"percentage": 25
}
}Response: 202 Accepted
{
"job_id": "01HX9...",
"job_type": "single_promotion_update",
"status": "pending"
}The endpoint is asynchronous. Use the returned job_id with the job-status endpoint if confirmation is required.
If a bulk upload is currently processing the same promotion_id, the update may be rejected with JOB_IN_PROGRESS. Retry once the bulk job reaches a terminal state.
