DPP Resolution: How We Wired It Up

How OpenEPCIS combined GS1 Digital Link, the IETF linkset format and content negotiation into one DPP resolution flow

What this page is

A walkthrough of one way to take a scanned QR code on a product through to a typed DPP response — the way we did it with GS1 standards. It's not a finished cross-sector product offering and it's not the canonical flow for every regulator and every customer. It's a worked example that exercises the standard primitives in a single path, so a developer or compliance reviewer can see how the pieces fit together.

The standards that show up below — GS1 Digital Link, the GS1 Conformant Resolver discovery contract, IETF application/linkset+json (RFC 9264), HTTP content negotiation, and ESPR Article 9 access tiers — are all published. The opinions in between are ours, and other implementations make different ones.

The flow at a glance

flowchart TD
    A["<b>Data carrier</b><br/>QR / NFC / RFID<br/>https://id.gs1.org/01/&lt;GTIN&gt;/21/&lt;serial&gt;"]
    A --> B["<b>GS1 resolver</b><br/>id.gs1.org or self-hosted<br/>looks up the linkset"]
    B --> C["<b>DPP endpoint</b><br/>content negotiation +<br/>linkType routing"]
    C --> D{"Access tier<br/>(ESPR Art. 9)"}
    D -->|Public| E["Return data"]
    D -->|AuthorizedOnly| F["Verify credential"]
    F -->|Valid| E
    F -->|Invalid| G["403 Forbidden"]
    E --> H["<b>Response</b><br/>JSON-LD / HTML /<br/>RDF / EPCIS events"]

Walking it through with one example

The example below uses a serialised timber product — chosen because timber under EUDR exercises the access-tier surface (a customs officer needs more detail than a consumer). The same primitives apply when the product is a battery, a garment, a smartphone or a frying pan; only the linkset entries and the JSON-LD payloads change.

The QR code carries a GS1 Digital Link URI:

https://id.gs1.org/01/09521234000020/21/LOG-2025-001
        └────┬───┘ └──────┬─────┘ └──────┬─────┘
          Base       GTIN (AI 01)   Serial (AI 21)

Application Identifiers (01, 21, …) are the same primitives GS1 has used in barcodes for decades. Encoding them in a URL means the same identifier is scannable by a phone camera, queryable by a server, and visible in a browser bar.

2 — Resolver lookup

The resolver matches the GTIN against its registry and returns an application/linkset+json document (IETF RFC 9264). One object per anchor, GS1 link relations as full-IRI keys, link descriptors as values. The discovery contract that lets downstream registries find and validate the deployment is the GS1 Conformant Resolver standard — see Resolver Setup for the full shape and the hosting choices we considered.

3 — Content negotiation

The same Digital Link URL serves different representations depending on the Accept header. A consumer's phone browser sends text/html and lands on a passport web page. A downstream system asks for application/ld+json and gets a typed JSON-LD record. RDF tooling asks for text/turtle. This is plain HTTP content negotiation, not anything GS1-specific:

Accept headerResponse
text/htmlConsumer-facing passport page
application/ld+jsonJSON-LD record
application/jsonPlain JSON
text/turtleRDF / Turtle

4 — linkType for facet selection

Layered on top of Accept, the ?linkType= query parameter requests a specific facet of the passport. Per the GS1 Digital Link spec the value is the bare relation name (the compact gs1:… form only appears as keys inside the linkset JSON itself).

The list below is the relations our resolver auto-derives today; a deployment can extend it with sector-specific relations as needed.

?linkType=What it returns
pipProduct Information Page (default)
epcisEPCIS event history
certificationInfoCertificates and declarations
safetyInfoSafety data sheet
recipeInfoComposition / BOM
serviceInfoService and repair
productSustainabilityInfoCarbon footprint, recycling

5 — Access tier (ESPR Article 9)

ESPR Article 9 defines three access tiers for DPP data. The resolver enforces them at the request boundary:

flowchart TD
    R[Request arrives at DPP endpoint] --> L{Data access<br/>tier}
    L -->|Public| Ret[Return]
    L -->|AuthorizedOnly| Auth[Verify credential<br/>or capability token]
    L -->|Restricted| Agr[Check bilateral<br/>agreement]
    Auth -->|Valid| Ret
    Auth -->|Invalid| Den[403 Forbidden]
    Agr -->|Granted| Ret
    Agr -->|Not granted| Den
TierWho can read itExamples
PublicAnyoneProduct name, manufacturer, recycling instructions, headline specs
AuthorizedOnlyMarket surveillance, customs, accredited recyclersTest reports, compliance dossiers, detailed supply chain
RestrictedNamed partners under agreementTrade secrets, proprietary formulations, full BOM

In our implementation the Authorized and Restricted tiers reuse the same identity layer that gates the EPCIS REST surface — a Keycloak-issued credential, a capability token, or (where we've wired it up) a Verifiable Credential presented via OID4VP. See Architecture → Wallet-agnostic Verifiable Credentials.

Instance URIs vs vocabulary URIs

The URIs that identify a specific product and the URIs that define what a property means are distinct, and confusing them is a common early mistake:

  • Instance URIhttps://id.gs1.org/01/09521234000020/21/LOG-2025-001 resolves to this specific timber log's record.
  • Vocabulary URIhttps://ref.openepcis.io/extensions/eu/eudr/commodityType is a definition of the commodityType term and resolves to documentation.

In a JSON-LD document the @id is an instance URI; predicates (battery:stateOfHealth, eudr:commodityType, …) expand to vocabulary URIs through the @context.

What this walkthrough is not

A few things this page deliberately doesn't claim:

  • It isn't a finished product offering. We've built the resolution mechanism and exercised it; the per-sector DPP credentials and the regulator-side acceptance criteria still depend on the underlying regulation landing (ESPR delegated acts, EU Central DPP Registry, sector EN standards). Status notes live on the per-domain implementation pages.
  • It isn't the only valid resolution flow. Other implementations put the resolver on a brand-owned domain, redirect rather than serve, gate everything behind authentication, fall back to a static page, or short-circuit content negotiation entirely. Each makes different trade-offs.
  • It isn't sector-specific. The primitives — Digital Link URI, linkset, content negotiation, linkType, access tier — are sector-neutral. The per-sector work is which fields go in the JSON-LD payload, not how the URL gets resolved.

Next steps