JSON-LD format
Why JSON-LD?β
The /api/dpp/{slug}/jsonld endpoint returns DPP data as JSON-LD (JSON for Linked Data). This format provides several advantages over plain JSON:
- Semantic interoperability β Every field has a globally unique, dereferenceable identifier. Two systems using the same vocabulary will interpret fields identically without out-of-band coordination.
- Graph database compatibility β JSON-LD maps directly to RDF triples and can be loaded into triple stores (Apache Jena, Amazon Neptune, GraphDB) for complex querying.
- schema.org tooling β Fields mapped to schema.org terms work with Google's Structured Data Testing Tool, Rich Results Test, and SEO enrichment tools.
- EU interoperability requirements β The EU's Ecodesign regulation and Digital Product Passport framework are moving towards semantic data exchange. JSON-LD positions integrations for future regulatory requirements.
The @contextβ
Traceable DPP JSON-LD responses use an ordered @context array: two standard remote contexts followed by an inline prefix map.
{
"@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/"
}
]
}
- schema.org: loaded as the base context. Standard product and organisation terms (
name,image,description,sku,brand,manufacturer,gtin, β¦) are emitted as bare, unprefixed keys and resolve against schema.org. - W3C Verifiable Credentials v1 (
https://www.w3.org/2018/credentials/v1): supports the credential / verification vocabulary. dpp:(https://schema.traceable.digital/dpp/v1/): Traceable's namespace for battery / DPP-specific terms with no schema.org equivalent.espr:(https://data.europa.eu/espr/): ESPR (EU 2024/1781) terms (circularity, economic operators, substances of concern, product category, granularity).gs1:(https://gs1.org/voc/): GS1 vocabulary for GTIN / identifier terms.
Note on the
bpv1_3:prefix. The response also emits keys under abpv1_3:prefix (BatteryPass v1.3 public-mandatory attributes). This prefix is not declared in the@contextabove, so a strict JSON-LD processor treats those keys as undefined terms. Consumers that need those attributes should read them as plain JSON keys.
@type valuesβ
DPP JSON-LD documents carry two @type values, both resolved against the schema.org base context (no prefix):
{
"@type": ["Product", "DigitalProductPassport"]
}
Product enables compatibility with general product tooling; DigitalProductPassport identifies the document as a Digital Product Passport.
The @idβ
The @id field is set to the product's decentralised identifier (DID) when one is present (dpp.did). When the product has no DID, @id is omitted entirely from the response (empty values are stripped before serialisation).
The DID is also surfaced separately as the dpp:did property, and the issuer DID as dpp:issuerDid.
Full example: EV battery DPP in JSON-LDβ
The following is representative of the structure emitted by the platform JSON-LD mapper. Individual keys are only present when the underlying value exists; empty values are stripped.
{
"@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": "did:web:app.traceable.digital:dpp:swiftvolt-48v-100ah-ev-pack",
"name": "SwiftVolt 48V 100Ah EV Pack",
"image": ["https://app.traceable.digital/.../product.jpg"],
"description": "48V 100Ah EV traction battery pack",
"sku": "SV-48100-EV",
"brand": { "@type": "Brand", "name": "SwiftVolt" },
"manufacturer": {
"@type": "Organization",
"name": "SwiftVolt Energy Systems GmbH",
"address": "BatteriestraΓe 14, 80333 Munich, DE",
"globalLocationNumber": "4012345000009",
"url": "https://swiftvolt.de",
"contactPoint": {
"@type": "ContactPoint",
"email": "compliance@swiftvolt.de"
}
},
"gtin": "09506000134352",
"gs1:serialNumber": "SN-2026-00421",
"dpp:batchId": "B-2026-014",
"dpp:productionDate": "2026-01-20",
"dpp:placeOfManufacture": "Munich, DE",
"dpp:dppId": "dpp_9f2cβ¦",
"dpp:version": 3,
"dpp:passportURNIssuer": "https://traceable.digital",
"dpp:lastVerified": "2026-03-18T08:45:11.000Z",
"dpp:passportVersion": "3.0",
"dpp:complianceStatus": "Compliant",
"dpp:certificates": [
{
"@type": "dpp:Certificate",
"name": "IEC 62619:2022",
"dpp:documentType": "Test report",
"dpp:verificationStatus": "Verified",
"dpp:issuer": "TΓV Rheinland",
"dpp:validUntil": "2029-01-15"
}
],
"espr:circularity": {
"espr:recycledContentPercentage": 16,
"espr:reparabilityScore": 7,
"espr:dismantlingInstructions": "https://swiftvolt.de/dismantling",
"dpp:materialComposition": [
{ "material": "Lithium", "percentage": 6 }
]
},
"dpp:carbonFootprint": {
"dpp:totalKgCO2e": 4948,
"dpp:kgCO2ePerKwh": 61.4,
"dpp:methodology": "ISO 14067:2018",
"dpp:performanceClass": "B",
"dpp:verificationBody": "TΓV Rheinland"
},
"dpp:batteryPerformance": {
"dpp:nominalVoltage": { "@type": "QuantitativeValue", "value": 48, "unitCode": "VLT" },
"dpp:ratedCapacity": { "@type": "QuantitativeValue", "value": 100, "unitCode": "AMP" },
"dpp:cycleLife": 2000,
"dpp:roundTripEfficiency": { "@type": "QuantitativeValue", "value": 96.2, "unitCode": "P1" },
"dpp:energyCapacity": { "@type": "QuantitativeValue", "value": 4.8, "unitCode": "KWH" },
"dpp:temperatureRange": { "@type": "QuantitativeValue", "minValue": -20, "maxValue": 60, "unitCode": "CEL" }
},
"dpp:batteryInformation": {
"dpp:chemistry": "NMC",
"dpp:batteryCategory": "EV - Traction",
"dpp:batteryStatus": "Active",
"dpp:passportURN": "urn:traceable:bat:9f2cβ¦",
"weight": { "@type": "QuantitativeValue", "value": 320, "unitCode": "KGM" }
},
"dpp:technicalSpecifications": [
{ "@type": "PropertyValue", "name": "form factor", "value": "prismatic" }
],
"espr:substancesOfConcern": [
{
"@type": "dpp:Substance",
"name": "Cobalt(II) oxide",
"dpp:casNumber": "1307-96-6",
"dpp:concentrationRange": "1β5%",
"dpp:locationInProduct": "Cathode"
}
],
"espr:economicOperators": [
{
"@type": "Organization",
"name": "SwiftVolt Energy Systems GmbH",
"espr:role": "Manufacturer",
"espr:eoriNumber": "DE1234567890123",
"identifier": { "@type": "PropertyValue", "propertyID": "EORI", "value": "DE1234567890123" }
}
],
"dpp:did": "did:web:app.traceable.digital:dpp:swiftvolt-48v-100ah-ev-pack",
"dpp:verificationStatus": "Verified",
"dpp:metadata": {
"dpp:issuedAt": "2026-02-01T10:14:22.000Z",
"dpp:granularity": "item_level",
"dpp:dateTimeOfLatestUpdate": "2026-03-18T08:45:11.000Z"
},
"espr:dppGranularity": "Item",
"dpp:dateTimeOfLatestUpdate": "2026-03-18T08:45:11.000Z",
"espr:productCategory": "EV Battery",
"bpv1_3:publicMandatoryAttributes": [
{
"@type": "dpp:BpvAttribute",
"bpv1_3:attrNum": 5,
"bpv1_3:name": "Battery category",
"bpv1_3:category": "General product & manufacturer information",
"bpv1_3:source": "formData",
"value": "EV - Traction"
}
],
"bpv1_3:publicMandatoryAttributeCount": 1
}
unitCode values are UN/CEFACT common codes (VLT volt, AMP ampere, KWH kilowatt-hour, KGM kilogram, CEL degree Celsius, P1 percent).
Parsing JSON-LD in JavaScriptβ
Install the jsonld npm package:
npm install jsonld
Compact the document to simplify property names using your own context:
import jsonld from 'jsonld';
async function compactDppJsonLd(slug) {
const response = await fetch(
`https://app.traceable.digital/api/dpp/${slug}/jsonld`
);
const document = await response.json();
// Compact to a simpler local context
const compacted = await jsonld.compact(document, {
'@context': {
name: 'https://schema.org/name',
manufacturer: 'https://schema.org/manufacturer',
dpp: 'https://schema.traceable.digital/dpp/v1/',
espr: 'https://data.europa.eu/espr/',
carbonFootprint: 'https://schema.traceable.digital/dpp/v1/carbonFootprint',
productCategory: 'https://data.europa.eu/espr/productCategory',
}
});
return compacted;
}
Read a nested node directly. The carbon footprint lives under the dpp:carbonFootprint key, so you can navigate to it without framing:
async function extractCarbonFootprint(slug) {
const response = await fetch(
`https://app.traceable.digital/api/dpp/${slug}/jsonld`
);
const doc = await response.json();
const cf = doc['dpp:carbonFootprint'];
if (!cf) return null;
return {
total: cf['dpp:totalKgCO2e'],
perKwh: cf['dpp:kgCO2ePerKwh'],
verificationBody: cf['dpp:verificationBody'],
};
}
Parsing JSON-LD in Pythonβ
Install pyld:
pip install PyLD requests
import requests
from pyld import jsonld
def fetch_and_compact_dpp(slug: str) -> dict:
response = requests.get(
f"https://app.traceable.digital/api/dpp/{slug}/jsonld",
timeout=10
)
response.raise_for_status()
document = response.json()
context = {
"@context": {
"name": "https://schema.org/name",
"productCategory": "https://data.europa.eu/espr/productCategory",
"carbonFootprint": "https://schema.traceable.digital/dpp/v1/carbonFootprint",
}
}
compacted = jsonld.compact(document, context)
return compacted
def extract_manufacturer_name(slug: str) -> str | None:
response = requests.get(
f"https://app.traceable.digital/api/dpp/{slug}/jsonld",
timeout=10
)
response.raise_for_status()
document = response.json()
# The manufacturer is a bare schema.org term (no prefix)
manufacturer = document.get("manufacturer")
if isinstance(manufacturer, dict):
return manufacturer.get("name")
return None
Plain JSON extraction (without a JSON-LD library)β
For systems that do not need full JSON-LD processing, you can treat the response as plain JSON and navigate to known fields directly:
async function extractKeyFields(slug) {
const response = await fetch(
`https://app.traceable.digital/api/dpp/${slug}/jsonld`
);
const doc = await response.json();
// Navigate to known paths directly (no library needed).
// schema.org terms are bare keys; Traceable / ESPR terms are prefixed.
return {
productName: doc['name'],
productCategory: doc['espr:productCategory'],
manufacturerName: doc['manufacturer']?.['name'],
carbonFootprintTotal: doc['dpp:carbonFootprint']?.['dpp:totalKgCO2e'],
lastVerified: doc['dpp:lastVerified'],
};
}
This approach is simple but brittle: it breaks if the context is updated to use prefixes differently. Use a JSON-LD library for production integrations.
Embedding in HTML head for search enginesβ
Paste the JSON-LD response directly into a <script> tag in your HTML <head>:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>SwiftVolt 48V 100Ah EV Pack β Battery Passport</title>
<!-- Traceable DPP JSON-LD β structured data for search engines -->
<script type="application/ld+json">
{
"@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"],
"name": "SwiftVolt 48V 100Ah EV Pack",
"manufacturer": {
"@type": "Organization",
"name": "SwiftVolt Energy Systems GmbH"
}
}
</script>
</head>
<body>
<!-- page content -->
</body>
</html>
The Traceable public DPP viewer at https://app.traceable.digital/dpp/{slug} automatically embeds the JSON-LD in its HTML <head>, so all published DPPs are already discoverable by search engines without any additional work.
Validating with Google's toolsβ
To validate your JSON-LD markup:
- Go to search.google.com/test/rich-results
- Enter the URL of your product page or paste the JSON-LD directly
- The tool will highlight any warnings or errors in the structured data
Note: Google primarily validates the bare schema.org terms (name, brand, manufacturer, image, β¦). The prefixed terms (dpp:, espr:, gs1:, bpv1_3:) will not be recognised by Google's tool but will not cause errors.
Vocabulary referenceβ
The emitted terms are grouped by prefix. schema.org terms are unprefixed; dpp: resolves to https://schema.traceable.digital/dpp/v1/, espr: to https://data.europa.eu/espr/, and gs1: to https://gs1.org/voc/. The bpv1_3: terms are emitted but not declared in the @context.
schema.org terms (unprefixed)β
| Term | Type | Description |
|---|---|---|
Product, DigitalProductPassport | @type | The two document types. |
name | Property | Product name. |
image | Property | Array of product image URLs. |
description | Property | Product description. |
sku | Property | Product model / SKU. |
brand | Property | { "@type": "Brand", "name": β¦ }. |
manufacturer | Property | Organization node with name, address, globalLocationNumber (GLN), url, and contactPoint. |
gtin | Property | GTIN of the product, when assigned. |
identifier | Property | PropertyValue node used for typed identifiers (e.g. EORI on economic operators). |
weight | Property | QuantitativeValue in kilograms (unitCode: "KGM"), under dpp:batteryInformation. |
dpp: terms (Traceable)β
| Term | Type | Description |
|---|---|---|
dpp:dppId | Property | Traceable DPP identifier. |
dpp:did / dpp:issuerDid | Property | Decentralised identifier of the passport and of its issuer. |
dpp:verificationStatus | Property | Verification status of the passport. |
dpp:version | Property | Integer version counter. |
dpp:passportVersion | Property | Version string ("N.0"). |
dpp:passportURN / dpp:passportURNIssuer | Property | Battery passport URN and the URN issuer (https://traceable.digital). |
dpp:complianceStatus | Property | Overall compliance status. |
dpp:lastVerified | Property | Publish/verification time of the served snapshot (ISO 8601). |
dpp:dateTimeOfLatestUpdate | Property | Date-time of the latest DPP update (BatteryPass v1.3 attr #4); also nested under dpp:metadata. |
dpp:batchId, dpp:productionDate, dpp:placeOfManufacture | Property | Production identification. |
dpp:certificates | Property | Array of dpp:Certificate nodes (name, dpp:documentType, dpp:verificationStatus, dpp:issuer, dpp:validUntil). |
dpp:carbonFootprint | Property | Object with dpp:totalKgCO2e, dpp:kgCO2ePerKwh, dpp:methodology, dpp:performanceClass, dpp:verificationBody. |
dpp:batteryPerformance | Property | Object with dpp:nominalVoltage, dpp:ratedCapacity, dpp:capacityFade, dpp:powerFade, dpp:cycleLife, dpp:roundTripEfficiency, dpp:energyCapacity, dpp:energyDensity, dpp:internalResistanceCell, dpp:temperatureRange (values are schema.org QuantitativeValue nodes with unitCode). |
dpp:batteryInformation | Property | Object with dpp:chemistry, dpp:batteryCategory, dpp:batteryStatus, dpp:passportURN, weight. |
dpp:technicalSpecifications | Property | Array of schema.org PropertyValue nodes. |
dpp:materialComposition | Property | Array of material entries, nested under espr:circularity. |
dpp:metadata | Property | Object with dpp:issuedAt, dpp:validUntil, dpp:granularity, dpp:dateTimeOfLatestUpdate. |
dpp:Certificate, dpp:Substance, dpp:BpvAttribute | Class | Node types used within the arrays above. |
espr: terms (ESPR / EU)β
| Term | Type | Description |
|---|---|---|
espr:productCategory | Property | Human-readable battery category literal (e.g. "EV Battery", "LMT Battery"). |
espr:dppGranularity | Property | DPP granularity: "Item", "Batch", or "Model". |
espr:circularity | Property | Object with espr:recycledContentPercentage, espr:reparabilityScore, espr:dismantlingInstructions, and dpp:materialComposition. |
espr:substancesOfConcern | Property | Array of dpp:Substance nodes (name, dpp:casNumber, dpp:concentrationRange, dpp:locationInProduct). |
espr:economicOperators | Property | Array of Organization nodes with espr:role and, where available, espr:eoriNumber. |
gs1: termsβ
| Term | Type | Description |
|---|---|---|
gs1:serialNumber | Property | Serial number of the individual product. |
bpv1_3: terms (not declared in @context)β
| Term | Type | Description |
|---|---|---|
bpv1_3:publicMandatoryAttributes | Property | Array of dpp:BpvAttribute nodes carrying the BatteryPass v1.3 public-mandatory longlist, each with bpv1_3:attrNum, bpv1_3:name, bpv1_3:category, bpv1_3:source, and value. |
bpv1_3:publicMandatoryAttributeCount | Property | Count of entries in the array above. |