Skip to main content

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 a bpv1_3: prefix (BatteryPass v1.3 public-mandatory attributes). This prefix is not declared in the @context above, 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:

  1. Go to search.google.com/test/rich-results
  2. Enter the URL of your product page or paste the JSON-LD directly
  3. 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)​

TermTypeDescription
Product, DigitalProductPassport@typeThe two document types.
namePropertyProduct name.
imagePropertyArray of product image URLs.
descriptionPropertyProduct description.
skuPropertyProduct model / SKU.
brandProperty{ "@type": "Brand", "name": … }.
manufacturerPropertyOrganization node with name, address, globalLocationNumber (GLN), url, and contactPoint.
gtinPropertyGTIN of the product, when assigned.
identifierPropertyPropertyValue node used for typed identifiers (e.g. EORI on economic operators).
weightPropertyQuantitativeValue in kilograms (unitCode: "KGM"), under dpp:batteryInformation.

dpp: terms (Traceable)​

TermTypeDescription
dpp:dppIdPropertyTraceable DPP identifier.
dpp:did / dpp:issuerDidPropertyDecentralised identifier of the passport and of its issuer.
dpp:verificationStatusPropertyVerification status of the passport.
dpp:versionPropertyInteger version counter.
dpp:passportVersionPropertyVersion string ("N.0").
dpp:passportURN / dpp:passportURNIssuerPropertyBattery passport URN and the URN issuer (https://traceable.digital).
dpp:complianceStatusPropertyOverall compliance status.
dpp:lastVerifiedPropertyPublish/verification time of the served snapshot (ISO 8601).
dpp:dateTimeOfLatestUpdatePropertyDate-time of the latest DPP update (BatteryPass v1.3 attr #4); also nested under dpp:metadata.
dpp:batchId, dpp:productionDate, dpp:placeOfManufacturePropertyProduction identification.
dpp:certificatesPropertyArray of dpp:Certificate nodes (name, dpp:documentType, dpp:verificationStatus, dpp:issuer, dpp:validUntil).
dpp:carbonFootprintPropertyObject with dpp:totalKgCO2e, dpp:kgCO2ePerKwh, dpp:methodology, dpp:performanceClass, dpp:verificationBody.
dpp:batteryPerformancePropertyObject 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:batteryInformationPropertyObject with dpp:chemistry, dpp:batteryCategory, dpp:batteryStatus, dpp:passportURN, weight.
dpp:technicalSpecificationsPropertyArray of schema.org PropertyValue nodes.
dpp:materialCompositionPropertyArray of material entries, nested under espr:circularity.
dpp:metadataPropertyObject with dpp:issuedAt, dpp:validUntil, dpp:granularity, dpp:dateTimeOfLatestUpdate.
dpp:Certificate, dpp:Substance, dpp:BpvAttributeClassNode types used within the arrays above.

espr: terms (ESPR / EU)​

TermTypeDescription
espr:productCategoryPropertyHuman-readable battery category literal (e.g. "EV Battery", "LMT Battery").
espr:dppGranularityPropertyDPP granularity: "Item", "Batch", or "Model".
espr:circularityPropertyObject with espr:recycledContentPercentage, espr:reparabilityScore, espr:dismantlingInstructions, and dpp:materialComposition.
espr:substancesOfConcernPropertyArray of dpp:Substance nodes (name, dpp:casNumber, dpp:concentrationRange, dpp:locationInProduct).
espr:economicOperatorsPropertyArray of Organization nodes with espr:role and, where available, espr:eoriNumber.

gs1: terms​

TermTypeDescription
gs1:serialNumberPropertySerial number of the individual product.

bpv1_3: terms (not declared in @context)​

TermTypeDescription
bpv1_3:publicMandatoryAttributesPropertyArray 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:publicMandatoryAttributeCountPropertyCount of entries in the array above.