API Keys API
These endpoints list, create, re-scope, and revoke API keys. A caller manages its own user's keys. An administrator can also manage any other user's keys.
A key is bounded by the key calling the endpoint:
- It cannot grant a scope it does not hold, whether creating a key or changing one.
- It cannot change or revoke a key that holds a scope it does not hold.
- It cannot revoke itself. Revoke a key with another key.
The key object
{
"id": "6a0f1c2e9b1d4e3f2a1b0c9d",
"prefix": "sk_AbCdEfGhI...",
"scopes": ["redact", "policies:read"],
"created": "2026-10-05T14:03:11.000Z",
"bootstrap": false,
"session": false,
"expiresAt": null,
"idleExpiresAt": null,
"lastUsedAt": null,
"clientAddress": null,
"userAgent": null,
"current": false
}
id- Used to change the key's scopes or revoke it.prefix- The first characters of the key, to tell keys apart. The key itself is never returned after it is created.bootstrap-truefor the key seeded fromPHILTER_BOOTSTRAP_API_KEY.session-truefor a session key, issued when a person signs in;falsefor a long-lived key.expiresAt- Session keys only: when the key's maximum lifetime ends.nullfor a long-lived key.idleExpiresAt- Session keys only: when the key expires unless it is used before then. Each request moves it forward.nullfor a long-lived key.lastUsedAt- Session keys only: the last request made with the key. Philter does not record when a long-lived key was last used.clientAddressanduserAgent- Session keys only: the address and user agent (such as a browser) of the client that signed in, so a person can tell their sessions apart. The address follows the trusted-proxy rules, and the user agent is cut to 256 characters. Both are kept only while the session lasts: when it is revoked or expires they are removed.nullfor a long-lived key, anduserAgentisnullwhen the client sent none.current-truefor the key making the request, so a client can mark the session it is using.
List the scopes
GET /api/api-keys/scopes
Returns every scope an API key can carry, with what it allows, in the order Philter declares them. Use it to offer scopes to choose from instead of hard-coding them; a scope Philter adds appears here. Any key can call it, whatever its scopes. See Scopes.
{
"scopes": [
{ "name": "redact", "description": "Redact text and documents, and explain redactions." },
{ "name": "contexts:read", "description": "List and read contexts and their entries, including exports." }
]
}
List your keys
GET /api/api-keys?offset=0&limit=25
Lists the active keys belonging to the calling key's user, oldest first, and the total. Requires api-keys:read. limit is capped at 100.
session(optional) -truelists only session keys,falseonly long-lived keys. Left out, both are listed.totalcounts only the keys listed, so paging works the same either way.order(optional, defaultasc) -asclists the oldest key first,descthe newest.sortaccepts onlycreated; see Listings.
{
"apiKeys": [
{
"id": "6a0f1c2e9b1d4e3f2a1b0c9d",
"prefix": "sk_AbCdEfGhI...",
"scopes": ["redact"],
"created": "2026-10-05T14:03:11.000Z",
"bootstrap": false,
"session": false,
"expiresAt": null,
"idleExpiresAt": null,
"lastUsedAt": null
}
],
"total": 1
}
List a user's keys
GET /api/users/{username}/api-keys?offset=0&limit=25
Lists the named user's active keys. Accepts session as List your keys does. Requires api-keys:read and an administrator. Returns 404 Not Found if there is no such user.
Create a key
POST /api/api-keys
POST /api/users/{username}/api-keys
The first form creates a key for the calling key's user and does not require an administrator, so a key can be rotated by the user that owns it. The second creates a key for the named user and requires an administrator; it returns 404 Not Found if there is no active user with that username. Both require api-keys:write.
A key for another user lets whoever holds it act as that user. Unless ADMIN_CROSS_USER_ACCESS_ENABLED is true, an administrator can create a key for another user only if that user has never had a key, including a revoked or session key: enough to provision a new user, who can then create and rotate their own keys and revoke the one the administrator created. With the setting on, a key for a user who has had one is also recorded as an admin_cross_user_access audit event.
{
"scopes": ["redact"]
}
scopes(required) - At least one scope, each held by the calling key. There is no default.
201 Created:
{
"id": "6a0f1c2e9b1d4e3f2a1b0c9d",
"username": "ci",
"apiKey": "sk_abcdefghijklmnopqrstuvwxyz012345",
"scopes": ["redact"]
}
The key value is returned once, in this response. Philter stores only its SHA-256 hash and cannot recover it.
| Status | Meaning |
|---|---|
| 400 | No scopes were given, or one of them is not a scope. |
| 403 | The key does not hold api-keys:write, is a session key, the caller is not an administrator (second form), a requested scope is not held by the calling key, or the user has had a key and ADMIN_CROSS_USER_ACCESS_ENABLED is off (second form, feature_disabled). The message says which. A session key cannot create keys, so a session cannot produce a credential that outlives it. |
curl -k "https://localhost:8080/api/api-keys" \
-H "Authorization: Bearer sk_abcdefghijklmnopqrstuvwxyz012345" \
-H "Content-Type: application/json" \
--data '{"scopes":["redact"]}'
To rotate a key: create a new key, update the integration to use it, then revoke the old key with the new one.
Change a key's scopes
PUT /api/api-keys/{keyId}/scopes
{
"scopes": ["redact"]
}
Replaces the key's scopes and returns the key. The key value does not change, so integrations keep working with the same credential. Requires api-keys:write.
| Status | Meaning |
|---|---|
| 400 | No scopes were given, or one of them is not a scope. |
| 403 | The key does not hold api-keys:write, a requested scope is not held by the calling key, the key being changed holds a scope the calling key does not, or the calling key is a session key and the change adds a scope. A session key can narrow a key's scopes but not widen them. |
| 404 | There is no active key with that ID that the caller may manage. A non-administrator gets this for another user's key. |
| 409 | The key is the one making the request. A key cannot change its own scopes, even to narrow them, since a client doing so would cut its own access; change them with another key. |
Revoke a key
DELETE /api/api-keys/{keyId}
Revokes the key and returns 204 No Content. A revoked key cannot be restored. Requires api-keys:write.
| Status | Meaning |
|---|---|
| 403 | The key does not hold api-keys:write, or the key being revoked holds a scope the calling key does not. |
| 404 | There is no active key with that ID that the caller may manage. A non-administrator gets this for another user's key. |
| 409 | The key is the one making the request. |
Sign out
DELETE /api/api-keys/current
Revokes the session key making the request and returns 204 No Content. Any key can call it, whatever its scopes, because it can only end the caller's own access; no administrator is needed.
| Status | Meaning |
|---|---|
| 409 | The calling key is a long-lived key. A long-lived key cannot revoke itself; revoke it with another key. |
Sign out every other session
DELETE /api/users/me/session-keys
Revokes every session key the caller's user holds except the key making the request, so a person can sign out the sessions they do not recognize and stay signed in where they are. Called with a long-lived key, it revokes all of the user's session keys. Long-lived keys are not affected. Requires api-keys:write; no administrator is needed, because it only ends the caller's own sessions.
List the sessions first with GET /api/api-keys?session=true, which gives each one's clientAddress, userAgent, lastUsedAt, and whether it is current. To end one session, revoke that key.
200 OK:
{
"revoked": 2
}
Revoke a user's session keys
DELETE /api/users/{username}/session-keys
Revokes every session key the user holds, signing the person out everywhere. Long-lived keys are not affected. Requires api-keys:write and an administrator.
200 OK:
{
"revoked": 2
}
| Status | Meaning |
|---|---|
| 404 | There is no user with that username. |
When revocation and scope changes take effect
Philter caches each resolved key for up to API_KEY_CACHE_TTL_SECONDS (default 60). Revoking a key or changing its scopes evicts it from the cache, so the change applies to the next request on the instance that handled it, and on every instance that shares the same Valkey/Redis cache. Where instances do not share a cache, or a request on another instance races the change, another instance can accept the old key or scopes until its cache entry expires, at most API_KEY_CACHE_TTL_SECONDS later. Session keys are checked against the database on every request, so their revocation and expiry apply at once on every instance.
Errors common to every endpoint
| Status | Meaning |
|---|---|
| 401 | The Authorization header is absent or the API key is not recognized. |
| 403 | The key does not hold the endpoint's scope, or the caller is not an administrator where one is required. The message says which. |
Auditing
| Event | Recorded when |
|---|---|
api_key_created |
A key was created. The principal is the new key, the associated object is the user it belongs to, and the details name the calling user and API key and the scopes. |
api_key_scopes_changed |
A key's scopes were changed. The details record the scopes before and after and name the calling user and API key. |
api_key_deleted |
A key was revoked. The details name the calling user and API key, or give the reason, such as signed out, signed out other sessions, or a password change. |
api_key_expired |
A session key passed its idle timeout or maximum lifetime. The principal is the key, the associated object is its user, and the details give the reason. |
These are security events, so they cannot be switched off. They are readable through GET /api/audit.