DPP / Label Provider Integration Guide

Step-by-step walkthrough for Digital Product Passport (DPP) providers and label providers who want to retrieve product data, QR codes, supply chain structure, and certificate information from the Retraced Public API.

Is this guide for you?

This guide is for partners who need to work with Retraced product data to power physical or digital label experiences, including:

If you need Life Cycle Assessment (LCA) data such as energy sources and per-supplier ESG data points, see the LCA Provider Guide instead.


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

A typical DPP / label provider workflow involves the following steps:

  1. Fetch the product — retrieve the style, variant, or SKU you need
  2. Get the QR code — read qrCodeUrl (the printable QR image) and check the tracing story is active
  3. Fetch supply chain data — if building your own DPP experience, retrieve facility-level tracing data
  4. Fetch certificates — retrieve supplier and product certificates
  5. Fetch material composition — fibre breakdown and countries of origin (mandatory DPP content under ESPR)
  6. Assemble and cache — compose your DPP page or label payload

The example below uses: 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

The /styles endpoint is unified: it returns styles, variants, and SKUs depending on the level query parameter and the parent filter you provide. Choose the level that matches your use case:

Level When to use
style High-level product data — useful when your DPP covers a whole product line
variant Colour / size grouping — useful when labels are per colour-way
sku Individual physical item — recommended for label providers who need exact barcode or EAN matching

1a. List all styles

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",
      "isStoryActive": true,
      "isArchived": false,
      "qrCodeUrl": "https://cdn.retraced.com/qr_01KMZMMN2A4B6C8D0E2F4G6H8J.svg",
      "weight": null,
      "createdAt": "2026-03-30T15:08:48.904Z",
      "updatedAt": "2026-03-30T15:08:49.545Z"
    }
  ]
}
STYLE_ID="style_01KMZMMMW8JA3S1RVX1PS8E19S"

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" }],
      "isStoryActive": true,
      "isArchived": false,
      "qrCodeUrl": "https://cdn.retraced.com/qr_01KN6YZP3K5M7N9P1Q3R5S7T9V.svg",
      "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" }],
      "isStoryActive": true,
      "isArchived": false,
      "qrCodeUrl": "https://cdn.retraced.com/qr_01KN6Z1JC92R4T6V8W0X2Y4Z6A.svg",
      "weight": null,
      "isWeightAvailable": false,
      "createdAt": "2026-04-02T11:25:19.000Z",
      "updatedAt": "2026-04-02T11:25:52.000Z"
    }
  ]
}

