The product update API allows you to send bulk catalog updates to Releva. Any catalog updates submitted through this API will be live within 10 seconds of receiving a successful response.
Do I need to call it? #
Yes, you need to call it whenever changes occur in your catalog โ for example when a product (or products) becomes out of stock, or in stock, or the price changes. This is the primary method Releva uses to learn about changes in your catalogue.
Endpoint #
| URL | POST https://releva.ai/api/v0/products |
| Authentication | Authorization: Bearer <secretKey> โ the secret key of the domain whose catalog you are updating. |
| Content type | application/json |
| Max request body | 800 KB. Split larger catalogs into several requests (a few hundred products per request is a good batch size). |
Request Parameters #
You may perform requests to the API using the following curl equivalent:
curl -H 'Content-Type: application/json' -H 'Authorization: Bearer <secretKey>' -XPOST https://releva.ai/api/v0/products -d'{
"products": [
{
"id": "productId1_en_uk",
"name": "Test Product",
"description": "Some Description",
"locale": "en",
"currency": "USD",
"listPrice": 12.5,
"discountPrice": 10.99,
"stockQuantity": 42,
"sku": "TP-001",
"categories": [
"men/shoes/hiking"
],
"available": true,
"url": "https://my-awesome-shop/uk/products/productId1",
"canonicalUrl": "https://my-awesome-shop/products/productId1",
"imageUrl": "https://cdn.my-awesome-shop/products/productId1_img1.png",
"publishedAt": "2019-06-19T23:45:56Z",
"marketHandle": "uk",
"markets": ["GB"],
"data": {"badge": "New in"},
"custom": {
"string": [
{"key": "color", "values": ["red", "blue"]}
],
"numeric": [
{"key": "size", "values": [14, 15, 18]}
],
"date": [
{"key": "in_promo_until", "values": ["2022-11-01T00:00:00.000Z"]}
]
},
"inventory": [
{
"location": [
{"lat": 39.9352959, "lon": 23.5825895}
]
}
]
}
]
}'
Note: publishedAt must include the time and a timezone offset (2019-06-19T23:45:56Z, 2019-06-19T23:45:56.000Z or 2019-06-19T23:45:56+0300). A bare date or a date-time without an offset is rejected with HTTP 400.
What if my products have variants? #
Send one product object per purchasable page, and describe its variants inline in the variants array. Releva keeps the product-level information (name, description, categories, URL) on the product itself and stores the per-variant attributes (price, availability, stock, SKU, barcode, image, options such as size and colour) in variants.
Two fields work together here:
groupIdโ the id of the product all these entries belong to. Use the samegroupIdfor every document that represents the same product (for example the same product in another language or another market).variantsโ the per-variant data.
Send them together. A groupId without a non-empty variants array is ignored, and a product sent without variants is automatically treated as a single-variant product whose group is the product itself.
curl -H 'Content-Type: application/json' -H 'Authorization: Bearer <secretKey>' -XPOST https://releva.ai/api/v0/products -d'{
"products": [
{
"id": "productId1_en_uk",
"name": "Test Product",
"description": "Some Description",
"locale": "en",
"currency": "GBP",
"listPrice": 12.5,
"discountPrice": 10.99,
"stockQuantity": 7,
"sku": "TP-001-S-RED",
"categories": [
"men/shoes/hiking"
],
"available": true,
"url": "https://my-awesome-shop/uk/products/productId1",
"canonicalUrl": "https://my-awesome-shop/products/productId1",
"imageUrl": "https://cdn.my-awesome-shop/products/productId1_img1.png",
"publishedAt": "2019-06-19T23:45:56Z",
"marketHandle": "uk",
"markets": ["GB"],
"groupId": "productId1",
"variants": [
{
"variantId": "variantId1",
"sku": "TP-001-S-RED",
"listPrice": 12.5,
"discountPrice": 10.99,
"available": true,
"stockQuantity": 7,
"imageUrl": "https://cdn.my-awesome-shop/products/productId1_red.png",
"barcode": "5901234123457",
"optionValues": [
{"name": "Size", "value": "S"},
{"name": "Color", "value": "Red"}
]
},
{
"variantId": "variantId2",
"sku": "TP-001-L-BLUE",
"listPrice": 14.5,
"available": false,
"stockQuantity": 0,
"imageUrl": "https://cdn.my-awesome-shop/products/productId1_blue.png",
"optionValues": [
{"name": "Size", "value": "L"},
{"name": "Color", "value": "Blue"}
]
}
],
"custom": {
"string": [
{"key": "size", "values": ["S", "L"]},
{"key": "color", "values": ["red", "blue"]}
]
}
}
]
}'
The product-level listPrice, discountPrice, available, stockQuantity, sku and imageUrl should describe the variant a customer sees first on the page โ normally the first available one. These are the values Releva shows in recommenders, emails and on-site blocks.
Sending each variant as its own product (legacy) #
The older integration style โ one Releva product per variant, with the parent product id passed as a product_id custom field โ is still accepted:
curl -H 'Content-Type: application/json' -H 'Authorization: Bearer <secretKey>' -XPOST https://releva.ai/api/v0/products -d'{
"products": [
{
"id": "variantId1",
"name": "Test Product",
"description": "Some Description",
"locale": "en",
"currency": "USD",
"listPrice": 12.5,
"discountPrice": 10.99,
"categories": [
"men/shoes/hiking"
],
"available": true,
"url": "https://my-awesome-shop/products/productId1",
"imageUrl": "https://cdn.my-awesome-shop/products/productId1_img1.png",
"publishedAt": "2019-06-19T23:45:56Z",
"custom": {
"string": [
{"key": "product_id", "values": ["productId1"]},
{"key": "size", "values": ["S"]},
{"key": "color", "values": ["red"]}
]
}
}
]
}'
New integrations should prefer the variants array: it keeps one entry per product page in recommenders and search instead of one entry per variant, and it is what the Facebook / Meta catalog sync uses to build variant-level catalog items.
Note: the deleteMissingVariants flag from earlier versions of this API is deprecated. It is still accepted by the endpoint for backwards compatibility but no longer has any effect. Remove variants you no longer sell from the variants array, and remove products you no longer sell with the Product Delete API.
Selling in several languages, currencies or markets #
Releva stores one document per (product, language, market) combination, and id is what identifies that document. If you sell the same product in more than one language or market, each version needs its own id. The convention our own connectors use is:
{yourProductId}_{locale} e.g. 12345_en
{yourProductId}_{locale}_{market} e.g. 12345_en_uk
Set locale and currency on every document, and for market-scoped catalogs also:
marketHandleโ your own identifier for the market (for exampleuk,europe). Releva uses it to scope recommendations and on-site search to the market the visitor is browsing.marketsโ the ISO-3166-1 alpha-2 country codes the document applies to, in upper case. A market that spans several countries carries them all, e.g.["FR", "DE", "ES", "IT", "NL"]. This is what the Facebook / Meta catalog sync uses to publish per-country prices and links.canonicalUrlโ the product’s canonical URL without the market or language prefix, whileurlstays the market-specific one. Advertising feeds usecanonicalUrlas the base link so one market’s path prefix is not inherited by the others.groupIdโ the same value across all languages and markets of the product, so they are recognised as one product.
The products Array #
This object is an array of Objects with the following properties. Any property that is not listed below is rejected with HTTP 400 โ pass extra data in custom or data instead.
| Field | Type | Description |
|---|---|---|
id |
String | A unique product ID identifying your product. It must be unique across all locales and markets โ see Selling in several languages, currencies or markets above. This is also the id used by the Product Delete API. |
name |
String | The product name. |
description |
String (Optional) | A short description of the product. |
locale |
ISO-639-1 String (Optional) | If you sell in multiple languages or currencies, set this to the current locale. Two-letter language codes only (en, bg) โ en-US is not valid. If you omit this field, the shop default will be used. |
currency |
ISO-4217 String (Optional) | The product currency. If you omit this field, the shop default will be used. |
listPrice |
Float | The regular price of the product. |
discountPrice |
Float (Optional) | If the product is on sale, set the discount price here. If not, do not include this field. It must be lower than listPrice. |
stockQuantity |
Integer (Optional) | How many units are still available for sale. Use 0 together with available: false for a sold-out product. |
sku |
String (Optional) | The SKU of the product (of the primary variant for a multi-variant product). Used by on-site search and, optionally, as the item identifier in advertising catalogs. |
categories |
Array[String] | Array of category paths where the product is accessible, e.g. ["men/shoes", "men/shoes/hiking", "sports/hiking/shoes"]. |
available |
Boolean | True if the product is available for sale, false otherwise. |
url |
Absolute URL String (Optional) | The product URL, as seen by a visitor of this locale/market. |
canonicalUrl |
Absolute URL String (Optional) | The product’s canonical URL, without a language or market prefix. Used as the base link in advertising feeds; falls back to url when omitted. |
imageUrl |
Absolute URL String (Optional) | The URL to the product’s image. If omitted, the shop’s default product image is used. |
publishedAt |
ISO-8601 String (Optional) | The date and time when this product was first available for sale. The time and the timezone offset are required, e.g. 2019-06-19T23:45:56Z. |
marketHandle |
String (Optional) | Your identifier for the market this document belongs to, e.g. uk. Used to scope recommendations and search to the visitor’s market. |
markets |
Array[String] (Optional) | The ISO-3166-1 alpha-2 country codes this document applies to, upper case, e.g. ["FR", "DE"]. Anything that is not exactly two upper-case letters is rejected. |
groupId |
String (Optional) | The id of the product this document belongs to. Use the same value for all variants, languages and markets of one product. Only honoured together with a non-empty variants array. |
variants |
Array[Object] (Optional) | Per-variant data โ see The variants Array below. |
data |
Object (Optional) | Arbitrary non-searchable product data. Use this to pass through product information that you would like to visualize in recommender results. |
custom |
Object (Optional) | The custom fields associated with the product, for example the available sizes โ see The custom Object below. |
inventory |
Array[Object] (Optional) | Product inventory information. |
inventory[].location |
Array[Object] (Optional) | Inventory locations in decimal longitude, latitude notation. |
inventory[].location[].lon |
Float | Inventory location longitude, e.g. 23.5825895. |
inventory[].location[].lat |
Float | Inventory location latitude, e.g. 39.9352959. |
The variants Array #
Each entry describes one purchasable variant of the product. Only per-variant data belongs here โ name, description, categories and URL stay on the product.
| Field | Type | Description |
|---|---|---|
variantId |
String | Your id for this variant. Must be unique within the product. |
sku |
String (Optional) | The variant SKU. |
listPrice |
Float (Optional) | The regular price of this variant. |
discountPrice |
Float (Optional) | The sale price of this variant, if it is on sale. |
available |
Boolean (Optional) | True if this variant can be bought. |
stockQuantity |
Integer (Optional) | How many units of this variant are still available. |
imageUrl |
Absolute URL String (Optional) | The variant image, when it differs from the product image. |
barcode |
String (Optional) | The variant barcode / GTIN / EAN. |
optionValues |
Array[Object] (Optional) | The variant options, as {"name": "Size", "value": "S"} pairs. Send one entry per option axis. |
optionValues[].name |
String | The option name, e.g. Size. |
optionValues[].value |
String | The option value for this variant, e.g. S. |
The custom Object #
Custom fields are what you filter, group and personalise on โ brand, colour, material, promotion end dates, and anything else specific to your catalog. They are grouped by value type, and each entry is a key with an array of values.
| Field | Type | Description |
|---|---|---|
custom.string |
Array[Object] (Optional) | Text fields, as {"key": "color", "values": ["red", "blue"]}. |
custom.numeric |
Array[Object] (Optional) | Numeric fields, as {"key": "size", "values": [14, 15, 18]}. |
custom.date |
Array[Object] (Optional) | Date fields, as {"key": "in_promo_until", "values": ["2022-11-01T00:00:00.000Z"]}. A plain 2022-11-01 is accepted here too. |
Releva adds a category_level_1 โฆ category_level_8 string field automatically from each of your categories paths, so you do not need to send those yourself.
Sending updates #
Each call updates the products it contains and leaves the rest of your catalog untouched. Within a product, always send the complete object rather than only the fields that changed:
discountPriceis cleared when it is not included โ that is how you take a product off sale.customis replaced by what you send; omitting it removes the product’s custom fields.variantsis replaced by what you send; omitting it collapses the product back to a single variant.
Price and availability changes are detected by comparing against what Releva already has, and they feed the price drop, back in stock and out of stock triggers โ so sending the same payload twice does not re-trigger a campaign.
Response Format #
A successful response will return HTTP Status 202 with an empty response body ({}).
An error response will return a HTTP 4xx or 5xx status code and have the following structure:
{
"message": "A description of the error and how to fix it, if it's a client error."
}
| Status | Meaning |
|---|---|
| 202 | Accepted. The update will be live within 10 seconds. |
| 400 | The request body failed validation, or the Authorization header is missing, malformed or does not match a known domain. message names the offending field. |
| 402 | The domain is disabled. Please reach out to support@releva.ai to enable it. |
| 413 | The request body is larger than 800 KB. Send the products in smaller batches. |