Skip to main content

DPP endpoints

Traceable exposes DPP data through two complementary systems, each serving a different purpose.

Two access methods

MethodURL patternResponseUse case
GS1 Digital Link resolver/01/{GTIN}[/21/{serial}][/10/{batch}]HTML (DPP viewer)QR codes on physical batteries
JSON data API/api/dpp/{slug}JSON / JSON-LDMachine-readable integration

The GS1 Digital Link format is what EU Battery Regulation 2023/1542 requires to be encoded in the data carrier (QR code) on the physical battery. The JSON API is for programmatic access to the underlying data.

Both methods resolve to the same underlying DPP. A battery without a GTIN assigned in Traceable will only have a slug-based URL — the GS1 resolver requires a GTIN.


GET /01/:gtin

GET /01/:gtin/21/:serial

GET /01/:gtin/10/:batch

Resolves a GTIN to the corresponding DPP and renders the battery passport viewer inline. The GS1 URI remains in the browser URL bar — there is no redirect to a slug-based URL.

This is the endpoint that QR codes on physical batteries should encode. It implements the GS1 Digital Link standard (ISO/IEC 18975).

Path structure

SegmentApplication IdentifierDescription
/01/{GTIN}AI 01GTIN (required). 8, 12, 13, or 14 digits with a valid GS1 check digit.
/21/{serial}AI 21Individual serial number (optional)
/10/{batch}AI 10Batch or lot number (optional)

Examples

https://app.traceable.digital/01/09506000134352
https://app.traceable.digital/01/09506000134352/21/ABC123
https://app.traceable.digital/01/09506000134352/21/ABC123/10/BATCH-2026-04

Validation

The GTIN is validated using the standard GS1 modulo-10 check digit algorithm before the database lookup. Requests with an invalid check digit return a 404 immediately — no database query is made.

Supported formats:

FormatDigitsExample
GTIN-8801234565
GTIN-1212012345678905
GTIN-13130123456789012
GTIN-141401234567890128

Response

  • 200 OK — HTML page rendering the DPP viewer for the resolved product. The DPP data is embedded in the page and in the HTML <head> as JSON-LD.
  • 404 Not Found — GTIN not found in the Traceable registry, GTIN check digit is invalid, or the matched product's DPP is not published.

This endpoint returns HTML, not JSON. To retrieve machine-readable DPP data for a product you know by GTIN, resolve the GTIN first to get the slug, then use the JSON API.

When generating the QR code URI for a battery product in Traceable, the platform uses this format when a GTIN is assigned:

https://app.traceable.digital/01/{GTIN}/21/{serialNumber}

If no GTIN has been assigned, the platform falls back to the slug-based URL:

https://app.traceable.digital/dpp/{slug}

The platform's QR code generator in the Operator Portal handles this automatically and displays a "GS1 Compliant" badge when the GTIN-based format is used.


JSON data API

GET /api/dpp/:slug

Retrieve the complete public DPP data for a battery product as JSON.

Path parameters

ParameterTypeRequiredDescription
slugstringYesThe URL-safe product identifier assigned at DPP creation. Example: swiftvolt-48v-100ah-ev-pack

Authentication

None. This endpoint returns public fields only and takes no credentials.

Restricted (PoLI-tier) fields are not available through this JSON API. There is no accessToken parameter and no restrictedFields in the response. Restricted fields are shown only on the rendered DPP web page, and only to a browser session that has completed the PoLI access flow.

Response — 200 OK

Returns the full DPP object for the requested product.

FieldTypeDescription
idstringInternal DPP record identifier
slugstringURL-safe product identifier. Immutable after publish.
productNamestringFull commercial product name
batteryCategorystringOne of: EV_BATTERY, LMT_BATTERY, INDUSTRIAL_BATTERY, SLI_BATTERY, PORTABLE_BATTERY
statusstringpublished — only published DPPs are returned by this endpoint
versionintegerVersion number, incremented each time the DPP is updated and republished
manufacturerobjectManufacturer name, country, registration number, and contact details
carbonFootprintobject | nullCarbon footprint data (present for EV and Industrial batteries; null for others)
recycledContentobjectRecycled content percentages for cobalt, lithium, nickel, lead
performanceobjectCapacity, energy density, cycle life, round-trip efficiency
hazardousSubstancesarrayList of hazardous substances above threshold, with CAS numbers and concentrations
supplyChainobjectSupply chain due diligence summary and responsible sourcing information
complianceobjectRegulatory compliance declarations and certification references
publishedAtstringISO 8601 timestamp when the DPP was first published
updatedAtstringISO 8601 timestamp of the most recent update
createdAtstringISO 8601 timestamp when the DPP record was created

