TracePass
Passports

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.

PATCH/api/v1/passports/{id}/fields/{key}
Download OpenAPI 3.1
PATCH/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.

The status follows who is writing, not the channel. An API key writes status: "approved" (it is a company-scoped service account). An OAuth token writes with its user's dashboard rights, so a role below admin lands pending_review. A value sent with source: "ai_suggested" always lands pending_review, and the request is refused (status 400) when only the economic operator may state the value or it must be measured (e.g. battery stateOfHealth); send it with another source if your organisation is making that statement. ai_approved and system are set by TracePass and are refused as request values.

An alternate addressing form exists at PATCH /api/v1/passports/by-serial/{serial}/fields/{key} — same body, same response, useful when your ERP only knows the customer-side serial. Counts as one v1 write. Honours Idempotency-Key.

No-op (200 unchanged: true): when the incoming value, computed status and source locale are all identical to the stored state, the endpoint skips the write entirely — no version bump, no audit entry, no snapshot — and returns 200 with unchanged: true alongside the current field and version. This prevents spurious version increments from ERP integrations that re-push the full product record on every sync cycle. The response body shape is the same as a real write; check the unchanged key to distinguish the two cases.

Required-field guard (400): once a passport is published, sending null or an empty string for a field that the regulation requires for this passport's category is refused with required_field_empty (HTTP 400). The fields array in the response body names every affected key. Drafts remain free to edit.

Size limits (400 / 413): a value may hold at most 1,000 array items (nested arrays included), 20,000 characters in any single string (each language of a translatable value counts separately) and 256 KB in total, nested at most 8 levels deep. A larger value is refused with field_value_too_large, naming the field, the limit type (items, characters, bytes or depth) and the limit. A request body declared larger than 1 MB is refused with 413 payload_too_large.

Write lock (409): once a passport is published, identity fields (gtin, serialNumber, uniqueProductIdentifier, globalUniqueProductIdentifier, productIdentifier, identifierStandard, batteryUniqueIdentifier, batteryIdDmcCode, batteryPassportIdentifier) cannot change if they already hold an approved value — the response is 409 identifier_locked with the field names in keys. A pending_review value can be corrected; an empty field can be filled. Writing the same value back (including a GTIN padded to 14 digits, or a whitespace-trimmed match) is accepted when the write keeps the value approved, as an API key does. A write with source: "ai_suggested", or from an OAuth user below admin, lands as pending_review and is refused. Writing to an archived passport returns 409 passport_archived. A concurrent write race returns 409 concurrent_update — reload and retry. Suspended and expired passports remain editable.

Path parameters

  • idrequired

    string (24 hex chars)

    Passport ID. To address by serial instead, use PATCH /api/v1/passports/by-serial/{serial}/fields/{key}.

    e.g. 6650b2c3d4e5f6a7b8c9d0e1

  • keyrequired

    string

    Field key (camelCase) as defined on the passport's template — e.g. nominalVoltage, batteryChemistry, recycledContentCobalt. 400 if the key isn't on the template.

    e.g. ratedCapacity

Headers

  • Authorizationrequired

    string

    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.

    e.g. Bearer tp_REDACTED_xxxxxxxxxxxx

  • Idempotency-Key

    string

    UUID v4 per logical operation.

Body fields

  • valuerequired

    string | number | boolean | array | object

    New value. The platform doesn't reformat strings into numbers — send the value in the type the template field expects.

    e.g. 5.24

  • source

    enum

    Tag the origin of the value. One of: manual, ai_suggested, reference_db, supplier. Default manual. ai_suggested lands in the review queue; the others write with your credential's rights. ai_approved and system are set by TracePass and refused here.

  • sourceLocale

    string (ISO 639-1)

    Locale of the value (one of the 24 EU locales). Drives translation direction + the public viewer's language resolution. Defaults to the passport's sourceLocale when omitted.

Request

curl -sS -X PATCH \
  https://app.tracepass.eu/api/v1/passports/6650b2c3d4e5f6a7b8c9d0e1/fields/ratedCapacity \
  -H "Authorization: Bearer tp_REDACTED_xxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{ "value": 5.24 }'

Response

{
  "field": {
    "value": 5.24,
    "source": "manual",
    "status": "approved",
    "accessLevel": "public",
    "sourceLocale": "en",
    "lastUpdatedAt": "2026-05-09T10:30:00.000Z",
    "lastUpdatedBy": "api_key:tp_89b2482d"
  },
  "version": 4
}