---
title: Get the EU DPP Registry address for a battery passport
description: "Get the unique product identifier to paste into the EU DPP Registry's online form for a battery passport. The operator registers it itself. Paid plans."
canonical: "https://www.tracepass.eu/docs/passport-registry-upi"
locale: en
source: "https://www.tracepass.eu/docs/passport-registry-upi"
---

# Get the EU DPP Registry address for a battery passport

> Get the unique product identifier to paste into the EU DPP Registry's online form for a battery passport. The operator registers it itself. Paid plans.

```http
POST /api/v1/passports/{id}/registry-upi
```

Returns the unique product identifier (UPI) to paste into the EU DPP Registry's online form for one published **battery** passport, as `{ upi, preparedAt }`. The economic operator registers the passport on the Registry website under its own EU Login (Product group: Batteries; Granularity: Item). TracePass prepares the address and records that it did; it never submits anything to the Registry.

**The first call freezes the address** on the passport as `registryUpi`, and later calls return that same address. The Registry keeps the URL it was given, so the URL has to keep resolving even if your account's domain changes later. Every call records the preparation on the passport (`registrationPreparedAt`, `registrationPreparedVia: "form"`). A preparation is not a registration: once the Registry has answered, record its answer with `POST /api/v1/passports/{id}/registration`.

Refusals answer **409** with a code in `error`: `not_a_battery`, `not_published` (only a published passport can be registered), `registered_in_production` (the passport is already registered in the production Registry), `registry_not_ready` (the passport has a blocking registry-readiness finding; `findingDetails` lists the findings, blocking ones first, from the same check as `GET /api/v1/passports/{id}/registry-readiness`) and `invalid_upi` (the address is not an `https://` URL of at most 2000 characters).

**Paid plans only** (`402` on Free: a Free passport expires after 30 days). OAuth callers must act for an admin or owner. No request body. Counts as one v1 write, and only when the call succeeds. Honours `Idempotency-Key`.

## Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `Authorization` | header | string | yes | `Bearer <token>` — either a `tp_` API key (Developer → API Keys; simplest, for server-to-server) or an OAuth 2.0 access token (Developer → OAuth Apps; for user-authorized apps, scoped + revocable). The Authentication page has the full OAuth flow and scope list. |
| `Idempotency-Key` | header | string | no | UUID v4. |
| `id` | path | string (24 hex chars) | yes | Passport ID of a published battery passport. |

## Examples

```bash
curl -sS -X POST \
  https://app.tracepass.eu/api/v1/passports/6650b2c3d4e5f6a7b8c9d0e1/registry-upi \
  -H "Authorization: Bearer tp_REDACTED_xxxxxxxxxxxx" \
  -H "Idempotency-Key: 7c2e4a91-3d5b-4f8e-a6c0-1b9d2e4f6a80"
```

```typescript
const res = await fetch(
  `https://app.tracepass.eu/api/v1/passports/${id}/registry-upi`,
  {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.TRACEPASS_API_KEY}`,
      "Idempotency-Key": crypto.randomUUID(),
    },
  },
);

if (res.status === 409) {
  const { error, findingDetails } = await res.json();
  // e.g. registry_not_ready: findingDetails lists the blocking findings first.
  console.log(error, findingDetails ?? []);
} else if (res.ok) {
  // Paste upi into the EU DPP Registry's online form, under your own EU Login.
  const { upi } = await res.json();
  console.log(upi);
}
```

```python
import os, uuid, requests
res = requests.post(
    f"https://app.tracepass.eu/api/v1/passports/{passport_id}/registry-upi",
    headers={
        "Authorization": f"Bearer {os.environ['TRACEPASS_API_KEY']}",
        "Idempotency-Key": str(uuid.uuid4()),
    },
)
if res.status_code == 409:
    body = res.json()
    print(body["error"], body.get("findingDetails", []))
else:
    res.raise_for_status()
    print(res.json()["upi"])  # paste into the Registry's online form
```

## Responses

### 200 — Address to paste

```json
{
  "upi": "https://id.tracepass.eu/01/04012345000016/21/A1B2C3",
  "preparedAt": "2026-11-06T10:00:00.000Z"
}
```

### 409 — Registry not ready

```json
{
  "error": "registry_not_ready",
  "findingDetails": [
    {
      "severity": "critical",
      "ruleId": "REG-READY-MANDATORY",
      "target": "batteryUniqueIdentifier",
      "why": "A mandatory field that applies to this battery is missing.",
      "fix": "Provide batteryUniqueIdentifier before submitting to the registry."
    }
  ]
}
```

### 409 — Already registered

```json
{ "error": "registered_in_production" }
```

### 402 — Free plan

```json
{ "error": "Available on every paid plan. Upgrade to use it." }
```

## Related

- [Record the Registry result](https://www.tracepass.eu/docs/record-passport-registration.md)
- [Build an upload file](https://www.tracepass.eu/docs/registry-upload-file.md)
- [Check registry readiness](https://www.tracepass.eu/docs/passport-registry-readiness.md)
