Keywords
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
Verb | Endpoint | Description |
---|---|---|
GET | /line-items/{lineItemId}/keywords | Retrieve a set of positive and negative keywords for a line item |
GET | /line-items/{lineItemId}/keywords/recommended | Retrieve a collection of recommended keywords for a line item |
POST | /line-items/{lineItemId}/keywords/add-remove | Add or remove keywords from a line item |
POST | /line-items/{lineItemId}/keywords/set-bid | Set a bid override at keyword level |
Keyword Attributes
Attribute | Data Type | Description |
---|---|---|
| string | Line Item ID, generated internally by Criteo Accepted values: string of int64 |
| object | Keywords structure, indexed by normalized keyword phrases each of them containing structure of:
|
| enum | Matching algorithm type to be used when comparing this keyword with shopper search phrases. Accepted values:
|
| enum | Status of Keyword review, only applicable for Accepted values:
|
| 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
Default: |
| boolean | Control flag to add or remove the keyword from the line-item Accepted values: |
| object | Keywords structure associated with line-item containing normalized keyword phrases and organized by match type. Parameters:
|
| string | Raw text of the keyword to be added or removed Accepted values: up to 255-chars string |
| timestamp | Timestamp when keyword was configured in the line-item (or recommended to line-item) Accepted values: |
| timestamp | Timestamp when keyword was last modified in the line-item (or recommended to line-item) Accepted values: |
*Required
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.
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.
* 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 v1The 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
Response | Description |
---|---|
🔵 | Call completed with success |
🔵 |
|
🔴 | Setting a bid for a positive keyword that doesn't exist on the line item. Setting a keyword bid above the line-item Setting a keyword bid below the retailer |
Updated about 15 hours ago