Products Guide — Styles, Variants & SKUs

How to create, read, update, and archive products through the Retraced public API. Everything goes through one unified resource — /styles — which serves all three levels of the product hierarchy. If you only read one section, read The Mental Model: once you understand how the API detects the product level and how field inheritance works, every endpoint behaves predictably.

Setup

BASE_URL="https://publicapi.retraced.com/api/v2"
API_KEY="your-api-key"        # provided by Retraced; wrap in single quotes if it contains $

Requests are authenticated with the API key header:

curl "$BASE_URL/styles?level=style" \
  -H "companyapikey: $API_KEY"

Important: If your API key contains $ characters, use single quotes around its value to prevent the shell from interpreting it as a variable:

-H 'companyapikey: $2b$10$...'   # correct — single quotes
-H "companyapikey: $2b$10$..."   # wrong — shell expands $2b to empty string → 401

All responses use the standard envelope:

{
  "metadata": { "success": true },
  "data": { }
}

Errors come back in the same envelope with metadata.success: false plus a machine-readable code:

{
  "metadata": { "success": false },
  "statusCode": 400,
  "code": "mixed_levels",
  "message": "All IDs must be the same product level. Found mixed levels: style, variant"
}

The Mental Model

One endpoint, three levels

Products form a 3-level hierarchy:

Style    "Summer Dress"            ── the product definition
  └── Variant  "Summer Dress — Blue"     ── a colorway
        └── SKU  "Summer Dress — Blue — M"     ── a sellable size/dimension

There are no separate /variants or /skus endpoints. All three levels live under /styles, and the API determines the level for you:

Operation How the level is determined
POST /styles By parentId: omitted → style; a style ID → variant; a variant ID → SKU; a SKU ID → 400 invalid_parent_level
GET /styles/:id, PUT /styles/:id Looked up from the ID — pass any level's ID, the right handler is chosen automatically
GET /styles (list) By the level query parameter (style is the default)
POST /styles/archive, /styles/unarchive From the IDs in the body — all IDs must be the same level (400 mixed_levels otherwise)

Field inheritance: null means "inherit"

For these fields, variants and SKUs inherit the parent's value whenever their own value is null:

description, gender, brands, departments, seasons, collections,
suppliers, mainSupplier, vendors, claimIntentions

Inheritance is resolved at read time — there is no copying. Consequences:

Materials and BOMs follow a different inheritance model (shared links with copy-on-write) — see the Materials Guide and BOM Guide.

Codes identify products across systems

Every product carries one or more codes — external identifiers like ERP numbers or EAN barcodes (codes: [{ "codeId": "ERP_ID", "codeValue": "DRS-001" }]). At least one code is required at every level, and code values must be unique per code type within your catalog. See Code Types.


Endpoint Reference

Endpoint Purpose
GET /styles?level=... List products at one hierarchy level
GET /styles/:id Get one product (any level, auto-detected)
POST /styles Create a style, variant, or SKU (level from parentId)
PUT /styles/:id Update a product (any level, auto-detected)
POST /styles/archive Archive products (cascades to children)
POST /styles/unarchive Unarchive products (cascade optional)
GET /style-types Look up styleTypeId values (product categorization)
GET /style-properties?type=... Look up brand / department / season / collection IDs

Before You Create: Look Up Reference IDs

Creating a style requires a styleTypeId, and fields like brands take IDs — not names. Fetch the valid IDs first:

# Product type hierarchy → styleTypeId
curl "$BASE_URL/style-types" \
  -H "companyapikey: $API_KEY"

# Brands, departments, seasons, collections → property IDs
curl "$BASE_URL/style-properties?type=brands" \
  -H "companyapikey: $API_KEY"

suppliers, mainSupplier, and vendors take company IDs from your Retraced network.


Creating Products

Create a style (top level)

Required: name, codes, styleTypeId, isComponent. Omit parentId.

curl -X POST "$BASE_URL/styles" \
  -H "companyapikey: $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Summer Dress",
    "styleTypeId": "YOUR_STYLE_TYPE_ID",
    "isComponent": false,
    "codes": [
      { "codeId": "ERP_ID", "codeValue": "DRS-001" }
    ],
    "description": "Lightweight summer dress in organic cotton",
    "gender": "FEMALE",
    "brands": ["YOUR_BRAND_ID"],
    "seasons": ["YOUR_SEASON_ID"],
    "suppliers": ["SUPPLIER_COMPANY_ID"],
    "mainSupplier": "SUPPLIER_COMPANY_ID",
    "weight": { "value": 250, "unit": "GRAM" }
  }'

Response (201): the created product, including its generated id:

