Keyword Review
Introduction
The keyword approval process is designed for advertisers and retailers to determine what keywords are prospects to be used for targeting on retailer inventory.
When an advertiser proposes a set of keywords for review, these keywords are then forwarded to the retailer reviewer, who evaluates them based on predefined criteria such as relevance, accuracy, and compliance with guidelines.
The reviewer may use tools and resources to verify the appropriateness of each keyword. If the keywords meet the standards, they are approved and added to the database. This iterative process ensures that only high-quality, relevant keywords are approved and utilized.
Endpoints
Verb | Endpoint | Description |
---|---|---|
GET | /accounts/{accountId}/keywords/in-review-report | Retrieve a Keywords Approval report to the specific supply account, with a count of keywords in review state at line-item |
GET | /line-items/{lineItemId}/keywords | Retrieve a set of all keywords in review state for the specific line-item |
POST | /line-items/{lineItemId}/keywords/review | Review keyword(s) for the specific line-item |
Keywords Approval Report Attributes
Attribute | Data Type | Description |
---|---|---|
| string | Line Item ID, respective to the amount of keywords in review Accepted values: string of int64 |
| string | Line Item name, respective to the amount of keywords in review Accepted values: up to 255-chars string |
| string | Campaign ID, respective to the amount of keywords in review Accepted values: string of int64 |
| string | Campaign name, respective to the amount of keywords in review Accepted values: up to 255-chars string |
| string | Account ID, respective to the amount of keywords in review Accepted values: string of int64 |
| string | Account name, respective to the amount of keywords in review Accepted values: up to 255-chars string |
| string | Retailer ID, respective to the amount of keywords in review Accepted values: string of int64 |
| string | Retailer name, respective to the amount of keywords in review Accepted values: up to 255-chars string |
| integer | Amount of keywords in review for the respective line-item Accepted values: |
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.
Keywords Review Attributes
Attribute | Data Type | Description |
---|---|---|
| string | Line Item ID, respective to the amount of keywords in review Accepted values: string of int64 |
| enum | Status of the Keyword Review (only applicable for Accepted values:
Default: |
| enum | The matching algorithm to be used when comparing this keyword with shopper search phrases Accepted values:
Writeable? N / Nullable? N |
| decimal | The bid override for the positive keyword. The keyword will use the default line item bid if the value is Accepted values: retailer's |
| object | Keywords supplied by the user matching this normalized keyword phrase binned by match type (see examples below) Parameters:
Writeable? Y / Nullable? Y |
| timestamp | Timestamp the keyword was created in the line-item (or recommended to line-item) Accepted values: |
| timestamp | Last timestamp the keyword was updated in the line-item (or recommended to line-item) Accepted values: |
Keywords Review Body Request
Attribute | Data Type | Description |
---|---|---|
| list | List of object pairs made of Parameters:
Writeable? N / Nullable? N |
| enum | Normalized keyword for approval review Accepted values: string keywords returned from previous endpoint (in |
| enum | Approval result of the Keyword Review Accepted values:
Writeable? N / Nullable? N |
Get Keywords Approval report
This endpoint retrieves a Keywords Approval report for the provided supply account ID (not applicable for demand accounts).
Results are paginated using offset
and limit
query parameters; if omitted, defaults to 0
and 500
, respectively - see API Response

https://api.criteo.com/{version}/retail-media/accounts/{accountId}/keywords/in-review-report?offset={offset}&limit={limit}
Sample Request
curl -L -X GET "https://api.criteo.com/{version}/retail-media/accounts/368471940340928512/keywords/in-review-report?offset=0&limit=25" \
-H "Accept: application/json" \
-H "Authorization: Bearer <MY_ACCESS_TOKEN>"
import requests
url = "https://api.criteo.com/{version}/retail-media/accounts/368471940340928512/keywords/in-review-report?offset=0&limit=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/accounts/368471940340928512/keywords/in-review-report?offset=0&limit=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();
}
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/368471940340928512/keywords/in-review-report?offset=0&limit=25")
.method("GET", body)
.addHeader("Accept", "application/json")
.addHeader("Authorization", "Bearer <MY_ACCESS_TOKEN>")
.build();
Response response = client.newCall(request).execute();
Sample Response
{
"meta": {
"count": 71,
"offset": 0,
"limit": 25
},
"data": [
{
"id": "4840685188706902009",
"type": "LineItemKeywordReviewReport",
"attributes": {
"lineItemId": "4840685188706902009",
"lineItemName": "LI Retailer ABC Category A",
"retailerId": "401887",
"retailerName": "Retailer ABC",
"campaignId": "124755545923269376",
"campaignName": "Campaign AAA",
"accountId": "97393138059194368",
"accountName": "Test Account",
"countKeywords": 2
}
},
{
"id": "6854840188706902009",
"type": "LineItemKeywordReviewReport",
"attributes": {
"lineItemId": "124773003405488128",
"lineItemName": "LI Retailer ABC Category B",
"retailerId": "401887",
"retailerName": "Retailer ABC",
"campaignId": "124755545923269376",
"campaignName": "Campaign AAA",
"accountId": "97393138059194368",
"accountName": "Test Account",
"countKeywords": 4
}
},
// ...
{
"id": "358669652976373760",
"type": "LineItemKeywordReviewReport",
"attributes": {
"lineItemId": "9979917896105882144",
"lineItemName": "LineItem ABC Producs Jan 2025",
"retailerId": "401887",
"retailerName": "Retailer ABC",
"campaignId": "106358893501214720",
"campaignName": "Sponsored Products Jan 2025",
"accountId": "97393138059194368",
"accountName": "Test Account",
"countKeywords": 5
}
}
],
"warnings": [],
"errors": []
}
Get Keywords to review
This endpoint retrieves the list of all Keywords review state for the provided Line-Item ID.
This endpoint evolved from previous versions to allow Supply Accounts considered as eligible reviewers to review keywords from line-items of other accounts not included in their consent access (applicable for Private Market networks).

https://api.criteo.com/{version}/retail-media/line-items/{lineItemId}/keywords
Sample Request
curl -L -X GET "https://api.criteo.com/{version}/retail-media/line-items/358669652976373760/keywords" \
-H "Authorization: Bearer <MY_ACCESS_TOKEN>"
import requests
url = "https://api.criteo.com/{version}/retail-media/line-items/358669652976373760/keywords"
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/line-items/358669652976373760/keywords');
$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();
}
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/line-items/358669652976373760/keywords")
.method("GET", body)
.addHeader("Accept", "application/json")
.addHeader("Authorization", "Bearer <MY_ACCESS_TOKEN>")
.build();
Response response = client.newCall(request).execute();
Sample Response
{
"data": {
"id": "124848016959148032",
"type": "RetailMediaKeywordsModel",
"attributes": {
"keywords": {
"top": {
"reviewState": "InReview",
"matchType": "PositiveExactMatch",
"bid": 2.00,
"inputKeywords": {
"positiveExact": [
"top",
"tops"
]
},
"createdAt": "2024-05-17T02:12:52.1208996",
"updatedAt": "2024-05-17T02:12:52.8112947"
},
"t-shirt": {
"reviewState": "InReview",
"matchType": "PositiveExactMatch",
"bid": 1.00,
"inputKeywords": {
"positiveExact": [
"t-shirts",
"tshirt",
"t shirt"
]
},
"createdAt": "2024-07-25T05:13:47.766958",
"updatedAt": "2024-07-25T05:13:48.2112064"
},
"top woman": {
"reviewState": "InReview",
"matchType": "PositiveExactMatch",
"bid": null,
"inputKeywords": {
"positiveExact": [
"tops for women",
"women's top"
]
},
"createdAt": "2024-07-29T06:45:17.5861897",
"updatedAt": "2024-07-29T06:45:17.8158725"
}
}
}
}
}
Review Keywords for a Line-item
This endpoint allows retailers users to review (approve/reject) one or multiple keywords for the respective Line-Item ID. The response will return the full list of Keywords Reviews (including still InReview
, Approved
and Rejected
).
Once keywords are reviewed through this endpoint, the Keywords Approval Report (available in /accounts/{accountId}/keywords/in-review-report
) will reflect the updated amount of keywords left for review.

