Docs
Product, Variant, and Stock Imports

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

  1. Prepare a newline-delimited JSONL file whose objects match one of the import schemas below.
  2. POST /v2/imports/upload with multipart/form-data that includes the file plus the matching import_type (product, variant, or stock).
  3. Store the job_id returned in the upload response.
  4. Periodically POST /v2/imports/check with the job_id until is_complete: true.
  5. 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 .jsonl file. 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.jsonl

The 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.

FieldTypeRequiredNotes
product_idstringStable ID from your system.
namestringShopper-facing name.
brandstringBrand or producer.
descriptionstringLong-form copy.
is_alcoholicbooleanEnables compliance logic.
is_preferredbooleanFlags recommendation priority.
is_featuredbooleanControls spotlight placement.
alcohol_by_volumenumberUse percentage value (e.g., 13.5).
broadtypestringMaps to an existing broad type name.
subtypestringMaps to an existing subtype name.
country_codestringISO-3166 alpha-2 code. Validated via pycountry.
region_codestringISO-3166-2 code; overrides country_code if set.
vintageintegerYear, if applicable.
tag_dataobject{ [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.

FieldTypeRequiredNotes
product_idstringMust already exist as a product import.
variant_skustringYour SKU identifier.
sizestringRendered on PDPs (e.g., 750 mL).
rewards_pointsintegerUsed in loyalty recommendations.
approx_marginnumberTypical margin for ranking.
approx_pricenumberShopper-facing baseline price.
is_primarybooleanToggle 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.

FieldTypeRequiredNotes
product_idstringRedundant but required for lineage.
variant_skustringMatches the variant entry.
store_idstringReferences a store you've already imported.
qty_in_stockintegerValues > 0 indicate availability.
list_pricenumberBefore discounts.
sale_pricenumberAfter discounts; can equal list price.
locationstringShopper-facing aisle/shelf hint.
last_salestringISO timestamp of last sale.
rolling_year_salesnumberTotal revenue for the last 12 months.
rolling_year_marginnumberMargin from last 12 months.
rolling_year_unitsintegerUnits 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.