{
  "metadata": { "success": true },
  "data": {
    "id": "style_01KMZMMMW8JA3S1RVX1PS8E19S",
    "name": "Summer Dress",
    "codes": [{ "codeId": "ERP_ID", "codeValue": "DRS-001" }],
    "isComponent": false,
    "isArchived": false,
    "createdAt": "2026-06-01T08:00:00.000Z"
  }
}
STYLE_ID="style_01KMZMMMW8JA3S1RVX1PS8E19S"

Field notes:

Field Notes
isComponent true marks a component / raw material (e.g. a button) rather than a finished good — components are what you reference in BOM lines
gender Free text (≤ 50 chars); convention: MALE, FEMALE, UNISEX
weight { "value": number, "unit": "GRAM" | "KILOGRAM" | ... } — both parts required; sending either as null stores null
customProperties A JSON string of arbitrary key-value metadata, e.g. "{\"fit\":\"slim\"}"
imagesFileIds File IDs from the Files API — see the File Upload Guide
ecoFootPrint Recyclability and hazardous-substance data (hazardousMaterials, recyclabilityRate, …)
forCompanyId Create the product on behalf of another company in your network
isStoryActive Enables the public traceability page — requires a supply chain attached to the product

Create a variant

Set parentId to the style ID. Required: name, codes, color.

curl -X POST "$BASE_URL/styles" \
  -H "companyapikey: $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Summer Dress — Blue",
    "parentId": "'"$STYLE_ID"'",
    "codes": [{ "codeId": "ERP_ID", "codeValue": "DRS-001-BLU" }],
    "color": "Blue"
  }'

Fields you omit (or set null) — description, gender, brands, … — are inherited from the style at read time. Provide values only where the variant genuinely differs.

VARIANT_ID="variant_01KN6YZNVKNBN1TJ4MR9T7FFD0"   # from the response

Create a SKU

Set parentId to the variant ID. Required: name, codes, dimension.

curl -X POST "$BASE_URL/styles" \
  -H "companyapikey: $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Summer Dress — Blue — M",
    "parentId": "'"$VARIANT_ID"'",
    "codes": [{ "codeId": "EAN_GTIN_13", "codeValue": "4006381333931" }],
    "dimension": "M"
  }'

Required fields by level

Level parentId Required fields Level-specific field
Style (omitted) name, codes, styleTypeId, isComponent
Variant a style ID name, codes, color color
SKU a variant ID name, codes, dimension dimension

Missing styleTypeId or isComponent on a style fails with 400 missing_style_type_id / 400 missing_is_component. An unknown parentId fails with 404 parent_not_found; a SKU parentId with 400 invalid_parent_level.


Reading Products

Get by ID (any level)

curl "$BASE_URL/styles/$VARIANT_ID" \
  -H "companyapikey: $API_KEY"

The level is auto-detected; inherited fields come back resolved (the variant's description shows the style's text if not overridden). Unknown IDs return 404 not_found.

List by level

# All styles (level=style is the default)
curl "$BASE_URL/styles?level=style&page=1&limit=50" \
  -H "companyapikey: $API_KEY"

# Variants of one style
curl "$BASE_URL/styles?level=variant&styleId=$STYLE_ID" \
  -H "companyapikey: $API_KEY"

# SKUs of one variant
curl "$BASE_URL/styles?level=sku&variantId=$VARIANT_ID" \
  -H "companyapikey: $API_KEY"

List responses are paginated:

{
  "metadata": {
    "success": true,
    "pagination": { "page": 1, "limit": 50, "total": 7, "totalPages": 1 }
  },
  "data": [ { "id": "style_01KMZ...", "name": "Summer Dress", "...": "..." } ]
}

Query parameters:

Parameter Type Default Description
level string style style, variant, sku
page number 1 1-based
limit number 20 Max 100
sort string createdAt createdAt, updatedAt, name, gender, typeName, color, styleId, dimension, variantId
order string desc asc, desc
name string Substring match on name
nameOrCode string Substring match (case-insensitive) on the name or any code value — ABC also matches ABCD. Works at every level, e.g. with level=sku to look up a SKU by its code
codeId string Filter by code type/ID, e.g. codeId=ERP_ID
codeValue string Filter by code value, e.g. codeValue=DRS-001. Combine: ?codeId=ERP_ID&codeValue=DRS-001
description string Substring match
gender string e.g. FEMALE
styleTypeId, styleLevelId string Exact match
brands, departments, seasons, collections, suppliers, vendors string Comma-separated ID lists
mainSupplier, companyId string Exact match
isComponent, isStoryActive, createdForMe boolean true or false — see note below
isArchived boolean false true returns archived products only; false (the default) non-archived only
updatedAfterUnixMs, updatedBeforeUnixMs number Updated-time window (Unix ms) — useful for incremental syncs
styleId string Parent filter when level=variant
color string When level=variant
variantId string Parent filter when level=sku
dimension string When level=sku
include string Comma-separated related data — see below

