Passports
Create, update, suspend, archive, and bulk-import Digital Product Passports. Includes the parties block for economic-operator chains.
/api/v1/passportsCreate a passport
Create a single Digital Product Passport. The passport is bound to a product (productId) and identified by a GS1 GTIN + serial number that's unique within that GTIN. New passports start in `draft` status — fields are populated via subsequent PATCH calls and the passport is published from the dashboard once review is complete.
/api/v1/passports/{id}Get a single passport
Read one passport by ID. Default response includes the full passport — translatable fields carry their `sourceLocale` and a per-locale `translations` map. Pass `?lang=<locale>` to have the server resolve every field through the public-viewer chain (viewer-locale translation → source-locale value → English → first-applied → universal source) and return one resolved value per field; the `translations` map is dropped from lang-resolved responses.
/api/v1/passports/{id}/qrRender the passport QR
Returns a freshly-rendered QR code for the passport, encoding its effective identifier URI. For GS1 passports this is the GS1 Digital Link URI (`/01/<gtin>/21/<serial>`); for ISO 15459 it is the resolver path on the configured host; for iec61406, did, and doi passports it is the `/x/<scheme>/<encodedValue>` resolver path (e.g. `https://id.tracepass.eu/x/did/<encodedDid>`). The resolver host rewrites `/x/*` to `/p/x/*` so the same viewer serves all five EN 18219 schemes. Use this when you want our renderer (consistent quiet zone, error correction, optional branding) instead of encoding the URI yourself. Defaults to `image/svg+xml`; `?format=png` returns a PNG and `?format=json` returns a `{ result: "<svg>" }` wrapper for embedding.
/api/v1/passports/{id}/complianceCheck passport compliance
Returns a three-tier compliance verdict for one passport — `compliant`, `compliant_with_warnings`, or `incomplete` — together with regulation-cited findings, so an integration can gap-check a passport, fix the cited gaps, then call again to confirm. That read → fix → verify loop is the point: the response tells an agent exactly what to set next.
/api/v1/passports/{id}/registry-readinessCheck passport registry readiness
Returns whether a passport would pass the **EU DPP Registry's formal submission gate** — `{ ready, findings[] }`. This is the registry's *mechanical* pre-submission check, distinct from and complementary to the substantive `/compliance` verdict: a passport can be registry-ready yet not substantively compliant, and vice-versa. Call it before submitting to the registry to catch formal gaps early.
/api/v1/passportsList passports
Paginated list of passports the workspace owns. Filter by `productId`, `status`, or a free-text search across GTIN and serial number. Counts against the daily passport-read budget (`maxV1PassportsPerDay`); the response items use the same shape as the single-read endpoint but trimmed to the listing fields.
/api/v1/passports/{id}/fields/{key}Update one field on a passport
Patch a single field on a passport. The value is validated against the field key (must exist on the passport's template) and persisted with an audit trail entry naming the credential (`via API key <prefix>` or `via OAuth app <client>`) and, when the `X-Source` header is set, the calling client, e.g. `(mcp)`. Use this in preference to writing the whole `fields` map when you only need to update one number — it's a smaller write and the per-field audit entry is what dashboard reviewers see.
/api/v1/passports/{id}/parties/{role}Upsert an economic-operator party
Create or replace the Party for a single role on a passport. The role path segment determines the slot you're writing — `manufacturer`, `importer`, `authorisedRepresentative`, `distributor`, `recycler`, `producerResponsibilityOrg`. Sending the same role twice is an upsert: the existing block is replaced atomically.
/api/v1/passports/{id}/suspendSuspend a passport
Reversible suspend. The public viewer flips to the suspended state page (HTTP 423 with structured body); QR scans effectively die without the URL going to 404. Use for recalls, disputes, internal holds, or product-quality investigations. Republish from the dashboard once resolved.
/api/v1/passports/{id}/archiveArchive a passport (irreversible)
**Irreversible.** Public viewer returns 404, the GS1 Digital Link URL stops resolving, the QR code dies for good. Use ONLY for products that never shipped — archiving a passport for a product already in customers' hands breaks every QR scan they'll ever make of it.
/api/v1/passports/{id}Delete a passport permanently
**Permanent and irreversible** — the passport row plus every cascaded dependent is removed from the database. Cascade includes AI extractions, agent-session state, supplier requests linked to this passport alone, scan + service events, and any uploaded documents whose only reference was this passport (documents shared across other passports/products stay). R2 storage under the passport's folder is swept too.
/api/v1/passports/batchBatch-create passports
Create up to 100 passports in one call. Each item carries the same body shape as the single-create endpoint (`{ productId, gs1: { gtin, serialNumber }, parties?, confirmOverage? }`). Partial-success per item — each gets its own status in the response array.
/api/v1/passports/{id}/snapshotsList passport snapshots
Returns a paginated list of immutability **snapshots** for a passport, newest first. A snapshot is written on publish and after every change to a published, suspended, expired or archived passport — field values, translations, parties, status transitions, supplier answers, AI writes, restores — whenever what the passport asserts actually changed (EN 18221:2026 4.2). Each entry includes the snapshot id, version number, reason (e.g. `published`, `field_edit`, `status_change`, `baseline`), who caused it (`actor`, when known), timestamp, content hash, and whether the hash still verifies — `hashValid: false` indicates at-rest tampering.
/api/v1/passports/{id}/snapshots/{snapshotId}Get passport snapshot
Returns the full archival record for one immutability **snapshot**: the complete JSON-LD payload exactly as the passport stood when the snapshot was taken — on publish, or after any change to a published, suspended, expired or archived passport — the snapshot metadata (version, reason, `actor` — who caused the change — and timestamp), and content hash re-verification. To get the version valid at a given date instead, use `?at=` on **List passport snapshots**. `hashValid: false` means the stored payload has been modified since the snapshot was taken — indicative of at-rest tampering.
/api/v1/passports/{id}/condition-flagsGet condition flags
Returns the resolved **condition profile** for a passport — `Record<flagKey, { value, status, source }>`. Condition flags are reviewer-approved yes/no facts about a product that gate conditional legal duties. For battery passports under Regulation (EU) 2023/1542, the registered flags are: `hasBMS` (Battery Management System present), `rechargeable` (battery is rechargeable), `externalStorageOnly` (Art. 8 exemption applies), and `isStationaryBess` (Stationary Battery Energy Storage System). Categories with no flags registered return an empty profile.
/api/v1/passports/{id}/condition-flagsSet condition flags
Write one or more condition flags to a passport. The request body is `Record<flagKey, boolean | null>` — `true` or `false` sets the flag; `null` clears it. Only keys registered for the passport's category are accepted; unknown keys return 400 with a list of valid keys for that category.
/api/v1/passports/{id}/measurementsCapture measurements
**Battery passports only** (Regulation (EU) 2023/1542 Art. 77, Annex XIII point 4 use data). The passport must be published; a draft or suspended passport returns 422 `passport_not_active`. A non-battery passport returns 422 `not_a_battery`. Send 1–500 measurements per call, each as `{ fieldKey, value, measuredAt, externalId?, unit? }` — `measuredAt` is an ISO 8601 timestamp, must not be in the future; `value` must be at most **16 KB when serialised** (a larger value returns 400 `invalid_value`). Accepted `fieldKey`s are the Annex XIII point 4 use-data fields: `stateOfHealth`, `stateOfCertifiedEnergy`, `remainingCapacity`, `remainingPowerCapability`, `remainingRoundTripEfficiency`, `evolutionOfSelfDischargeRate`, `currentInternalResistancePack`, `capacityFade`, `powerFade`, `internalResistanceIncrease`, `dynamicRatedCapacity`, `dynamicPowerCapability`, `dynamicInternalResistance`, `dynamicEnergyRoundTripEfficiency`, `dynamicExpectedLifetimeCycles`, `numberOfFullEquivalentChargingCycles`, `numberOfChargingEvents`, `currentStateOfCharge`, `negativeEvents`, `temperatureConditionsHistorical`.
/api/v1/passports/{id}/measurementsList measurement history
Returns the full measurement history for a battery passport, newest first (by `measuredAt`). Each entry is a `PassportMeasurement` object with `_id`, `passportId`, `fieldKey`, `value`, `measuredAt`, `receivedAt`, `externalId?`, `unit?`, `materialized` (whether this entry became the passport's current value), and `source`. **Battery passports only** — a non-battery passport returns 422 `not_a_battery`.
/api/v1/passports/{id}/measurements/latestGet latest measurements
Returns the most recent measurement for each accepted Annex XIII point 4 use-data field. The response is `{ data: { <fieldKey>: LatestMeasurement | null } }` — every accepted key is always present; a key is `null` when no measurement has been received for that field yet. A `LatestMeasurement` carries `value`, `measuredAt`, and optionally `unit` and `externalId`. **Battery passports only** — a non-battery passport returns 422 `not_a_battery`.