## Introduction

**Commerce Max Retailer Budgets** introduces a buying model in which campaign budgets are funded by the retailer rather than the advertiser.

This guide covers the API changes required for third-party buying platforms to create and manage Retailer Budgets Sponsored Products campaigns end-to-end.

### Business Context

Prior to this release, the Retail Media API did not expose retailer scoping on balances, campaigns, or line items. Retailer-budgets buying requires platforms to associate balances, campaigns, and line items to the same retailer — the API now enforces these constraints explicitly.

### Prerequisites

- API version `2026-01` or later is required to see retailer-billed balances. On prior versions, retailer-budgets balances are hidden by default to prevent integration surprises during rollout.
- Platforms must use the supply account ID when querying balances if they wish to discover retailer-budgets balances.

### Key Concepts

- **Retailer-budgets balance** — a budget object funded by the retailer, scoped to a specific `RetailerId`. Cannot be created via API; must be retrieved via `GET /balances`.
- **`RetailerId`** — the identifier of the retailer that funds the balance and scopes the campaign. Must be consistent across balance → campaign → line item.
- **`budgetModel`** — a new field on the retailer search response indicating which budget models (e.g., `retailerBilled`, `capped`, `uncapped`) are supported at a given retailer.

**Backward compatibility for balance endpoints:**

- The `poNumber` field on balance responses is **removed** in `2026-01` and replaced by two separate fields: `retailerPoNumber` and `criteoPoNumber`. This is a breaking change for consumers of `GET /balances` on prior versions who rely on `poNumber`.
- All other new fields (`retailerId`, `privateMarketBillingType`) are additive. Retailer-budgets balances are hidden by default on prior API versions.

## Endpoints Overview

All endpoints changes related to Retailer Budgets are also documented in the following pages:

