API Keys
Create, list, rotate, revoke, and audit tenant-scoped API keys — including MCP-only keys — from Settings → API keys.
API keys
API keys authenticate machine-to-machine calls against the Pact REST API and
the MCP transport. Every key is tenant-scoped: a key is only ever visible
to — and only ever acts inside — the tenant that minted it. The management
surface lives under /v1/api-keys (api/routes/api_keys.py) and backs the
Settings → API keys page.
Admin only
Every route on /v1/api-keys is guarded by verify_api_key and
require_module("admin"). You need an admin-module key or session to list,
create, rotate, or revoke keys.
Secret format and storage
A key's secret is minted by generate_secret() (models/api_key.py). The
environment field on the create request chooses the prefix:
environment: "live"→pact_live_…environment: "test"→pact_test_…
The server stores only two things: the public prefix (the base plus the first 8 hex characters, shown in lists so you can tell keys apart) and the SHA-256 hash of the full secret. The plaintext secret is materialized exactly once, returned in the create/rotate response, and never persisted. If you lose it, you rotate — there is no reveal endpoint.
curl -X POST https://api.pact.place/v1/api-keys \
-H "Authorization: Bearer pact_live_…" \
-H "Content-Type: application/json" \
-d '{"name":"CI deploy key","environment":"live","scopes":["read:contacts"]}'
# → { "prefix": "pact_live_ab12cd34", "secret": "pact_live_…", "warning": "Save this secret …" }
Lifecycle
- 1
Create
POST /v1/api-keysmints a key with a name, optionalscopes,rate_limit_per_minute(default 100), and optionalexpires_at. The plaintextsecretis in the response body — copy it now. - 2
List / inspect
GET /v1/api-keysandGET /v1/api-keys/{id}return metadata only (prefix, scopes, rate limit, last-used, rotation lineage). They never return the secret or hash. Passinclude_revoked=trueto see revoked keys. - 3
Rotate
POST /v1/api-keys/{id}/rotatemints a replacement linked to the original viarotated_from_idand hands you a fresh secret. By default the old key keeps a 24-hour grace window (itsrevoked_atis set to now + 24h) so you can swap config without downtime. Passimmediate=trueto hard-revoke the old key at once. - 4
Revoke
DELETE /v1/api-keys/{id}setsrevoked_at = now(). The next request authenticated by that key gets a 401.
MCP-only keys
Set mcp_only: true at creation to restrict a key to the MCP transport
(/mcp/). If such a key is presented to the REST API, verify_api_key
rejects it with 403 and a message pointing you at Settings → API keys —
it deliberately fails loudly rather than falling through to a confusing 401.
The client_label field (free text like "Claude Desktop", "Cursor") is shown
in the key list so you can see which client each key was minted for.
Usage and audit
Every lifecycle action (created, used, rotated, revoked) writes an
append-only row to api_key_audit. GET /v1/api-keys/{id}/usage?days=N
returns a per-day request histogram sourced from api_key_audit rows where
action = 'used' (default window 7 days, max 90). Key creation and revocation
also emit SOC-2 audit events (auth.api_key.created / auth.api_key.deleted)
and domain events (api_key.created, api_key.rotated, api_key.revoked)
that outbound webhooks can subscribe to.