JSON-LD for Digital Product Passports (and where EPCIS 2.0 fits)

A DPP is only useful to machines if it is machine-readable. This guide shows the real JSON-LD a qr3 passport exposes via the live resolver, how one URL serves both humans and machines through content negotiation, and where the complementary EPCIS 2.0 event standard fits.

by QR3 Redaktion

JSON-LD for Digital Product Passports (and where EPCIS 2.0 fits)

A Digital Product Passport is meant to be read by people and by machines: a recycler's intake system, a customs API, a marketplace crawler, a sustainability auditor's script. A human-only HTML page does not cut it. The passport has to be machine-readable — a structured payload a program can parse, link, and reason over without scraping.

This guide is for developers asking the obvious next question: once I have a DPP, how does a machine actually read it? The answer for qr3 is JSON-LD over the public resolver. We will show the real response, explain how the same URL serves humans and machines, and then place EPCIS 2.0 — the complementary GS1 event standard — in context.

What machine-readability means for a DPP

Machine-readable is more than "returns JSON". For a product passport it means three things:

  • Structured — fields a parser can address (gtin, name, …), not prose to scrape.
  • Typed and linked — terms anchored to shared vocabularies so Product means the same thing to everyone. That is the Linked Data in JSON-LD.
  • Stable to fetch — one durable URL per item that a script can GET for the product's whole life.

JSON-LD (JSON for Linking Data) delivers all three. It is ordinary JSON plus an @context that maps each key to a term in a public vocabulary — here schema.org and the GS1 Web Vocabulary. A crawler that already understands schema.org understands the passport with zero custom integration.

The DPP as JSON-LD (real curl + verified response)

Every qr3 passport resolves at a GS1 Digital Link URL: https://qr3.app/dpp/{gtin}/{serial}. Add ?format=jsonld to ask for the Linked-Data view. Against the live battery demo:

curl -s "https://qr3.app/dpp/04019999999902/DEMO-BAT-01?format=jsonld"

returns:

{
  "@context": ["https://schema.org", "https://gs1.org/voc/"],
  "@type": "Product",
  "gtin": "04019999999902",
  "name": "EcoMax 5000 (Demo)"
}

Three things to note:

  • @context is an array of two vocabularies — schema.org for the general web and gs1.org/voc/ for GS1's product terms. Keys resolve against both.
  • @type: "Product" tells any Linked-Data consumer exactly what kind of entity this is.
  • The values (gtin, name) are real and live — this is the actual payload, not a mock.

That is the whole point: a recycler's script does not need a qr3-specific client. It does an HTTP GET, parses JSON-LD it already understands, and reads the GTIN and product name straight off.

One URL, two audiences: content negotiation

The same https://qr3.app/dpp/{gtin}/{serial} URL serves a human-friendly HTML passport and the machine view — the server decides what to return based on what the caller asks for. Two ways to ask:

You want Query param Or Accept header
Human HTML page (default) Accept: text/html
JSON-LD (Linked Data) ?format=jsonld Accept: application/ld+json
Plain JSON ?format=json
Linkset (related resources) ?format=linkset
DCAT-AP (dataset metadata) ?format=dcat-ap

So a phone camera opening the QR lands on the readable HTML passport, while a script asks the identical URL for application/ld+json and gets structured data:

# Machine view via header negotiation — same URL, no query string
curl -s -H "Accept: application/ld+json" \
  "https://qr3.app/dpp/04019999999902/DEMO-BAT-01"

One identifier, one URL, many representations. The GTIN/serial stays stable; the view adapts to the caller. That is exactly what makes a DPP durable and interoperable at the same time.

Where EPCIS 2.0 fits (events vs. passport)

A common follow-up: what about EPCIS — isn't that the GS1 standard for this? Important distinction:

  • A DPP is the static description of one product item — its identity, materials, carbon footprint, recyclability. It answers "what is this thing?" The JSON-LD above is that snapshot.
  • EPCIS 2.0 is GS1's standard for supply-chain events — the visibility data of what happened, where, when, and why: an item was commissioned, shipped, received, recycled. It answers "what happened to this thing, and where is it?"

They are complementary, not competing. The passport tells you the product is a 5.2 kWh battery with 35% recycled content; an EPCIS event trail would tell you it was manufactured in Hamburg on a given date, shipped through a DC, and arrived at a recycler. EPCIS 2.0 itself is JSON/JSON-LD-friendly, so the two share the same Linked-Data worldview and the same GS1 identifiers (GTIN + serial) as the join key.

qr3 scope (be precise): qr3 outputs the DPP as JSON-LD — that is what this post demonstrates. qr3 does not provide EPCIS event capture or EPCIS endpoints. Treat EPCIS 2.0 here as the conceptual, complementary standard you would adopt alongside a DPP for full supply-chain traceability, not as a qr3 feature.

So the mental model is: the DPP (qr3, JSON-LD) is the product's identity sheet; EPCIS 2.0 (separate system) is its travel log. Same identifiers, two questions answered.

Generating a DPP that exposes JSON-LD

You do not do anything special to "enable" JSON-LD — create the passport and the resolver serves every representation automatically:

import { QR3 } from "@qr3/sdk";

const client = new QR3({ apiKey: process.env.QR3_API_KEY! });

const passport = await client.dpp.create({
  gtin: "04019999999902",
  serial: "SN-00012345",
  product_name: "PowerCell 5 kWh LFP",
  manufacturer: "ExampleTech GmbH",
  origin_country: "DE",
  category: "battery",
  market_countries: ["DE", "FR", "AT"],
  status: "live",
  battery_data: {
    capacity_kwh: 5,
    carbon_footprint_kg: 62,
    recycled_content_pct: 12,
    recyclability_pct: 95,
  },
});

// The passport now resolves at https://qr3.app/dpp/04019999999902/SN-00012345
// Humans get HTML; machines append ?format=jsonld (or send Accept: application/ld+json).
console.log(passport.qr.svg); // QR encodes the GS1 Digital Link to the resolver

Once created, the item's URL answers both audiences immediately — no extra publishing step for the machine view.

FAQ

Why JSON-LD and not plain JSON? Plain JSON is structured but not self-describing: a consumer must learn your field names. JSON-LD adds @context, mapping every key to schema.org / GS1 terms, so any Linked-Data consumer understands it without a custom integration. If you only need a quick read, ?format=json is still available.

Does qr3 implement EPCIS 2.0? No. qr3 outputs the DPP as JSON-LD. EPCIS 2.0 is the separate, complementary GS1 standard for supply-chain events; you would run it alongside, joined by the shared GTIN + serial.

How do I get the machine view? Append ?format=jsonld to the resolver URL, or send Accept: application/ld+json. Both return the same Linked-Data payload.

Is the @context stable? It pins schema.org plus the GS1 Web Vocabulary (gs1.org/voc/) — both public, versioned vocabularies, so consumers can rely on the term meanings.

Sources

Start for free and create a DPP that exposes JSON-LD: app.qr3.app/sign-up