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:
- A variant created without
descriptionreturns its style's description, and keeps tracking it: update the style and the variant's reads change immediately. - Setting a value on a variant/SKU overrides the parent from then on.
- Setting the field back to
nullin aPUTre-enables inheritance — this is the only way to "un-override". - Variant and SKU responses include a read-only
overriddenFlagarray naming the fields that are currently overridden at that level.
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
trueorfalse. PassisComponent=false/isStoryActive=falseto filter for thefalsevalue, or omit the parameter to skip the filter. Any other value (e.g.1,yes) is rejected with a400.
Including related data
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=imagesonly 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 carriesfeatureImageUrl(the product's first image), andGET /styles/{id}returns the fullimagesarray at every level without anyinclude.
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:
codesis 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.nullre-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 }'
- Archive always cascades downward — archiving a style archives its variants and SKUs.
shouldUnarchiveChildrenhas no effect on archive. - Unarchive cascades by default; pass
shouldUnarchiveChildren: falseto restore only the listed products. - Archived products disappear from lists unless you pass
isArchived=true.
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:
- Unknown code IDs (including a multi-entry ID without its numeric suffix) fail with
400 invalid_code_ids. - Code values must be unique per code type across your catalog — duplicates fail with
400 codes_not_unique. PUTreplaces the full codes array (see Updating Products).
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
PUTreplacescodeswholesale. Always send the complete desired codes array — sending only the new code deletes the others.null≠ omitted in updates. Omitting a field keeps its value; sendingnullclears it — and on variants/SKUs that means re-inherit from the parent.- Inheritance is live. A variant that never overrode
descriptionreflects style edits immediately. CheckoverriddenFlagon variant/SKU responses to see what is overridden. - Archive always cascades; unarchive is optional.
shouldUnarchiveChildrenonly affects/styles/unarchive. - Boolean query filters accept only
trueorfalse(isComponent,isStoryActive,isArchived,createdForMe) — any other value is rejected with a400. Omit the parameter to skip the filter. styleTypeIdandisComponentare required for styles only — andisComponentcannot be changed later.- Multi-entry code IDs need a numeric suffix (
OTHER_ID1, notOTHER_ID). weightis all-or-nothing —valuewithoutunit(or vice versa) is stored asnull.include=imagesworks on variant/SKU lists only — style lists silently ignore it. UsefeatureImageUrl(present on every list result) orGET /styles/{id}, which returns the fullimagesarray at every level without anyinclude.- List
limitis capped at 100; archive/unarchive bodies at 100 IDs. - Use
updatedAfterUnixMsfor incremental syncs instead of re-pulling the whole catalog.
Related Reading
- BOM Guide — Bills of Materials: shared links, ownership, copy-on-write
- Materials Guide — material composition trees, percentages, countries of origin
- File Upload Guide — obtaining file IDs for
imagesFileIds - API Reference — interactive endpoint and schema documentation
- All integration guides