LCA Provider Integration Guide
Step-by-step walkthrough for Life Cycle Assessment (LCA) providers who need to pull product data, supply chain structure, company energy sources, and material composition from the Retraced Public API.
Setup
BASE_URL="https://publicapi.retraced.com/api/v2"
# API key provided by the brand you are integrating with
API_KEY="your-api-key"
All examples use curl. The API key is passed via the companyapikey header.
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 return metadata.success: false plus a machine-readable code field.
Overview
The typical LCA data collection workflow involves four steps:
- Fetch the product hierarchy - styles, variants, and SKUs
- Fetch the supply chain - which facilities produce the product and at which tier
- Fetch energy source data - ESG section 2.1.6.1 for each supplier company
- Fetch material composition - material headers and material lines for the product
The example below uses a single product: Summer Dress 2024 (style style_01KMZMMMW8JA3S1RVX1PS8E19S), one variant in Blue (variant_01KN6YZNVKNBN1TJ4MR9T7FFD0), and one SKU in size S (sku_01KN6Z1JB710ANAN120JH5BBB0).
Step 1 - Fetch the product hierarchy
Your own product IDs. If the brand set an
externalIdwhen creating a product, every product ID field in this API - path IDs,styleId,variantId,link.styleId,entity.styleId,component.styleId- also accepts that value. IDs in responses are always Retraced IDs, and each product returns itsexternalIdnext toid.An
externalIdis resolved inside the catalogue of the account whose API key you are using. If you are a partner working on a brand's products with your own key, use the Retraced IDs the brand gave you - the brand'sexternalIdvalues are not visible in your own namespace and will not resolve.
1a. List all styles
Retrieve the top-level product catalogue. Use nameOrCode to search for a specific product.
curl "$BASE_URL/styles?level=style&page=1&limit=50" \
-H "companyapikey: $API_KEY"
Response (200):
{
"metadata": {
"success": true,
"pagination": { "page": 1, "limit": 50, "total": 7, "totalPages": 1 }
},
"data": [
{
"id": "style_01KMZMMMW8JA3S1RVX1PS8E19S",
"forCompanyId": "G3JITT4AM9L",
"name": "Summer Dress 2024",
"codes": [{ "codeId": "ERP_ID", "codeValue": "DRS-2024-001" }],
"description": "Lightweight summer dress in organic cotton",
"gender": "female",
"styleLevelId": "APPAREL_END_CONSUMER_FINISHED_GOODS",
"styleTypeId": "Y0RL9LGNUWOL9RXB07",
"brands": [],
"departments": [],
"seasons": [],
"suppliers": [],
"mainSupplier": null,
"isComponent": false,
"isArchived": false,
"weight": null,
"createdAt": "2026-03-30T15:08:48.904Z",
"updatedAt": "2026-03-30T15:08:49.545Z"
}
]
}
STYLE_ID="style_01KMZMMMW8JA3S1RVX1PS8E19S"
Weight for functional units:
weightis{ "value": number, "unit": "GRAM" | "KILOGRAM" | ... }ornullwhen the brand hasn't entered it. Variant and SKU responses additionally exposeisWeightAvailable, and inherit the parent's weight when their own is not set - if you need a mass for your functional unit and the SKU returnsnull, check the parent style.
1b. List variants for the style
curl "$BASE_URL/styles?level=variant&styleId=$STYLE_ID&page=1&limit=50" \
-H "companyapikey: $API_KEY"
Response (200):
{
"metadata": {
"success": true,
"pagination": { "page": 1, "limit": 50, "total": 1, "totalPages": 1 }
},
"data": [
{
"id": "variant_01KN6YZNVKNBN1TJ4MR9T7FFD0",
"forCompanyId": "G3JITT4AM9L",
"name": "Summer Dress 2024 - Blue",
"codes": [{ "codeId": "ERP_ID", "codeValue": "DRS-2024-001-BLU" }],
"description": "Lightweight summer dress in organic cotton",
"gender": "female",
"styleLevelId": "APPAREL_END_CONSUMER_FINISHED_GOODS",
"styleTypeId": "Y0RL9LGNUWOL9RXB07",
"suppliers": [],
"mainSupplier": null,
"isComponent": false,
"isArchived": false,
"weight": null,
"isWeightAvailable": false,
"createdAt": "2026-04-02T11:24:17.139Z",
"updatedAt": "2026-04-02T11:24:39.088Z"
}
]
}
VARIANT_ID="variant_01KN6YZNVKNBN1TJ4MR9T7FFD0"
1c. List SKUs for the variant
curl "$BASE_URL/styles?level=sku&variantId=$VARIANT_ID&page=1&limit=50" \
-H "companyapikey: $API_KEY"
Response (200):
{
"metadata": {
"success": true,
"pagination": { "page": 1, "limit": 50, "total": 7, "totalPages": 1 }
},
"data": [
{
"id": "sku_01KN6Z1JB710ANAN120JH5BBB0",
"forCompanyId": "G3JITT4AM9L",
"name": "Summer Dress 2024 - Blue - S",
"codes": [{ "codeId": "EAN_GTIN_13", "codeValue": "4006381333931" }],
"description": "Lightweight summer dress in organic cotton",
"gender": "female",
"styleLevelId": "APPAREL_END_CONSUMER_FINISHED_GOODS",
"styleTypeId": "Y0RL9LGNUWOL9RXB07",
"suppliers": [],
"mainSupplier": null,
"isComponent": false,
"isArchived": false,
"weight": null,
"isWeightAvailable": false,
"createdAt": "2026-04-02T11:25:19.079Z",
"updatedAt": "2026-04-02T11:25:52.639Z"
}
]
}
Note: Supply chain and material data can be attached at the style, variant, or SKU level - and child products inherit from their parent when they have nothing of their own (see the notes in Steps 2 and 4). In this example we continue at the style level using
STYLE_ID; substituteVARIANT_IDorSKU_IDto work at a finer granularity.
Step 2 - Fetch the supply chain
Retrieve the supplier network for the product. Each node in the response represents a facility, grouped by production process and tier.
The endpoint accepts a style, variant, or SKU ID. If the product you query has no supply chain of its own, the platform automatically falls back to the closest ancestor's active supply chain - so querying a SKU returns the style's chain when the brand mapped at style level.
curl "$BASE_URL/supply-chains/$STYLE_ID" \
-H "companyapikey: $API_KEY"
Response (200):
{
"metadata": { "success": true },
"data": {
"productId": "style_01KMZMMMW8JA3S1RVX1PS8E19S",
"productName": "Summer Dress 2024",
"nodesByProductionProcesses": [
{
"productionProcessId": "MANUFACTURING",
"productionProcessName": "Manufacturing",
"tier": "T2",
"nodes": [
{
"tier": "T2",
"companyId": "G3JITT4AM9L",
"companyName": "Developers' CMT & Fabric",
"country": "DK",
"coordinates": { "latitude": 55.681986, "longitude": 12.600743 },
"address": {
"line1": "Ekvipagemestervej 10",
"line2": null,
"city": "København",
"zipCode": "1438",
"country": "DK"
},
"facilityProcesses": ["Cut make trim (CMT)"],
"productionProcess": "Manufacturing"
}
]
},
{
"productionProcessId": "SOURCING_AND_IMPORT",
"productionProcessName": "Sourcing and import",
"tier": "T1",
"nodes": [
{
"tier": "T1",
"companyId": "PZ86G3I3SXB",
"companyName": "Creative Sourcing Co. Ltd.",
"country": "CN",
"coordinates": { "latitude": 31.322372, "longitude": 120.722366 },
"address": {
"line1": "Jiangsu",
"line2": null,
"city": null,
"zipCode": null,
"country": "CN"
},
"facilityProcesses": ["Sourcing agency"],
"productionProcess": "Sourcing and import"
}
]
}
]
}
}
Reading the response:
tier-T1is the tier closest to the finished product (e.g. final assembly / sourcing), with higher numbers further upstream toward raw materials.coordinatesandaddressarenullwhen the brand hasn't geo-located the facility or hasn't entered a full address.facilityProcesses- the facility-level process display names, e.g."Cut make trim (CMT)"(empty array if none recorded). These are labels for display, not the IDs used when writing orders - the matching IDs (CUT_MAKE_TRIM, …) come fromGET /api/v2/facility-processes.- An empty
nodesByProductionProcessesarray is a valid 200 response - it means the product (and its ancestors) have no active mapped supply chain. A404 not_found("Style not found") means the product ID itself doesn't exist.
Collect the companyId values for the next step:
COMPANY_IDS=("G3JITT4AM9L" "PZ86G3I3SXB")
Step 3 - Fetch energy source data (ESG section 2.1.6.1)
For each supplier company from Step 2, retrieve their declared energy sources using the templateId=2.1.6.1 filter. This returns only data points belonging to the Energy sources impact area.
for COMPANY_ID in "${COMPANY_IDS[@]}"; do
echo "=== ESG Energy Sources: $COMPANY_ID ==="
curl "$BASE_URL/companies/$COMPANY_ID/esg?templateId=2.1.6.1" \
-H "companyapikey: $API_KEY"
echo
done
Example response (200):
{
"metadata": { "success": true },
"data": {
"companyId": "G3JITT4AM9L",
"environmental": {
"sectionId": "2.1",
"name": "Environmental",
"totalDataPoints": 4,
"answeredDataPoints": 3,
"dataPoints": [
{
"retracedId": "energy_source_grid",
"name": "Share of grid electricity",
"description": "Percentage of total energy consumption sourced from the national grid",
"impactArea": "Energy sources",
"impactAreaId": "2.1.6.1",
"templateId": "2.1.6.1",
"value": 62,
"unit": "%",
"year": 2023
},
{
"retracedId": "energy_source_solar",
"name": "Share of on-site solar energy",
"description": "Percentage of total energy consumption generated from on-site solar panels",
"impactArea": "Energy sources",
"impactAreaId": "2.1.6.1",
"templateId": "2.1.6.1",
"value": 28,
"unit": "%",
"year": 2023
},
{
"retracedId": "energy_source_renewable_cert",
"name": "Share of renewable energy certificates (RECs)",
"description": "Percentage of electricity consumption covered by renewable energy certificates",
"impactArea": "Energy sources",
"impactAreaId": "2.1.6.1",
"templateId": "2.1.6.1",
"value": 10,
"unit": "%",
"year": 2023
},
{
"retracedId": "energy_source_fossil",
"name": "Share of fossil fuel energy",
"description": "Percentage of total energy consumption sourced from fossil fuels",
"impactArea": "Energy sources",
"impactAreaId": "2.1.6.1",
"templateId": "2.1.6.1",
"value": null,
"unit": "%",
"year": null
}
]
},
"social": {
"sectionId": "2.2",
"name": "Social",
"totalDataPoints": 0,
"answeredDataPoints": 0,
"dataPoints": []
},
"governance": {
"sectionId": "2.3",
"name": "Governance",
"totalDataPoints": 0,
"answeredDataPoints": 0,
"dataPoints": []
}
}
}
How to read it:
- The response always contains three sections -
environmental(2.1),social(2.2),governance(2.3). WithtemplateId=2.1.6.1, onlyenvironmentalcarries data points. - Data points with
value: nullhave not been filled in by the supplier.valueis polymorphic - percentages and quantities come as numbers, but other data points can hold strings or booleans. totalDataPoints/answeredDataPointsare counts after your filters are applied, not the size of the company's whole ESG profile.
ESG filter semantics
All filters combine with AND:
| Query param | Matching | Example |
|---|---|---|
templateId |
Exact match on the template/section ID | templateId=2.1.6.1 (Energy sources) |
retracedId |
Exact match on a specific data point | retracedId=energy_source_grid |
name |
Case-insensitive partial match on the data point name | name=solar |
impactArea |
Case-insensitive partial match on the impact area label | impactArea=energy |
When a company returns 404
GET /companies/:id/esg responds 404 company_not_found when the company has no ESG profile visible to you - either the ID is wrong, or the supplier hasn't shared a company profile with your network. There is no separate 403; missing access looks identical to a missing company. Handle this per supplier and continue with the rest (the collection script below does).
Step 4 - Fetch material composition
Material composition is structured in two layers: material headers (a named composition, e.g. "Shell Fabric") and material lines (the individual fibres/materials, organized as a tree). The full write model - including inheritance and copy-on-write - is covered in the Materials Guide; for LCA collection you only need to read.
4a. List material headers for the product
styleId accepts a style, variant, or SKU ID. Children automatically hold links to compositions created on their parent, so querying a SKU also surfaces the style's composition.
curl "$BASE_URL/material-headers?styleId=$STYLE_ID&page=1&limit=50" \
-H "companyapikey: $API_KEY"
Response (200):
{
"metadata": {
"success": true,
"pagination": { "page": 1, "limit": 50, "total": 1, "totalPages": 1 }
},
"data": [
{
"id": "mtrialhdr_01KTSC4N9GE3Q0V2J6M8R1WXYZ",
"name": "Shell",
"forCompanyId": "G3JITT4AM9L",
"description": "Outer body material composition",
"link": {
"styleId": "style_01KMZMMMW8JA3S1RVX1PS8E19S",
"isMainMaterial": true,
"isOwner": true
},
"createdAt": "2026-03-30T15:10:00.000Z",
"createdByUserId": "user-1",
"createdByCompanyId": "G3JITT4AM9L",
"updatedAt": "2026-03-30T15:10:00.000Z",
"updatedByUserId": "user-1",
"updatedByCompanyId": "G3JITT4AM9L"
}
]
}
A product can have several headers (shell, lining, trims). link.isMainMaterial marks the product's primary composition - usually the one you want for the LCA. link.isOwner tells you whether this product owns the composition or inherits it from its parent.
MATERIAL_HEADER_ID="mtrialhdr_01KTSC4N9GE3Q0V2J6M8R1WXYZ"
4b. List material lines for the header
Add include=rawMaterials,countriesOfOrigin to resolve the raw material catalog entries and country details in the same call.
curl "$BASE_URL/material-lines?materialHeaderId=$MATERIAL_HEADER_ID&include=rawMaterials,countriesOfOrigin&page=1&limit=50" \
-H "companyapikey: $API_KEY"
Response (200):
{
"metadata": {
"success": true,
"pagination": { "page": 1, "limit": 50, "total": 2, "totalPages": 1 }
},
"data": [
{
"id": "mtrialln_01KTSC5A2B3C4D5E6F7G8H9J0K",
"materialHeaderId": "mtrialhdr_01KTSC4N9GE3Q0V2J6M8R1WXYZ",
"rawMaterialId": "COTTON_BALE",
"percentage": 95,
"weights": 120,
"weightUnit": "GSM",
"parentId": null,
"hierarchy": 0,
"position": 100,
"countriesOfOrigin": ["IN"],
"createdAt": "2026-03-30T15:10:00.000Z",
"rawMaterial": {
"id": "COTTON_BALE",
"name": "Cotton bale",
"category": "NATURAL_MATERIAL",
"isRecycled": false,
"isSynthetic": false
},
"countries": [
{
"countryCode": "IN",
"countryName": "India",
"region": "Asia"
}
]
},
{
"id": "mtrialln_01KTSC5A2B3C4D5E6F7G8H9J1L",
"materialHeaderId": "mtrialhdr_01KTSC4N9GE3Q0V2J6M8R1WXYZ",
"rawMaterialId": "ELASTANE_FIBER",
"percentage": 5,
"weights": null,
"weightUnit": null,
"parentId": null,
"hierarchy": 0,
"position": 200,
"countriesOfOrigin": ["DE"],
"createdAt": "2026-03-30T15:10:01.000Z",
"rawMaterial": {
"id": "ELASTANE_FIBER",
"name": "Elastane fiber",
"category": "SYNTHETIC_MATERIAL",
"isRecycled": false,
"isSynthetic": true
},
"countries": [
{
"countryCode": "DE",
"countryName": "Germany",
"region": "Europe"
}
]
}
]
}
How to read the lines:
- Lines form a tree. Rebuild it client-side from
parentId(root lines haveparentId: null,hierarchy: 0); child lines describe the breakdown of their parent (e.g. a "Recycled cotton" line under a blended yarn). Order siblings byposition. percentageis relative to the parent level - sibling percentages under the same parent sum to 100.countriesOfOriginis an ISO 3166-1 alpha-2 code array (where the material was sourced).rawMaterialIdreferences Retraced's raw-material catalog; therawMaterialobject (viainclude=rawMaterials) carries the category andisRecycled/isSyntheticflags relevant for impact factors.
Full LCA Collection Script
Complete bash script that collects all four data sets for a given product ID.
#!/usr/bin/env bash
set -euo pipefail
BASE_URL="https://publicapi.retraced.com/api/v2"
API_KEY="your-api-key"
STYLE_ID="style_01KMZMMMW8JA3S1RVX1PS8E19S"
api_get() { curl -s "$BASE_URL$1" -H "companyapikey: $API_KEY"; }
echo "=== Step 1a: List styles ==="
api_get "/styles?level=style&page=1&limit=50" | python3 -m json.tool
echo "=== Step 1b: List variants ==="
api_get "/styles?level=variant&styleId=$STYLE_ID&page=1&limit=50" | python3 -m json.tool
echo "=== Step 1c: List SKUs (first variant - adjust as needed) ==="
VARIANT_ID=$(api_get "/styles?level=variant&styleId=$STYLE_ID&page=1&limit=1" \
| python3 -c "import sys,json; d=json.load(sys.stdin); print(d['data'][0]['id'])")
api_get "/styles?level=sku&variantId=$VARIANT_ID&page=1&limit=50" | python3 -m json.tool
echo "=== Step 2: Supply chain ==="
SUPPLY_CHAIN=$(api_get "/supply-chains/$STYLE_ID")
echo "$SUPPLY_CHAIN" | python3 -m json.tool
echo "$SUPPLY_CHAIN" > supply_chain.json
echo "=== Step 3: ESG energy sources per supplier ==="
COMPANY_IDS=$(echo "$SUPPLY_CHAIN" \
| python3 -c "
import sys, json
sc = json.load(sys.stdin)['data']
ids = {n['companyId'] for g in sc['nodesByProductionProcesses'] for n in g['nodes'] if n['companyId']}
print('\n'.join(ids))
")
for COMPANY_ID in $COMPANY_IDS; do
echo "--- $COMPANY_ID ---"
# Some suppliers have no shared ESG profile → 404 company_not_found; continue
api_get "/companies/$COMPANY_ID/esg?templateId=2.1.6.1" | python3 -m json.tool || true
done
echo "=== Step 4a: Material headers ==="
MATERIAL_HEADERS=$(api_get "/material-headers?styleId=$STYLE_ID&page=1&limit=50")
echo "$MATERIAL_HEADERS" | python3 -m json.tool
echo "=== Step 4b: Material lines ==="
HEADER_IDS=$(echo "$MATERIAL_HEADERS" \
| python3 -c "import sys, json; d=json.load(sys.stdin); print('\n'.join(h['id'] for h in d['data']))")
for HEADER_ID in $HEADER_IDS; do
echo "--- Header: $HEADER_ID ---"
api_get "/material-lines?materialHeaderId=$HEADER_ID&include=rawMaterials,countriesOfOrigin&page=1&limit=50" \
| python3 -m json.tool
done
echo "=== Done ==="
Quick Reference
| Data | Endpoint | Key parameter |
|---|---|---|
| List styles | GET /styles?level=style |
nameOrCode to search |
| List variants | GET /styles?level=variant |
styleId (parent style) |
| List SKUs | GET /styles?level=sku |
variantId (parent variant) |
| Supply chain | GET /supply-chains/:id |
accepts style, variant, or SKU ID |
| ESG energy sources | GET /companies/:companyId/esg?templateId=2.1.6.1 |
one call per supplier |
| Material headers | GET /material-headers?styleId= |
accepts style, variant, or SKU ID |
| Material lines | GET /material-lines?materialHeaderId= |
include=rawMaterials,countriesOfOrigin |
Gotchas Checklist
- Empty supply chain ≠ error.
nodesByProductionProcesses: []with a 200 means nothing is mapped (or the story is inactive);404 not_foundmeans the product ID doesn't exist. - Query any level. Supply chains and materials inherit upward - a SKU query falls back to the variant's/style's data automatically.
- ESG 404s are normal.
company_not_foundusually means the supplier hasn't shared a profile with your network. Skip and continue. - ESG counts are filtered counts.
totalDataPoints/answeredDataPointsdescribe the filtered result set, not the full profile. - Material percentages are per tree level. Siblings under the same parent sum to 100; don't sum across hierarchy levels.
- Weight inherits. A
nullSKU weight may still be resolvable from the parent style; checkisWeightAvailable. - Boolean query filters on
/stylesaccept onlytrueorfalse(isArchived,isComponent, …) - any other value is rejected with a400; omit the parameter to skip the filter. - List
limitis capped at 100 - page through large catalogues withpage, or useupdatedAfterUnixMsfor incremental pulls.
Related Reading
- Materials Guide - the full material composition model: trees, percentages, inheritance, copy-on-write
- BOM Guide - component-level Bills of Materials, if you need per-component breakdowns
- Products Guide - the Style → Variant → SKU hierarchy and field inheritance
- API Reference - interactive endpoint and schema documentation
- All integration guides