Skip to main content

List Users (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.

Enumerate every user in the calling org — both active members and unexpired invitations — with role, status, the number of API keys they hold, and their all-time credit consumption. Designed for quarterly seat audits, dormant-user cleanup, and "who's burning credits?" investigations from outside the webapp UI.

Tool key: admin_list_users

Parameters

NameTypeRequiredDefaultDescription
role"member" | "admin"NoFilter by role.
status"active" | "invited"Noactive = a team_membership row exists; invited = an unexpired invitation with status='sent'.
limitint (1-500)No100Page size.
cursorstringNoOpaque cursor returned by nextCursor of a prior page.

Required Integrations

None.

Response

{
"users": [
{
"userId": "9a3a9b40-3a6f-4f0a-9f8e-1b7f0b2c0d10",
"email": "alice@acme.com",
"name": "Alice",
"role": "admin",
"status": "active",
"createdAt": "2026-04-17T02:10:09.740Z",
"apiKeyCount": 2,
"lifetimeCredits": 1234
}
],
"nextCursor": null
}
FieldNotes
userIdnull for invited-only rows that have no public.users row yet.
emailLower-cased canonical form.
createdAtteam_membership.createdAt for active rows; invitation.sentAt for invited rows.
apiKeyCountExcludes system-managed (OAuth/onboarding) keys.
lifetimeCreditsAll-time sum across the user's tool_metering rows in this org. Rounded to 6 decimals.

Note: the original spec included a lastSignInAt field; that column does not exist on public.users and is omitted. If you need sign-in tracking, that's a separate auth-instrumentation request.

Pagination

Page-by-page through the org with cursor:

# Page 1
curl "https://phoenix.hginsights.com/api/admin/users?limit=50"
# → { ..., "nextCursor": "eyJ2IjoxLCJraW5kIjoiYWN0aXZlIiwidGltZ..." }

# Page 2
curl "https://phoenix.hginsights.com/api/admin/users?limit=50&cursor=eyJ2IjoxLCJraW5kIjoiYWN0aXZlIiwidGltZ..."

Cursors are opaque base64url-encoded JSON. They embed a version discriminator (v: 1) so future cursor-shape changes can be detected cleanly. A cursor produced by admin_list_users is rejected with invalid_cursor if passed to a different action.

Use Cases

  • Quarterly seat audits: ?role=admin&status=active to confirm the expected admin set; ?status=invited to find stale invitations.
  • Credit-spike triage: dump the page, sort by lifetimeCredits client-side, identify the top consumer, then call admin_get_consumption_by_api_key to localise the spend to a specific key.
  • Onboarding dashboards: list invited users + days since sentAt to surface stuck onboardings.

Example Usage

List the first 100 active members

{
"jsonrpc": "2.0",
"id": "1",
"method": "tools/call",
"params": {
"name": "admin_list_users",
"arguments": { "status": "active" }
}
}

List all admins

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

How It Works

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

  1. Active members are queried from team_memberships and deduplicated per userId (a single user can sit on multiple teams within an org). Role aggregates via BOOL_OR(role = 'admin') so any admin membership wins; the timestamp is the earliest createdAt across the user's memberships.
  2. Invited rows come from team_invitations with status='sent' AND expires_at > now(). Same email invited to multiple teams collapses via GROUP BY lower(email).
  3. apiKeyCount and lifetimeCredits aggregate from api_keys and tool_metering separately (avoiding a Cartesian fan-out from joining all three at once).

After the tenant queries return, email/name for active rows is enriched from public.users via a single inArray lookup. No cross-schema joins.

Each call writes an audit row with action view_users, metadata = { filter: { role, status }, returnedCount }.

Error codes

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

See also