---
title: Authentication
description: Two auth methods — API key (Bearer) and OAuth 2.0 with PKCE — plus Idempotency-Key for safe retries and per-plan rate limits on the v1 surface.
canonical: "https://www.tracepass.eu/docs/authentication"
locale: en
source: "https://www.tracepass.eu/docs/authentication"
---

# Authentication

> Two auth methods — API key (Bearer) and OAuth 2.0 with PKCE — plus Idempotency-Key for safe retries and per-plan rate limits on the v1 surface.

TracePass v1 supports two auth methods. An **API key** (a static Bearer token) is the simplest — best for server-to-server and ERP integrations. **OAuth 2.0 with PKCE** is for third-party apps and AI assistants that act on a user's behalf with scoped, revocable access. Both arrive as a Bearer token in the standard `Authorization`header; every write also accepts an `Idempotency-Key`for safe retries. Available on every plan including Free. Daily call caps apply only to Free (100/day) and Basic (200/day); Starter and above are unlimited.

## API keys

Mint keys at **Developer → API keys** in the dashboard. Keys are workspace-scoped: every key is tied to one workspace and inherits its plan, limits, and audit log. Keys carry the `tp_` prefix followed by an opaque random suffix — there's no separate live / test prefix, and no env-namespaced sandbox. Test against your Free or Basic workspace if you need a non-production environment.

Pass the key as a Bearer token on every request:

```bash
curl -sS https://app.tracepass.eu/api/v1/products \
  -H "Authorization: Bearer tp_REDACTED_xxxxxxxxxxxx"
```

**Tip —** A leaked key is one click to revoke — open Developer → API keys, hit **Revoke**, and the key starts returning 401 within seconds. Audit-log entries for the key remain so you can reconcile what was written under it.

## OAuth 2.0 (user-authorized apps)

When you're building an app that other TracePass users connect — or an AI assistant a user authorizes — use OAuth 2.0 instead of asking them to paste an API key. The user approves specific scopes on a consent screen; the access token they generate is limited to the intersection of their dashboard role and the scopes granted. Register your app at **Developer → OAuth Apps** (or programmatically via Dynamic Client Registration at `/api/oauth/register`). We support the **authorization-code flow with PKCE** — public clients (SPA / mobile / MCP) need no secret; confidential server-side clients get one.

### The flow

Discovery metadata lives at `/.well-known/oauth-authorization-server` (RFC 8414). The short version: (1) send the user to `/api/oauth/authorize` with your `client_id`, `redirect_uri`, requested `scope`, `state`, and a PKCE `code_challenge` (`S256`); (2) they approve, and we redirect back with a single-use `code`; (3) exchange it at `/api/oauth/token` with your `code_verifier` for a 15-minute access token (plus a refresh token if `offline_access` was granted). Refresh tokens rotate on every use; revoke at `/api/oauth/revoke` (RFC 7009).

### Scopes

Request only what your app needs — the consent screen shows the user exactly these. A write scope additionally requires the authorizing user to have a write-capable dashboard role, so a viewer who grants `passports:write` still can't write. API keys, by contrast, are all-or-nothing and ignore scopes.

| Scope           | Grants                                      |
| --------------- | ------------------------------------------- |
| passports:read  | View products and digital product passports |
| passports:write | Create, update, and publish passports       |
| documents:read  | Read uploaded documents and extractions     |
| documents:write | Upload documents and run AI extractions     |
| suppliers:read  | View supplier requests                      |
| suppliers:write | Create and manage supplier requests         |
| offline\_access | Issue a refresh token (stay connected)      |

**Tip —** Users see and disconnect the apps they've authorized under **Developer → OAuth Apps → Connected Apps**; disconnecting revokes the app's tokens immediately. You can delete your own registered apps from the same screen.

## Idempotency-Key

Every `POST`, `PATCH`, and `DELETE` on the v1 surface accepts an `Idempotency-Key` header. Send a UUID v4 (or any opaque string ≤ 255 chars) per logical operation; the platform stores the first response for 24 hours and replays it if the same key + same workspace shows up again. Network retries become safe — you won't double-create a passport because your HTTP client retried on a transient 5xx.

```bash
curl -sS https://app.tracepass.eu/api/v1/passports \
  -H "Authorization: Bearer tp_REDACTED_xxxxxxxxxxxx" \
  -H "Idempotency-Key: 7b4f1e2c-9a3d-4e5b-8c1a-2d3e4f5a6b7c" \
  -H "Content-Type: application/json" \
  -d '{ "productId": "...", "gs1": { "gtin": "...", "serialNumber": "..." } }'
```

Reusing the same key with a **different request body** returns`422 Unprocessable Entity` with `error: "Idempotency-Key has already been used with a different request body. Use a new key for the new request, or reuse the original body."` — the platform doesn't silently overwrite, because that's almost always a client bug. Generate a fresh key for genuinely new operations.

## Rate limits

Two ceilings apply per UTC day, per workspace:

### 1\. v1 write budget — \`maxV1WritesPerDay\`

Counts every `POST` / `PATCH` / `DELETE` on the v1 surface. Reads aren't budgeted on this counter.

### 2\. v1 passport-read budget — \`maxV1PassportsPerDay\`

Counts every `GET` on the passport endpoints (single read + paginated list). Separate from the write budget so an ERP pushing updates doesn't starve your read quota and vice versa.

| Plan       | Writes / day | Passport reads / day |
| ---------- | ------------ | -------------------- |
| Free       | 100          | 100                  |
| Basic      | 200          | 200                  |
| Starter    | Unlimited    | Unlimited            |
| Growth     | Unlimited    | Unlimited            |
| Scale      | Unlimited    | Unlimited            |
| Pro        | Unlimited    | Unlimited            |
| Enterprise | Unlimited    | Unlimited            |

When you hit either ceiling the response is `429 Too Many Requests` with a body like `{ "error": "API rate limit: 200 writes/day via /api/v1. Currently 200 today; requested 1. Retry tomorrow (UTC) or upgrade your plan." }`. The window resets at 00:00 UTC; we don't support rolling windows because they make the limit harder to reason about downstream.

A separate quota — `maxDpps` — caps total active DPPs in your workspace and is enforced via a `402` response with an `overage_required` body when the plan supports per-passport overage. See the [Create a passport](/docs/create-passport) endpoint for the overage flow.

**Need more headroom?** Either upgrade your plan in the dashboard (instant) or contact [support@tracepass.eu](mailto:support@tracepass.eu) for an Enterprise quote. We don't do per-scan billing on the public passport viewer — only writes via this API count.

## What we don't support (yet)

The OAuth **client-credentials** grant (machine identity with no user) and IP allow-listing are on the roadmap but not shipped. For pure server-to-server access today, use an API key — mint one per integration so you can revoke at the integration level.
