Overview
Promotion Management API
POST PATCH — https://openapi.doordash.com/marketplace/api/v2/promotions/stores/{store_location_id}
The Promotion Management API enables merchants to create and update promotions in real-time. Promotions are discounts on in-store items that auto-apply in a customer's cart when purchase requirements are met.
Authentication and credentials are managed through the DoorDash Developer Portal.
The API currently supports one promotion per individual item. Promotions cannot be applied to modifier items, as the promo update will drop modifiers on the store.
Supported Promotion Types
DoorDash supports the following promotion types:
Same SKU Promotions
| Type | Description |
|---|---|
BUY_X_FOR_Y | Buy X items for a fixed total price |
BUY_X_SAVE_Y | Buy X items and save a flat dollar amount |
BUY_X_GET_Y_Z_PERCENT_OFF | Buy X items, get Y items at Z% off |
Multi-SKU / Mix & Match Promotions
| Type | Description |
|---|---|
BUY_X_FOR_Y | Buy X items across multiple SKUs for a fixed total price |
BUY_X_SAVE_Y | Buy X items across multiple SKUs and save a flat amount |
BUY_X_GET_Y_Z_PERCENT_OFF | Buy X across multiple SKUs, get Y items at Z% off |
To enable Mix & Match, include multiple SKU IDs in purchase_items and set promotion_options.promotion_conditions to ["MIX_AND_MATCH"].
Endpoints
POST — Create Promotion
Creates new promotions for a store. Send the full promotion payload — not incremental updates.
Request
Headers
Refer to the JWT authentication documentation for authentication details.
Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
promotion_id | String | ✅ Required | Primary key identifying a merchant-provided promotion |
promotion_type | String (Enum) | ✅ Required | One of: BUY_X_FOR_Y, BUY_X_SAVE_Y, BUY_X_GET_Y_Z_PERCENT_OFF |
purchase_criteria | Object | ✅ Required | Defines what the customer must buy |
purchase_criteria.purchase_items | String[] | ✅ Required | List of Merchant Supplied Item IDs (MSIDs) eligible to trigger this promotion. Use multiple MSIDs for Mix & Match |
purchase_criteria.purchase_quantity | Integer | ✅ Required | Number of items customer must purchase to redeem the promotion |
redemption_limit | Object | Optional | Limits how many times the promo applies per order |
redemption_limit.limit_per_order | Integer | Optional | Max redemptions allowed in a single order. Default: 3 |
discount_options | Object | ✅ Required | Defines the discount value |
discount_options.discount_price_off | Long (cents) | Conditional | Flat discount amount. Required when promotion_type = BUY_X_SAVE_Y. Example: 100 = $1.00 off |
discount_options.discount_total_price | Long (cents) | Conditional | Bundle total price. Required when promotion_type = BUY_X_FOR_Y. Example: 300 = $3.00 total |
discount_options.discount_percentage | Long | Conditional | Percentage discount. Required when promotion_type = BUY_X_GET_Y_Z_PERCENT_OFF. Example: 50 = 50% off |
discount_options.discount_quantity | Long | Conditional | Number of items to discount. Used with BUY_X_GET_Y_Z_PERCENT_OFF |
promotion_options | Object | Optional | Additional promotion configuration |
promotion_options.promotion_conditions | Enum[] | Optional | Set to ["MIX_AND_MATCH"] to allow cross-SKU eligibility |
start_time | Timestamp (UTC) | ✅ Required | Promotion start time. Example: 2023-07-07T14:48:00.000Z |
end_time | Timestamp (UTC) | ✅ Required | Promotion end time. Example: 2023-07-08T14:48:00.000Z |
Response
202 Accepted — Success
{
"operation_id": "string",
"operation_status": "SUCCESS",
"message": "string"
}
operation_status possible values: QUEUED · IN_PROGRESS · SUCCESS · FAILED · PARTIAL_SUCCESS
Error Codes
| HTTP Code | Code | Message | field_errors.field | field_errors.error |
|---|---|---|---|---|
| 400 | validation_error | One or more request values couldn't be validated | Name of the field that failed validation | The validation error message |
| 401 | authentication_error | The [exp] is in the past; the JWT is expired | — | — |
| 403 | authorization_error | Authorization error: credentials provided are invalid | — | — |
| 404 | unknown_business_id | — | — | — |
| 422 | request_rate_limited | — | — | — |
| 429 | request_rate_limited | — | — | — |
| 500 | service_fault | Internal service failure, please try again later | — | — |
PATCH — Update Promotion
Updates existing promotions for a store. Send the full promotion payload — not incremental updates.
Request
Same headers and body parameters as POST Create Promotion.
⚠️ Always send the complete promotions payload. Incremental updates (modifying a single attribute) are not supported.
Response
Same response structure as POST.
⚠️ If you PATCH a promotion that doesn't exist, you will receive a
202 Acceptedbut the promotion will be dropped during async validation. Additional feedback mechanisms are planned for a future release.
Rate Limits & Batch Sizes
| Endpoint | Batch Size | Rate Limit |
|---|---|---|
| Promotion Management API (POST) | 1,000 items | 5–10 QPS |
Error Reference
| HTTP Code | Error Reason | Message | How to Fix |
|---|---|---|---|
| 400 | validation_errors | code: validation_errors; fieldErrors: {} | One or more fields failed validation. Verify all required fields are present and correctly typed. Check the request body against the parameter table above. |
| 401 | authentication_error | The [exp] is in the past; the JWT is expired | Your JWT token has expired. Regenerate your token and retry the request. |
| 403 | authorization_error | Authorization error: credentials provided are invalid | Verify your credentials in the DoorDash Developer Portal. Ensure the token has the correct permissions for this store. |
| 404 | unknown_business_id | — | The store_location_id in the URL does not exist or is not associated with your account. Verify the store ID. |
| 422 | request_rate_limited | — | Reduce request frequency and retry. |
| 429 | RATE_LIMIT_EXCEEDED | Developer account endpoint rate limit exceeded | You have exceeded the allowed rate (5–10 QPS). Implement exponential backoff, reduce request frequency, and batch promotions (up to 1,000 items per request). |
| 500 | service_fault | Internal service failure, please try again later | Retry the request. If the issue persists, contact DoorDash Developer Support. |
Payload Examples
BUY_X_FOR_Y — Buy 2 Coke for $3 (Same SKU)
{
"promotion": {
"promotion_id": "101",
"promotion_type": "BUY_X_FOR_Y",
"purchase_criteria": {
"purchase_quantity": 2,
"purchase_items": ["coke_msid"]
},
"redemption_limit": {
"limit_per_order": 3
},
"discount_options": {
"discount_total_price": 300
},
"start_time": "2023-07-07T14:48:00.000Z",
"end_time": "2023-07-08T14:48:00.000Z"
}
}
BUY_X_FOR_Y + MIX_AND_MATCH — Buy 2 Coke OR Buy 2 Sprite for $3 (Multi-SKU)
{
"promotion": {
"promotion_id": "101",
"promotion_type": "BUY_X_FOR_Y",
"purchase_criteria": {
"purchase_quantity": 2,
"purchase_items": ["coke_msid", "sprite_msid"]
},
"redemption_limit": {
"limit_per_order": 3
},
"discount_options": {
"discount_total_price": 300
},
"promotion_options": {
"promotion_conditions": ["MIX_AND_MATCH"]
},
"start_time": "2023-07-07T14:48:00.000Z",
"end_time": "2023-07-08T14:48:00.000Z"
}
}
BUY_X_SAVE_Y — Buy 2 Coke, Save $1
{
"promotion": {
"promotion_id": "101",
"promotion_type": "BUY_X_SAVE_Y",
"purchase_criteria": {
"purchase_quantity": 2,
"purchase_items": ["coke_msid"]
},
"redemption_limit": {
"limit_per_order": 3
},
"discount_options": {
"discount_price_off": 100
},
"start_time": "2023-07-07T14:48:00.000Z",
"end_time": "2023-07-08T14:48:00.000Z"
}
}
BUY_X_GET_Y_Z_PERCENT_OFF — Buy 1 Coke, Get 1 Coke 50% Off
{
"promotion": {
"promotion_id": "101",
"promotion_type": "BUY_X_GET_Y_Z_PERCENT_OFF",
"purchase_criteria": {
"purchase_quantity": 1,
"purchase_items": ["coke_msid"]
},
"redemption_limit": {
"limit_per_order": 3
},
"discount_options": {
"discount_quantity": 1,
"discount_percentage": 50
},
"start_time": "2023-07-07T14:48:00.000Z",
"end_time": "2023-07-08T14:48:00.000Z"
}
}
BUY_X_FOR_Y + MIX_AND_MATCH — Buy Any 2 Lays Chips for $5 (Multi-SKU)
{
"promotion_id": "123",
"promotion_type": "BUY_X_FOR_Y",
"purchase_criteria": {
"purchase_items": ["a100", "a200", "a300"],
"purchase_quantity": 2
},
"redemption_limit": {
"limit_per_order": 2
},
"discount_options": {
"discount_total_price": 500
},
"promotion_options": {
"promotion_conditions": ["MIX_AND_MATCH"]
},
"start_time": "2025-01-01T00:00:00Z",
"end_time": "2026-01-01T00:00:00Z"
}
Mix & Match Discount Distribution
When a Mix & Match promotion applies across items with different prices, DoorDash distributes the discount as follows:
- Higher-priced items are discounted first — the system selects the highest-priced eligible items to maximize customer savings.
- Discount is distributed proportionally — the discount amount on each item is calculated based on the weight of
(price × discounted quantity) / total price. - Rounding — fractional amounts are rounded up to the nearest cent to avoid penny discrepancies.
- Equal-price items — when items have the same price, discounts are applied using "First In" logic (items added first to the cart receive the discount).
Example — Buy 2 for $6, cart has 3 items:
- Item A: $4 (added 1st), Item B: $4 (added 2nd), Item C: $5 (added 3rd)
- Discounted items: Item C ($5) and Item A ($4) → discount = $9 − $6 = $3
- Item B remains at full price
Example order payload showing discount distribution
{
"categories": [
{
"merchant_supplied_id": "Drinks",
"name": "Drinks",
"items": [
{
"name": "Coke Soda Bottle (20 fl oz)",
"quantity": 1,
"price": 379,
"merchant_supplied_id": "8010333",
"applied_item_discount": {
"discount_amount": 77,
"promo_id": "83867509-6f27-38f9-952f-fe141bd8e43a",
"promo_quantity": {
"discount_item_promo_quantity": 1
}
},
"applied_item_discount_details": [
{
"total_discount_amount": 77,
"promo_id": "83867509-6f27-38f9-952f-fe141bd8e43a",
"promo_quantity": {
"discount_item_promo_quantity": 1
},
"doordash_funded_discount_amount": 0,
"merchant_funded_discount_amount": 77
}
]
},
{
"name": "Diet Mountain Dew Citrus Soda Bottle (20 fl oz)",
"quantity": 2,
"price": 359,
"merchant_supplied_id": "8050480",
"applied_item_discount": {
"discount_amount": 71,
"promo_id": "83867509-6f27-38f9-952f-fe141bd8e43a",
"promo_quantity": {
"discount_item_promo_quantity": 1
}
},
"applied_item_discount_details": [
{
"total_discount_amount": 71,
"promo_id": "83867509-6f27-38f9-952f-fe141bd8e43a",
"promo_quantity": {
"discount_item_promo_quantity": 1
},
"doordash_funded_discount_amount": 0,
"merchant_funded_discount_amount": 71
}
]
}
]
}
]
}
FAQ
How long will it take for a new promotion to be created or updated?
It can take up to 15 minutes for a new or updated promotion to go live.
What are the rate limits and batch sizes?
| Endpoint | Batch Size | Rate Limit |
|---|---|---|
| Promotion Management API (POST) | 1,000 | 5–10 QPS |
What will happen if we PATCH a promotion that doesn't exist?
If you send the request in the correct format, we will return a 202 Accepted response but drop the promotion during async validation. Additional feedback mechanisms are planned for a future release.
How do I remove a promotion that is no longer running intra-day?
Share the expiration as the current day minus 1 (i.e., set end_time to yesterday).
How do I verify whether a promotion went through correctly in advance?
At the moment, you cannot submit promotions in advance and receive confirmation of future activation. Additional feedback is planned for a future release.
Can we leverage the same OpenAPI provider type as other endpoints?
Yes, you will use the same provider type across all endpoints.
If a new promo is created, should we send all promos again via POST or can we send a PATCH?
Send the complete promotions payload — incremental updates on a single attribute are not supported.
Do you support Mix & Match deals?
Yes. Mix & Match is supported. To enable it:
- Include multiple SKU IDs in
purchase_items - Add
"MIX_AND_MATCH"topromotion_options.promotion_conditions
There is no upper limit on the number of SKUs, but at least two SKUs are required.
Can the same item be part of multiple deals?
No. We only support one deal per item in the integration. If multiple promotions are submitted for a single item, the promo will be dropped.
Can customers stack multiple discounts?
Yes. Customers will see stacked promotions when applicable. Promos can stack regardless of funding source, but must be different deal types (e.g., STP vs. complex deal). If multiple deals of the same type are live on one item, the system surfaces the deal based on funding source priority:
CPG funded → Ibotta → Merchant Ingested → DoorDash funded
How are promos gated by loyalty handled?
Your DoorDash technical account manager can enable the loyalty-gated feature if it is required for your integration.
If an item is adjusted in the cart, will the promotion still apply?
Yes. If the cart still qualifies for a promotion after an item adjustment, the promotion will continue to apply to the remaining eligible items.
What rounding methodology is used for fractional discount calculations?
We round up to the nearest cent. This ensures no penny discrepancies ($0.01–$0.02 variances) between item-level deductions and the total promotion amount shown to the customer.
When all items are the same price, which items get the discount if the cart exceeds the deal quantity?
When items have identical prices, the system uses "First In" logic — the discount applies to items based on the order they were added to the cart (the order in the payload).
Example — Buy 3 for $4, cart has 4 items:
- 2× Item A ($X, added 1st), 1× Item B ($X, added 2nd), 1× Item C ($X, added 3rd)
- Discounted: 2× Item A + 1× Item B
- Item C remains at full price (last added)
If I submit a future-dated promo for a SKU that already has a live promo running, will the original promo keep showing until the new one starts?
No. Submitting a new promo immediately replaces the existing one, regardless of the new promo's start date. If the new promo is future-dated, it won't render on the storefront yet — and the original live promo is already gone. The item will show no deal until the new promo's start date arrives.
Fix: Wait until your current promo period ends before submitting the next one. There is no promo queuing — it's last-write-wins.