Balances Endpoints

View and manage all available balances across campaigns

📘

Getting Started

Learn more about balances here.

⚠️

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 are hidden by default on prior API versions.

You can learn more about retailer budgets on this page.


Endpoints

MethodEndpointDescription
GET/accounts/{accountId}/balancesRetrieve all balances associated with a specific account.
GET/accounts/{accountId}/balances/{balanceId}Retrieve a specific balance
POST/accounts/{accountId}/balancesCreate a new balance for a specified account.
PATCH/accounts/{accountId}/balances/{balanceId}Modify balance's metadata (name, start/end date - for deposited funds, see below)
POST/accounts/{accountId}/balances/{balanceId}/add-fundsAdd/remove funds deposited in a specific balance.
GET/balances/{balanceId}/campaignsRetrieve all campaigns linked to a specific balance.
POST/balances/{balanceId}/campaigns/appendAdd campaigns to a specific balance.
POST/balances/{balanceId}/campaigns/deleteRemove campaigns from a specific balance.
GET/balances/{balanceId}/historyRetrieve all changes made historically to a balance

Balance Parameters

Attribute

Data Type

Description

id

string

Balance ID

Accepted values: string of int64
Writeable? N / Nullable? N

name*

string

Balance name

Accepted values: up to 255-char strings
Writeable? Y / Nullable? N

deposited

decimal

Amount of funds deposited; uncapped if null

Accepted values: deposited ≥ 0.0
Writeable? Y / Nullable? Y

spent

decimal

Amount of funds already spent

Accepted values: 0 ≤ spentdeposited
Writeable? N / Nullable? N

remaining

decimal

Amount of funds already spent

Accepted values: 0 ≤ remainingdeposited (or null, if deposited not set)
Writeable? N / Nullable? Y

startDate*

timestamp

Balance start date; if time zone is not set, will consider Account's time zone as default

Accepted values: yyyy-mm-dd(in ISO-8601 )
Writeable? Y / Nullable? N

endDate

timestamp

Balance end date; if time zone is not set, will consider Account's time zone as default

Accepted values: yyyy-mm-dd(in ISO-8601 )
Default: if null or absent, balance will be available indefinitely
Writeable? Y / Nullable? Y

status

enum

Balance current status

Accepted values: active, scheduled, ended
Writeable? N / Nullable? N

createdAt

timestamp

Timestamp of balance creation, in UTC

Accepted values: yyyy-mm-ddThh:mm:ss±hh:mm (in ISO-8601)
Writeable? N / Nullable? N

updatedAt

timestamp

Timestamp of last balance update, in UTC

Accepted values: yyyy-mm-ddThh:mm:ss±hh:mm (in ISO-8601)
Writeable? N / Nullable? N

memo

string

An optional memo note that can be set in the balance

Accepted values: up to 250-char strings
Writeable? Y / Nullable? Y

balanceType

enum

The balance type is computed based on the deposited amount:

Accepted values: capped, uncapped
Writeable? N / Nullable? N

  • capped: if the deposited amount is provided.
  • uncapped: when there is no amount defined (set to null)

spendType

enum

The type of balance that will be used based on the campaign type

Accepted values: Onsite, Offsite, OffsiteAwareness
Writeable? N / Nullable? N

privateMarketBillingType

enum

Billing type of the balance

Accepted values: notApplicable, billByRetailer, billByCriteo

⚠️ Note:

  • balances created through the API will, automatically, be denoted asbillByRetailer
  • only balances denoted as billByRetailer can be modified through the API
  • billByCriteo are balances created in our Commerce Max platform and can only be edited in our UI
  • notApplicable is an initial or default value if the privateMarketBillingType is not set yet. If it is observed it would be treated as the default value billByRetailer.

Writeable? N / Nullable? N

RetailerId

string

Retailer this balance is scoped to.
Present only on retailer budgets.

Nullable? Y (null for balances without retailer budgets)

retailerPoNumber

string?

Retailer purchase order number. Replaces the removed poNumber field.

Nullable? Y

criteoPoNumber

string?

Criteo purchase order number.
Replaces the removed poNumber field.

Nullable? Y

poNumber

string

Purchase order number.
Removed since 2026.01and replaced by retailerPoNumber and criteoPoNumber

Accepted values: up to 32-char strings
Writeable? Y / Nullable? Y

*Required for Balance creation

📘

