Most articles about the EU Digital Product Passport (DPP) explain what a passport must contain. Far fewer show you how to create one — and almost none show you how to do it for a catalog of 500 or 50,000 SKUs without clicking through a form 50,000 times.
This guide is the developer's version. Every step below is a real, working call against the qr3.app API (https://qr3.app/v1). If you manage product data in an ERP, a PIM or a database, you can wire DPP generation straight into your existing pipeline.
Why an API matters for DPPs
A Digital Product Passport is not a one-off document. Under the Ecodesign for Sustainable Products Regulation (ESPR, EU 2024/1781) and the EU Battery Regulation 2023/1542, every regulated unit needs a passport that stays current over its entire lifetime. For a manufacturer that means:
- Scale — hundreds to tens of thousands of products, each with its own GTIN/serial.
- Freshness — data (carbon footprint, recycled content, repair info) changes and must be updated, not re-created.
- Integration — the source of truth is your ERP/PIM, not a web form.
That is an API problem. A manual web tool gets you the first ten passports; an API gets you all of them.
1. Authentication
Every request uses a bearer token (create an API key in the dashboard). The base URL is https://qr3.app/v1.
curl https://qr3.app/v1/dpp \
-H "Authorization: Bearer $QR3_API_KEY"
Or with the official SDK:
import { QR3 } from "@qr3/sdk";
const client = new QR3({ apiKey: process.env.QR3_API_KEY! });
2. Create a battery passport
A DPP is created with POST /dpp. The top-level fields are the same for every category; the category-specific data goes into battery_data, textile_data or general_data.
curl -X POST https://qr3.app/v1/dpp \
-H "Authorization: Bearer $QR3_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"gtin": "09506000134376",
"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,
"carbon_footprint_class": "B",
"recycled_content_pct": 12,
"recyclability_pct": 95,
"manufacturer_warranty_years": 8,
"lithium_content_pct": 6.5,
"certifications": ["CE", "UN38.3"]
}
}'
The same call with the SDK:
const passport = await client.dpp.create({
gtin: "09506000134376",
serial: "SN-00012345",
product_name: "PowerCell 5 kWh LFP",
manufacturer: "ExampleTech GmbH",
origin_country: "DE",
category: "battery",
market_countries: ["DE", "FR", "AT"],
battery_data: {
capacity_kwh: 5,
carbon_footprint_kg: 62,
recycled_content_pct: 12,
recyclability_pct: 95,
manufacturer_warranty_years: 8,
},
});
console.log(passport.id); // dpp_xxxxxxxx
console.log(passport.qr.svg); // print-ready GS1 Digital Link QR
The response includes a consumer-facing landing page (localized into 25 EU languages) and a GS1 Digital Link QR code ready for label printing — no separate QR step required.
3. Validate before you create
Want to catch missing or invalid fields before persisting? POST /dpp/validate runs the exact EU validation rules without creating anything. Ideal as a pre-commit check in CI.
const result = await client.dpp.validate({
gtin: "09506000134376",
product_name: "PowerCell 5 kWh LFP",
manufacturer: "ExampleTech GmbH",
origin_country: "DE",
category: "battery",
battery_data: {
capacity_kwh: 5,
carbon_footprint_kg: 62,
recycled_content_pct: 12,
recyclability_pct: 95,
manufacturer_warranty_years: 8,
},
});
if (!result.valid) {
console.error(result.errors);
// [{ field: "battery_data.recyclability_pct", message: "..." }]
}
4. Batch-create a whole catalog
For a product catalog, send up to 100 passports per request to POST /dpp/batch. Loop over your ERP export and you have your entire range in minutes.
const items = products.map((p) => ({
gtin: p.gtin,
serial: p.serial,
product_name: p.name,
manufacturer: "ExampleTech GmbH",
origin_country: "DE",
category: "battery" as const,
battery_data: {
capacity_kwh: p.capacityKwh,
carbon_footprint_kg: p.co2Kg,
recycled_content_pct: p.recycledPct,
recyclability_pct: p.recyclablePct,
manufacturer_warranty_years: p.warrantyYears,
},
}));
// Chunk into batches of 100
const result = await client.dpp.batch({ items: items.slice(0, 100) });
5. Get the QR code for label printing
Each passport exposes its GS1 Digital Link QR in four print formats. Pull them via the passport object or directly:
curl https://qr3.app/v1/dpp/dpp_xxxxxxxx/qr.svg \
-H "Authorization: Bearer $QR3_API_KEY" -o label.svg
qr.svg, qr.png, qr.pdf and qr.eps are all available — SVG/EPS for your label printer, PNG for the web.
6. Keep data current (without breaking the QR)
The GTIN/serial/lot are immutable after creation — that keeps the printed GS1 URI stable forever. Everything else can be updated with PUT /dpp/{id}:
await client.dpp.update(passport.id, {
battery_data: {
capacity_kwh: 5,
carbon_footprint_kg: 58, // re-measured, lower footprint
recycled_content_pct: 16, // 2031 target reached early
recyclability_pct: 95,
manufacturer_warranty_years: 8,
},
});
The QR on the physical product never changes; the data behind it does. That is the entire point of a dynamic passport.
7. Submit to the EU registry
When the EU central DPP registry is in scope for your product, submit a passport with one call (Business plan and up):
const reg = await client.dpp.registerForEuRegistry(passport.id);
console.log(reg.data.eu_registry_status); // "pending"
console.log(reg.data.registry_request_id);
8. React to scans with webhooks
DPP scans are events. Subscribe to qr.scanned and you can stream them into your analytics, trigger re-orders, or flag a recall — in real time. Payloads are signed (HMAC-SHA256); always verify the signature.
import { verifyWebhook } from "@qr3/sdk";
app.post("/webhooks/qr3", async (req, res) => {
const event = verifyWebhook(req.body, req.headers["qr3-signature"], secret);
if (event.type === "qr.scanned") {
console.log(event.data.country, event.data.dpp_id);
}
res.sendStatus(200);
});
Putting it together: DPP generation in CI
The end state most manufacturers want: product data lives in the ERP/PIM, and a scheduled job keeps the passports in sync.
- Export changed products from your ERP.
client.dpp.validate(...)each one — fail the build on validation errors.client.dpp.batch(...)new products;client.dpp.update(...)changed ones.- Push the returned
qr.svgURLs to your label-printing system.
No web form, no copy-paste, no drift between your master data and your passports.
FAQ
Do I need a separate QR-code tool? No. Every DPP returns a GS1 Digital Link QR in SVG/PNG/PDF/EPS. The QR is the access point to the passport.
Can I update a passport after the label is printed?
Yes — that is the core idea. GTIN/serial are immutable so the printed URI stays valid; all data fields are updatable via PUT /dpp/{id}.
How many passports can I create at once?
Up to 100 per POST /dpp/batch request. Chunk larger catalogs; rate limits apply per plan.
Which categories are supported?
battery and textile ship with full EU-validation today; general covers other product types. Battery and textile include a live EU compliance check (ESPR / AGEC).
Sources
Start for free and create your first DPP via API: app.qr3.app/sign-up