Boolean filters accept true or false. Pass isComponent=false / isStoryActive=false to filter for the false value, or omit the parameter to skip the filter. Any other value (e.g. 1, yes) is rejected with a 400.

include pulls related records into each list/get result. Availability differs by level:

Value style variant sku What you get
type The style type (categorization)
material Material composition headers
bom BOM headers
styleProperties Resolved brand / department / season / collection objects
tracingStory The active supply-chain story attached to the product
images Product images on list queries (not available at style level — see note below)
style The parent style
variant The parent variant
skuCount Number of SKUs under the variant
styleLink Linked styles

Images: include=images only affects list queries, and only at variant/SKU level — style lists silently ignore it. You still get images for styles two ways: every list result carries featureImageUrl (the product's first image), and GET /styles/{id} returns the full images array at every level without any include.

curl "$BASE_URL/styles?level=sku&variantId=$VARIANT_ID&include=images,style,tracingStory" \
  -H "companyapikey: $API_KEY"

Updating Products

PUT /styles/:id works on any level (auto-detected). Two rules matter:

  1. codes is replaced wholesale. The codes array you send becomes the product's complete set of codes — send the full desired list every time, not just additions.
  2. null re-enables inheritance on inheritable fields (variants/SKUs); omitting a field leaves it unchanged.
# Update the style's description and codes
curl -X PUT "$BASE_URL/styles/$STYLE_ID" \
  -H "companyapikey: $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Summer Dress",
    "description": "Updated description",
    "codes": [
      { "codeId": "ERP_ID", "codeValue": "DRS-001" },
      { "codeId": "PLM_ID", "codeValue": "PLM-9999" }
    ]
  }'

Override and re-inherit

# Variant: override brands (stop inheriting from the style)
curl -X PUT "$BASE_URL/styles/$VARIANT_ID" \
  -H "companyapikey: $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Summer Dress — Blue",
    "brands": ["DIFFERENT_BRAND_ID"],
    "codes": [{ "codeId": "ERP_ID", "codeValue": "DRS-001-BLU" }]
  }'

# Variant: reset to inherit brands from the style again
curl -X PUT "$BASE_URL/styles/$VARIANT_ID" \
  -H "companyapikey: $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Summer Dress — Blue",
    "brands": null,
    "codes": [{ "codeId": "ERP_ID", "codeValue": "DRS-001-BLU" }]
  }'

parentId and isComponent cannot be changed after creation (they are not part of the update body). styleLevelId can be set on update.


Archiving & Unarchiving

Both endpoints take up to 100 IDs, all of the same level, and return 204 No Content.

# Archive a style — ALWAYS cascades to its variants and SKUs
curl -X POST "$BASE_URL/styles/archive" \
  -H "companyapikey: $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "ids": ["'"$STYLE_ID"'"] }'

# Unarchive the style and all its children (default)
curl -X POST "$BASE_URL/styles/unarchive" \
  -H "companyapikey: $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "ids": ["'"$STYLE_ID"'"], "shouldUnarchiveChildren": true }'

# Unarchive only the style — children stay archived
curl -X POST "$BASE_URL/styles/unarchive" \
  -H "companyapikey: $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "ids": ["'"$STYLE_ID"'"], "shouldUnarchiveChildren": false }'

Full Lifecycle Script

#!/usr/bin/env bash
set -euo pipefail

BASE_URL="https://publicapi.retraced.com/api/v2"
API_KEY="your-api-key"
STYLE_TYPE_ID="your-style-type-id"   # from GET /style-types

api_post() { curl -s -X POST "$BASE_URL$1" -H "companyapikey: $API_KEY" -H "Content-Type: application/json" -d "$2"; }
api_put()  { curl -s -X PUT  "$BASE_URL$1" -H "companyapikey: $API_KEY" -H "Content-Type: application/json" -d "$2"; }
api_get()  { curl -s "$BASE_URL$1" -H "companyapikey: $API_KEY"; }
extract_id() { echo "$1" | grep -o '"id":"[^"]*"' | head -1 | cut -d'"' -f4; }

echo "=== Step 1: Create Style ==="
STYLE_RESP=$(api_post "/styles" '{
  "name": "Demo Jacket",
  "styleTypeId": "'"$STYLE_TYPE_ID"'",
  "isComponent": false,
  "codes": [{"codeId": "ERP_ID", "codeValue": "JKT-001"}],
  "gender": "UNISEX",
  "weight": {"value": 500, "unit": "GRAM"}
}')
STYLE_ID=$(extract_id "$STYLE_RESP")
echo "Created style: $STYLE_ID"

