Platform API Keys
Every request into the Platform MCP endpoint or a policy-facing route authenticates with a Platform API key. Keys are minted per organization, hashed at rest with SHA-256, and shown to the operator exactly once at creation time.
Where to find them
Settings → Platform MCP → API Keys.
The panel lists every active key for the current organization, when each was last used, and the scope it was minted with. Revoked keys are removed immediately — there is no soft-delete window.
Key lifecycle
Generate
- Enter a human-readable Name — e.g.
CI/CD Agent,drift-a2d policy,codex-cli laptop. - Optionally choose a server under Bind to MCP server. Leaving this empty mints a full-scope key that works everywhere. Selecting a server mints a policy-scoped key restricted to that asset — see Policy-Scoped Keys.
- Click Generate Key.
- Copy the raw key from the confirmation dialog. This is the only time A²D will show it. If you lose it, revoke and regenerate.
The raw key is not stored anywhere on the A²D side after creation. The database holds only a SHA-256 hash. Treat the key like any other production secret — never commit it, never paste it into a chat or support ticket.
Use
Send the key on every request in one of two headers:
Authorization: Bearer <key>or
X-API-Key: <key>Both are accepted equivalently on every A²D route that authenticates via Platform API key.
Revoke
Click Revoke next to the key row. The next request using that key
returns 401 Unauthorized — usually within one second.
Rotate
Keys have no built-in expiry today. To rotate:
- Generate a new key with the same name suffixed by a date.
- Update your consumers to use the new key.
- Revoke the old key from the same panel.
Scope
Every key carries a scope column that determines which routes it can reach.
| Scope | Data plane/api/platform-mcp/mcp | Policy routes/api/platform/[id]/mcp/{spec,validate,evidence} |
|---|---|---|
full | ✅ every tool | ✅ every asset in the org |
policy | ❌ 403 | ✅ only the asset ids in bound_asset_ids |
Existing keys created before scoping was added default to full. No
migration is needed — legacy consumers keep working. To lock a
new-issue key down to the policy surface, use Bind to MCP server
when generating it.
Storage model
Keys live in the platform_api_keys Supabase table.
| Column | Purpose |
|---|---|
id | UUID primary key. |
organization_id | Owning org — enforced by RLS. |
key_hash | SHA-256 hex of the raw key. Unique. |
name | Human-readable label. |
scope | full (default) or policy. |
bound_asset_ids | UUID array of mcp_servers.id when scope is policy. Capped at 100. |
created_at | Mint time. |
created_by | User id of the operator who minted it. |
expires_at | Optional expiry — not yet surfaced in the UI. |
last_used_at | Fire-and-forget touch on every successful validate. |
Hashing uses sha256(raw_key) — the same digest PostgreSQL produces
via encode(sha256(raw_key), 'hex'). That symmetry lets service-role
SQL and the app validate keys with identical logic.
Audit trail
Every call to a policy-facing route is written to policy_api_audit
with:
key_id— which key made the call.asset_id— which MCP server was targeted.route—spec/validate/evidence.status,latency_ms,user_agent,ip_inet,received_at.
Inserts are fire-and-forget: an audit failure never blocks a policy response. See Policy-Scoped Keys for the query recipes.
Data-plane calls to /api/platform-mcp/mcp are captured through the
usual platform logs, not the policy audit table.
Curl smoke tests
Verify a key can reach the data plane
curl -sS -X POST https://<host>/api/platform-mcp/mcp \
-H "Authorization: Bearer $A2D_KEY" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | jq '.result.tools | length'A full-scope key returns the tool count. A policy-scoped key returns
403 with "policy-scoped keys cannot access the data plane".
Verify a key can fetch a spec
curl -sS https://<host>/api/platform/<mcp-server-id>/mcp/spec \
-H "Authorization: Bearer $A2D_KEY" | jq '.tools | length'A full-scope key or a policy-scoped key bound to <mcp-server-id>
returns 200. A policy-scoped key bound elsewhere returns 403
with "Key not bound to this asset".
Related surfaces
- Platform MCP overview — endpoint, transport, tools.
- Policy-Scoped Keys — bind a key to a specific asset.
- Flex Gateway Policies — the primary consumer of policy-scoped keys.
Next Steps
- Mint a key and try the smoke tests above.
- Restrict a new key to one MCP server with Policy-Scoped Keys.
- Wire the key into a policy following MCP Tool Drift Detection (via A²D).
Platform API keys are how anything outside a browser talks to A²D — mint them narrow, rotate them often, revoke them the moment they leak.