📝

A Few Things to Know

Shoppers browsing through the retailer's search page may utilize search for phrases associated with line item products where the line item may not be allowed to search on that page. Similar to this scenario, the shopper can also enter search phrases that align with products permitted to serve on a page. The keyword API endpoint allows you to control your Onsite Sponsored Products line item by providing visibility to users what keywords are applied to line items, and also allows for users to determine which keyword to target positively or negatively. The endpoint can also provide keyword bidding capabilities to optimize line items based on relevant keywords.

Check out the Onsite Sponsored Products page for a complete summary of Criteo's keyword service.

Endpoints

VerbEndpointDescription
GET/line-items/{lineItemId}/keywordsRetrieve a set of positive and negative keywords for a line item
GET/line-items/{lineItemId}/keywords/recommendedRetrieve a collection of recommended keywords for a line item
POST/line-items/{lineItemId}/keywords/add-removeAdd or remove keywords from a line item
POST/line-items/{lineItemId}/keywords/set-bidSet a bid override at keyword level

Keyword Attributes

AttributeData TypeDescription
id*stringLine Item ID, generated internally by Criteo

Accepted values: string of int64
Writeable? N / Nullable? N
matchType*enumMatching algorithm type 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 do not bid
* NegativeBroadMatch: normalized keyword is a substring of the normalized search phrase do not bid
Default: PositiveExactMatch
Writeable? N / Nullable? N
reviewStateenumStatus of Keyword review, only applicable for PositiveExactMatch match type keywords.
Keywords not reviewed by the automatic keyword service will be reviewed and approved by the retailer.

Accepted values:
*InReview: keyword has been submitted manually, and the review is still pending
*Approved: keyword was approved manually
* AutoApproved: keyword was approved automatically
*Rejected: keyword was rejected manually
*AutoRejected: keyword was rejected automatically
*Recommended: keyword was recommended by our keyword model
Default: InReview
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 ≤ line-item's maxBid, available in the endpoints detailed in Catalog Endpoints and Onsite Display Line Items, respectively
Default: null
Writeable? Y / Nullable? Y
isDeletedbooleanControl flag to add or remove the keyword from the line-item

Accepted values: true / false
Writeable? N / Nullable? N
inputKeywords*objectKeywords structure associated with line-item containing normalized keyword phrases and organized by match type.

Parameters:

* positiveExact: list of supplied positive exact phrases
* negativeExact: list of supplied negative exact phrases
* negativeBroad: list of supplied negative broad phrases
phrasestringRaw text of the keyword to be added or removed

Accepted values: up to 255-chars string
Writeable? Y / Nullable? N
createdAttimestampTimestamp when keyword was configured in the line-item (or recommended to line-item)

Accepted values: yyyy-mm-ddThh:mm:ss(in ISO-8601)
Writeable? N / Nullable? N
updatedAttimestampTimestamp when keyword was last modified in the line-item (or recommended to line-item)

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

Get Keywords by Line Item

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 'Accept: application/json' \
  -H 'Authorization: Bearer <MY_ACCESS_TOKEN>'
import http.client

conn = http.client.HTTPSConnection("api.criteo.com")
payload = ''
headers = {
  'Accept': 'application/json',
  'Authorization': 'Bearer <MY_ACCESS_TOKEN>'
}
conn.request("GET", "/{version}/retail-media/line-items/358669652976373760/keywords", payload, headers)
res = conn.getresponse()
data = res.read()
print(data.decode("utf-8"))
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();
<?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();
}

Sample Response

