Skip to main content

Admin actions

Workspace administration — team management, billing, API keys, ownership transfer, workspace deletion — is gated by an out-of-band email code-paste handshake. This is T2, the highest safety tier. See Safety tiers for the model.

Why it exists

Agents are good at typing, bad at judgment. A prompt-injected agent could be tricked into:

  • Inviting an attacker to your workspace
  • Revoking your only working API key
  • Cancelling your subscription
  • Deleting the workspace

The code-paste handshake makes these actions require physical possession of the API-key-holder's email inbox. Even a fully-compromised agent cannot fake a code that arrives via a different channel.

The two-call flow

┌──────────────────────────────────────────────────────────────────┐
│ STEP 1: agent calls admin.request_action │
│ │
│ Agent → MCP server: │
│ admin.request_action({ │
│ action: "team.invite_member", │
│ subject: "[email protected]", │
│ summary: "Invite [email protected] as MANAGER" │
│ }) │
│ │
│ Server: │
│ - generates 6-digit code │
│ - stores SHA-256(code) in OobConfirmCode │
│ - emails code to the API-key-holder user │
│ - returns { requestId, expiresAt, codeHint: "••••••" } │
└──────────────────────────────────────────────────────────────────┘

┌──────────────────────────────────────────────────────────────────┐
│ STEP 2: user pastes the code; agent confirms │
│ │
│ Agent → User (in chat): │
│ "Check your email for a 6-digit code from FunnelFizz." │
│ │
│ User reads email, pastes code in chat. │
│ │
│ Agent → MCP server: │
│ admin.confirm_action({ requestId, code: "482910" }) │
│ │
│ Server: │
│ - validates code (SHA-256 hash match) │
│ - increments attempts counter │
│ - on match: mints AdminToken bound to (action, subject) │
│ - returns { adminToken, expiresAt } │
└──────────────────────────────────────────────────────────────────┘

┌──────────────────────────────────────────────────────────────────┐
│ STEP 3: agent calls the destructive tool with admin_token │
│ │
│ Agent → MCP server: │
│ team.invite_member({ │
│ email: "[email protected]", │
│ role: "MANAGER", │
│ adminToken: "ffa_..." │
│ }) │
│ │
│ Server: │
│ - consumes admin_token (single-use, atomic) │
│ - verifies action matches "team.invite_member" │
│ - verifies subject matches "[email protected]" │
│ - sends the invite │
└──────────────────────────────────────────────────────────────────┘

Sample agent transcript

USER: Invite [email protected] to the workspace as a manager.

AGENT: I'm requesting an admin confirmation code for that. Check your
email for a 6-digit code from FunnelFizz — it'll arrive in a few
seconds. The code expires in 10 minutes.

(Agent calls admin.request_action.)

USER: Got it — 482910.

AGENT: Confirmed. Inviting [email protected] as MANAGER now.

(Agent calls admin.confirm_action then team.invite_member.)

Invite sent to [email protected]. They'll receive an email to
accept the invite and join the workspace.

What requires admin code-paste

ToolAction stringSubject
team.invite_memberteam.invite_memberinvitee email
team.remove_memberteam.remove_memberuser id
team.change_roleteam.change_role<userId>:<newRole>
billing.cancel_subscriptionbilling.cancel(none)
billing.resume_subscriptionbilling.resume(none)
billing.change_planbilling.change_planpriceId
api_key.createapi_key.createrequested scopes
api_key.revokeapi_key.revokekey id
workspace.deleteworkspace.delete(none)
workspace.transfer_ownershipworkspace.transfer_ownershipnew owner userId

Self-revoke for API keys is a special case: pass confirmSelf: true and no admin_token to revoke the calling key itself. Used for compromise mitigation when the user can't reach an alternate key.

Failure modes

ErrorCauseResolution
wrong_codeMistyped digitsTry again. 5 wrong attempts consumes the request — start over.
too_many_attempts5 wrong attemptsRequest a new code.
expired10 minutes elapsed since requestRequest a new code.
consumedCode already used or token already consumedRequest a new code.
wrong_keyToken issued to a different API keyVerify you're using the right key.
wrong_action / wrong_subjectMismatch between code-paste and destructive callRestart the flow with matching values.

Browser-controlling agents

Agents with computer use (Claude with computer use, OpenClaw, etc.) can:

  • Navigate to the user's email inbox in a browser tab
  • Read the latest message from [email protected]
  • Extract the 6-digit code
  • Paste it back into the chat / call admin.confirm_action directly

The protocol is identical. Browser automation just removes the human-in-the-loop step.