Generate EU Digital Product Passports via API

Most DPP guides tell you what a passport must contain. This one shows how to create EU-compliant Digital Product Passports programmatically — with curl, the qr3 SDK, batch creation, validation, GS1 QR codes and webhooks.

by QR3 Redaktion

Generate EU Digital Product Passports via API

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.

  1. Export changed products from your ERP.
  2. client.dpp.validate(...) each one — fail the build on validation errors.
  3. client.dpp.batch(...) new products; client.dpp.update(...) changed ones.
  4. Push the returned qr.svg URLs 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