{
    "data": {
        "id": "358669652976373760",
        "type": "RetailMediaKeywordsModel",
        "attributes": {
            "keywords": {
                "vegetable": {
                    "matchType": "NegativeBroadMatch",
                    "bid": null,
                    "inputKeywords": {
                        "negativeBroad": [
                            "vegetable"
                        ],
                        "negativeExact": [],
                        "positiveExact": []
                    },
                    "createdAt": "2025-01-01T00:00:00",
                    "updatedAt": "2025-01-01T00:00:00"
                },
                "tomato": {
                    "matchType": "NegativeExactMatch",
                    "bid": null,
                    "inputKeywords": {
                        "negativeBroad": [],
                        "negativeExact": [
                            "tomatoes"
                        ],
                        "positiveExact": []
                    },
                    "createdAt": "2025-01-01T00:00:00",
                    "updatedAt": "2025-01-01T00:00:00"
                },
                "strawberry": {
                    "matchType": "NegativeExactMatch",
                    "bid": null,
                    "inputKeywords": {
                        "negativeBroad": [],
                        "negativeExact": [
                            "strawberry"
                        ],
                        "positiveExact": []
                    },
                    "createdAt": "2025-01-01T00:00:00",
                    "updatedAt": "2025-01-01T00:00:00"
                },
                "broccoli": {
                    "matchType": "NegativeExactMatch",
                    "bid": null,
                    "inputKeywords": {
                        "negativeBroad": [],
                        "negativeExact": [
                            "broccoli"
                        ],
                        "positiveExact": []
                    },
                    "createdAt": "2025-01-01T00:00:00",
                    "updatedAt": "2025-01-01T00:00:00"
                },
                "banana": {
                    "matchType": "NegativeExactMatch",
                    "bid": null,
                    "inputKeywords": {
                        "negativeBroad": [],
                        "negativeExact": [
                            "banana"
                        ],
                        "positiveExact": []
                    },
                    "createdAt": "2025-01-01T00:00:00",
                    "updatedAt": "2025-01-01T00:00:00"
                },
                "pasta": {
                    "reviewState": "Approved",
                    "matchType": "PositiveExactMatch",
                    "bid": null,
                    "inputKeywords": {
                        "negativeBroad": [],
                        "negativeExact": [],
                        "positiveExact": [
                            "pasta"
                        ]
                    },
                    "createdAt": "2025-01-01T00:00:00",
                    "updatedAt": "2025-01-01T00:00:00"
                },
                "milk": {
                    "reviewState": "Approved",
                    "matchType": "PositiveExactMatch",
                    "bid": 0.50,
                    "inputKeywords": {
                        "negativeBroad": [],
                        "negativeExact": [],
                        "positiveExact": [
                            "milk"
                        ]
                    },
                    "createdAt": "2025-01-01T00:00:00",
                    "updatedAt": "2025-01-01T00:00:00"
                },
                "juice": {
                    "reviewState": "Approved",
                    "matchType": "PositiveExactMatch",
                    "bid": null,
                    "inputKeywords": {
                        "negativeBroad": [],
                        "negativeExact": [],
                        "positiveExact": [
                            "juice"
                        ]
                    },
                    "createdAt": "2025-01-01T00:00:00",
                    "updatedAt": "2025-01-01T00:00:00"
                },
                "egg": {
                    "reviewState": "Approved",
                    "matchType": "PositiveExactMatch",
                    "bid": 0.50,
                    "inputKeywords": {
                        "negativeBroad": [],
                        "negativeExact": [],
                        "positiveExact": [
                            "eggs"
                        ]
                    },
                    "createdAt": "2025-01-01T00:00:00",
                    "updatedAt": "2025-01-01T00:00:00"
                },
                "fruit": {
                    "matchType": "NegativeBroadMatch",
                    "bid": null,
                    "inputKeywords": {
                        "negativeBroad": [
                            "fruits"
                        ],
                        "negativeExact": [],
                        "positiveExact": []
                    },
                    "createdAt": "2025-01-01T00:00:00",
                    "updatedAt": "2025-01-01T00:00:00"
                }
            }
        }
    },
    /* omitted if no errors */
    "errors": [],
    /* omitted if no warnings */
    "warnings": []
}

