Authentication

How a partner API request presents an API key, and what the server does with it.

Every partner API endpoint requires an API key. API keys are issued by Pentr. A key has the form pentr_ followed by 48 lowercase hexadecimal characters, 54 characters in all.

Presenting the key

The server reads the key from one of three headers:

curl -H "X-API-Key: $PENTR_API_KEY" https://<pentr-host>/api/v1/usage
curl -H "Authorization: Bearer $PENTR_API_KEY" https://<pentr-host>/api/v1/usage
curl -H "Authorization: ApiKey $PENTR_API_KEY" https://<pentr-host>/api/v1/usage

The Authorization header is examined first. If its value begins with Bearer or ApiKey — case-sensitive, followed by a space — the rest of the value, with surrounding whitespace removed, is the key. Otherwise the key is read from X-API-Key. A recognised scheme is final: once Authorization carries Bearer or ApiKey, X-API-Key is not read, even if the Authorization key is empty or wrong.

Headers sent Result
Authorization: Bearer <valid key> Authenticated
Authorization: ApiKey <valid key> Authenticated
X-API-Key: <valid key> alone Authenticated
Authorization: Bearer <wrong key> and X-API-Key: <valid key> 401 Invalid or inactive API key
Authorization: Bearer (empty) and X-API-Key: <valid key> 401 API key required
Authorization: bearer <valid key> (lowercase) alone 401 API key required
Authorization: bearer <valid key> and X-API-Key: <valid key> Authenticated, from X-API-Key
Authorization: Basic … and X-API-Key: <valid key> Authenticated, from X-API-Key
No key in any header 401 API key required

Send the key in one header only.

Failures

A request that does not authenticate is answered 401 with one of three bodies.

detail When
API key required No key was found, or the key read was empty
Invalid or inactive API key The key matches no active key
Organization not found The key matches, but its organization no longer exists
{"detail": "Invalid or inactive API key"}

The 401 carries no WWW-Authenticate header. On an existing route, authentication runs before the request's parameters and fields are validated, so a request with no key is answered 401 even when a path parameter or a body field is invalid. A body that is not valid JSON is the exception: it is answered 422 before any key is read. A path that matches no route, or a method the route does not have, is answered 404 or 405 before any key is read.

What the server stores about a key

Besides a bcrypt hash of the key and a name, the stored key has two fields that bear on requests:

  • is_active — only active keys are compared with the key presented. An inactive key is answered 401 Invalid or inactive API key, the same body as a key that was never issued.
  • last_used_at — set to the current UTC time each time the key authenticates a request.

No partner endpoint returns either field. A key has no expiry date and no scopes: an active key authorizes every partner endpoint for its organization.

Whom a call acts for

A key belongs to one organization, and a call made with it acts for that organization: it reads and writes the organization's assets, engagements, schedules and webhooks. The asset, engagement, report and schedule endpoints act as a user of that organization, and an organization has one user, its owner. An engagement or schedule created with a key is recorded as created by the owner. An asset created with a key has no creator recorded, and a verification attempt made with a key has no acting user recorded.

For an organization with no user, those endpoints answer 500 No user found for organization. Three groups of endpoints do not look up a user and are unaffected: GET /api/v1/usage, POST /api/v1/schedules/preview and the webhook endpoints.

Signing keys are a different thing

The route /api/keys on the console's own UI API manages the evidence locker's signing keys, not API keys.

Last updated 2026-09-24