List API Keys (admin)
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
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
user_id | string | No | – | Filter by owner. |
scope | "user" | "admin" | No | – | Filter by scope. When omitted, all scopes are returned (admin + user + legacy NULL-coerced). |
include_system_managed | bool | No | false | Include OAuth-issued and onboarding-managed keys. |
limit | int (1-500) | No | 100 | Page size. |
cursor | string | No | – | Opaque 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
}
| Field | Notes |
|---|---|
keyPrefix | Always exactly 12 characters. The full raw key is never returned in any field, audit row, or error message. |
scope | Pre-#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 / userName | Enriched from public.users outside the tenant query. null if the owner no longer exists. |
isSystemManaged | OAuth/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 bylastUsedAtascending client-side, revoke keys withlastUsedAtolder 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_keyto 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:
api_keysis filtered by the requesteduserId/scope/isSystemManagedflags.keyPrefixis computed in SQL viaLEFT(raw_key, 12), so the raw key never crosses the SQL/JS boundary.- Ordering is
(createdAt DESC, id DESC). The cursor seek is a strict tuple compare to avoid theWHERE a < x AND b < yskip 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
| Code | Trigger |
|---|---|
validation_error | Args failed Zod validation. |
invalid_cursor | Cursor is malformed, oversized (>4096 chars), wrong version, or from a different action. |
forbidden_admin_scope | Key is user-scoped or the user is no longer an org admin. |