Get Recommended Keywords by Line Item

This endpoint retrieves a collection of recommended keywords for a line item, created automatically by our keyword models.

📝

A FEW THINGS TO KNOW

  • Only the top 100 keywords will be returned
  • Automatic recommended keywords can change day to day, as are determined based on click volumes by users on Retailer's environment. Although significant changes are not expected, it is possible that the long tail of the top 100 keywords change slightly
https://api.criteo.com/{version}/retail-media/line-items/{lineItemId}/keywords/recommended

Sample Request

curl -X GET "https://api.criteo.com/{version}/retail-media/line-items/487311207809134592/keywords/recommended" \
  -H 'Accept: application/json' \
  -H "Authorization: Bearer <MY_ACCESS_TOKEN>"
import requests

url = "https://api.criteo.com/{version}/retail-media/line-items/487311207809134592/keywords/recommended"

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/line-items/487311207809134592/keywords/recommended")
  .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/line-items/487311207809134592/keywords/recommended');
$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": {
        "type": "RecommendedKeywords",
        "attributes": {
            "keywords": {
                "bar sound": {
                    "reviewState": "AutoApproved",
                    "matchType": "PositiveExactMatch",
                    "bid": 1.0,
                    "inputKeywords": {
                        "positiveExact": [
                            "bar sound",
                            "sound bar",
                            "sound-bar",
                            "sound bars",
                            "sounds bar"
                        ]
                    },
                    "createdAt": "2025-01-01T07:00:00",
                    "updatedAt": "2025-01-01T08:00:00"
                },
                "dolby soundbar": {
                    "reviewState": "Recommended",
                    "matchType": "PositiveExactMatch",
                    "bid": null,
                    "inputKeywords": {
                        "positiveExact": [
                            "dolby soundbars",
                            "soundbar dolby"
                        ]
                    },
                    "createdAt": "2025-01-01T07:00:00",
                    "updatedAt": "2025-01-01T08:00:00"
                },
                "atmos": {
                    "matchType": "NegativeBroad",
                    "inputKeywords": {
                        "negativeBroad": [
                            "atmos"
                        ]
                    },
                    "createdAt": "2025-01-01T07:00:00",
                    "updatedAt": "2025-01-01T08:00:00"
                }
            },
            "recommended": [
                "atmos",
                "dolby soundbars",
                "sound bar",
                "sound bars",
                "soundbar dolby"
            ]
        }
    },
    /* omitted if no errors */
    "errors": [],
    /* omitted if no warnings */
    "warnings": []
}

Add or remove Keyword from Line Item

https://api.criteo.com/{version}/retail-media/line-items/{lineItemId}/keywords/add-remove

📘

Negative Targeting v1

The negative keyword targeting in this API will eventually replace the Negative Keyword Targeting endpoints. You may continue using those endpoints without disrupting your services. We recommend reviewing and testing the new Keyword endpoints to prepare for a future migration to these new endpoints.

Sample Request

curl -L -X POST 'https://api.criteo.com/{version}/retail-media/line-items/325713346766241792/keywords/add-remove' \
-H 'Content-Type: application/json' \
-H 'Accept: text/plain' \
-H 'Authorization: Bearer <MY_ACCESS_TOKEN>' \
--data-raw '{
	"data": {
		"type": "RetailMediaKeywordAddRemove",
		"id": "325713346766241792",
		"attributes": {
			"keywords": [
				{
					"phrase": "cookies and bread",
					"matchType": "PositiveExactMatch",
					"isDeleted": "false"
				},
				{
					"phrase": "raspberry",
					"matchType": "NegativeExactMatch",
					"isDeleted": "false"
				},
                
				{
					"phrase": "potatoes",
					"matchType": "NegativeExactMatch",
					"isDeleted": "true"
				}
			]
		}
	}
}'
import http.client
import json