Response — 404 Not Found

Returned when the slug does not match any product, or the product's DPP is not published.

{
"error": "DPP not found or not published",
"code": "PRODUCT_NOT_FOUND",
"details": {}
}

Response — 429 Too Many Requests

The wait time is carried by the Retry-After response header (seconds). There is no retryAfter field in the body.

{
"error": "Rate limit exceeded",
"code": "RATE_LIMITED"
}

Examples

curl

curl -s https://app.traceable.digital/api/dpp/swiftvolt-48v-100ah-ev-pack | jq .

JavaScript (Node.js / browser fetch)

async function fetchDpp(slug: string): Promise<DppObject> {
const response = await fetch(
`https://app.traceable.digital/api/dpp/${encodeURIComponent(slug)}`
);

if (response.status === 404) {
throw new Error(`DPP not found for slug: ${slug}`);
}

if (response.status === 429) {
const retryAfter = response.headers.get('Retry-After');
throw new Error(`Rate limited. Retry after ${retryAfter} seconds.`);
}

if (!response.ok) {
const data = await response.json().catch(() => ({}));
throw new Error(`Failed to fetch DPP: ${data.error ?? response.statusText}`);
}

return response.json();
}

Python (requests)

import requests

def fetch_dpp(slug: str) -> dict:
url = f"https://app.traceable.digital/api/dpp/{slug}"
response = requests.get(url, timeout=10)

if response.status_code == 404:
raise ValueError(f"DPP not found or not published for slug: {slug}")

if response.status_code == 429:
retry_after = response.headers.get("Retry-After", "60")
raise RuntimeError(f"Rate limited. Retry after {retry_after}s.")

response.raise_for_status()
return response.json()

GET /api/dpp/:slug/jsonld

Retrieve a DPP in JSON-LD format for semantic web interoperability.

Path parameters

ParameterTypeRequiredDescription
slugstringYesThe URL-safe product identifier

Authentication

None required.

Response — 200 OK

Returns a JSON-LD document representing the DPP. The Content-Type is application/ld+json.

The @id of the returned document is the canonical URI for the product:

  • If the product has a GTIN assigned: https://app.traceable.digital/01/{GTIN}/21/{serial} (GS1 Digital Link)
  • If no GTIN: https://app.traceable.digital/dpp/{slug}

The document uses:

  • @context: An array combining https://schema.org, the W3C Verifiable Credentials v1 context, and a Traceable term map that binds the dpp:, espr:, and gs1: prefixes
  • @type: ["Product", "DigitalProductPassport"] (unprefixed schema.org terms)

Response — 404 Not Found

{
"error": "DPP not found or not published",
"code": "PRODUCT_NOT_FOUND",
"details": {}
}

Example JSON-LD response (product with GTIN)

