Every version, download and status badge in this README is read live — from npm, from
Maven Central, from PyPI, and (the API contract) from the running node's own openapi.json.
Nothing here is hand-maintained.
Generated mirror. This kit and the
@opendpp/*package sources here are authored in the privateopendpp-nodebackend and synced automatically — a change made here cannot flow back and is overwritten on the next sync, so pull requests here can't be accepted. Issues are very welcome: this repo is the door for conformance gaps, standards proposals and bug reports. SeeCONTRIBUTING.md.
Everything an integrator needs to consume, conform to, and validate OpenDPP's two interoperability projections — without access to the product source: the official schemas, validated samples, the field mappings, a runnable conformance validator, and the public OpenAPI contract.
OpenDPP is a B2B Digital Product Passport service for the EU ESPR / battery regulation. It is both a vertical product and an open-interoperability reference: every public passport URL serves standards-conformant output under HTTP content negotiation. This repo is that contract, in the open. The product backend is a separate, private repository — you don't need it to integrate.
Audience: system integrators (AAS / IDTA) and developers/agencies (UNTP + W3C Verifiable Credentials). The hands-on walkthrough is the live portal at
/app/developers → Integrate; this repo is the machine-readable contract.
| Door | Format | How to consume | Conforms to |
|---|---|---|---|
| AAS / IDTA | application/aas+json + AASX (OPC/ZIP) |
GET /passport/{id} (or /01/{gtin14}) with Accept: application/aas+json |
IDTA-01001-3-1 AAS v3.0/3.1 |
| UNTP + VC | enveloping vc+jwt (W3C VC-JOSE-COSE, ES256) — or embedded vc+ld+json (W3C Data Integrity, ecdsa-jcs-2019) |
GET /passport/{id} (SKU/type) or GET /unit/{id} (per-unit, item granularity) with Accept: application/vc+jwt (or application/vc+ld+json); issuer key at GET /tenants/{tenantId}/did.json; revocation at GET /tenants/{tenantId}/status/revocation |
UNTP DigitalProductPassport v0.7.0, did:web, W3C Bitstring Status List |
Both doors are addressed by GS1 Digital Links — a passport resolves at /01/{gtin} (SKU/model) or
/8003/{grai}, a serialised unit at /01/{gtin}/21/{serial}, and POST /api/v1/gs1/decode turns raw
scan-data / AI element strings into those links. Every link OpenDPP emits is validated against GS1's own
Barcode Syntax Engine — check it yourself with the gs1 validator door (below), decode a scan
interactively in the developer console, or read where GS1
Digital Link sits in the EU standards picture on
the standards page.
The public machine-readable contract for the whole API surface is
openapi.json (also live at https://opendpp-node.eu/openapi.json). Conformance
status of each capability: CONFORMANCE.md.
Check an AAS Environment or a UNTP credential you produce against the official schemas, offline:
cd validate
npm install # the validator's pinned deps — no OpenDPP code
node validate.mjs aas ../samples/battery-aas-environment.json
node validate.mjs untp ../samples/battery-vc-credential.json
node validate.mjs semanticids ../samples/battery-aas-environment.json # IDTA template-identity check
node validate.mjs registry ../samples/battery-registry-pointer-model.json # CIRPASS-2 EU-registry (NON-NORMATIVE)
node validate.mjs shacl ../samples/battery-passport.jsonld # OpenDPP SHACL shapes (NON-NORMATIVE)
node validate.mjs sdjwt ../samples/battery-vc.sdjwt # SD-JWT-VC: disclosures + ES256 signature
node validate.mjs en18223 ../samples/battery-passport.jsonld # EN 18223:2026 compressed form (OpenDPP-authored, NON-NORMATIVE)
node validate.mjs en18223-expanded ../samples/battery-passport-expanded.jsonld # EN 18223:2026 Annex A expanded form
node validate.mjs epcis ../samples/epcis-document.json # GS1 EPCIS 2.0 document (official 2.0.1 schema)
node validate.mjs gs1 ../samples/gs1-digital-link.txt # GS1 Digital Link: grammar + check digits (GS1's engine)Exit 0 = conformant · 1 = schema errors (printed) · 2 = usage error. See
validate/.
Small, Apache-2.0 packages with no third-party dependencies that make integrating with the hosted node easier. Developed in the (private) product backend, published from this repo so npm provenance attestations work, and mirror-managed here.
| Package | Latest | Downloads | Unpacked | What it does |
|---|---|---|---|---|
@opendpp/gs1 |
GS1 Digital Link builders + mod-10 / GLN check-digit helpers | |||
@opendpp/csv |
CSV → passport mapper to the public ingest shape (bulk import) | |||
@opendpp/webhooks |
Webhook event types + a constant-time HMAC-SHA256 signature verifier | |||
@opendpp/eori |
EORI validation vs the EU Commission EOS authoritative service + offline syntax/parse helpers | |||
@opendpp/aeo |
Authorised Economic Operator (trusted-trader) lookup vs the EU Commission EOS service | |||
@opendpp/testdata |
Deterministic synthetic sample passports for every ESPR category + EPCIS-shaped supply-chain event chains (CLI + API) | |||
@opendpp/vies |
EU VAT-number validation against the EU Commission's authoritative VIES service + offline VAT-ID syntax/parse helpers |
Only @opendpp/testdata depends on anything at all — its two siblings, @opendpp/csv and
@opendpp/gs1. Every badge above is live from the npm registry, so the table can never go stale.
npm install @opendpp/gs1 # or @opendpp/csv, @opendpp/webhooks, @opendpp/eori, @opendpp/aeo, @opendpp/testdata, @opendpp/viesimport { isValidGTIN, generateDigitalLinkUri } from "@opendpp/gs1";
import { mapCsvRowsToPassports } from "@opendpp/csv"; // CSV rows → POST /api/v1/passports/bulk
import { verifyWebhookRequest } from "@opendpp/webhooks"; // verify an inbound OpenDPP webhook
import { validateEori } from "@opendpp/eori"; // EORI existence check vs the EU EOS service
import { lookupAeo } from "@opendpp/aeo"; // Authorised Economic Operator lookup
import { checkVatId } from "@opendpp/vies"; // validate an EU VAT number vs VIESSources + per-package docs are under packages/. The closed surface — eIDAS sealing,
did:web / status-list issuance, the resolver, and ESPR persistence — stays a service you call.
opendpp-interop/
├── openapi.json the public API contract (curated integration surface)
├── CONFORMANCE.md per-capability conformance matrix + how to verify each
├── idta-semantic-ids.json CC-BY IDTA submodel-template semanticId allowlist (identity checks)
├── schemas/ the official / reference JSON Schemas OpenDPP's CI validates against (vendored)
│ ├── aas-v3.schema.json
│ ├── untp-dpp-v0.7.0.schema.json
│ ├── cirpass2-eu-registry-pointer.schema.json (CIRPASS-2, NON-NORMATIVE)
│ └── epcis-2.0.1.schema.json (official GS1 EPCIS 2.0.1)
├── shapes/ OpenDPP-authored SHACL shapes (NON-NORMATIVE) for the DPP / battery vertical
│ └── opendpp-dpp-shapes.ttl
├── samples/ validated reference artifacts (a battery via both doors + a textile UNTP credential + the EU-registry pointer + the JSON-LD passport + its CIRPASS-2 renderer expansion)
│ ├── battery-passport.jsonld the public application/ld+json passport (validated by the `shacl` and `en18223` doors)
│ ├── battery-passport-expanded.jsonld the same passport in the EN 18223 Annex A expanded form (`en18223-expanded` door)
│ ├── epcis-document.json a GS1 EPCIS 2.0 traceability document (validated by the `epcis` door)
│ └── gs1-digital-link.txt GS1 Digital Link URIs / AI element strings (validated by the `gs1` door)
├── packages/ the @opendpp/* npm client libraries (gs1 · csv · webhooks · eori · aeo · testdata · vies; mirror-managed, see packages/README.md)
└── validate/ the offline conformance validator (validate.mjs: aas · untp · semanticids · registry · shacl · sdjwt · en18223 · gs1 · epcis)
The exact schemas OpenDPP's own CI validates against, copied verbatim (upstream is authoritative —
see schemas/README.md and NOTICE):
- the official IDTA AAS metamodel 3.1 JSON Schema (
aas-specsv3.1.2) —schemas/aas-v3.schema.json(official IDTA-01001-3-1, JSON Schema draft-2019-09). - UNTP DPP v0.7.0 —
schemas/untp-dpp-v0.7.0.schema.json(draft-2020-12). - CIRPASS-2 EU-registry pointer (NON-NORMATIVE) —
schemas/cirpass2-eu-registry-pointer.schema.json(the CIRPASS-2mock-eu-registrydefault-schema.json, draft-2020-12, pinned commitb383c4d). - the official GS1 EPCIS 2.0.1 JSON Schema —
schemas/epcis-2.0.1.schema.json(draft-07) — what the node's native EPCIS capture (POST /api/v1/events/epcis) and its lineage projection validate against.
shapes/opendpp-dpp-shapes.ttl is an OpenDPP-authored,
NON-NORMATIVE SHACL shapes set for the DPP / battery (ESPR) vertical. It validates OpenDPP's public
application/ld+json passport (the JSON-LD door, e.g. GET /passport/{id} with
Accept: application/ld+json) against shapes that target OpenDPP's one DPP vocabulary
(https://opendpp-node.eu/ns/dpp#): the EN 18223 header attributes, the lifecycle status, the
responsible economicOperator, the manufacturingFacility, and the battery data elements, which sit at
the document root under their elementId as EN 18223 clause 5.2 serialises them (category, battery
category, rated capacity, carbon footprint, durability, material composition).
It is a reasonable starter set — not an exhaustive ESPR-battery rulebook.
It exists to fill the gap left by the CIRPASS-2 dpp-validator, which ships placeholder
example.org SHACL shapes. CIRPASS-2's own validator is not used as an oracle.
Validate the bundled JSON-LD passport (offline — the loader stubs the remote @context URL, so
expansion uses the inline context with no network):
node validate/validate.mjs shacl samples/battery-passport.jsonldExit 0 = conforms · 1 = shape violations (printed per-violation). The shapes are designed against
the actual RDF the sample expands to, so the bundled passport conforms.
Intent to offer upstream. These shapes are offered as a starter contribution to CIRPASS-2 — they are NOT accepted, normative, an EU / CIRPASS-2 conformance suite, "certified", or "EU-official". They are OpenDPP's own content (Apache-2.0), unlike the vendored third-party schemas. See CONFORMANCE.md and NOTICE.
Reference artifacts in samples/ — a fictional battery (described both ways) and a
textile garment. These are real, live outputs fetched verbatim from the demo service (battery type
passport …/01/09501101532007, unit VM-LFP100-2026-000001; textile passport …/01/09501101531000),
so you can reproduce and verify every one against the live API (each samples/*-VALIDATION.md has
the curl). Synthetic demo data — see NOTICE:
battery-aas-environment.json,battery.aasx— the battery as an AAS v3.0/3.1 Environment / AASX package.{aluminium,chemicals,construction,cosmetics,electronics,iron-steel,textiles,toys}.aasx— one AASX package per ESPR category (#114/#115), each carrying that category's standardized submodel views. Every one validates against both the official AAS 3.1 JSON Schema (aas-specsv3.1.2) and theaas-test-engines3.0 gold standard (the 3.0/3.1 common subset): the kit CI extractsaasx/data/environment.jsonfrom each and JSON-Schema-validates it, and the IDTAaas-test-engines3.0 metamodel gold standard (latest 1.0.3 — no 3.1 test cases yet) runs in OpenDPP's backend CI.battery-vc-credential.json— the UNTP DPP credential (unsigned form, SKU/type,idGranularity:"model").battery-vc.jwt— the envelopingvc+jwt(paste into a JOSE debugger).battery-vc-di.jsonld— the embedded W3C Data Integrity form (ecdsa-jcs-2019).battery-vc.sdjwt,battery-vc-presented.sdjwt— the credential as a conformant SD-JWT-VC (cryptographic selective disclosure) and a 2-of-4 holder presentation (node validate/validate.mjs sdjwt …reconstructs the disclosures + verifies the ES256 signature againstdid.json).battery-unit-vc-credential.json,battery-unit-vc.jwt,battery-unit-vc-di.jsonld,battery-unit-vc.sdjwt— a per-unit credential for one serialised battery (item granularity, the real GS1 AI-21 serial asitemNumber), in the same four forms as the type credential above.battery-issuer-did.json— the issuerdid:webdocument (the verification key; the textile credential shares it — same demo tenant).textile-vc-credential.json,textile-vc.jwt— a non-battery (textiles) UNTP credential, proving the per-category typed mapping: fiber composition → typedmaterialProvenance, recycled content → a self-declared circularityperformanceClaim(not just thecharacteristicsopen bag).battery-passport.jsonld— the publicapplication/ld+jsonpassport (the JSON-LD door), validated by the OpenDPP SHACL shapes (node validate/validate.mjs shacl …).battery-registry-pointer-model.json,battery-registry-pointer-item.json— the CIRPASS-2 EU-registry pointer (NON-NORMATIVE) for the model and a per-unit item (node validate/validate.mjs registry …).cirpass2-renderer-expanded.json— the CIRPASS-2 reference renderer (dpp-renderer-beGET /fetch/v1) output for the JSON-LD passport: the live Jena-parsed + Titanium-JSON-LD-expanded RDF (30 nodes, every IRI underopendpp-node.eu). Captured by running the renderer (reproduction in a dated one-time capture, its runbook retired 2026-09-09), not viavalidate.mjs. NON-NORMATIVE — reference renderer, never certified.epcis-document.json— a GS1 EPCIS 2.0 traceability document (four events: two commissionings, a packing aggregation, a shipping) in the shapePOST /api/v1/events/epciscaptures and the lineage walk emits; authored here rather than fetched, and validated bynode validate/validate.mjs epcis ….AAS-VALIDATION.md,VC-VALIDATION.md— how each artifact was validated.
Structural validation (above) is separate from cryptographic verification. To verify a vc+jwt
end-to-end you need no OpenDPP code — just standard WebCrypto / JOSE:
- Decode the
vc+jwt(compact JWS); read thekidfrom the protected header. - Resolve the issuer DID:
GET /tenants/{tenantId}/did.json→ find theverificationMethodwhoseidmatches thekid; read itspublicKeyJwk. - Verify the JWS signature (ES256) with that public key.
- Check revocation: dereference
credentialStatusagainstGET /tenants/{tenantId}/status/revocation(a signed W3C Bitstring Status List).
For the embedded vc+ld+json form, verify the DataIntegrityProof per the W3C ecdsa-jcs-2019
cryptosuite (RFC 8785 JCS canonicalization → ECDSA P-256 over SHA-256(proofOptions) ‖ SHA-256(doc)
→ multibase base58btc proofValue); off-the-shelf libraries
(e.g. @digitalbazaar/ecdsa-jcs-2019-cryptosuite) verify it directly.
OpenDPP derives both projections from one canonical passport via a single tier-masking seam, so they never drift. Only PUBLIC-tier data enters either projection — privileged/restricted metadata never does.
- AAS: General Product Information, ComplianceMetadata, per-category submodel views, and the eIDAS
seal submodel. Concepts OpenDPP coins are honestly
urn:opendpp:concept:*— never presented as eCl@ss. SubmodelsemanticIds carry a real IDTA template IRI only where machine-checked against the published IDTA registry — verify it yourself withnode validate/validate.mjs semanticids <aas-file>against the CC-BY allowlistidta-semantic-ids.json(template identity, not structural conformance). - UNTP: the passport maps to a
DigitalProductPassportcredentialSubject; SKU/type credentials areidGranularity:"model", per-unit credentials areidGranularity:"item"with the GS1 AI-21 serial asitemNumber, linked back to their type credential.
A third, non-normative mapping: the registry-side projection. ESPR Art. 13 anticipates a
decentralised EU registry holding only a thin pointer per product — not the passport data, just
enough to find and identify it. OpenDPP projects a passport (or a per-unit BatteryUnit) into the
pointer-only index record the CIRPASS-2 mock-eu-registry reference expects, and validates it
against the vendored
cirpass2-eu-registry-pointer.schema.json
(the registry's default-schema.json, draft-2020-12).
NON-NORMATIVE. CIRPASS-2 is an EU-funded reference ecosystem for exploration; it is not the EU registry, not CEN-CENELEC JTC 24, and confers no certification. OpenDPP claims its pointer validates against the reference — never "CIRPASS-2-certified" / "EU-registry-compliant" / "EU-official". See CONFORMANCE.md.
Pointer fields — the mapping is deterministic; OpenDPP refuses to register rather than emit a placeholder for any required field it cannot honestly source:
| Pointer field | OpenDPP source | Encoding / notes |
|---|---|---|
upi |
MODEL: canonical GS1 Digital Link product key. ITEM: the compact resolvable unit URL | MODEL: https://id.gs1.org/01/<gtin> for a GTIN-keyed product (or /8003/<grai> for a GRAI). ITEM (#932): ${BASE_URL}/u/<22-char base64url of the unit id> — the live EU DPP Registry caps a registered UPI at 50 characters and dereferences it, which the 40-fixed-char id.gs1.org unit form cannot satisfy for real AI-21 serials; the unit's GS1 identity stays in modelUpi/batchUpi and its Digital Link. Gated fail-closed at build time (registryUpiError, @opendpp/gs1). |
reoId |
EconomicOperator.regId namespaced by regIdScheme |
<SCHEME>-<regId>, e.g. VAT-LT000000000001. The schemes are the four EN 18219 clause 6 admits — VAT | DUNS | LEI | GLN; max 50 chars. Refuses if no regId, and refuses an operator whose scheme is UNDECLARED. |
liveURL |
the public resolver | ${BASE_URL}/passport/<id> (MODEL) or /unit/<id> (ITEM) — the same URL the vc+jwt / AAS content-negotiation serve. |
backupURL |
a distinct retrieval URL | the stored GS1 Digital Link (Passport.digitalLinkUri / BatteryUnit.digitalLinkUri), or ${backupBaseUrl}/… if configured. Must differ from liveURL. |
commodityCode |
Passport.metadata.commodityCode (or hsCode / taricCode / hs) |
HS / TARIC code; schema pattern ^[0-9]{4,10}$. Refuses to register if absent — no placeholder. |
facilitiesId |
["GLN-<Facility.gln>"] |
GS1 GLN-13 (mod-10 valid). Refuses if the passport has no bound Facility — the same non-registrable state that makes the vc+jwt / AAS paths return 406; never emits a placeholder GLN. |
granularityLevel |
the entity kind | MODEL for a SKU/type Passport, ITEM for a BatteryUnit. BATCH is reserved — OpenDPP has no first-class batch entity yet, so it never emits BATCH. |
deactivated (ITEM only) |
unit lifecycle | true when the unit is RECYCLED or has ceasedAt set (Art. 77(8) cease-to-exist), else false. An archived / decommissioned / recycled passport or unit still resolves (the persistence duty) — it is never silently dropped from the index. |
modelUpi (ITEM only) |
the parent type Passport's upi |
links the item back to its model entry. |
batchUpi (ITEM only) |
defaults to modelUpi |
OpenDPP has no batch entity, so the item's batch UPI is its model UPI. |
Granularity model (MODEL / BATCH / ITEM). The pointer schema enforces this end-to-end via
conditional allOf rules, and OpenDPP honours them exactly:
- MODEL — a SKU/type passport. The schema forbids
modelUpiandbatchUpi; OpenDPP emits neither. - BATCH — reserved, unused. The schema requires
modelUpiand forbidsbatchUpi. OpenDPP has no first-class batch entity, so it never produces aBATCHpointer (it is not faked as a model or an item). - ITEM — a serialised
BatteryUnit. The schema requiresdeactivated,modelUpi, andbatchUpi; OpenDPP supplies all three (withbatchUpidefaulting tomodelUpi).
Discovery search-keys (CIRPASS-2 dpp-data-extractor). The extractor crawls the resolved passport
for 14 search-keys. OpenDPP populates 6 of 14 today; the remaining 8 are roadmap (tracked
by #116 / R7). The 6 are resolved from OpenDPP's AAS output: the extractor matches an AAS leaf
semanticId by equalsIgnoreCase || endsWith against bare eCl@ss IRDIs, and OpenDPP carries exactly
those bare IRDIs on its discoverability-critical leaves (Nameplate ManufacturerName /
ManufacturerProductDesignation, CarbonFootprint nested PcfCO2eq / ReferenceImpactUnitForCalculation,
ProductClassifications ProductClassId / ClassificationSystem) in the OpenDPP emitters
src/utils/aas-mapper.ts + src/utils/aas-category-templates.ts. (The CIRPASS-2 viewer
separately consumes JSON-LD / RDF via an inline @context — it reads OpenDPP's JSON-LD door, not
its AAS or VC door. Its backend dpp-renderer-be GET /fetch/v1 was run against a live OpenDPP
passport and Titanium-JSON-LD-expanded it cleanly — see the ✅ viewer row in
CONFORMANCE.md + the cirpass2-renderer-expanded.json
sample; OpenDPP's JSON-LD context is built in src/utils/jsonld.ts.)
| Search-key | Status | Search-key | Status |
|---|---|---|---|
manufacturerName |
✅ | recyclingRate |
🗺 |
productName |
✅ | recyclingRateUom |
🗺 |
carbonFootprint |
✅ | energyConsumption |
🗺 |
carbonFootprintUom |
✅ | energyConsumptionUom |
🗺 |
codeValue |
✅ | weight |
🗺 |
codeSet |
✅ | weightUom |
🗺 |
durability |
🗺 | ||
durabilityUom |
🗺 |
Legend: ✅ populated today · 🗺 roadmap (#116 / R7).
Validate it yourself (offline, no OpenDPP code):
node validate/validate.mjs registry samples/battery-registry-pointer-model.json
node validate/validate.mjs registry samples/battery-registry-pointer-item.jsonGenerated from this same public OpenAPI contract and version-locked to it — an SDK's MAJOR.MINOR
is the contract version it targets, so it can never drift from the surface it calls. Maintained in
OpenDPP/opendpp-sdk:
| SDK | Coordinates | Latest | Install |
|---|---|---|---|
| TypeScript | @opendpp/sdk |
npm install @opendpp/sdk |
|
| Java / Kotlin | eu.opendpp-node:opendpp-sdk |
implementation("eu.opendpp-node:opendpp-sdk:1.16.0") |
|
| Python | opendpp-sdk |
pip install opendpp-sdk |
The Java artifact is built on the JDK HttpClient with Jackson; Kotlin consumes the very same
coordinates idiomatically — there is no separate Kotlin package. The Python client is pydantic v2 +
urllib3, generated from the same pipeline.
Both projections and the API surface are anchored to the public OpenAPI spec, so any OpenAPI generator works — no OpenDPP-specific tooling:
# TypeScript types from the live public spec (no dependency on this repo):
npx openapi-typescript https://opendpp-node.eu/openapi.json -o opendpp.d.ts
# Or a full client in any language via openapi-generator:
npx @openapitools/openapi-generator-cli generate \
-i https://opendpp-node.eu/openapi.json -g <lang> -o ./opendpp-sdkThis repo (OpenDPP/opendpp-interop) |
the public interop boundary — schemas, samples, validator, OpenAPI contract, mappings. Apache-2.0. |
| Live service | https://opendpp-node.eu — the running passports, resolvers, and did:web / status-list endpoints. |
| Client SDKs | OpenDPP/opendpp-sdk — @opendpp/sdk (TypeScript, npm) and eu.opendpp-node:opendpp-sdk (Java / Kotlin, Maven Central) and opendpp-sdk (Python, PyPI), generated from this OpenAPI contract. See Typed SDKs. |
| Knowledge bundle | OpenDPP/opendpp-knowledge — the API as an OKF bundle for AI agents. |
| Product backend | a separate, private repository. Not required to integrate. |
Apache-2.0 for this kit's own content (validator, docs, samples). Vendored third-party
schemas keep their upstream terms — see NOTICE. Issues and PRs welcome.
"OpenDPP" is a trademark of Opendpp UAB. The Apache-2.0 license covers the code, not the name:
forks may rebuild but must rename, and "OpenDPP-conformant" is reserved for output validated
against the hosted node — see TRADEMARK.md.
