View Categories

Product Create / Update

9 min read

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 same groupId for 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 example uk, 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, while url stays the market-specific one. Advertising feeds use canonicalUrl as 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:

  • discountPrice is cleared when it is not included โ€” that is how you take a product off sale.
  • custom is replaced by what you send; omitting it removes the product’s custom fields.
  • variants is 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.