The @context binds three prefixes — dpp: (Traceable's DPP terms), espr: (EU ESPR terms), and gs1: (GS1 vocabulary). Standard product and organisation fields use unprefixed schema.org terms. Battery-specific values such as the carbon footprint and recycled content are emitted as flat summary objects, not deep lifecycle breakdowns. The example below is trimmed to the shape of the response — a live passport carries more fields.

{
"@context": [
"https://schema.org",
"https://www.w3.org/2018/credentials/v1",
{
"dpp": "https://schema.traceable.digital/dpp/v1/",
"espr": "https://data.europa.eu/espr/",
"gs1": "https://gs1.org/voc/"
}
],
"@type": ["Product", "DigitalProductPassport"],
"@id": "https://app.traceable.digital/01/09506000134352/21/SN-2026-00421",
"name": "SwiftVolt 48V 100Ah EV Pack",
"sku": "SV-48100-EV",
"gtin": "09506000134352",
"brand": { "@type": "Brand", "name": "SwiftVolt" },
"manufacturer": {
"@type": "Organization",
"name": "SwiftVolt Energy Systems GmbH",
"url": "https://swiftvolt.de"
},
"dpp:dppId": "dpp_swiftvolt_48v_100ah",
"dpp:version": 3,
"dpp:carbonFootprint": {
"dpp:totalKgCO2e": 294.7,
"dpp:kgCO2ePerKwh": 61.4,
"dpp:methodology": "PEFCR for batteries"
},
"espr:circularity": {
"espr:recycledContentPercentage": 16
}
}

For the full JSON-LD shape, every emitted field, and the vocabulary behind each prefix, see the JSON-LD format data model page and the Parse JSON-LD output integration example.


GET /api/dpp/:slug/v/:version

Retrieve a specific historical version of a published DPP, exactly as it was published at that version. This is intended for regulators and auditors who hold a copy of a passport and need to retrieve and compare a prior published state.

Path parameters

ParameterTypeRequiredDescription
slugstringYesThe URL-safe product identifier
versionintegerYesThe published version number to retrieve. Must be a positive integer (1 or greater)

Authentication

None required. The endpoint serves only publicly visible data, with restricted fields stripped from the historical snapshot the same way they are stripped from the live response.

Availability

Version history is retrievable only for products that are currently published. If a product is unpublished, withdrawn, or deprecated at the time of your request, this endpoint returns 404 even though historical snapshots exist. The endpoint serves the recorded byte content of the snapshot, with public-field gating applied at read time.

Response — 200 OK

Returns the gated DPP snapshot for the requested version as JSON. The response also includes integrity headers describing the snapshot:

HeaderDescription
X-DPP-VersionThe version number served
X-Snapshot-Sha256SHA-256 over the bytes served
X-Snapshot-BytesSize in bytes of the served snapshot
X-Snapshot-Published-AtISO 8601 timestamp when this version was published

Other responses

StatusCodeCondition
400INVALID_VERSIONversion is not a positive integer
404PRODUCT_NOT_FOUNDThe product is not currently published, or the slug is unknown
404VERSION_NOT_FOUNDThe product is published but no snapshot exists for that version
429RATE_LIMITEDRate limit exceeded; honour the Retry-After header
503SNAPSHOT_INTEGRITY_FAILUREThe snapshot index exists but the stored content could not be served intact. This fails closed rather than serve a partial record. Retry after the interval in Retry-After

Error bodies use the nested envelope:

{
"error": {
"code": "VERSION_NOT_FOUND",
"message": "No snapshot exists for the requested version"
}
}

Withdrawn products

When an operator withdraws a published battery passport (for example after a recall), the public surfaces respond differently depending on whether you are calling the machine API or loading the human-facing page. This distinction matters for integrators.

Machine API: HTTP 410 Gone

GET /api/dpp/:slug and GET /api/dpp/:slug/jsonld return 410 Gone for a withdrawn product, with a body describing the withdrawal:

{
"error": {
"code": "PRODUCT_WITHDRAWN",
"message": "This battery passport has been withdrawn by the manufacturer",
"reason": "Recall",
"withdrawnAt": "2026-05-10T09:00:00.000Z",
"operatorDetails": "Voluntary recall of affected production batch",
"successorUrl": "/dpp/replacement-product-slug",
"successorName": "Replacement product name",
"manufacturerContact": "compliance@example.com"
}
}

successorUrl, successorName, operatorDetails, and manufacturerContact may be null if not provided. The response carries Cache-Control: public, max-age=0, must-revalidate so a later reinstatement is not served stale.

Handle 410 explicitly. A QR scanner or integration that treats every non-200 as "not found" will hide the recall information. Branch on 410 and surface the withdrawal reason and any successor link to your user.

Human page: 200 with a withdrawal notice

The browser-facing pages (/dpp/{slug} and the GS1 resolver /01/{gtin}) return HTTP 200 and render a styled withdrawal notice (reason, date, manufacturer statement, successor link, contact), marked noindex so search engines drop it. They do not return 410, because a page route serves a human-readable notice rather than a machine error.

Status summary

Product state/api/dpp/:slug and /jsonld/dpp/:slug and /01/:gtin (browser)
Unknown slug404 PRODUCT_NOT_FOUND404
Draft or submitted (not yet published)404 PRODUCT_NOT_FOUND404
Published200 (DPP data)200 (DPP viewer)
Withdrawn410 PRODUCT_WITHDRAWN200 (withdrawal notice, noindex)
Deprecated404 PRODUCT_NOT_FOUND404