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:

RequirementDescription
OAuth2 client credentialsIssued during onboarding
promotions scopeEnabled on the OAuth client
Brand allowlistingAccess granted for the target brand
Site synchronisationAll site_ids already registered with Deliveroo
Catalogue synchronisationPromoted 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/json

Do 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

EnvironmentBase URL
Productionhttps://api.developers.deliveroo.com
OAuth token endpointhttps://auth.developers.deliveroo.com/oauth2/token
Developer Portalhttps://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:

  1. Request a presigned upload URL
  2. Upload the promotion file directly to S3
  3. 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.json

Response: 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

StatusMeaning
pending_uploadUpload URL issued; file not yet received
processingFile received and processing is in progress
completedTerminal: All promotions were processed successfully
partial_successTerminal: Some promotions succeeded and others failed
failedTerminal: 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_type defines the promotion mechanic
  • condition defines what the customer basket must satisfy
  • reward defines 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_at to 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.json

Example 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.