Product images: on list queries, include=images returns photos at variant and SKU level only — style lists ignore it, but every list result carries featureImageUrl (the product's first image). Fetching a single product with GET /styles/{id} returns the full images array at every level, no include needed. Each image entry carries a CDN URL you can embed in your DPP page.


Step 2 — Get the QR code

Every style, variant, and SKU exposes a qrCodeUrl field:

"qrCodeUrl": "https://cdn.retraced.com/qr_01KN6Z1JC92R4T6V8W0X2Y4Z6A.svg"

qrCodeUrl is the printable QR code image — a publicly accessible SVG hosted on Retraced's CDN. It is the ready-to-print artwork, not the page the consumer lands on.

The QR code itself encodes the Retraced forwarding URL (on the rtrcd.com short domain). When a consumer scans the printed label, that URL redirects them to the consumer-facing DPP page for the product. If you need the encoded destination as a string (e.g. to write it to an NFC tag instead of printing the QR), decode the SVG's QR content with any QR library — do not attempt to construct the destination URL from the image filename, as the URL format is managed by Retraced and can differ per environment and product generation.

The QR is stable once generated. Retraced generates a product's QR code once and does not regenerate it, so qrCodeUrl (and the destination the QR encodes) stays the same over the product's life — it does not change when the brand later edits supply chain or material data. This makes a fetch-once-and-cache approach safe, and it means you can print the QR before the tracing story is switched on: the printed label keeps working when isStoryActive flips to true later.

How to use it:

Scenario What to do
Printing physical labels Place the SVG from qrCodeUrl directly into your label artwork
EPC / NFC tags Decode the QR image and write the encoded URL as the NDEF record URI
Digital labels or hang-tags Embed the SVG and/or link to the decoded destination
Building your own DPP experience Ignore the Retraced QR entirely and generate your own — see Step 3

Note: qrCodeUrl may be null if no QR code has been generated for the product yet. Also check isStoryActive: true before printing — the QR image can exist while the brand's tracing story is still switched off, in which case the destination shows no meaningful data.

Filter to DPP-ready products

isStoryActive signals that the brand has attached tracing data and activated the consumer-facing story. It is filterable server-side:

curl "$BASE_URL/styles?level=sku&variantId=$VARIANT_ID&isStoryActive=true&page=1&limit=100" \
  -H "companyapikey: $API_KEY"

Boolean filters accept true or false. Pass isStoryActive=true or isStoryActive=false to filter either way; any other value (e.g. 1, yes) is rejected with a 400. To get products regardless of story status, omit the parameter entirely.

After filtering, still verify qrCodeUrl is non-null on each item before sending it to print.

Inspect the tracing story (optional)

To see which tracing setup powers the consumer story, request include=tracingStory:

curl "$BASE_URL/styles/$STYLE_ID?include=tracingStory" \
  -H "companyapikey: $API_KEY"

The product response then carries a tracingStory object (or null if none is attached):

"tracingStory": {
  "id": "trcstory_01KMZMMN5B7C9D1E3F5G7H9J1K",
  "tracingType": "SUPPLY_CHAINS",
  "tracingId": "supplychain_01KMZMMN6C8D0E2F4G6H8J0K2M",
  "isActive": true,
  "tracingName": "Summer Dress 2024 — Supply Chain",
  "createdAt": "2026-03-30T15:08:49.000Z",
  "updatedAt": "2026-03-30T15:08:49.000Z"
}

tracingType tells you what kind of tracing backs the story: SUPPLY_CHAINS, SUPPLY_CHAIN_MAPPINGS, CASCADE_MAPPINGS, TRACING_PURCHASE_ORDERS, or TRACING_SALES_ORDERS. For most DPP integrations you don't need to branch on this — isStoryActive on the product is the go/no-go signal, and Step 4 returns the resolved supply chain regardless of type.


Step 3 — Build your own DPP experience (optional)

If you want to host your own consumer-facing DPP page rather than forwarding consumers to the Retraced-hosted destination, you can assemble the full product experience yourself using:

In this case you generate your own QR code pointing at your DPP destination, and use the Retraced API only as the data source.


Step 4 — Fetch supply chain data

Retrieve the full supplier network for a product. The supply chain endpoint takes a product ID (style, variant, or SKU) and returns all mapped facilities grouped by production process and tier.

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. You do not need to retry with the parent ID.

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:

Collect the companyId values — you will need them to match certificates in Step 5:

COMPANY_IDS=("G3JITT4AM9L" "PZ86G3I3SXB")

Step 5 — Fetch certificates

Certificates in Retraced are held at the company level — each certificate belongs to the supplier company that holds it. To find certificates relevant to a product, list the certificates visible to your API key and match them to the supplier companyId values you collected from the supply chain response in Step 4.

Certificate types

Type Description
COMPANY Company-level certification (e.g. Fair Trade, SA8000)
SCOPE Scope certificate (e.g. GOTS Scope Certificate)
MEMBERSHIP Industry body or standard membership
PRODUCT Product-level certificate attached to a specific product
TRANSACTION Transaction certificate for a specific order or batch

For DPP use cases the most relevant types are COMPANY, SCOPE, and PRODUCT. Note that TRANSACTION certificates are access-restricted: fetching one by ID returns 403 forbidden unless your company created it or owns it.

Certificate statuses

status reflects the review state of the certificate: DRAFT, REQUESTED, APPROVED, REJECTED, ACKNOWLEDGED, or NOT_VERIFIABLE. For consumer-facing DPP display, use APPROVED or ACKNOWLEDGED only. DRAFT certificates are visible only to their creator.

5a. List certificates

curl "$BASE_URL/certificates?page=1&limit=100&status=APPROVED" \
  -H "companyapikey: $API_KEY"

Supported query parameters:

Param Notes
page 1-based page number. Default 1.
limit Page size. Default 20, max 500.
sort Sort column: name, companyName, status, updatedAt, expirationDate, type, documentNumber, licenseNumber, issueDate, issuedBy.
order asc or desc (default desc).
status One of the statuses above. Filter server-side to avoid paging through non-public records.
type One of the certificate types above.
companyId Exact match on the supplier company that holds the certificate. Most useful in DPP flows (one call per supplier collected in Step 4).
standardId Exact match on the standard id.
filter URL-encoded JSON blob for fine-grained filtering. Supported keys: name, companyName, status, type, explanation, issuedBy, documentNumber, licenseNumber, facilityProcesses, standardIds, companyId. Example: filter=%7B%22companyId%22%3A%22PZ86G3I3SXB%22%7D.

Response (200):

{
  "metadata": {
    "success": true,
    "pagination": { "page": 1, "limit": 100, "total": 2, "totalPages": 1 }
  },
  "data": [
    {
      "id": "cert_01KP2Q4R6S8T0V2W4X6Y8Z0A2B",
      "name": "GOTS Scope Certificate — Developers' CMT & Fabric",
      "type": "SCOPE",
      "companyId": "G3JITT4AM9L",
      "companyName": "Developers' CMT & Fabric",
      "companyOfficialName": "Developers' CMT & Fabric ApS",
      "documentNumber": "CU1234567GOTS",
      "licenseNumber": null,
      "issueDate": "2025-01-15T00:00:00.000Z",
      "expirationDate": "2026-01-14T23:59:59.000Z",
      "issuedBy": "CCPB S.r.l.",
      "explanation": "GOTS v6.0 scope certificate covering cut-make-trim processes",
      "status": "APPROVED",
      "fileUrl": "documents/G3JITT4AM9L/cert_01KP2Q4R6S8T0V2W4X6Y8Z0A2B.pdf",
      "fileContentType": "application/pdf"
    },
    {
      "id": "cert_01KP2Q5S7T9V1W3X5Y7Z9A1B3C",
      "name": "Fair Trade Certified — Creative Sourcing Co. Ltd.",
      "type": "COMPANY",
      "companyId": "PZ86G3I3SXB",
      "companyName": "Creative Sourcing Co. Ltd.",
      "companyOfficialName": "Creative Sourcing Co. Ltd.",
      "documentNumber": null,
      "licenseNumber": "FT-CN-1122",
      "issueDate": "2024-06-01T00:00:00.000Z",
      "expirationDate": "2026-05-31T23:59:59.000Z",
      "issuedBy": "Fairtrade International",
      "explanation": null,
      "status": "APPROVED",
      "fileUrl": null,
      "fileContentType": "application/pdf"
    }
  ]
}

fileUrl in list responses is not downloadable. In the list, fileUrl is the raw storage object name (or null when no file is attached) — the underlying bucket is private, so this value cannot be fetched directly. Treat it as a has-a-document indicator. To actually download the certificate document, fetch the certificate by ID (below), which returns a signed URL.

5b. Match certificates to supply chain suppliers

The companyId query param filters by a single supplier. For a full supply chain, make one call per supplier and merge the results:

# COMPANY_IDS collected from Step 4
COMPANY_IDS=("G3JITT4AM9L" "PZ86G3I3SXB")

for CID in "${COMPANY_IDS[@]}"; do
  curl -s -G "$BASE_URL/certificates" \
    --data-urlencode "companyId=$CID" \
    --data "status=APPROVED" \
    --data "limit=500" \
    -H "companyapikey: $API_KEY"
done

Alternatively, fetch approved certificates in one call and filter client-side — useful when the supplier set is large or you want to cache a single response:

ALL_CERTS=$(curl -s "$BASE_URL/certificates?page=1&limit=500&status=APPROVED" \
  -H "companyapikey: $API_KEY")

echo "$ALL_CERTS" | python3 -c "
import sys, json
data = json.load(sys.stdin)
company_ids = {'G3JITT4AM9L', 'PZ86G3I3SXB'}
relevant = [c for c in data['data'] if c['companyId'] in company_ids]
print(json.dumps(relevant, indent=2))
"

5c. Get a single certificate (with a downloadable file URL)

curl "$BASE_URL/certificates/$CERT_ID" \
  -H "companyapikey: $API_KEY"

Returns the same certificate shape, but here fileUrl is a signed download URL valid for 1 hour. Fetch the document promptly after the call — for DPP pages, request a fresh signed URL on demand rather than caching fileUrl.

Error cases:

Status Code Meaning
404 certificate_not_found Certificate doesn't exist or isn't visible to your API key (includes other companies' DRAFT certificates)
403 forbidden TRANSACTION certificate you neither created nor own

Pagination

The certificates endpoint uses 1-based pagination with page (starting at 1) and limit. If metadata.pagination.totalPages is greater than 1, fetch subsequent pages:

# Page 1: records 1–100
curl "$BASE_URL/certificates?page=1&limit=100" -H "companyapikey: $API_KEY"
# Page 2: records 101–200
curl "$BASE_URL/certificates?page=2&limit=100" -H "companyapikey: $API_KEY"

Step 6 — Fetch material composition

Fibre composition and countries of origin are mandatory DPP content for textiles under ESPR. The Public API exposes them as material headers (a named composition, e.g. "Shell") and material lines (the individual fibres, organized as a tree):

# Headers for the product (styleId accepts style, variant, or SKU IDs — inherits from parent)
curl "$BASE_URL/material-headers?styleId=$STYLE_ID&page=1&limit=50" \
  -H "companyapikey: $API_KEY"

# Lines for a header, with raw material catalog data and country details resolved
curl "$BASE_URL/material-lines?materialHeaderId=$MATERIAL_HEADER_ID&include=rawMaterials,countriesOfOrigin&page=1&limit=50" \
  -H "companyapikey: $API_KEY"

Lines carry percentage (relative to siblings at the same tree level), countriesOfOrigin (ISO alpha-2 codes), and rawMaterial details including isRecycled / isSynthetic flags. The full model — tree reconstruction, inheritance, percentages — is covered with response examples in the Materials Guide and the LCA Provider Guide (Step 4).


  1. Fetch products — use level=sku with the EAN/barcode to find the exact physical item, or level=style for a full product range scan; add isStoryActive=true to fetch only DPP-ready products
  2. Read qrCodeUrl — if your platform prints the Retraced QR code, place the SVG into your label artwork; decode it if you need the destination URL for NFC
  3. Verify before printingisStoryActive must be true and qrCodeUrl non-null; a missing or inactive story means a dead consumer experience
  4. Fetch supply chain — call GET /api/v2/supply-chains/{id} with the product ID (any level) to get facility-level tracing data for your DPP page
  5. Collect supplier company IDs — extract companyId from all supply chain nodes
  6. Fetch and match certificates — call GET /api/v2/certificates?status=APPROVED&companyId={supplierId} once per supplier, or fetch all approved certificates and filter client-side; fetch by ID when you need the actual document
  7. Fetch material composition — headers and lines for the fibre breakdown and countries of origin
  8. Compose your DPP payload — combine product metadata, supply chain nodes, certificates, and materials into your DPP data model
  9. Cache appropriately — product and supply chain data are stable; refresh on a daily schedule or when the brand notifies you of updates (updatedAfterUnixMs on /styles supports incremental pulls). Never cache signed certificate fileUrls — they expire after 1 hour.

Full collection script

#!/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 1: Fetch style ==="
STYLE=$(api_get "/styles/$STYLE_ID")
echo "$STYLE" | python3 -m json.tool
QR_URL=$(echo "$STYLE" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['data'].get('qrCodeUrl') or '')")
STORY_ACTIVE=$(echo "$STYLE" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['data'].get('isStoryActive'))")
echo "QR code image: $QR_URL (story active: $STORY_ACTIVE)"

echo "=== Step 4: Supply chain ==="
SUPPLY_CHAIN=$(api_get "/supply-chains/$STYLE_ID")
echo "$SUPPLY_CHAIN" | python3 -m json.tool

COMPANY_IDS=$(echo "$SUPPLY_CHAIN" | python3 -c "
import sys, json
sc = json.load(sys.stdin)['data']
ids = sorted({n['companyId'] for g in sc['nodesByProductionProcesses'] for n in g['nodes'] if n['companyId']})
print('\n'.join(ids))
")
echo "Supplier company IDs: $COMPANY_IDS"

echo "=== Step 5: Certificates ==="
ALL_CERTS=$(api_get "/certificates?page=1&limit=500&status=APPROVED")

echo "$ALL_CERTS" | python3 -c "
import sys, json
data = json.load(sys.stdin)
company_ids = set('''$COMPANY_IDS'''.split())
relevant = [c for c in data['data'] if c['companyId'] in company_ids]
print(json.dumps(relevant, indent=2))
"

echo "=== Step 6: Material composition ==="
HEADERS=$(api_get "/material-headers?styleId=$STYLE_ID&page=1&limit=50")
echo "$HEADERS" | python3 -m json.tool

HEADER_IDS=$(echo "$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
  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 Notes
List styles GET /api/v2/styles?level=style Add nameOrCode to search, isStoryActive=true for DPP-ready only
List variants GET /api/v2/styles?level=variant&styleId=
List SKUs GET /api/v2/styles?level=sku&variantId= Use EAN code to locate exact SKU
Get single product GET /api/v2/styles/{id} Returns qrCodeUrl and the full images array (any level, no include needed); include=tracingStory for story details
Supply chain GET /api/v2/supply-chains/{id} Accepts style, variant, or SKU ID; inherits from ancestors
List certificates GET /api/v2/certificates Top-level filters: status, type, companyId, standardId; fileUrl not downloadable here
Get certificate GET /api/v2/certificates/{id} fileUrl is a 1-hour signed download URL
Material headers GET /api/v2/material-headers?styleId= Accepts style, variant, or SKU ID
Material lines GET /api/v2/material-lines?materialHeaderId= include=rawMaterials,countriesOfOrigin

Known limitations and caveats