- [Balances Endpoint Guide](https://developers.criteo.com/retail-media/docs/balances-endpoints)
- [Campaigns Endpoint Guide](https://developers.criteo.com/retail-media/docs/campaigns-endpoints)
- [Auction Line Items Endpoint Guide](https://developers.criteo.com/retail-media/docs/onsite-sponsored-products-line-items)
- [Retailer Search Endpoint Guide](https://developers.criteo.com/retail-media/docs/retailer-search)

| Verb | Endpoint | Description |
| --- | --- | --- |
| **GET** | `/retail-media/balances/{balanceId}` | Get a single balance. Now includes `retailerId`, `retailerPoNumber`, `criteoPoNumber`. |
| **GET** | `/retail-media/accounts/{accountId}/balances` | List balances. Now includes retailer fields. Retailer-budgets balances hidden on prior versions. |
| **GET** | `/retail-media/balances/{balanceId}/history` | Get balance change history. Now includes `retailerPoNumber`, `criteoPoNumber`. |
| **POST** | `/retail-media/balances/{balanceId}/campaigns/append` | Add campaigns to a balance. Validates retailer consistency. |
| **POST** | `/retail-media/balances/{balanceId}/campaigns/delete` | Remove campaigns from a balance. Returns error for retailer-billed balances. |
| **POST** | `/retail-media/accounts/{accountId}/campaigns` | Create a campaign. Now accepts and validates `retailerId`. |
| **GET** | `/retail-media/campaigns` | List campaigns. Now returns `retailerId`; supports filtering by retailer. |
| **GET** | `/retail-media/campaigns/{campaignId}` | Get a campaign. Now returns `retailerId`. |
| **POST** | `/retail-media/campaigns/{campaignId}/auction-line-items` | Create a line item. Enforces `targetRetailerId` matches campaign retailer. |
| **POST** | `/retail-media/accounts/{accountId}/retailers/search` | Search retailers. Now returns `budgetModel` in `campaignAvailabilities`. |

## Attributes

### New and Changed Fields on Balances

| Attribute | Data Type | Mutable | Description |
| --- | --- | --- | --- |
| `retailerId` | `string?` | init | Retailer this balance is scoped to.<br>Present only on retailer-budgets balances.<br>_Nullable?_ Y (null for non-retailer-billed) |
| `retailerPoNumber` | `string?` | always | Retailer purchase order number. **Replaces** the removed `poNumber` field.<br>_Nullable?_ Y |
| `criteoPoNumber` | `string?` | always | Criteo purchase order number.<br>**Replaces** the removed `poNumber` field.<br>_Nullable?_ Y |
| `privateMarketBillingType` | `enum` | init | Billing type for Private Market. Values: `NotApplicable`, `BillByRetailer`, `BillByCriteo`, `Unknown` |
| ~~`poNumber`~~ | ~~string~~ | — | **Removed** in `2026-01`. Replaced by `retailerPoNumber` and `criteoPoNumber`. |

### New Field on Campaigns

A new field `retailerId` has been added to the following endpoints:

- `/accounts/{accountId}/campaigns`
- `/campaigns/{campaignId}`

| Attribute | Data Type | Description |
| --- | --- | --- |
| `retailerId` | `string` | The retailer this campaign is associated with.<br>Required when using a retailer-budgets balance.<br>**Writeable?** Y (at create)<br>**Nullable?** Y (for non-retailer-budgets campaigns) |

### New Fields on Retailer Search

The new fields are added to the following endpoint:

- `/accounts/{accountId}/retailers/search`

| Attribute | Data Type | Description |
| --- | --- | --- |
| `budgetModel` | `string` | Budget model(s) supported for the given `buyType`/`campaignType` combination at this retailer.<br>Values: `capped`, `uncapped`, `retailerBudgets`, `criteoBudgets`<br>**Writeable?** N<br>**Nullable?** N |

### New Error Codes

| Error code | Endpoint | Meaning |
| --- | --- | --- |
| `RetailerBilledBalanceImmutable` | `DELETE /balances/{balanceId}/campaigns` | Cannot remove a retailer-budgets balance from a campaign. |
| `RetailerMismatchWithBalance` | `POST /accounts/{accountId}/campaigns` | Campaign `RetailerId` does not match the balance’s `RetailerId`. |
| `RetailerMismatchWithCampaign` | `POST /campaigns/{campaignId}/auction-line-items` | Line item `targetRetailerId` does not match the campaign’s `RetailerId`. |

## Endpoint Changes

## Get List of Balances for an Account

This endpoint returns a paginated list of balances for an account. On API version `2026-01` and later, retailer-budgets balances are included and `retailerId`, `retailerPoNumber`, and `criteoPoNumber` are returned. The old `poNumber` field is removed.

**Query parameters**

- `offset` (int, default 0),
- `limit` (int, default 25, max 500),
- `limitToId` (List<string>)

**Sample Request**

```bash
curl -L -X GET 'https://api.criteo.com/2026-01/retail-media/accounts/625702934721171442/balances' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer <MY_ACCESS_TOKEN>'
```

**Sample Response**

```json
{
  "metadata": {
    "totalItemsAcrossAllPages": 135,
    "currentPageSize": 25,
    "currentPageIndex": 0,
    "totalPages": 6
  },
  "data": [
    {
      "id": "100000000000000001",
      "type": "BalanceV1",
      "attributes": {
        "name": "Sample Name",
        "retailerPoNumber": null,
        "criteoPoNumber": "PO-CRITEO-123",
        "retailerId": null,
        "memo": "Sample memo",
        "deposited": 10.0,
        "spent": 10.0,
        "remaining": 0.0,
        "startDate": "2020-04-13",
        "endDate": null,
        "status": "ended",
        "createdAt": "2020-04-13T15:39:48+00:00",
        "updatedAt": "2023-06-13T13:35:53+00:00",
        "balanceType": "capped",
        "spendType": "onsite",
        "privateMarketBillingType": "notApplicable"
      }
    },
    {
      "id": "100000000000000002",
      "type": "BalanceV1",
      "attributes": {
        "name": "Sample Name",
        "retailerPoNumber": "PO-RETAILER-123",
        "criteoPoNumber": "PO-CRITEO-123",
        "retailerId": 123,
        "memo": "Sample memo",
        "deposited": 100.0,
        "spent": 0.16,
        "remaining": 99.84,
        "startDate": "2026-03-24",
        "endDate": null,
        "status": "active",
        "createdAt": "2026-03-24T18:15:58+00:00",
        "updatedAt": "2026-05-20T17:17:40+00:00",
        "balanceType": "capped",
        "spendType": "onsite",
        "privateMarketBillingType": "notApplicable"
      }
    }
  ],
  "warnings": [],
  "errors": []
}
```

## Add Campaigns to a Balance

This endpoint adds one or more campaigns to a balance. It validates that campaigns are retailer-native when the balance is retailer-budget.

**Sample Request**

```bash
curl -L -X POST 'https://api.criteo.com/2026-01/retail-media/balances/814886589256347648/campaigns/append' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer <MY_ACCESS_TOKEN>' \
-d '{
  "data": {
    "attributes": {
      "ids": ["718038552188952576", "234105251251242423"]
    },
    "type": "AppendCampaignsRequest"
  }
}'
```

**Sample Response**

```json
{
  "data": {
    "attributes": {
      "ids": ["718038552188952576", "234105251251242423"]
    },
    "type": "BalanceCampaignsV1"
  },
  "warnings": [],
  "errors": []
}
```

**Sample Response — Error (non-retailer-native campaign)**

```json
{
  "errors":[
    {
      "message": "Only retailer-sold campaigns are allowed to be mapped to a retailer-budget balance.",
      "status": 400
    }
  ]
}
```

## Remove Campaign(s) from a Balance

This endpoint removes one or more campaigns from a balance.

**Sample Request**

```bash
curl -L -X POST 'https://api.criteo.com/2026-01/retail-media/balances/814886589256347648/campaigns/delete' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer <MY_ACCESS_TOKEN>' \
-d '{
  "data": {
    "attributes": {
      "ids": ["234105251251242423"]
    },
    "type": "DeleteCampaignsRequest"
  }
}'
```

**Sample Response**

```json
{
  "data": {
    "attributes": {
      "ids": ["718038552188952576"]
    },
    "type": "BalanceCampaignsV1"
  },
  "warnings": [],
  "errors": []
}
```

## Create New Campaigns

This endpoint creates a new campaign. For retailer-budgets campaigns, `retailerId` is required and must match the `retailerId` on the drawable balance.

**Sample Request**

```bash
curl -L -X POST 'https://api.criteo.com/2026-01/retail-media/accounts/625702934721171442/campaigns' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer <MY_ACCESS_TOKEN>' \
-d '{
  "data": {
    "type": "Campaign",
    "attributes": {
      "name": "Retailer Budgets Campaign Q2",
      "startDate": "2026-07-01T00:00:00+00:00",
      "clickAttributionWindow": "30D",
      "viewAttributionWindow": "None",
      "retailerId": "1298",
      "drawableBalanceIds": ["814886589256347648"]
    }
  }
}'
```

**Sample Response**

```json
{
  "data": {
    "id": "718038552188952576",
    "type": "Campaign",
    "attributes": {
      "name": "Retailer Budgets Campaign Q2",
      "accountId": "625702934721171442",
      "type": "auction",
      "status": "inactive",
      "retailerId": "1298",
      "drawableBalanceIds": ["814886589256347648"],
      "budget": null,
      "budgetSpent": 0.0,
      "budgetRemaining": null,
      "startDate": "2026-07-01T00:00:00+00:00",
      "endDate": null,
      "clickAttributionWindow": "30D",
      "viewAttributionWindow": "None",
      "createdAt": "2026-05-08T00:00:00+00:00",
      "updatedAt": "2026-05-08T00:00:00+00:00"
    }
  }
}
```

## Get Campaigns by Account ID and Campaign ID

Both endpoints now return `retailerId` in the campaign attributes. The list endpoint supports filtering by `retailerId`.

**Sample Request — Get a Single Campaign**

```bash
curl -L -X GET 'https://api.criteo.com/2026-01/retail-media/campaigns/718038552188952576' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer <MY_ACCESS_TOKEN>'
```

**Sample Response**

```json
{
  "data": {
    "id": "718038552188952576",
    "type": "Campaign",
    "attributes": {
      "name": "Retailer Budgets Campaign Q2",
      "accountId": "625702934721171442",
      "type": "auction",
      "status": "active",
      "retailerId": "1298",
      "drawableBalanceIds": ["814886589256347648"],
      "budget": null,
      "budgetSpent": 1250.00,
      "budgetRemaining": null,
      "startDate": "2026-07-01T00:00:00+00:00",
      "endDate": null,
      "clickAttributionWindow": "30D",
      "viewAttributionWindow": "None",
      "createdAt": "2026-05-08T00:00:00+00:00",
      "updatedAt": "2026-07-01T00:00:00+00:00"
    }
  }
}
```

## Create an Auction Line Item

Creates an auction line item. For retailer-budgets campaigns, `targetRetailerId` is required and must match the campaign’s `retailerId`. Only one retailer is allowed per retailer-billed campaign.

**Sample Request**

```bash
curl -L -X POST 'https://api.criteo.com/2026-01/retail-media/campaigns/718038552188952576/auction-line-items' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer <MY_ACCESS_TOKEN>' \
-d '{
  "data": {
    "type": "SponsoredProductsLineItem",
    "attributes": {
      "name": "Retailer Budget Line Item",
      "targetRetailerId": "1298",
      "startDate": "2026-07-01",
      "bidStrategy": "automated",
      "optimizationStrategy": "conversion",
      "keywordStrategy": "genericAndBranded"
    }
  }
}'
```

**Sample Response**

```json
{
  "data": {
    "id": "234105251251242423",
    "type": "SponsoredProductsLineItem",
    "attributes": {
      "name": "Retailer Budget Line Item",
      "campaignId": "718038552188952576",
      "targetRetailerId": "1298",
      "startDate": "2026-07-01T00:00:00+00:00",
      "endDate": null,
      "status": "inactive",
      "budget": null,
      "budgetSpent": 0.0,
      "budgetRemaining": null,
      "targetBid": 0.3,
      "maxBid": null,
      "monthlyPacing": null,
      "dailyPacing": null,
      "isAutoDailyPacing": false,
      "bidStrategy": "automated",
      "optimizationStrategy": "conversion",
      "keywordStrategy": "genericAndBranded",
      "flightSchedule": null,
      "createdAt": "2026-05-08T00:00:00+00:00",
      "updatedAt": "2026-05-08T00:00:00+00:00"
    }
  },
  "warnings": [],
  "errors": []
}
```

**Sample Response — Error (retailer mismatch)**

```json
{
  "errors": [
    {
      "message": "The line item targetRetailerId must match the campaign retailerId.",
      "status": 400
    }
  ]
}
```

## Search Retailer for an Account

This endpoint allows searching for available retailers for an account. The `campaignAvailabilities` object now includes `budgetModel`, allowing platforms to determine which budget models are supported at a given retailer before creating a campaign.

**Sample Request**

```bash
curl -L -X POST 'https://api.criteo.com/2026-01/retail-media/accounts/625702934721171442/retailers/search' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer <MY_ACCESS_TOKEN>' \
-d '{}'
```

**Sample Response**

```json
{
  "data": [
    {
      "id": "retailer-789",
      "type": "Retailer",
      "attributes": {
        "name": "Example Retailer",
        "campaignAvailabilities": [
          {
            "buyType": "auction",
            "campaignType": "sponsoredProducts",
            "isAvailable": true,
            "budgetModel": "retailerBilled",
            "validCombinations": [
              {
                "pageType": "search",
                "pageEnvironmentType": "offsite"
              }
            ]
          }
        ]
      }
    }
  ]
}
```

## Responses

| Response | Title | Detail | Troubleshooting |
| --- | --- | --- | --- |
| 🟢 `200` | Success |  | Request executed successfully. |
| 🔴 `400` | Retailer-budget balance mapping | “Only retailer-sold campaigns are allowed to be mapped to a retailer-billed balance.” | Only campaigns scoped to the same retailer as the balance can be appended via `/campaigns/append`. |
| 🔴 `400` | `RetailerMismatchWithBalance` | Campaign `retailerId` does not match the balance `retailerId`. | Ensure the `retailerId` in campaign settings matches the `retailerId` on the balance you are associating. |
| 🔴 `400` | `RetailerMismatchWithCampaign` | Line item `targetRetailerId` does not match the campaign `retailerId`. | Ensure `targetRetailerId` on the line item matches the `retailerId` set on the parent campaign. |
| 🔴 `400` | `Error deserializing request` | A required field is missing or has an invalid value. | Review the request body against the attributes table above. |
| 🔴 `403` | `Unauthorized` |  | Verify your access token and that your account has access to the retailer in question.