echo "=== Step 2: Create Variant (Red) ==="
VARIANT_RESP=$(api_post "/styles" '{
  "name": "Demo Jacket — Red",
  "parentId": "'"$STYLE_ID"'",
  "codes": [{"codeId": "ERP_ID", "codeValue": "JKT-001-RED"}],
  "color": "Red"
}')
VARIANT_ID=$(extract_id "$VARIANT_RESP")
echo "Created variant: $VARIANT_ID"

echo "=== Step 3: Create SKUs (S, M, L) ==="
for SIZE in S M L; do
  SKU_RESP=$(api_post "/styles" '{
    "name": "Demo Jacket — Red — '"$SIZE"'",
    "parentId": "'"$VARIANT_ID"'",
    "codes": [{"codeId": "ERP_ID", "codeValue": "JKT-001-RED-'"$SIZE"'"}],
    "dimension": "'"$SIZE"'"
  }')
  echo "Created SKU ($SIZE): $(extract_id "$SKU_RESP")"
done

echo "=== Step 4: Update Style ==="
api_put "/styles/$STYLE_ID" '{
  "name": "Demo Jacket (Updated)",
  "description": "Premium demo jacket for integration testing",
  "codes": [{"codeId": "ERP_ID", "codeValue": "JKT-001"}]
}' > /dev/null

echo "=== Step 5: Read back ==="
api_get "/styles/$STYLE_ID" | python3 -m json.tool
api_get "/styles?level=variant&styleId=$STYLE_ID" | python3 -m json.tool
api_get "/styles?level=sku&variantId=$VARIANT_ID" | python3 -m json.tool

echo "=== Step 6: Archive (cascades to all children) ==="
api_post "/styles/archive" '{"ids": ["'"$STYLE_ID"'"]}' > /dev/null

echo "=== Step 7: Unarchive everything ==="
api_post "/styles/unarchive" '{"ids": ["'"$STYLE_ID"'"], "shouldUnarchiveChildren": true}' > /dev/null

echo "=== Done ==="

Code Types

Single-entry (max one per product):

Code ID Meaning
ERP_ID ERP system identifier
PLM_ID Product Lifecycle Management ID
MAIN_SUPPLIERS_ID Main supplier's internal ID
PIM_ID Product Information Management ID
UPC_GTIN_12 12-digit barcode
EAN_GTIN_13 13-digit barcode
ZALANDO_ID Zalando marketplace ID
FASHIONCLOUD_ID FashionCloud ID

Multi-entry (several per product, with an explicit numeric suffix you assign):

Code ID Example
ALTERNATIVE_SUPPLIERS_ID<N> ALTERNATIVE_SUPPLIERS_ID1, ALTERNATIVE_SUPPLIERS_ID2, …
OTHER_ID<N> OTHER_ID1, OTHER_ID2, …

Rules:


Error Reference

Status / code Where Meaning
401 token_or_api_key_invalid All endpoints Missing or invalid companyapikey
404 parent_not_found POST /styles parentId doesn't exist in your catalog
400 invalid_parent_level POST /styles parentId is a SKU — SKUs cannot have children
400 missing_style_type_id POST /styles Creating a style without styleTypeId
400 missing_is_component POST /styles Creating a style without isComponent
400 invalid_code_ids Create & update Unknown code ID (check spelling and multi-entry suffix)
400 codes_not_unique Create & update A code value is already used by another product
404 not_found GET/PUT /styles/:id Product ID doesn't exist
404 not_found Archive / unarchive None of the provided IDs were found
400 mixed_levels Archive / unarchive IDs span multiple product levels

Field limits: name ≤ 255 characters, description ≤ 4000, gender ≤ 50, color / dimension ≤ 255.


Gotchas Checklist

  1. PUT replaces codes wholesale. Always send the complete desired codes array — sending only the new code deletes the others.
  2. null ≠ omitted in updates. Omitting a field keeps its value; sending null clears it — and on variants/SKUs that means re-inherit from the parent.
  3. Inheritance is live. A variant that never overrode description reflects style edits immediately. Check overriddenFlag on variant/SKU responses to see what is overridden.
  4. Archive always cascades; unarchive is optional. shouldUnarchiveChildren only affects /styles/unarchive.
  5. Boolean query filters accept only true or false (isComponent, isStoryActive, isArchived, createdForMe) — any other value is rejected with a 400. Omit the parameter to skip the filter.
  6. styleTypeId and isComponent are required for styles only — and isComponent cannot be changed later.
  7. Multi-entry code IDs need a numeric suffix (OTHER_ID1, not OTHER_ID).
  8. weight is all-or-nothingvalue without unit (or vice versa) is stored as null.
  9. include=images works on variant/SKU lists only — style lists silently ignore it. Use featureImageUrl (present on every list result) or GET /styles/{id}, which returns the full images array at every level without any include.
  10. List limit is capped at 100; archive/unarchive bodies at 100 IDs.
  11. Use updatedAfterUnixMs for incremental syncs instead of re-pulling the whole catalog.