https://api.criteo.com/{version}/retail-media/line-items/{lineItemId}/keywords/review
Sample Request
curl -L -X POST "https://api.criteo.com/{version}/retail-media/line-items/358669652976373760/keywords/review" \
-H "Authorization: Bearer <MY_ACCESS_TOKEN>" \
-H "Content-Type: application/json" \
-d '{
"data": {
"attributes": {
"keywords": [
{
"phrase": "top",
"reviewState": "rejected"
},
{
"phrase": "t-shirt",
"reviewState": "approved"
}
]
}
}
}'
import requests
import json
url = "https://api.criteo.com/{version}/retail-media/line-items/358669652976373760/keywords/review"
payload = json.dumps({
"data": {
"attributes": {
"keywords": [
{
"phrase": "vegetable",
"reviewState": "approved"
},
{
"phrase": "tomato",
"reviewState": "rejected"
}
]
}
}
})
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\":{\"keywords\":[{\"phrase\":\"vegetable\",\"reviewState\":\"approved\"},{\"phrase\":\"tomato\",\"reviewState\":\"rejected\"}]}}}");
Request request = new Request.Builder()
.url("https://api.criteo.com/{version}/retail-media/line-items/358669652976373760/keywords/review")
.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":{"keywords":[{"phrase":"vegetable","reviewState":"approved"},{"phrase":"tomato","reviewState":"rejected"}]}}}');
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": {
"type": "RetailMediaKeywordsReviewResult",
"attributes": {
"keywords": [
{
"phrase": "top",
"reviewState": "Rejected"
},
{
"phrase": "t-shirt",
"reviewState": "Approved"
},
{
"phrase": "top woman",
"reviewState": "InReview"
}
]
}
},
"warnings": [],
"errors": []
}
Responses
Response | Title | Detail | Troubleshooting |
---|---|---|---|
🟢 | Call executed with success | ||
🟢 | Entity request created with success | ||
🔴 | Keyword Error | One or more keywords given are not found on this line item | Double-check the value of the keyword informed in the Review request; it should contain only existing keywords with |
🔴 | Model validation error | Error converting value "xyz" to type 'Criteo.RetailMedia. | Value "xyz" provided is not a valid value for that respective parameter. Check the error details for more information |
🔴 | Forbidden | The account ID provided is not suitable for those endpoints (only supply accounts), the account is not included in the consent from the API app or the API app doesn't have Manage access to the Campaign domain/scope. |
Updated 11 days ago