Product, Variant, and Stock Imports
Prepare JSONL files and call the imports API to load catalog data into Retrend.
Retrend's import service lets you push bulk catalog data to the platform with the same validations that power the dashboard. This guide walks through preparing JSON Lines files for Product, Variant, and Stock imports and calling the two endpoints you'll use most often:
POST <https://api.retrend.ai/v2/imports/upload>uploads a JSONL file for one import type.POST <https://api.retrend.ai/v2/imports/check>polls the background job created during the upload.
These examples assume you already created a session token as described in API Dependencies and you
will authenticate each request with organization-id and x-session headers.
Import workflow
- Prepare a newline-delimited JSONL file whose objects match one of the import schemas below.
POST /v2/imports/uploadwithmultipart/form-datathat includes the file plus the matchingimport_type(product,variant, orstock).- Store the
job_idreturned in the upload response. - Periodically
POST /v2/imports/checkwith thejob_iduntilis_complete: true. - Review validation errors in the dashboard (the API responds synchronously only for structural problems).
Upload request format
The upload endpoint accepts multipart/form-data with two fields:
import_type— one of the allowed values listed above. Each file may target exactly one type.file— binary attachment of your.jsonlfile. Keep file sizes manageable (tens of thousands of lines is typical).
curl -X POST "https://api.retrend.ai/v2/imports/upload" \
-H "organization-id: $RETREND_ORG_ID" \
-H "x-session: $RETREND_SESSION" \
-F import_type=product \
-F file=@products.jsonlThe response includes success (boolean) and job_id (UUID). success reflects whether the upload was accepted; data
validation happens asynchronously based on the job_id.
Preparing JSONL payloads
Each JSON object in your file represents one record. Use UTF-8 encoding, avoid trailing commas, and ensure optional
fields are either omitted or set to null. Timestamps must be ISO 8601 strings.
Product schema
Products define the canonical catalog items your variants reference.
| Field | Type | Required | Notes |
|---|---|---|---|
product_id | string | ✅ | Stable ID from your system. |
name | string | ✅ | Shopper-facing name. |
brand | string | ✅ | Brand or producer. |
description | string | ✅ | Long-form copy. |
is_alcoholic | boolean | ✅ | Enables compliance logic. |
is_preferred | boolean | ✅ | Flags recommendation priority. |
is_featured | boolean | ✅ | Controls spotlight placement. |
alcohol_by_volume | number | ✅ | Use percentage value (e.g., 13.5). |
broadtype | string | — | Maps to an existing broad type name. |
subtype | string | — | Maps to an existing subtype name. |
country_code | string | — | ISO-3166 alpha-2 code. Validated via pycountry. |
region_code | string | — | ISO-3166-2 code; overrides country_code if set. |
vintage | integer | — | Year, if applicable. |
tag_data | object | — | { [groupName]: string[] }. Each list must contain at least one value. |
Example product lines:
{"product_id":"MERLOT-375","name":"Merlot Reserve","brand":"Heritage Estates","description":"Medium-bodied red with plum notes.","is_alcoholic":true,"is_preferred":true,"is_featured":false,"alcohol_by_volume":13.4,"broadtype":"Wine","subtype":"Red","country_code":"US","vintage":2021,"tag_data":{"profile":["fruit-forward","smooth"]}}
{"product_id":"BITTERS-001","name":"Orange Bitters","brand":"Heritage Estates","description":"Bright bitters for cocktails.","is_alcoholic":true,"is_preferred":false,"is_featured":false,"alcohol_by_volume":45.0,"broadtype":"Spirits","subtype":"Bitters","country_code":"US","tag_data":{"pairing":["citrus"]}}Variant schema
Variants represent purchasable UPC/SKU combinations for a product. Every variant must reference a valid product_id.
| Field | Type | Required | Notes |
|---|---|---|---|
product_id | string | ✅ | Must already exist as a product import. |
variant_sku | string | ✅ | Your SKU identifier. |
size | string | ✅ | Rendered on PDPs (e.g., 750 mL). |
rewards_points | integer | ✅ | Used in loyalty recommendations. |
approx_margin | number | ✅ | Typical margin for ranking. |
approx_price | number | ✅ | Shopper-facing baseline price. |
is_primary | boolean | ✅ | Toggle to mark the default variant. |
Example variant lines:
{"product_id":"MERLOT-375","variant_sku":"MERLOT-375-CASE","size":"750 mL","rewards_points":150,"approx_margin":9.25,"approx_price":24.99,"is_primary":true}
{"product_id":"MERLOT-375","variant_sku":"MERLOT-375-SPLIT","size":"375 mL","rewards_points":80,"approx_margin":4.5,"approx_price":13.0,"is_primary":false}Stock schema
Stock rows connect a variant to a store and track localized pricing plus velocity metrics.
| Field | Type | Required | Notes |
|---|---|---|---|
product_id | string | ✅ | Redundant but required for lineage. |
variant_sku | string | ✅ | Matches the variant entry. |
store_id | string | ✅ | References a store you've already imported. |
qty_in_stock | integer | ✅ | Values > 0 indicate availability. |
list_price | number | ✅ | Before discounts. |
sale_price | number | ✅ | After discounts; can equal list price. |
location | string | — | Shopper-facing aisle/shelf hint. |
last_sale | string | — | ISO timestamp of last sale. |
rolling_year_sales | number | — | Total revenue for the last 12 months. |
rolling_year_margin | number | — | Margin from last 12 months. |
rolling_year_units | integer | — | Units sold over the last 12 months. |
Example stock lines:
{"product_id":"MERLOT-375","variant_sku":"MERLOT-375-CASE","store_id":"store-100","qty_in_stock":22,"list_price":24.99,"sale_price":22.49,"location":"Aisle 7 - Red Wines","last_sale":"2024-05-01T14:23:00Z","rolling_year_sales":5500.42,"rolling_year_margin":2100.77,"rolling_year_units":240}
{"product_id":"MERLOT-375","variant_sku":"MERLOT-375-SPLIT","store_id":"store-100","qty_in_stock":12,"list_price":13.0,"sale_price":11.0,"rolling_year_sales":1600.0,"rolling_year_margin":640.5,"rolling_year_units":190}Checking import status
Once you have a job_id, poll /v2/imports/check until the response indicates completion:
curl -X POST "https://api.retrend.ai/v2/imports/check" \
-H "organization-id: $RETREND_ORG_ID" \
-H "x-session: $RETREND_SESSION" \
-H "Content-Type: application/json" \
-d "{\"job_id\": \"12345678-1234-1234-1234-123456789012\"}"The API responds with:
{
"is_complete": false
}Keep polling (with exponential backoff) until is_complete switches to true. After completion, review any row-level
issues inside the dashboard's import history to address validation failures.