Keyword Approval

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

VerbEndpointDescription
GET/accounts/{accountId}/keywords/in-review-reportRetrieve a Keywords Approval report to the specific supply account, with a count of keywords in review state at line-item
GET/line-items/{lineItemId}/keywordsRetrieve a set of all keywords in review state for the specific line-item
POST/line-items/{lineItemId}/keywords/reviewReview keyword(s) for the specific line-item

  

Keywords Approval Report Attributes

AttributeData TypeDescription
id / lineItemIdstringLine Item ID, respective to the amount of keywords in review

Accepted values: string of int64
Writeable? N / Nullable? N
lineItemNamestringLine Item name, respective to the amount of keywords in review

Accepted values: up to 255-chars string
Writeable? Y / Nullable? N
campaignIdstringCampaign ID, respective to the amount of keywords in review

Accepted values: string of int64
Writeable? N / Nullable? N
campaignNamestringCampaign name, respective to the amount of keywords in review

Accepted values: up to 255-chars string
Writeable? Y / Nullable? N
accountIdstringAccount ID, respective to the amount of keywords in review

Accepted values: string of int64
Writeable? N / Nullable? N
accountNamestringAccount name, respective to the amount of keywords in review

Accepted values: up to 255-chars string
Writeable? Y / Nullable? N
retailerIdstringRetailer ID, respective to the amount of keywords in review

Accepted values: string of int64
Writeable? N / Nullable? N
retailerNamestringRetailer name, respective to the amount of keywords in review

Accepted values: up to 255-chars string
Writeable? Y / Nullable? N
countKeywordsintegerAmount of keywords in review for the respective line-item

Accepted values: countKeywords ≥ 1
Writeable? N / Nullable? N

Keywords Review Attributes

AttributeData TypeDescription
idstringLine Item ID, respective to the amount of keywords in review

Accepted values: string of int64
Writeable? N / Nullable? N
reviewStateenumStatus of the Keyword Review (only applicable for PositiveExactMatch keywords)

Accepted values:

* InReview - keyword has been submitted manually, and the review is still pending
* Approved - keyword was approved manually
* Rejected - keyword was rejected manually
* AutoApproved - keyword was approved automatically
* AutoRejected - keyword was rejected automatically
* Recommended - keyword was recommended by our keyword model

Default: InReview
Writeable? N / Nullable? N
matchTypeenumThe matching algorithm to be used when comparing this keyword with shopper search phrases

Accepted values:

* PositiveExactMatch - normalized keyword is an exact match for the normalized search phrase bid
* NegativeExactMatch - normalized keyword is an exact match for the normalized search phrase to not bid
* NegativeBroadMatch - normalized keyword is a substring of the normalized search phrase to not bid

Writeable? N / Nullable? N
biddecimalThe bid override for the positive keyword. The keyword will use the default line item bid if the value is null. The currency of the bid is the default currency for the retailer.

Accepted values: retailer's minBidbid ≤ retailer's maxBid
Default: null
Writeable? Y / Nullable? Y
inputKeywordsobjectKeywords supplied by the user matching this normalized keyword phrase binned by match type (see examples below)

Parameters:

* positiveExact - collection of supplied positive exact phrases
* negativeExact - collection of supplied negative exact phrases
* negativeBroad - collection of supplied negative broad phrases

Writeable? Y / Nullable? Y
createdAttimestampTimestamp the keyword was created in the line-item (or recommended to line-item)

Accepted values: yyyy-mm-ddThh:mm:ss (in ISO-8601 )
Writeable? N / Nullable? N
updatedAttimestampLast timestamp the keyword was updated in the line-item (or recommended to line-item)

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

Keywords Review Body Request

AttributeData TypeDescription
keywords*listList of object pairs made of phrase and reviewState to approve/reject keywords

Parameters:

* phrase - keyword to review
* reviewState - approval result

Writeable? N / Nullable? N
phrase*enumNormalized keyword for approval review

Accepted values: string keywords returned from previous endpoint (in "reviewState": "InReview"
Writeable? N / Nullable? N
reviewState*enumApproval result of the Keyword Review

Accepted values:

* Approved - keyword was approved manually
* Rejected - keyword was rejected manually

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

🔒

This endpoint is available only for API apps with Manage access to Campaign domain

https://api.criteo.com/preview/retail-media/accounts/{accountId}/keywords/in-review-report?offset=0&limit=25

Sample Request

curl -L -X GET "https://api.criteo.com/preview/retail-media/accounts/368471940340928512/keywords/in-review-report?offset=0&limit=25" \
    -H "Authorization: Bearer <MY_ACCESS_TOKEN>"
import requests

url = "https://api.criteo.com/preview/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/preview/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/preview/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 the previous Keywords endpoint GET /line-items/{lineItemId}/keywords 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/preview/retail-media/line-items/{lineItemId}/keywords

Sample Request

curl -L -X GET "https://api.criteo.com/preview/retail-media/line-items/358669652976373760/keywords" \
    -H "Authorization: Bearer <MY_ACCESS_TOKEN>"
import requests

url = "https://api.criteo.com/preview/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/preview/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/preview/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.

🔒

This endpoint is available only for API apps with Manage access to Campaign domain

https://api.criteo.com/preview/retail-media/line-items/{lineItemId}/keywords/review

Sample Request

curl -L -X POST "https://api.criteo.com/preview/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/preview/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/preview/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

ResponseTitleDetailTroubleshooting
🟢 200Call executed with success
🟢 201Entity request created with success
🔴 400Keyword ErrorOne or more keywords given are not found on this line itemDouble-check the value of the keyword informed in the Review request; it should contain only existing keywords with "reviewState": "InReview" retrieved from Get Keywords endpoint
🔴 400Model validation errorError 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
🔴 403ForbiddenThe 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.
Review the Types of Permissions in Authorization Requests