---
title: Upsert an economic-operator party
description: Set or replace one economic-operator party on a passport (manufacturer, importer, distributor, authorised representative). GLN validated. Audit-logged.
canonical: "https://www.tracepass.eu/docs/upsert-party"
locale: en
source: "https://www.tracepass.eu/docs/upsert-party"
---

# Upsert an economic-operator party

> Set or replace one economic-operator party on a passport (manufacturer, importer, distributor, authorised representative). GLN validated. Audit-logged.

```http
PATCH /api/v1/passports/{id}/parties/{role}
```

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.

Identity is fully expressed by the body: `legalName` (required), `gln` (GS1 Global Location Number — strongly recommended for multi-role disambiguation), `country` (ISO 3166-1 alpha-2), `legacyOperatorId` (VAT / EORI / supplier code as a free-text fallback), `url`. At least one of `gln` or `legacyOperatorId` must be set — without an identifier the Party doesn't actually identify anyone. When two roles share the same `gln`, the JSON-LD emission collapses them into one party with multiple roles.

Counts as one v1 write. Honours `Idempotency-Key`. The Battery Regulation requires at least { manufacturer, importer (if applicable), recycler } before a passport is accepted; the platform's regulatory-completeness scorecard surfaces missing roles in the dashboard. The matching `DELETE /api/v1/passports/{id}/parties/{role}` removes a role idempotently.

## 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 per logical operation. |
| `id` | path | ObjectId | yes | Passport ID. |
| `role` | path | enum | yes | One of: `manufacturer`, `importer`, `authorisedRepresentative`, `distributor`, `recycler`, `producerResponsibilityOrg`. Roles outside this list return 400. |
| `legalName` | body | string (1-200 chars) | yes | Legal entity name as it appears in the commercial register. Required — a Party with no name doesn't help any procurement reader, regardless of which identifier it carries. |
| `gln` | body | string (13 digits) | no | GS1 Global Location Number with valid mod-10 check digit. Strongly recommended — when two roles share the same `gln`, the JSON-LD emits a single party with both roles attached. Must validate as a real GLN; partial / digits-only strings return 400. |
| `country` | body | string (ISO 3166-1 alpha-2) | no | Two-letter uppercase country code — e.g. `DE`, `BG`, `FR`. |
| `legacyOperatorId` | body | string (1-120 chars) | no | Free-text fallback identifier for parties that don't have a GLN — VAT number, EORI, supplier code, or any internal ID your ERP carries. Required when `gln` is omitted. |
| `url` | body | string (URL, max 500 chars) | no | Organisation page — typically the company website or a corporate-info subpage. |

## Examples

```bash
curl -sS -X PATCH \
  https://app.tracepass.eu/api/v1/passports/6650b2c3d4e5f6a7b8c9d0e1/parties/recycler \
  -H "Authorization: Bearer tp_REDACTED_xxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "legalName": "EuroBat Recyclers GmbH",
    "gln": "4012345678901",
    "country": "DE",
    "url": "https://eurobat-recyclers.example"
  }'
```

```typescript
await fetch(
  `https://app.tracepass.eu/api/v1/passports/${id}/parties/recycler`,
  {
    method: "PATCH",
    headers: {
      Authorization: `Bearer ${process.env.TRACEPASS_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      legalName: "EuroBat Recyclers GmbH",
      gln: "4012345678901",
      country: "DE",
      url: "https://eurobat-recyclers.example",
    }),
  },
);
```

```python
import os, requests
res = requests.patch(
    f"https://app.tracepass.eu/api/v1/passports/{passport_id}/parties/recycler",
    headers={"Authorization": f"Bearer {os.environ['TRACEPASS_API_KEY']}"},
    json={
        "legalName": "EuroBat Recyclers GmbH",
        "gln": "4012345678901",
        "country": "DE",
        "url": "https://eurobat-recyclers.example",
    },
)
res.raise_for_status()
```

## Responses

### 200 — Upserted

```json
{
  "role": "recycler",
  "party": {
    "legalName": "EuroBat Recyclers GmbH",
    "gln": "4012345678901",
    "country": "DE",
    "url": "https://eurobat-recyclers.example",
    "status": "active"
  },
  "version": 5
}
```

### 400 — Validation error

```json
{
  "error": "Validation error",
  "details": {
    "fieldErrors": {
      "gln": ["Party must have at least one identifier (gln or legacyOperatorId)"]
    }
  }
}
```

### 404 — Not found

```json
{ "error": "Passport not found" }
```

## Related

- [Create a passport (parties at create time)](https://www.tracepass.eu/docs/create-passport.md)
- [Update one field on a passport](https://www.tracepass.eu/docs/update-field.md)