Field Definitions

  • Writeable (Y/N): Indicates if the field can be modified in requests.
  • Nullable (Y/N): Indicates if the field can accept null/empty values.
  • Primary Key: A unique, immutable identifier of the entity, generated internally by Criteo. Primary keys are typically ID fields (e.g., retailerId, campaignId, lineItemId) and are usually required in the URL path.

Balance History Parameters

Attribute

Data Type

Description

dateOfModification

timestamp

Timestamp of balance update

Accepted values: yyyy-mm-ddThh:mm:ss±hh:mm (in ISO-8601)
Writeable? N / Nullable? N

modifiedByUser

string

Username who modified the insertion order

Accepted values: strings in format "j.doe"
Writeable? N / Nullable? N

changeType

enum

Definition of the type of change in a balance

Accepted values: BalanceCreated, BalanceCapped, BalanceUncapped, EndDate, StartDate, CriteoPoNumber, RetailerPoNumber, ValueAdd

  • BalanceCreated: new balance is created
  • BalanceUncapped: capped balance is changed to uncapped
  • BalanceCapped: uncapped balance is changed to capped
  • StartDate: start date is modified
  • EndDate: end date is modified

changeDetails

object

Structure with the change details (from Balance History endpoint)

Parameters:

  • previousValue: previous value of a property of the balance
  • currentValue`: current value of a property of the balance
  • changeValue: change value of a property of the balance

Get all Balances for an Account

This endpoint lists all balances in an account.

Results are paginated using pageIndex and pageSize query parameters; if omitted, defaults to 0 and 25, respectively - see API Response

https://api.criteo.com/{version}/retail-media/accounts/{accountId}/balances?pageIndex={pageIndex}&pageSize={pageSize}
⚠️

On API version 2026-01 and later, retailer budgets are included and retailerId, retailerPoNumber, and criteoPoNumber are returned. The old poNumber field is removed.
Learn more on retailer budgets here.

Legacy version behavior (2025-10 and earlier): Only standard Criteo budget balances are returned. retailerId, retailerPoNumber, and criteoPoNumber are not present in the response. The legacy poNumber field is present.

Sample Request

curl -X GET "https://api.criteo.com/{version}/retail-media/accounts/18446744073709551616/balances?pageIndex=0&pageSize=25" \
    -H "Authorization: Bearer <MY_ACCESS_TOKEN>"
import requests

url = "https://api.criteo.com/{version}/retail-media/accounts/18446744073709551616/balances?pageIndex=0&pageSize=25"

payload={}
headers = {
  'Accept': 'application/json',
  'Authorization': 'Bearer <MY_ACCESS_TOKEN>'
}

response = requests.request("GET", url, headers=headers, data=payload)

print(response.text)
OkHttpClient client = new OkHttpClient().newBuilder()
  .build();

MediaType mediaType = MediaType.parse("text/plain");

RequestBody body = RequestBody.create(mediaType, "");

Request request = new Request.Builder()
  .url("https://api.criteo.com/{version}/retail-media/accounts/18446744073709551616/balances?pageIndex=0&pageSize=25")
  .method("GET", body)
  .addHeader("Accept", "application/json")
  .addHeader("Authorization", "Bearer <MY_ACCESS_TOKEN>")
  .build();

Response response = client.newCall(request).execute();
<?php
require_once 'HTTP/Request2.php';
$request = new HTTP_Request2();
$request->setUrl('https://api.criteo.com/{version}/retail-media/accounts/18446744073709551616/balances?pageIndex=0&pageSize=25');
$request->setMethod(HTTP_Request2::METHOD_GET);
$request->setConfig(array(
  'follow_redirects' => TRUE
));
$request->setHeader(array(
  'Accept' => 'application/json',
  'Authorization' => 'Bearer <MY_ACCESS_TOKEN>'
));

try {
  $response = $request->send();
  if ($response->getStatus() == 200) {
    echo $response->getBody();
  }
  
  else {
    echo 'Unexpected HTTP status: ' . $response->getStatus() . ' ' .
    $response->getReasonPhrase();
  }
}

catch(HTTP_Request2_Exception $e) {
  echo 'Error: ' . $e->getMessage();
}

Sample Response - Versions prior to 2026.01

{
    "metadata": {
        "totalItemsAcrossAllPages": 94,
        "currentPageSize": 25,
        "currentPageIndex": 0,
        "totalPages": 4,
        "nextPage": "https://api.criteo.com/{version}/retail-media/accounts/18446744073709551616/balances?pageIndex=1&pageSize=25"
    },
    "data": [
        {
            "id": "14094543095747588032",
            "type": "BalanceResponseV2",
            "attributes": {
                "name": "Balance 123",
                "poNumber": "13993827",
                "memo": "uncapped balance, free to spend!",
                "deposited": null,
                "spent": 42931.28,
                "remaining": null,
                "startDate": "2020-04-06",
                "endDate": null,
                "status": "active",
                "createdAt": "2020-04-06T00:02:41+00:00",
                "updatedAt": "2020-04-06T00:02:41+00:00",
                "balanceType": "uncapped",
                "spendType": "Onsite",
                "privateMarketBillingType": "notApplicable"
            }
        },
 
        // ...
 
        {
            "id": "4237496305219757554",
            "type": "BalanceResponseV2",
            "attributes": {
                "name": "Balance 789",
                "poNumber": "",
                "memo": "10k for the special 2s-day promotion",
                "deposited": 10000.00,
                "spent": 923.40,
                "remaining": 9076.60,
                "startDate": "2025-02-01",
                "endDate": null,
                "status": "scheduled",
                "createdAt": "2025-01-06T00:48:11+00:00",
                "updatedAt": "2025-01-07T22:19:57+00:00",
                "balanceType": "capped",
                "spendType": "Onsite",
              "privateMarketBillingType": "notApplicable"
            }
        }
    ]
}

Sample response - Versions 2026.01onward

{
  "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": []
}

Get Specific Balance

Retrieves the balance details of one specific balances belonging to an account.

⚠️

Returns a single balance including the retailer scoping fields. The poNumber field present on prior API versions is removed in 2026-01.

Legacy version behavior (2025-10 and earlier): Returns 400 with "This version endpoint doesn't support retailer-sold balance. Use latest version instead." when the requested balance is a retailer budget balance.

https://api.criteo.com/{version}/retail-media/accounts/{accountId}/balances/{balanceId}

Sample Request

curl -L 'https://api.criteo.com/{version}/retail-media/accounts/18446744073709551616/balances/4237496305219757554' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer <MY_ACCESS_TOKEN>'

Sample Response

{
  "data": {
    "id": "100000000000000001",
    "type": "BalanceV1",
    "attributes": {
      "name": "Sample Name",
      "retailerPoNumber": null,
      "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": []
}

Create a New Account Balance

This endpoint creates a new balance in the specified account.

https://api.criteo.com/{version}/retail-media/accounts/{accountId}/balances

Sample Request

curl -L -X POST 'https://api.criteo.com/{version}/retail-media/accounts/18446744073709551616/balances' \
    -H 'Content-Type: application/json' \
    -H 'Accept: application/json' \
    -H 'Authorization: Bearer <MY_ACCESS_TOKEN>' \
    -d '{
            "data": {
                "attributes": {
                    "name": "Balance 2025 Q1",
                    "startDate": "2025-01-01",
                    "spendType": "Onsite",
                    "poNumber": null,
                    "deposited": 12500.00,
                    "endDate": "",
                    "memo": "Balance for campaigns in 2025 Q1"
                }
            }
        }'
import requests
import json

url = "https://api.criteo.com/{version}/retail-media/accounts/18446744073709551616/balances"

payload = json.dumps({
    "data": {
        "attributes": {
            "name": "Balance 2025 Q1",
            "startDate": "2025-01-01",
            "spendType": "Onsite",
            "poNumber": None,
            "deposited": 12500.00,
            "endDate": "",
            "memo": "Balance for campaigns in 2025 Q1"
        }
    }
})
headers = {
    'Accept': 'application/json',
    'Content-Type': 'application/json',
    'Authorization': 'Bearer <MY_ACCESS_TOKEN>'
}

response = requests.request("POST", url, headers=headers, data=payload)

print(response.text)
OkHttpClient client = new OkHttpClient().newBuilder()
  .build();

MediaType mediaType = MediaType.parse("application/json");

RequestBody body = RequestBody.create(mediaType, "{\"data\":{\"attributes\":{\"name\":\"Balance 2025 Q1\",\"startDate\":\"2025-01-01\",\"spendType\":\"Onsite\",\"poNumber\": null,\"deposited\": 12500.00,\"endDate\":\"\",\"memo\":\"Balance for campaigns in 2025 Q1\"}}}");

Request request = new Request.Builder()
  .url("https://api.criteo.com/{version}/retail-media/accounts/18446744073709551616/balances")
  .method("POST", body)
  .addHeader("Content-Type", "application/json")
  .addHeader("Accept", "application/json")
  .addHeader("Authorization", "Bearer <MY_ACCESS_TOKEN>")
  .build();

Response response = client.newCall(request).execute();
<?php
require_once 'HTTP/Request2.php';
$request = new HTTP_Request2();
$request->setUrl('https://api.criteo.com/{version}/retail-media/accounts/18446744073709551616/balances');
$request->setMethod(HTTP_Request2::METHOD_POST);
$request->setConfig(array(
    'follow_redirects' => TRUE
));
$request->setHeader(array(
    'Content-Type' => 'application/json',
    'Accept' => 'application/json',
    'Authorization' => 'Bearer <MY_ACCESS_TOKEN>'
));
$request->setBody('{"data":{"attributes":{"name":"Balance 2025 Q1","startDate":"2025-01-01","spendType":"Onsite","poNumber":null,"deposited":12500.00,"endDate":"","memo":"Balance for campaigns in 2025 Q1"}}');
try{
    $response = $request->send();
    if ($response->getStatus() == 200) {
        echo $response->getBody();
    }
    else {
        echo 'Unexpected HTTP status: ' . $response->getStatus() . ' ' .
        $response->getReasonPhrase();
    }
}
catch(HTTP_Request2_Exception $e) {
    echo 'Error: ' . $e->getMessage();
}

Sample Response

{
    "id": "697385288434028544",
    "type": "BalanceResponseV2",
    "data": {
        "attributes": {
            "name": "Balance 2025 Q1",
            "poNumber": null,
            "memo": "Balance for campaigns in 2025 Q1",
            "deposited": 12500.00,
            "spent": 0.00,
            "remaining": 12500.00,
            "startDate": "2025-01-01",
            "endDate": null,
            "status": "active",
            "createdAt": "2025-04-08T10:00:09+00:00",
            "updatedAt": "2025-04-08T10:00:09+00:00",
            "balanceType": "capped",
            "spendType": "Onsite",
            "privateMarketBillingType": "billByRetailer"
        }
    },
    "warnings": [],
    "errors": []
}

Modify Balance Metadata

This endpoint modifies the metadata of a specified balance, like name, poNumber, startDate or endDate. To modify deposited funds, check the following endpoint.

⚠️

Only Balances created through the API can be modified through this endpoint, i.e., with billing type billByRetailer.
Retailer budget balances are read-only via API. Any PATCH request against a retailer budget balance returns 403 regardless of API version.
Balance attributes (dates, PO numbers, amounts) are managed by Criteo on behalf of the retailer.

https://api.criteo.com/{version}/retail-media/accounts/{accountId}/balances/{balanceId}

Sample Request

curl -L -X PATCH 'https://api.criteo.com/{version}/retail-media/accounts/18446744073709551616/balances/697385288434028544' \
    -H 'Content-Type: application/json' \
    -H 'Accept: application/json' \
    -H 'Authorization: Bearer <MY_ACCESS_TOKEN>' \
    -d '{
            "data": {
                "attributes": {
                    "startDate": "2025-01-01",
                    "endDate": "2025-04-01",
                    "poNumber": "PO 12345",
                    "memo": "Balance for campaigns in 2025 Q1 (with start and end date)"
                }
            }
        }'
import requests
import json

url = "https://api.criteo.com/{version}/retail-media/accounts/18446744073709551616/balances/697385288434028544"

payload = json.dumps({
    "data": {
        "attributes": {
            "startDate": "2025-01-01",
            "endDate": "2025-04-01",
            "poNumber": "PO 12345",
            "memo": "Balance for campaigns in 2025 Q1 (with start and end date)"
        }
    }
})
headers = {
    'Content-Type': 'application/json',
    'Accept': 'application/json',
    'Authorization': 'Bearer <MY_ACCESS_TOKEN>'
}

response = requests.request("PATCH", url, headers=headers, data=payload)

print(response.text)
OkHttpClient client = new OkHttpClient().newBuilder()
  .build();

MediaType mediaType = MediaType.parse("application/json");

RequestBody body = RequestBody.create(mediaType, "{\"data\":{\"attributes\":{\"startDate\":\"2025-01-01\",\"endDate\":\"2025-04-01\",\"poNumber\":\"PO 12345\",\"memo\":\"Balance for campaigns in 2025 Q1 (with start and end date)\"}}}");

Request request = new Request.Builder()
  .url("https://api.criteo.com/{version}/retail-media/accounts/18446744073709551616/balances/697385288434028544")
  .method("PATCH", body)
  .addHeader("Content-Type", "application/json")
  .addHeader("Accept", "application/json")
  .addHeader("Authorization", "Bearer <MY_ACCESS_TOKEN>")
  .build();

Response response = client.newCall(request).execute();
<?php
require_once 'HTTP/Request2.php';
$request = new HTTP_Request2();
$request->setUrl('https://api.criteo.com/{version}/retail-media/accounts/18446744073709551616/balances/697385288434028544');
$request->setMethod(HTTP_Request2::METHOD_POST);
$request->setConfig(array(
    'follow_redirects' => TRUE
));
$request->setHeader(array(
    'Content-Type' => 'application/json',
    'Accept' => 'application/json',
    'Authorization' => 'Bearer <MY_ACCESS_TOKEN>'
));
$request->setBody('{"data":{"attributes":{"startDate":"2025-01-01","endDate":"2025-04-01","poNumber":"PO 12345","memo":"Balance for campaigns in 2025 Q1 (with start and end date)"}}}');
try{
    $response = $request->send();
    if ($response->getStatus() == 200) {
        echo $response->getBody();
    }
    else {
        echo 'Unexpected HTTP status: ' . $response->getStatus() . ' ' .
        $response->getReasonPhrase();
    }
}
catch(HTTP_Request2_Exception $e) {
    echo 'Error: ' . $e->getMessage();
}

Sample Response

{
    "id": "697385288434028544",
    "type": "BalanceResponseV2",
    "data": {
        "attributes": {
            "name": "Balance 2025 Q1",
            "poNumber": "PO 12345",
            "memo": "Balance for campaigns in 2025 Q1 (with start and end date)",
            "deposited": 12500.00,
            "spent": 0.00,
            "remaining": 12500.00,
            "startDate": "2025-01-01",
            "endDate": "2025-04-01",
            "status": "active",
            "createdAt": "2025-04-08T10:00:09+00:00",
            "updatedAt": "2025-04-08T10:00:09+00:00",
            "balanceType": "capped",
            "spendType": "Onsite",
            "privateMarketBillingType": "billByRetailer"
        }
    },
    "warnings": [],
    "errors": []
}

Add or Remove Balance Funds

This endpoint allows adding or removing funds deposited in a specific balance.

ℹ️

Only Balances created through the API can be modified through this endpoint, ie, with billing type billByRetailer

https://api.criteo.com/{version}/retail-media/accounts/{accountId}/balances/{balanceId}/add-funds

Request Body Parameters

Attribute

Data Type

Description

deltaAmount*

decimal

Difference amount of fund to be added/removed from balance; it cannot reduce the current amount of funds deposited to less than zero

Accepted values: deltaAmountdeposited * (-1)
Writeable? N / Nullable? N

poNumber

string

New purchase order number

Accepted values: up to 32-char strings
Writeable? Y / Nullable? Y

memo*

string

A memo note that should be set in the balance together with this modification

Accepted values: up to 250-char strings
Writeable? Y / Nullable? Y

*Required

Sample Request

curl -L -X PATCH 'https://api.criteo.com/{version}/retail-media/accounts/18446744073709551616/balances/697385288434028544/add-funds' \
    -H 'Content-Type: application/json' \
    -H 'Accept: application/json' \
    -H 'Authorization: Bearer <MY_ACCESS_TOKEN>' \
    -d '{
            "data": {
                "attributes": {
                    "deltaAmount": -2500.00,
                    "poNumber": "PO 12346",
                    "memo": "Reduced balance for campaigns in 2025 Q1"
                }
            }
        }'
import requests
import json

url = "https://api.criteo.com/{version}/retail-media/accounts/18446744073709551616/balances/697385288434028544/add-funds"

payload = json.dumps({
    "data": {
        "attributes": {
            "deltaAmount": -2500.00,
            "poNumber": "PO 12346",
            "memo": "Reduced balance for campaigns in 2025 Q1"
        }
    }
})
headers = {
    'Content-Type': 'application/json',
    'Accept': 'application/json',
    'Authorization': 'Bearer <MY_ACCESS_TOKEN>'
}

response = requests.request("POST", url, headers=headers, data=payload)

print(response.text)
OkHttpClient client = new OkHttpClient().newBuilder()
  .build();

MediaType mediaType = MediaType.parse("application/json");

RequestBody body = RequestBody.create(mediaType, "{\"data\":{\"attributes\":{\"deltaAmount\": -2500.00,\"poNumber\":\"PO 12346",\"memo":\"Reduced balance for campaigns in 2025 Q1\"}}}");

Request request = new Request.Builder()
  .url("https://api.criteo.com/{version}/retail-media/accounts/18446744073709551616/balances/697385288434028544/add-funds")
  .method("POST", body)
  .addHeader("Content-Type", "application/json")
  .addHeader("Accept", "application/json")
  .addHeader("Authorization", "Bearer <MY_ACCESS_TOKEN>")
  .build();

Response response = client.newCall(request).execute();
<?php
require_once 'HTTP/Request2.php';
$request = new HTTP_Request2();
$request->setUrl('https://api.criteo.com/{version}/retail-media/accounts/18446744073709551616/balances/697385288434028544/add-funds');
$request->setMethod(HTTP_Request2::METHOD_POST);
$request->setConfig(array(
    'follow_redirects' => TRUE
));
$request->setHeader(array(
    'Content-Type' => 'application/json',
    'Accept' => 'application/json',
    'Authorization' => 'Bearer <MY_ACCESS_TOKEN>'
));
$request->setBody('{"data":{"attributes":{"deltaAmount":-2500.00,"poNumber":"PO 12346","memo":"Reduced balance for campaigns in 2025 Q1"}}}');
try{
    $response = $request->send();
    if ($response->getStatus() == 200) {
        echo $response->getBody();
    }
    else {
        echo 'Unexpected HTTP status: ' . $response->getStatus() . ' ' .
        $response->getReasonPhrase();
    }
}
catch(HTTP_Request2_Exception $e) {
    echo 'Error: ' . $e->getMessage();
}

Sample Response

{
    "id": "697385288434028544",
    "type": "BalanceResponseV2",
    "data": {
        "attributes": {
            "name": "Balance 2025 Q1",
            "poNumber": "PO 12346",
            "memo": "Reduced balance for campaigns in 2025 Q1",
            "deposited": 10000.00,
            "spent": 0.00,
            "remaining": 10000.00,
            "startDate": "2025-01-01",
            "endDate": "2025-04-01",
            "status": "active",
            "createdAt": "2025-04-08T10:00:09+00:00",
            "updatedAt": "2025-04-08T10:00:09+00:00",
            "balanceType": "capped",
            "spendType": "Onsite",
            "privateMarketBillingType": "billByRetailer"
        }
    },
    "warnings": [],
    "errors": []
}

Get all Campaigns on a Specific Balance

This endpoint lists all campaigns on the specified balance.

Results are paginated using pageIndex and pageSize query parameters; if omitted, defaults to 0 and 25, respective - see API Response

https://api.criteo.com/{version}/retail-media/balances/{balanceId}/campaigns?pageIndex={pageIndex}&pageSize={pageSize}

Response Body Parameters

Attribute

Data Type

Description

id

string

Campaign ID, respective to the campaign(s) currently appended to the balance

Accepted values: string of int64
Writeable? N / Nullable? N

Sample Request

curl -X GET "https://api.criteo.com/{version}/retail-media/balances/14094543095747588032/campaigns?pageIndex=0&pageSize=25" \
    -H "Authorization: Bearer <MY_ACCESS_TOKEN>"
OkHttpClient client = new OkHttpClient().newBuilder()
  .build();

MediaType mediaType = MediaType.parse("text/plain");

RequestBody body = RequestBody.create(mediaType, "");

Request request = new Request.Builder()
  .url("https://api.criteo.com/{version}/retail-media/balances/14094543095747588032/campaigns?pageIndex=0&pageSize=25")
  .method("GET", body)
  .addHeader("Accept", "application/json")
  .addHeader("Authorization", "Bearer <MY_ACCESS_TOKEN>")
  .build();

Response response = client.newCall(request).execute();
import requests

url = "https://api.criteo.com/{version}/retail-media/balances/14094543095747588032/campaigns?pageIndex=0&pageSize=25"

payload={}
headers = {
  'Accept': 'application/json',
  'Authorization': 'Bearer <MY_ACCESS_TOKEN>'
}

response = requests.request("GET", url, headers=headers, data=payload)

print(response.text)
<?php
require_once 'HTTP/Request2.php';
$request = new HTTP_Request2();
$request->setUrl('https://api.criteo.com/{version}/retail-media/balances/14094543095747588032/campaigns?pageIndex=0&pageSize=25');
$request->setMethod(HTTP_Request2::METHOD_GET);
$request->setConfig(array(
  'follow_redirects' => TRUE
));
$request->setHeader(array(
  'Accept' => 'application/json',
  'Authorization' => 'Bearer <MY_ACCESS_TOKEN>'
));

try {
  $response = $request->send();
  if ($response->getStatus() == 200) {
    echo $response->getBody();
  }
  
  else {
    echo 'Unexpected HTTP status: ' . $response->getStatus() . ' ' .
    $response->getReasonPhrase();
  }
}

catch(HTTP_Request2_Exception $e) {
  echo 'Error: ' . $e->getMessage();
}

Sample Response

{
    "data": [
        {
            "id": "8343086999167541140",
            "type": "RetailMediaCampaign"
        },
        {
            "id": "3683145960016759663",
            "type": "RetailMediaCampaign"
        }
    ],
    "metadata": {
        "totalItemsAcrossAllPages": 2,
        "currentPageSize": 25,
        "currentPageIndex": 0,
        "totalPages": 1,
        "nextPage": null,
        "previousPage": null
    }
}

Add Campaigns to a Specific Balance

This endpoint adds one or more campaigns to the specified balance. The results are provided in a single page. In this example, a campaign had already existed on the balance before two new additions.

⚠️

The API enforces compatibility between the campaigns and the balance — all must share the same billing type, retailer (for retailer budget), and demand account. If any campaign in the request fails validation, the entire request is rejected and no campaigns are mapped.

Validation rules:

  • All campaigns must be retailer budget campaigns when the balance is a retailer budget balance (and vice versa) — otherwise BillingTypeMismatchWithBalance
  • The retailerId on every campaign must match the balance's retailerId — otherwise RetailerMismatchWithBalance
  • All campaigns must belong to the same demand account as the balance — otherwise 403
https://api.criteo.com/{version}/retail-media/balances/{balanceId}/campaigns/append

Request Body Parameters

Attribute

Data Type

Description

id

string

Campaign ID, required to define to which campaign(s) the balance should be appended or deleted

Accepted values: string of int64
Writeable? N / Nullable? N

Sample Request

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

Sample Response

{
  "data": {
    "type": "BalanceCampaignsV1",
    "attributes": {
      "ids": ["100000000000000001", "100000000000000002"]
    }
  },
  "warnings": [],
  "errors": []
}

Remove Campaigns from a Specific Balance

This endpoint removes one or more campaigns from the specified balance.

The response contains the remaining mapped campaign IDs after removal.

⚠️

Legacy versions (2025-10 and earlier) will return an error 500.

https://api.criteo.com/{version}/retail-media/balances/{balanceId}/campaigns/delete

Request Body Parameters

Attribute

Data Type

Description

id

string

Campaign ID, required to define to which campaign(s) the balance should be appended or deleted

Accepted values: string of int64
Writeable? N / Nullable? N

Sample Request

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

Sample Response

{
  "data": {
    "type": "BalanceCampaignsV1",
    "attributes": {
      "ids": ["100000000000000002"]
    }
  },
  "warnings": [],
  "errors": []
}

Get Balance History

This endpoint lists all changes made to a specific balance.

Results are paginated using offset and limit query parameters; if omitted, defaults to 0 and 500, respectively - see API Response

Additional query parameter limitToChangeTypes can be used to inform a comma-separated list of changeType values.

⚠️

Retailer-Budgets

Returns a chronological list of changes to a balance. PO number updates appear as separate entries keyed by changeType — not as field keys on every entry.

Observed changeType values: BalanceCreated, BalanceCapped, BalanceUncapped, EndDate, StartDate, CriteoPoNumber, RetailerPoNumber, ValueAdd

Each entry's changeDetails carries:

  • previousValue — value before the change
  • currentValue — value after the change
  • changeValue — delta where applicable (e.g. ValueAdd); null otherwise

Legacy version behavior (2025-10 and earlier): Returns 400 with "This version endpoint doesn't support retailer-sold balance. Use latest version instead." when the requested balance is a retailer budget balance.

📘

Modified Users

modifiedByUser - When a balance is updated via Criteo's Retail Media UI, the user login name will be provided. For instance, if “Kip Heaney” updated the balance on 2023-11-07, it indicates that Kip made the change through the Criteo Retail Media UI.

If a balance is updated through the Criteo API, the name of the API application responsible for the change will be shown. For example, on 2024-03-20, the balance was updated by the API application Retail Media API Application.

https://api.criteo.com/{version}/retail-media/balances/{balanceId}/history?offset={offset}&limit={limit)&limitToChangeTypes={changeTypes}

Sample Request: retrieve all changes

curl -L -X GET 'https://api.criteo.com/preview/retail-media/balances/{balanceId}/history' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer <MY_ACCESS_TOKEN>'

Sample Response: retrieve all changes

{
  "metadata": {
    "count": 4,
    "offset": 0,
    "limit": 25
  },
  "data": [
    {
      "type": "BalanceHistoryChangeDataCaptureV1",
      "attributes": {
        "changeType": "BalanceCreated",
        "changeDetails": {
          "previousValue": null,
          "currentValue": "100.00",
          "changeValue": null
        },
        "dateOfModification": "2026-03-24T14:15:58-04:00",
        "modifiedByUser": "User123",
        "memo": null
      }
    },
    {
      "type": "BalanceHistoryChangeDataCaptureV1",
      "attributes": {
        "changeType": "CriteoPoNumber",
        "changeDetails": {
          "previousValue": null,
          "currentValue": "PO-CRITEO-123",
          "changeValue": null
        },
        "dateOfModification": "2026-03-24T14:16:28-04:00",
        "modifiedByUser": "User123",
        "memo": null
      }
    },
    {
      "type": "BalanceHistoryChangeDataCaptureV1",
      "attributes": {
        "changeType": "RetailerPoNumber",
        "changeDetails": {
          "previousValue": null,
          "currentValue": "PO-RETAILER-123",
          "changeValue": null
        },
        "dateOfModification": "2026-03-24T14:16:45-04:00",
        "modifiedByUser": "User123",
        "memo": null
      }
    }
  ],
  "warnings": [],
  "errors": []
}

Responses

Response

Title

Detail

Troubleshooting

🟢 200

Call executed with success

🟢 201

Balance request created with success

🔴 400

Error deserializing request

Field xyz is not valid

Review the value of field xyz provided in the request

🔴 400

Change data capture type xxx is not supported

Change data capture type xxx is not supported

The value of limitToChangeTypes is not supported for /balances/{balanceId} /history - ensure to use a comma-separated list of availablechangeType values defined above

🔴 400

Invalid name

Balance name should be unique. There exists balance with the specified name. Balance creation/update has been canceled

Check value of name in the request trying to create/edit a balance. In case of editing, either provided a new name value or omit this parameter to maintain its same value

🔴 400

Invalid deltaamount

Can not decrease funds to less than zero

Review value of deltaAmount and make sure it's greater than current balance's deposited * (-1)

🔴 400

Invalid operation

Can only change the field xxx of a balance not billed by retailer.

Cannot edit balances not created through the API; only balances with billing type billByRetailer can be modified

🔴 400

Invalid operation

Can not add funds to a balance not billed by retailer.

Cannot edit balances not created through the API; only balances with billing type billByRetailer can be modified

🔴 403

Authorization error

Resource access forbidden: does not have permissions

One of the permission levels was not respected. Make sure that the respective API app has access to:

  • Read/Manage the domain "Balance" (depending on the requested action). Review the Types of Permissions in Authorization Requests
  • the accountId or balanceId provided in the request

🔴 400

RetailerMismatchWithBalance

The retailerId on the campaign doesn't match the retailerId on the balance.

🔴 400

BillingTypeMismatchWithBalance

The billing type on the campaign doesn't match the billing type on the balance.


Balance Append Endpoint Errors

⚠️

The "append" endpoint uses a different error code from campaign create: code: "campaigns-balance-mapping-validation-error", title: "Validation error".

The specific mismatch type is identified by a bracketed prefix in the detail field.

Note: Balance append/delete endpoints are available on preview only for now. Requests using the current stable version (2026-01) returns an error500.

Statuscodedetail prefixDescription
🔴400campaigns-balance-mapping-validation-error[retailer-id-mismatch]The retailerId on one or more campaigns in ids doesn't match the balance's retailerId. Entire request rejected — no campaigns are mapped.
🔴400campaigns-balance-mapping-validation-error[balance-type-mismatch]Billing type mismatch — e.g. retailer budget campaign appended to a Criteo budget balance, or vice versa. Entire request rejected.
🔴403Campaigns in ids belong to a different demand account than the balance.

Balance Modify Endpoint Error

StatusBodyMeaning
🔴403RFC 9110 HTTP Forbidden (no custom error body)Retailer budget balances are read-only via API. PATCH is always rejected regardless of API version.

Legacy Version Access

StatuscodetitleWhen
🔴400validation-error"This version endpoint doesn't support retailer-sold balance. Use latest version instead."Accessing a retailer budget balance or its history via the account-scoped path on 2025-10 or earlier.
🔴500(empty body)POST /balances/{balanceId}/campaigns/delete on 2025-10 or earlier.

Did anything feel unclear or missing on this page?