conn = http.client.HTTPSConnection("api.criteo.com")
payload = json.dumps({
  "data": {
    "type": "RetailMediaKeywordAddRemove",
    "id": "325713346766241792",
    "attributes": {
      "keywords": [
        {
          "phrase": "cookies and bread",
          "matchType": "PositiveExactMatch",
          "isDeleted": "false"
        },
        {
          "phrase": "raspberry",
          "matchType": "NegativeExactMatch",
          "isDeleted": "false"
        },
        {
          "phrase": "potatoes",
          "matchType": "NegativeExactMatch",
          "isDeleted": "true"
        }
      ]
    }
  }
})
headers = {
  'Content-Type': 'application/json',
  'Accept': 'text/plain',
  'Authorization': 'Bearer <MY_ACCESS_TOKEN>'
}
conn.request("POST", "/{version}/retail-media/line-items/358669652976373760/keywords/add-remove", payload, headers)
res = conn.getresponse()
data = res.read()
print(data.decode("utf-8"))
OkHttpClient client = new OkHttpClient().newBuilder()
  .build();
MediaType mediaType = MediaType.parse("application/json");
RequestBody body = RequestBody.create(mediaType, "{\n\t\"data\": {\n\t\t\"type\": \"RetailMediaKeywordAddRemove\",\n\t\t\"id\": \"325713346766241792\",\n\t\t\"attributes\": {\n\t\t\t\"keywords\": [\n\t\t\t\t{\n\t\t\t\t\t\"phrase\": \"cookies and bread\",\n\t\t\t\t\t\"matchType\": \"PositiveExactMatch\",\n\t\t\t\t\t\"isDeleted\": \"false\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"phrase\": \"raspberry\",\n\t\t\t\t\t\"matchType\": \"NegativeExactMatch\",\n\t\t\t\t\t\"isDeleted\": \"false\"\n\t\t\t\t},\n                \n\t\t\t\t{\n\t\t\t\t\t\"phrase\": \"potatoes\",\n\t\t\t\t\t\"matchType\": \"NegativeExactMatch\",\n\t\t\t\t\t\"isDeleted\": \"true\"\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t}\n}");
Request request = new Request.Builder()
  .url("https://api.criteo.com/{version}/retail-media/line-items/358669652976373760/keywords/add-remove")
  .method("POST", body)
  .addHeader("Content-Type", "application/json")
  .addHeader("Accept", "text/plain")
  .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/line-items/358669652976373760/keywords/add-remove');
$request->setMethod(HTTP_Request2::METHOD_POST);
$request->setConfig(array(
  'follow_redirects' => TRUE
));
$request->setHeader(array(
  'Content-Type' => 'application/json',
  'Accept' => 'text/plain',
  'Authorization' => 'Bearer <MY_ACCESS_TOKEN>'
));
$request->setBody('{
\n	"data": {
\n		"type": "RetailMediaKeywordAddRemove",
\n		"id": "325713346766241792",
\n		"attributes": {
\n			"keywords": [
\n				{
\n					"phrase": "cookies and bread",
\n					"matchType": "PositiveExactMatch",
\n					"isDeleted": "false"
\n				},
\n				{
\n					"phrase": "raspberry",
\n					"matchType": "NegativeExactMatch",
\n					"isDeleted": "false"
\n				},
\n                
\n				{
\n					"phrase": "potatoes",
\n					"matchType": "NegativeExactMatch",
\n					"isDeleted": "true"
\n				}
\n			]
\n		}
\n	}
\n}');
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
200 successful status will return an empty object array

{}

Set bid on Keyword

💡Info: Bids can be set on a keyword at any time, even when the keyword are still InReview state

https://api.criteo.com/{version}/retail-media/line-items/{lineItemId}/keywords/set-bid

Sample Request

