Skip to content
EU Central DPP Registry live 19 Jul 2026Battery passports mandatory 18 Feb 2027See the timeline
All posts

Technology

Issue 100,000 passports before lunch: bulk minting patterns for the DPP API

Bulk minting patterns, idempotency keys and QR export at catalogue scale — the engineering guide to taking a Digital Product Passport integration from pilot to production.

Published February 10, 20264 min read

A pilot mints one passport from a curl command and everyone claps. Production mints four hundred thousand — one per unit coming off three lines in two plants — and the engineering questions change completely: batching, retries, partial failures, print-queue integration, and the quiet horror of accidentally minting the same serial twice.

This is the integration guide we wish every team had before their first production run. Everything below uses our API, but the patterns apply to any DPP platform worth shipping on.

The one rule: one unit, one identity, forever

A Digital Product Passport identifier is permanent. It gets printed, engraved or embedded in a physical object; there is no rollback migration for atoms. Every engineering decision below serves one invariant:

A physical unit maps to exactly one passport identifier, no matter how many times your integration crashes, retries or replays.

Which brings us immediately to idempotency.

Idempotency keys are not optional

Networks fail mid-request. Job queues redeliver. Deploys restart workers. If your minting call is not idempotent, each of those ordinary events risks a duplicate identity — and a duplicate identity on a physical product is not a bug you fix, it is a recall you manage.

Every write endpoint accepts an Idempotency-Key. Derive it from the physical identity, never from a random UUID generated at request time:

Deterministic idempotency from physical identity
// GOOD: key derived from what the unit *is*
const key = `mint:${plantCode}:${gtin}:${serial}`;
 
const passport = await dpp.passports.create(
  { category: "battery", gtin, serial, data },
  { idempotencyKey: key }
);
// Replayed request → same passport, no duplicate. Every time.
The bug factory
// BAD: new key per attempt — retries mint duplicates
const key = crypto.randomUUID();

With deterministic keys, your minting pipeline becomes safely re-runnable end to end: crash at unit 61,204, restart the whole job from zero, and units 1–61,203 return their existing passports at a fraction of the cost and latency of a mint.

Batches: the unit of production work

Minting per-unit over HTTP means 100k round trips. The batch endpoint takes up to 100,000 mint requests in one call and processes them asynchronously:

Bulk minting a production run
const batch = await dpp.batches.create({
  category: "battery",
  defaults: { data: { chemistry: "Li-NMC", kwh: 82 } },
  items: units.map((unit) => ({
    gtin: unit.gtin,
    serial: unit.serial,
    data: { cellLot: unit.cellLot },
  })),
}, { idempotencyKey: `batch:${plantCode}:${runId}` });
 
// → { id: "bat_7c31...", status: "processing", total: 100000 }

Poll GET /v1/batches/:id or — better — subscribe to the batch.completed webhook. The result you get back is not a boolean; it is a per-item ledger:

Batch result: a ledger, not a boolean
{
  "id": "bat_7c31...",
  "status": "completed",
  "succeeded": 99987,
  "failed": 13,
  "failures": [
    { "index": 4402, "serial": "A8F3-2K91-77QD",
      "error": "serial_already_minted" },
    { "index": 8811, "serial": "A8F3-2K91-",
      "error": "serial_format_invalid" }
  ]
}

Design for partial failure

Thirteen failures out of a hundred thousand is a normal Tuesday — a truncated serial from a mislabeled reel, a unit minted yesterday by a manual test. The production-grade pattern:

  1. Land the ledger in your own database, keyed by serial.
  2. Auto-triage: serial_already_minted is usually fine (fetch and reuse the existing passport); format errors go to a human queue.
  3. Re-submit only the corrected failures — as a new batch with the same deterministic item keys, which makes the retry safe by construction.

QR export at print speed

Minting creates identities; your line needs printable artwork. Two integration shapes:

  • Pull per unitGET /v1/passports/:id/qr?format=svg when a label printer requests it. Simple, fine below ~1 unit/second.
  • Batch export — request the QR archive alongside the batch: one ZIP of print-ready SVGs (vector — laser-marking and high-DPI thermal printers want vectors, not PNGs), named by serial, delivered via signed URL on batch completion. This is what high-speed lines integrate into their print servers.

Verify scannability on the physical result, not the file: ISO/IEC 18004 error-correction level, module size at your print resolution, and quiet-zone margins survive design review far more often than they survive a curved, textured battery casing. Scan-test production samples with the cheapest phone in the office.

Webhooks close the loop

Passports are living records — state of health, lifecycle events, corrections — so production integrations run event-driven:

The three webhooks every integration should handle
batch.completed    → land the ledger, trigger QR export, release print job
passport.updated   → mirror version bumps into your ERP audit trail
registry.synced    → store the EU DPP Registry acknowledgement as evidence

That last one matters more than it looks: the registry acknowledgement is your compliance receipt. Persist it next to the unit record; market surveillance queries are answered from your database, not from memory. (Why the registry works this way.)

Throughput, rate limits and the math of a deadline

Capacity planning for a compliance deadline deserves real numbers, not vibes. The relevant ones:

  • Batch processing throughput runs at roughly 5,000 mints/minute per workspace — a full 100k batch completes in ~20 minutes. Three batches can run concurrently; beyond that they queue.
  • Synchronous API limits are 50 requests/second per key. This is plenty for event-driven updates (state-of-health writes, lifecycle events) and irrelevant for minting, which belongs in batches anyway.
  • QR archive generation adds 5–10 minutes for a 100k batch. If your print schedule is tight, request the archive in the batch call itself rather than as a follow-up, so it renders while minting completes.

Now the deadline math that matters: a manufacturer with 2 million units of annual EU volume, starting integration in September 2026 for a February 2027 obligation, needs to mint a starting inventory plus ~40,000 units/week ongoing. Against the throughputs above, the API is never the bottleneck — your data readiness is. Which is the entire thesis of the data collection playbook: the pipes are fast; it is the water that arrives late.

Two operational habits to adopt from day one:

  • Alert on ledger anomalies, not just failures. A batch that succeeds 100% when the last fifty batches averaged 99.98% deserves a look too — perfect batches sometimes mean your dedupe silently swallowed a mislabeled run.
  • Track registry.synced lag as an SLO. Minting without registration is inventory, not compliance. If sync lag grows past hours, you want the page before the shipment leaves the dock.

A reference architecture in five lines

For a plant producing ~2,000 units/day:

  1. MES schedules a run → your integration service creates a batch with deterministic keys.
  2. batch.completed → ledger lands in Postgres; failures triaged; QR archive fetched.
  3. Print server pulls SVGs by serial as labels print inline.
  4. End-of-line scan verifies each printed code resolves — catching print defects while the unit is still on the line.
  5. Registry receipts archived per unit via webhook.

Every step re-runnable, every identity deterministic, every failure visible. That is the whole trick.

EV Battery Pack · 82 kWh
id.eudigipassport.eu/01/09506000117843
State of health
98% · 340 cyc
Chemistry
Lithium-ion NMC
Usable capacity
82 kWh
Carbon footprint
61 kg CO₂e/kWh
Warranty
8 yr / 160k km

Shopper scan · Batteries

The sandbox mints batches of 100k with fake GTINs, free. Get your keys and run this entire post against it this afternoon.

Put this into practice

Book a 30-minute walkthrough and leave with a dated readiness plan for your product category.

Book a consultation