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
| Tool | Action string | Subject |
|---|---|---|
team.invite_member | team.invite_member | invitee email |
team.remove_member | team.remove_member | user id |
team.change_role | team.change_role | <userId>:<newRole> |
billing.cancel_subscription | billing.cancel | (none) |
billing.resume_subscription | billing.resume | (none) |
billing.change_plan | billing.change_plan | priceId |
api_key.create | api_key.create | requested scopes |
api_key.revoke | api_key.revoke | key id |
workspace.delete | workspace.delete | (none) |
workspace.transfer_ownership | workspace.transfer_ownership | new 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
| Error | Cause | Resolution |
|---|---|---|
wrong_code | Mistyped digits | Try again. 5 wrong attempts consumes the request — start over. |
too_many_attempts | 5 wrong attempts | Request a new code. |
expired | 10 minutes elapsed since request | Request a new code. |
consumed | Code already used or token already consumed | Request a new code. |
wrong_key | Token issued to a different API key | Verify you're using the right key. |
wrong_action / wrong_subject | Mismatch between code-paste and destructive call | Restart 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_actiondirectly
The protocol is identical. Browser automation just removes the human-in-the-loop step.
Related
- Safety tiers — T0/T1/T2 explainer.
- MCP — main MCP overview.
- Mutations — T1 funnel + entity target_token flows.