# DPPAutomate > DPPAutomate issues, manages and resolves EU Digital Product Passports (DPPs). A DPP is the structured, machine-readable record EU regulation (ESPR - Ecodesign for Sustainable Products Regulation) requires for products placed on the EU market. DPPAutomate generates a passport from a single product photo, keeps it compliant, and exposes it to the world through a GS1 Digital Link resolver, a public JSON-LD endpoint, a full REST API and an MCP server. It is built agentic-first: hand an LLM a workspace master API key and it can run the entire DPP workflow end to end. A passport has 12 sections (identification, economic operator, production, materials, chemical compliance, environmental impact, sustainability, care instructions, end-of-life, compliance, data interoperability, metadata) plus an AI-written summary. Coverage spans the ESPR core plus product groups such as batteries (EU Battery Regulation 2023/1542, Annex XIII), textiles, electronics, furniture, packaging, tyres, toys and steel/aluminium. ## Start here (machine-readable) - [OpenAPI 3.1 spec](https://dppautomate.com/api/openapi.json): the complete, unversioned contract for every endpoint, schema and error code. Fetch this once to learn the whole API surface. - [API reference](https://dppautomate.com/en/docs/api): human-readable reference for the Layer 2 management API. - [Quickstart](https://dppautomate.com/en/docs/quickstart): the fastest path from zero to your first passport. ## How it works (the model) - A **workspace** owns passports, learnings, settings and API keys. - A **master API key** is scoped to one workspace. Create it in the dashboard under Settings > API Keys. Give it to an LLM and it can run your entire DPP workspace. - **AI mode** governs persistence: in *Review* mode AI-generated and imported passports become drafts awaiting human approval; in *Auto* mode they become active immediately. - **Learnings** are natural-language instructions that steer future AI generations. They are captured automatically from human corrections on draft approval, or added manually. ## Two access layers - **Layer 1 - public regulatory access (no auth).** `GET https://dppautomate.com/api/public/v1/passports/{dppUid}` returns the published passport as JSON-LD (`application/ld+json`), redacted per its data access level. This is what QR codes and crawlers resolve. For battery passports (EU Battery Regulation 2023/1542, Annex XIII), an optional `Authorization: Bearer ep_grant_...` header - a per-workspace grant token issued to one named third party - widens only the `batteryPassport` section to that grant's role (`legitimate_interest`: repairers, remanufacturers, second-life operators, recyclers/waste-treatment operators; `authority`: notified bodies, market surveillance authorities, the Commission). No other section is affected, and any missing/invalid/expired token behaves exactly like no token. - **GS1 Digital Link resolver.** This domain is a GS1 DL resolver: `GET https://dppautomate.com/01/{gtin}/21/{serial}` 302-redirects to `https://dppautomate.com/dpp/{dppUid}`. The GTIN is validated (GS1 mod-10 check digit) and normalised to GTIN-14. - **Layer 2 - the management API (Bearer master key).** Base path `https://dppautomate.com/api/v1`. Authenticate every request with `Authorization: Bearer ep_live_sk_...`. Key permissions are RESOURCE SCOPES of the form `:` - e.g. `passports:read`, `passports:write`, `passports:delete`, `documents:*`, `drafts:*`, `learnings:*`, `webhooks:*`, `keys:*`, `team:*`, `billing:read|write`, `grants:*`, `ai:*`, `settings:*`, `notifications:*`, `deadlines:*`, `integrations:*`, `organization:*`, `activities:read`, `exports:read`, `workspace:*`, `support:read|write`, `analytics:read`, `account:delete`. Each operation in /api/openapi.json states the scope it needs. The legacy coarse scopes still work: `read` = every `*:read`; `write` = every `*:write` except `keys:write`, `team:write`, `grants:write`, `billing:write`; `delete` = every `*:delete` except `keys:delete`, `team:delete`, `grants:delete`, `account:delete`; `admin` or `*` = everything. A `402` is a billing gate (CAPACITY_REACHED, WORKSPACE_NEEDS_PLAN or MODEL_NOT_ALLOWED). Billing is read-only over the API EXCEPT for a plan UPGRADE - see below. ## Rate limits Per key, per minute, by billing tier: sandbox 60, starter 120, operator 600, scale 1200, enterprise 6000 requests/min. A `429` includes `Retry-After` and `X-RateLimit-Limit` / `X-RateLimit-Remaining` / `X-RateLimit-Reset` headers - back off and retry after the window resets. ## Errors - branch on the code, never the message Every Layer 2 error is `{ "error": { "message": string, "code": string, "status": number } }`. `code` is a stable machine identifier and `message` is prose that may be reworded at any time, so write your control flow against `code`. The same catalogue is published as an `enum` on the `Error` schema and as a table in the description of /api/openapi.json, generated from the one server-side table that maps codes to statuses. - `401` - `UNAUTHORIZED` (missing, malformed, revoked or expired key), `AUTHENTICATION_REQUIRED` (the backend needed an identity and none was presented). - `402` (pay or upgrade, and the only 402s that exist) - `CAPACITY_REACHED` (every active-passport slot used), `WORKSPACE_NEEDS_PLAN` (no usable plan), `AI_QUOTA_REACHED` (monthly AI allowance spent), `FEATURE_NOT_IN_PLAN` (tier does not include the capability), `PLAN_LIMIT_REACHED` (tier's numeric allowance - categories, seats - used up), `MODEL_NOT_ALLOWED` (AI model above your tier). - `403` - `FORBIDDEN` (the key lacks the required scope), `ACCESS_DENIED` (not an active member of the workspace; deliberately indistinguishable from "no such workspace"), `INSUFFICIENT_ROLE` (member, but the role is too low), `OWNER_REQUIRED` (plan change and account erasure are owner-only), `ACCESS_NOT_APPROVED` (account still awaiting approval). - `404` - `NOT_FOUND`. Also returned when a resource exists but belongs to another tenant, so existence is never leaked. - `400` - `BAD_REQUEST`, `INVALID_ARGUMENT` (a value is unacceptable; the message names the field), `INVALID_STATE` (the resource exists but is not in a state that permits this - approving a non-draft, rotating a non-active key, accepting a consumed invite), `INVALID_SCOPE`, `INVALID_CURSOR`, `INVALID_SORT`, `CONFIRMATION_REQUIRED`, `EXPORT_TOO_LARGE`, `EXPORT_SCAN_TOO_LARGE`, `INVALID_TIER`, `INVALID_INTERVAL`, `TIER_NOT_SELF_SERVE`, `ALREADY_ON_TIER`. - `409` - `PASSPORT_VERSION_CONFLICT` (someone else wrote first: re-read and retry), `BILLING_STATE_INVALID`, `CHECKOUT_REQUIRED` (no live subscription to change - first checkout is dashboard-only). - `413` - `PAYLOAD_TOO_LARGE`. `422` - `IDEMPOTENCY_KEY_CONFLICT` (same key, different body). `429` - `RATE_LIMITED` (honour `Retry-After`). - `501` - `NOT_IMPLEMENTED`, `DOWNGRADE_NOT_SUPPORTED` (both are deliberate refusals, not outages: use the dashboard). - `500` - `INTERNAL_ERROR` (retry with backoff), `SERVER_MISCONFIGURED` (not caller-fixable; retrying will not help). Inside a `200` bulk response, per-item failures carry their own `code` - `GENERATION_FAILED` or `METERING_FAILED` - on the failing element. Those never appear as an HTTP status. ## Key endpoints - [Generate a passport from a product image](https://dppautomate.com/api/openapi.json): `POST https://dppautomate.com/api/v1/passports/generate` (write). Body `{ "image": "data:image/png;base64,...", "category"?: string, "create"?: boolean }`. The main agentic entry point. Respects AI mode (Review -> draft, Auto -> active); `create:false` returns a preview without saving. - Passports: `GET|POST https://dppautomate.com/api/v1/passports`, `GET|PATCH|DELETE https://dppautomate.com/api/v1/passports/{id}`, `GET .../{id}/qr` (svg/png/json), `GET .../passports/export` (json|jsonld|csv|excel|pdf|qr). - Bulk import: `POST https://dppautomate.com/api/v1/passports/import` (up to 2,000 rows/request; batches over 200 rows run as an async job - poll `GET .../passports/import/{jobId}`). Bulk export: `GET .../passports/export` (json/jsonld/csv/excel/pdf capped at 5,000 rows, qr capped at 500 rows). Over the cap, json/jsonld/csv return `202` with a `jobId` instead of a `400` - poll `GET .../passports/export/{jobId}` for `status` ('processing'/'completed'/'failed'), `truncated` (true if the workspace scan hit its ceiling before finishing) and, once completed, `downloadUrl`. excel/pdf/qr stay synchronous-only and still return `400 EXPORT_TOO_LARGE` over the cap. - Battery telemetry (EU Battery Regulation Annex XIII dynamic data): `POST|GET https://dppautomate.com/api/v1/passports/{id}/telemetry`. POST accepts one reading or `{ "readings": [...] }` (batch capped at 500, emits exactly ONE `battery.telemetry_ingested` webhook per call - never one per reading); GET returns cursor-paginated history, newest first (`meta.next_cursor`, `null` on the last page - the old `meta.cursor`/`meta.isDone` pair is GONE). A reading with `eventType: "status_change"` that actually flips the battery's lifecycle status also emits one `battery.lifecycle_status_changed` event (old + new status). - Passport documents (the actual Annex XIII compliance artefacts - EU Declaration of Conformity, due-diligence report, carbon-footprint declaration, dismantling diagram, safety data sheet, compliance test report): `GET|POST https://dppautomate.com/api/v1/passports/{id}/documents`, `GET|DELETE .../documents/{documentId}`. Upload is ONE call - no separate upload-URL step: POST `{ "documentType": , "filename": string, "content": "" | "data:application/pdf;base64,...", "contentType"?: "application/pdf"|"image/png"|"image/jpeg" }` (same base64 convention as the `image` field on generate). Max 20 MiB per file, validated against the file as stored; oversized -> `413 PAYLOAD_TOO_LARGE`. `documentType` decides public visibility and is echoed back as `accessTier` (`public` | `legitimate_interest` | `authority`). `GET .../documents/{documentId}` returns metadata plus `downloadUrl` - a PERMANENT, unauthenticated file URL, so treat it as a secret rather than a session link. DELETE returns 204, but is refused with 400 once the passport has been published (mandated artefacts must stay resolvable for printed QR codes). Scopes: `documents:read|write|delete` - a `passports:*` key does NOT carry them. Supports `Idempotency-Key`. - Passport version history (the append-only legal record - use this to audit or reconstruct your own bulk edits): `GET https://dppautomate.com/api/v1/passports/{id}/versions` (cursor-paginated, newest first) and `GET .../versions/{version}`. `{version}` is the per-passport version NUMBER (1, 2, 3...) from the list, not a database id. A snapshot is taken at first publication (`published`/`approved`/`imported`), at every edit to an ALREADY-active passport (`updated`), and on reactivation from archive (`restored`) - draft edits are deliberately not recorded. The list is an INDEX and omits the snapshot; fetch the payload from the item endpoint, which returns all 12 sections plus `batteryPassport`, `summary` and the `status` declared at that moment. There is deliberately NO restore endpoint: to roll back, read a snapshot and PATCH the passport with it, which leaves an honest `updated` version row rather than a forged one. Scope: `passports:read`. - Duplicate a passport: `POST https://dppautomate.com/api/v1/passports/{id}/duplicate`. Returns `201` with the new record. The clone gets a fresh `dppUid` and batch id and its serial number is cleared (a serial identifies one physical item); every other section, including `batteryPassport`, is carried over. It is an ordinary create: it consumes a capacity slot (`402 CAPACITY_REACHED` if you are at your plan's limit) and lands as a DRAFT in Review mode or active in Auto mode. Scope `passports:write`. Supports `Idempotency-Key` - use it, or a retry buys you a second passport. - **Upgrade your plan when you run out of capacity:** `POST https://dppautomate.com/api/v1/billing/subscription/change` with `{ "tierId": "starter"|"operator"|"scale", "interval"?: "monthly"|"annual" }`. This is the ONE billing mutation on the API, and it exists precisely because `402 CAPACITY_REACHED` is the thing that otherwise stops you dead. **UPGRADES ONLY.** The target must rank strictly above your EFFECTIVE tier (`GET /workspace` -> `entitlement.tierId` - never `purchasedTierId`, which is only what was once attempted). Refusals: `400 ALREADY_ON_TIER`, `501 DOWNGRADE_NOT_SUPPORTED` (a downgrade can strand published passports, so a human does it), `400 TIER_NOT_SELF_SERVE` (Enterprise is sales-led), `409 CHECKOUT_REQUIRED` (no live subscription to change - first checkout needs a card and an SCA challenge you cannot answer), `403 OWNER_REQUIRED` (admins read billing, only the owner changes the plan). Omitting `interval` keeps your current one - it will never move you from annual to monthly. If the response has `requiresAction: true` the upgrade is NOT yet effective and must be confirmed in the dashboard. Scope `billing:write` (a legacy `write` key does NOT have it). Supports `Idempotency-Key`. Everything else - first checkout, cancel, reactivate, payment method - is a deliberate `501` pointing at the dashboard. - Organization verification: `POST https://dppautomate.com/api/v1/organization/verify` (no body). Checks the stored profile's VAT against EU VIES, plus offline GLN checksum and EORI format, and records the outcome. `isVerified` is only ever set true by a REAL check; a format-only pass never grants the badge, and when VIES is unreachable (`vat: "unavailable"`) an existing verification is not downgraded. Owner/admin, rate limited per workspace - back off, do not loop. Scope `organization:write`. - AI model: `GET|PUT https://dppautomate.com/api/v1/ai/model`. GET returns `selectedModelId`, `defaultModelId`, your effective `tierId` and the model catalog with an `allowed` flag - read it first, because `allowed` is what decides whether PUT succeeds. PUT `{ "modelId": string }` is refused with `402 MODEL_NOT_ALLOWED` for a model above your tier, never silently downgraded. Scopes `ai:read` / `ai:write`. - Support tickets: `GET|POST https://dppautomate.com/api/v1/support/tickets`. POST `{ "subject": string, "message": string }` records the ticket and emails the support inbox - the reply-to comes from your authenticated identity and is NOT accepted from the body. Scopes `support:read` / `support:write`. Supports `Idempotency-Key`: every call pages a human. - Analytics (read-only aggregates, no passport records): `GET https://dppautomate.com/api/v1/analytics` (passport counts by status/category, compliance and sustainability distributions, activity trend) and `GET .../analytics/views[?passport_id=...]` (how often your published passports were resolved publicly: totals, 30-day series, source/country/device breakdowns; headline numbers exclude bots and report them as `botCount`). A `passport_id` from another workspace returns `404`, never a misleading "0 views". Scope `analytics:read` - deliberately NOT granted by `passports:read`. - **GDPR Art. 17 account erasure (the widest-reaching call in the API):** `POST https://dppautomate.com/api/v1/gdpr/erasure` with `{ "confirm": "" }`. It does NOT act on your key's workspace alone - it erases EVERY workspace your ACCOUNT owns, cancelling each subscription and destroying every passport (including published ones behind printed QR codes), document, key, invoice and membership. Four guards: the dedicated `account:delete` scope (granted by nothing else - not `team:delete`, not a legacy `delete` key), owner role, `confirm` equal to your own account id exactly as `GET /v1/workspace` returns it as `userId` (NOT `confirm: true`, and NOT a workspace name - that would name the wrong object), and a server-side re-derivation of your identity so the id can only ever confirm, never select. **It is incomplete by construction:** your LOGIN IDENTITY lives with the auth provider and can only be removed from the dashboard account page, which is why the response carries `identityRemains: true`. If you only need to remove ONE workspace, use `DELETE /v1/workspaces/{id}` instead. - Drafts (Review-mode queue): `GET https://dppautomate.com/api/v1/drafts`, `POST .../drafts/{id}/approve`, `POST .../drafts/{id}/reject`. - Learnings: `GET|POST https://dppautomate.com/api/v1/learnings`, `PATCH|DELETE .../learnings/{id}`. - Workspace settings (always the workspace YOUR key belongs to - no id in the path): `GET https://dppautomate.com/api/v1/workspace` (returns identity, `role`, your acting account `userId`, and the `entitlement` summary whose `tierId` is the EFFECTIVE tier every gate reads), `GET|PUT .../workspace/mode` (`{ "mode": "review" | "auto" }`), `GET|PUT .../workspace/auto-publish-threshold` (`{ "minConfidence": 50-95 }` - values outside the band are CLAMPED, not rejected, so read the response back), `GET .../workspace/onboarding`, `POST .../workspace/onboarding/dismiss`. - Workspace lifecycle: `POST https://dppautomate.com/api/v1/workspaces` (`{ "name": string, "slug"?, "color"? }`, 201, supports `Idempotency-Key`), `GET|PATCH|DELETE .../workspaces/{id}`, `POST .../workspaces/{id}/transfer-ownership`, `POST .../invites/{token}/accept`. **TENANCY - read this before you call it:** an API key is bound to ONE workspace for its lifetime. The `{id}` in these paths must be the id of YOUR key's workspace (from `GET /workspace`); it CONFIRMS which workspace you mean, it does not select one. Any other id returns `404`, never `403` - including another workspace the same person owns. Consequence: a workspace you create over the API is NOT operable with the key that created it; issue a key inside the new workspace to work in it. Accepting an invite is the one exception - the token names the workspace, and accepting does not repoint your key either. - **Destroying or handing over a workspace (both irreversible):** `DELETE .../workspaces/{id}` cascades - it cancels the Stripe subscription and erases every passport (including published ones behind printed QR codes), document, key, invoice and member, then the workspace itself. `POST .../workspaces/{id}/transfer-ownership` (`{ "memberId": string, "confirm": string }`) permanently demotes you to `admin`, and only the new owner can undo it. Both require `confirm` to equal the workspace's EXACT current NAME (from `GET /workspace`) - NOT `confirm: true`. A boolean is a constant you could emit from a retry loop without ever having looked at what you are about to destroy; the name cannot be produced without first reading that specific workspace. A missing or wrong phrase returns `400 CONFIRMATION_REQUIRED` and nothing is changed. Scopes: delete needs `team:delete`, transfer and invite-accept need `team:write` - none of these are granted by `workspace:write`, and none by a legacy `write`/`delete` key. Delete is additionally owner-only, and a personal workspace or your last remaining workspace can never be deleted. - Webhooks (signed events - `passport.*`, `draft.*`, `battery.telemetry_ingested`, `battery.lifecycle_status_changed`, `access_grant.created`, `access_grant.revoked`, `passport.export_completed`): `GET|POST https://dppautomate.com/api/v1/webhooks` and `.../webhooks/{id}` lifecycle + `.../deliveries` + `.../test`. `GET /webhooks` returns the endpoints as a paginated `data` array; the constant event catalog is `meta.available_events` (it used to be `data.events`, which made the list impossible to paginate honestly). - Third-party battery-passport access grants (EU Battery Regulation Annex XIII `legitimate_interest`/`authority` credentials for recyclers, repairers, second-life partners and authorities): `GET|POST https://dppautomate.com/api/v1/access-grants`, `GET .../access-grants/{id}`, `POST .../access-grants/{id}/revoke` (idempotent, requires the delete permission). POST returns the plaintext `ep_grant_...` token ONCE - store it immediately, it can never be retrieved again. `expiresAt` (epoch ms, future) is mandatory. Supports `Idempotency-Key`. Creation/revocation each emit a matching `access_grant.*` webhook. ## Pagination - read this before you write a paging loop EVERY list endpoint uses ONE contract: `?cursor=&limit=` -> `{ data, meta: { limit, next_cursor }, links: { self, next } }`. `limit` defaults to 25 and is clamped to 100 (telemetry 50/200, `/passports/recent` defaults to 5). Pass `meta.next_cursor` back verbatim as the next `cursor`, or just follow `links.next`, which is a complete ready-to-fetch URL with your filters and sort preserved. **`next_cursor === null` is the ONLY end-of-results signal.** A page shorter than `limit` does NOT mean you have reached the end, and a full page does not mean there is more. Loop until `next_cursor` is `null`. If you stop early on a short page you WILL silently miss rows - this was a real bug in `GET /passports` (filters used to be applied to an already-fetched page) and it is fixed, but the rule is the contract, not an implementation detail. Filters (`status`, `search`, `category`, `favorite`, category `keywords`) are applied inside the query, not to a page that was already fetched, so a returned row is always a matching row and a page is filled across underlying page boundaries. `links.prev` does not exist on cursor endpoints: these cursors are forward-only, and a permanently-null field would misreport that as "there is no previous page". The one exception is the deprecated offset mode below, where `prev` is real. Sorting: `?sort=` or `?sort=-` for descending. Every allowed value is backed by a real database index (or by a complete snapshot ordered before it is paged), so the ordering holds ACROSS page boundaries - there is no per-page in-memory sorting anywhere. An unsupported value returns `400 INVALID_SORT` listing exactly what that endpoint accepts, rather than quietly giving you a different order. Allowlists: passports / `/passports/recent` / `/passports/categories` / `/passports/favorites` -> `created_at`, `updated_at`, `name` (default `-created_at`); `/passports/{id}/versions` -> `version` (default `-version`); `/passports/{id}/telemetry` -> `recorded_at` (default `-recorded_at`); `/access-grants`, `/drafts`, `/learnings`, `/notifications`, `/integrations`, `/webhooks`, `/api-keys`, `/team/members`, `/team/invites`, `/support/tickets`, `/activities`, `/passports/{id}/documents` -> `created_at` (plus `name` on `/api-keys`, `updated_at` on `/ai/conversations`, `due_date` on `/deadlines`, which defaults to `due_date`). `/passports/search` takes no `sort`: it is relevance-ranked, and reordering it by date would throw the ranking away. Each operation in /api/openapi.json lists its own accepted values. Snapshot-backed lists (`/drafts`, `/learnings`, `/notifications`, `/integrations`, `/webhooks`, `/api-keys`, `/team/*`, `/support/tickets`, `/deadlines`, `/activities`, `/passports/{id}/documents`, `/passports/search`) also return `meta.total`, the size of the complete result set. Cursor-backed lists cannot know that number and omit it rather than inventing one. Deprecated: `GET /passports` still accepts legacy `page`/`per_page` (and only those two params select it). That mode returns `meta.total`/`page`/`per_page`/`total_pages`, real `links.prev`, a `Deprecation: true` response header and `meta.deprecated: true`. It is computed over a snapshot bounded to the 5,000 most-recent matching rows and cannot see past it. Use cursors. Idempotency: `POST` create/mutate endpoints (including generate, import, archive/restore/favorite and telemetry ingest) accept an `Idempotency-Key` header (opaque, <= 256 chars, remembered 24h) so retries are safe. Request bodies are size-limited (2 MB JSON by default, 8 MB for the base64 image on generate, 16 MB for bulk import, ~27 MB for a document upload - the base64 expansion of the 20 MiB per-file cap). ## MCP server (Model Context Protocol) For MCP-capable agents we expose a Streamable HTTP MCP endpoint: `POST https://dppautomate.com/api/mcp` (JSON-RPC 2.0, stateless). Authenticate with the same `Authorization: Bearer ep_live_sk_...` master key. Tools include `list_passports`, `search_passports`, `get_passport`, `create_passport`, `generate_passport_from_image`, `get_compliance_readiness`, `get_workspace_stats`, `list_drafts`, `update_passport`, `approve_draft`, `reject_draft`, `import_passports`, and the `*_learning` family. The full human-in-the-loop cycle is generate -> list_drafts -> update_passport -> approve_draft / reject_draft. ## Learn about Digital Product Passports - [What is the ESPR](https://dppautomate.com/en/espr): the Ecodesign for Sustainable Products Regulation that mandates DPPs. - [DPP timeline](https://dppautomate.com/en/dpp-timeline): rollout dates by product group. - [DPP deadlines tracker](https://dppautomate.com/en/dpp-deadlines-tracker): the honest master matrix answering "when is the digital product passport mandatory?" - binding dates (battery passport 18 Feb 2027; toy DPP 1 Aug 2030; ESPR registry 19 Jul 2026) vs indicative delegated-act windows (textiles/steel/aluminium/tyres ~2026-2027, furniture ~2028), each cited to EUR-Lex/EC and stamped last verified July 2026. Electronics and cosmetics have no fixed DPP date. - Per-industry "when is a DPP mandatory for X" pages: [batteries](https://dppautomate.com/en/dpp-mandatory-batteries) (18 Feb 2027, confirmed), [textiles](https://dppautomate.com/en/dpp-mandatory-textiles) (~2027, indicative), [electronics](https://dppautomate.com/en/dpp-mandatory-electronics) (no fixed date), [furniture](https://dppautomate.com/en/dpp-mandatory-furniture) (~2028, indicative), [toys](https://dppautomate.com/en/dpp-mandatory-toys) (1 Aug 2030, confirmed), [cosmetics](https://dppautomate.com/en/dpp-mandatory-cosmetics) (no fixed date, not an ESPR priority group). - [Battery passport](https://dppautomate.com/en/battery-passport) and [2027 battery deadlines](https://dppautomate.com/en/battery-passport-deadlines-2027): EU Battery Regulation specifics. - [GS1 Digital Link](https://dppautomate.com/en/gs1-digital-link): how QR codes resolve to passports. - [DPP platform](https://dppautomate.com/en/dpp-platform), [pricing](https://dppautomate.com/en/pricing) and [DPP cost](https://dppautomate.com/en/dpp-cost): the product and what it costs. - [ERP / PIM integration](https://dppautomate.com/en/erp-pim-integration) and [integrations](https://dppautomate.com/en/integrations): connecting your existing systems. - [Glossary](https://dppautomate.com/en/glossary) and [blog](https://dppautomate.com/en/blog): background reading. - Free DPP readiness check: https://dppautomate.com/en/dpp-readiness-check ## Product-group passports - [Textile DPP](https://dppautomate.com/en/textile-dpp) - [Electronics DPP](https://dppautomate.com/en/electronics-dpp) - [Furniture DPP](https://dppautomate.com/en/furniture-dpp) - [Packaging DPP](https://dppautomate.com/en/packaging-dpp) - [Tyres DPP](https://dppautomate.com/en/tyres-dpp) - [Toys DPP](https://dppautomate.com/en/toys-dpp) - [Steel & aluminium DPP](https://dppautomate.com/en/steel-aluminium-dpp) ## Company - [About](https://dppautomate.com/en/about): DPPAutomate is built and operated from Winterthur, Switzerland. - [Contact](https://dppautomate.com/en/contact): talk to the team. - [Trust & security](https://dppautomate.com/en/trust): how we handle data. ## Optional - [Privacy policy](https://dppautomate.com/en/privacy) - [Terms of service](https://dppautomate.com/en/terms) - [Cookie policy](https://dppautomate.com/en/cookie-policy) - [Data processing agreement](https://dppautomate.com/en/dpa)