FeaturesPlatform MCPPlatform API Keys

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

  1. Enter a human-readable Name — e.g. CI/CD Agent, drift-a2d policy, codex-cli laptop.
  2. 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.
  3. Click Generate Key.
  4. 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:

  1. Generate a new key with the same name suffixed by a date.
  2. Update your consumers to use the new key.
  3. Revoke the old key from the same panel.

Scope

Every key carries a scope column that determines which routes it can reach.

ScopeData 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.

ColumnPurpose
idUUID primary key.
organization_idOwning org — enforced by RLS.
key_hashSHA-256 hex of the raw key. Unique.
nameHuman-readable label.
scopefull (default) or policy.
bound_asset_idsUUID array of mcp_servers.id when scope is policy. Capped at 100.
created_atMint time.
created_byUser id of the operator who minted it.
expires_atOptional expiry — not yet surfaced in the UI.
last_used_atFire-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.
  • routespec / 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".

Next Steps

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.