Skip to main content

List API Keys (admin)

Requires admin-scoped API key

This tool is only available when authenticated with an admin-scoped Phoenix API key. User-scoped keys never see it in tools/list and receive 403 forbidden_admin_scope if they try to call it directly. See Admin operations overview for details.

Inventory every API key in the calling org — across all users — with owner email, scope, last-used timestamp, and a 12-character key prefix for human identification. The raw key is never returned, only the prefix; the prefix is computed in SQL via LEFT(raw_key, 12) so the secret never enters JS land.

Tool key: admin_list_api_keys

Parameters

NameTypeRequiredDefaultDescription
user_idstringNoFilter by owner.
scope"user" | "admin"NoFilter by scope. When omitted, all scopes are returned (admin + user + legacy NULL-coerced).
include_system_managedboolNofalseInclude OAuth-issued and onboarding-managed keys.
limitint (1-500)No100Page size.
cursorstringNoOpaque cursor from nextCursor of a prior page.

Required Integrations

None.

Response

{
"apiKeys": [
{
"id": "snrq6up60g6ozkza7hm0z61v",
"name": "ops-script",
"keyPrefix": "phx_43b6c30f",
"scope": "admin",
"userId": "41edc4be-8ece-4e8a-98e6-ab12fadc0774",
"userEmail": "alice@acme.com",
"userName": "Alice",
"isSystemManaged": false,
"createdAt": "2026-05-02T15:32:52.116Z",
"lastUsedAt": "2026-05-02T15:35:02.900Z"
}
],
"nextCursor": null
}
FieldNotes
keyPrefixAlways exactly 12 characters. The full raw key is never returned in any field, audit row, or error message.
scopePre-#1150 rows with scope = NULL are coerced to "user" here. When filtering by scope=user the SQL predicate covers both 'user' and NULL rows.
userEmail / userNameEnriched from public.users outside the tenant query. null if the owner no longer exists.
isSystemManagedOAuth/onboarding-managed keys. Excluded by default (include_system_managed=false).

Filters and defaults

The default behaviour returns all scopes because this is an admin inventory tool — hiding admin keys from the admin auditing them would defeat the purpose. Narrow with scope=user or scope=admin when needed.

Pagination

Cursor-based, same shape as admin_list_users. Cursor is strictly bound to this tool — passing one to a different action returns invalid_cursor.

Use Cases

  • Quarterly key rotation: filter by scope=user, sort by lastUsedAt ascending client-side, revoke keys with lastUsedAt older than 90 days.
  • Leaked-key triage: a prefix shows up in a public commit; call this tool, match the prefix, get owner + scope + last-used, then call admin_get_consumption_by_api_key to assess blast radius before revoking.
  • Audit reporting: dump the full inventory monthly into a SOC2 evidence collector or a security review spreadsheet.

Example Usage

List active admin keys

{
"jsonrpc": "2.0",
"id": "1",
"method": "tools/call",
"params": {
"name": "admin_list_api_keys",
"arguments": { "scope": "admin" }
}
}

List one user's keys including OAuth-issued ones

{
"jsonrpc": "2.0",
"id": "1",
"method": "tools/call",
"params": {
"name": "admin_list_api_keys",
"arguments": {
"user_id": "9a3a9b40-3a6f-4f0a-9f8e-1b7f0b2c0d10",
"include_system_managed": true
}
}
}

How It Works

The org slug is derived from the API key. Inside executeInTenantSchema:

  1. api_keys is filtered by the requested userId / scope / isSystemManaged flags.
  2. keyPrefix is computed in SQL via LEFT(raw_key, 12), so the raw key never crosses the SQL/JS boundary.
  3. Ordering is (createdAt DESC, id DESC). The cursor seek is a strict tuple compare to avoid the WHERE a < x AND b < y skip bug.

After the tenant query, owner email/name is enriched from public.users. No cross-schema joins.

Each call writes an audit row with action view_api_keys, metadata = { filter: { user_id, scope, include_system_managed }, returnedCount }.

Error codes

CodeTrigger
validation_errorArgs failed Zod validation.
invalid_cursorCursor is malformed, oversized (>4096 chars), wrong version, or from a different action.
forbidden_admin_scopeKey is user-scoped or the user is no longer an org admin.

See also