curl -L -X POST 'https://api.criteo.com/{version}/retail-media/line-items/358669652976373760/keywords/set-bid' \
-H 'Content-Type: application/json' \
-H 'Accept: text/plain' \
-H 'Authorization: Bearer <MY_ACCESS_TOKEN>' \
--data-raw '{
  "data": {
    "type": "RetailMediaKeywordsSetBid",
    "id": "358669652976373760",
    "attributes": {
      "keywords": [
        {
          "phrase": "eggs",
          "bid": "0.50"
        },
        {
          "phrase": "milk",
          "bid": "0.50"
        }
      ]
    }
  }
}'
import http.client
import json

conn = http.client.HTTPSConnection("api.criteo.com")
payload = json.dumps({
  "data": {
    "type": "RetailMediaKeywordsSetBid",
    "id": "358669652976373760",
    "attributes": {
      "keywords": [
        {
          "phrase": "eggs",
          "bid": "0.50"
        },
        {
          "phrase": "milk",
          "bid": "0.50"
        }
      ]
    }
  }
})
headers = {
  'Content-Type': 'application/json',
  'Accept': 'text/plain',
  'Authorization': 'Bearer <MY_ACCESS_TOKEN>'
}
conn.request("POST", "/{version}/retail-media/line-items/358669652976373760/keywords/set-bid", payload, headers)
res = conn.getresponse()
data = res.read()
print(data.decode("utf-8"))
import http.client
import json

conn = http.client.HTTPSConnection("api.criteo.com")
payload = json.dumps({
  "data": {
    "type": "RetailMediaKeywordsSetBid",
    "id": "358669652976373760",
    "attributes": {
      "keywords": [
        {
          "phrase": "eggs",
          "bid": "0.50"
        },
        {
          "phrase": "milk",
          "bid": "0.50"
        }
      ]
    }
  }
})
headers = {
  'Content-Type': 'application/json',
  'Accept': 'text/plain',
  'Authorization': 'Bearer <MY_ACCESS_TOKEN>'
}
conn.request("POST", "/{version}/retail-media/line-items/358669652976373760/keywords/set-bid", payload, headers)
res = conn.getresponse()
data = res.read()
print(data.decode("utf-8"))
<?php
require_once 'HTTP/Request2.php';
$request = new HTTP_Request2();
$request->setUrl('https://api.criteo.com/{version}/retail-media/line-items/358669652976373760/keywords/set-bid');
$request->setMethod(HTTP_Request2::METHOD_POST);
$request->setConfig(array(
  'follow_redirects' => TRUE
));
$request->setHeader(array(
  'Content-Type' => 'application/json',
  'Accept' => 'text/plain',
  'Authorization' => 'Bearer <MY_ACCESS_TOKEN>'
));
$request->setBody('{\n  "data": {\n    "type": "RetailMediaKeywordsSetBid",\n    "id": "358669652976373760",\n    "attributes": {\n      "keywords": [\n        {\n          "phrase": "eggs",\n          "bid": "0.50"\n        },\n        {\n          "phrase": "milk",\n          "bid": "0.50"\n        }\n      ]\n    }\n  }\n}');
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
200 successful status will return an empty object array

{}

Responses

ResponseDescription
🔵 200 OKCall completed with success
🔵 201 OK- The call to add/remove the keyword from the line item was executed successfully

- The call to set keyword bid to the line item was executed successfully
🔴 400 - Bad RequestSetting a bid for a positive keyword that doesn't exist on the line item.
Error Message
"On line item {lineItemId} normalized keyword \"{keyword}\"/en_US not found"

Setting a keyword bid above the line-item maxBid value will result in a 400 bad request error message. In this example, the maxBid value is at least 0.40.
Error Message
"Invalid bid value, bid greater than maximum of 1.00000000, found for keyword: <keyword>"

Setting a keyword bid below the retailer minBid value will result in a 400 bad request error message. In this example, the minBid value is at least 0.40.
Error Message
"Invalid bid value, bid less than minimum of 0.4000, found for keyword: <keyword>"