API / Enrollment & identity
Use an enrollment token to create an agent key. Free self-signup is also part of the API, but it is
currently paused. Any valid agent key can call /v1/auth/me to inspect its authority.
Redeem an enrollment token: POST /v1/enroll
Section titled “Redeem an enrollment token: POST /v1/enroll”Redeem an enrollment token (pk_enroll_...) issued from the console for a permissioned agent key
(pk_agent_...). The enrollment token in the request body is the credential.
Request
Section titled “Request”{ "token": "pk_enroll_...", // required "agent_handle": "support-bot", // optional; idempotent binding key "agent_name": "Support Bot" // optional}| Field | Type | Notes |
|---|---|---|
token | string | The pk_enroll_... enrollment token. Validated against its stored hash. |
agent_handle | string? | Client-chosen agent identifier. Same handle -> same agent (idempotent). |
agent_name | string? | Friendly display name for the agent. |
curl -sS -X POST "$EXTROVERT_API_BASE_URL/v1/enroll" \ -H "Content-Type: application/json" \ -d '{ "token": "pk_enroll_...", "agent_handle": "support-bot" }'Response 201
Section titled “Response 201”{ "agent_id": "agent_Q1x", "agent_key": "pk_agent_pkey_7Hq2...", "scopes": ["mailbox:create", "mailbox:read", "mailbox:send"], "org_id": "org_...", "project_id": "prj_..."}Enrollment creates a project-tier key (pk_agent_...) bound to the token’s org and project
(org_id and project_id). The agent cannot change either value. Every inbox, webhook, and rule it
creates belongs to that project. Org- and inbox-tier keys (pk_agent_org_... and pk_agent_inbox_...)
use explicit tier prefixes and are issued only from the console/admin plane. See
Key tiers.
Errors
Section titled “Errors”The redeem is rejected when the token is unknown, revoked, expired, or exhausted. See Errors for the envelope and status codes.
Idempotency
Section titled “Idempotency”Redeeming with the same agent_handle returns the same agent. It does not create a duplicate agent or
consume an inbox slot. The handle is unique within the token’s project, so the same handle in another
project identifies a different agent.
Self-signup: POST /v1/agent/sign-up
Section titled “Self-signup: POST /v1/agent/sign-up”When free signup is enabled, this call creates a free account and first inbox, returns a
verification-only agent key, and emails a code to human_email. Pass that code to
/v1/agent/verify to receive a durable scoped agent key; this does not grant Full account control.
The inbox uses free.extrovertmail.com.
Request
Section titled “Request”{ "human_email": "you@example.com", // required; where the code is sent "username": "support" // optional; local part for the first inbox}curl -sS -X POST "$EXTROVERT_API_BASE_URL/v1/agent/sign-up" \ -H "Content-Type: application/json" \ -d '{ "human_email": "you@example.com" }'Response 201
Section titled “Response 201”{ "customer_id": "cust_8Vd", "agent_id": "agent_Q1x", "agent_key": "pk_agent_pkey_7Hq2...", "key_prefix": "pk_agent_pkey_7Hq2", "scopes": ["signup:verify"], "address": "support@free.extrovertmail.com", "verified": false, "otp_sent_to": "you@example.com", "otp_expires_at": "2026-06-18T18:15:00Z", "message": "A verification code was sent to your email. Call POST /v1/agent/verify with it to unlock full permissions."}The unverified key can call only the verification endpoint. It cannot read or send mail, create another inbox, or export IMAP and SMTP credentials.
If the verification email cannot be sent, the API returns 502 otp_send_failed. Re-call sign-up with
the same human_email to rotate the limited key and send a fresh code.
Verify a signup code: POST /v1/agent/verify
Section titled “Verify a signup code: POST /v1/agent/verify”Confirm the emailed code and exchange the signup:verify key for a full-permission key. Send the
verification-only key as the bearer token.
Request
Section titled “Request”{ "otp": "492013" }curl -sS -X POST "$EXTROVERT_API_BASE_URL/v1/agent/verify" \ -H "Authorization: Bearer $EXTROVERT_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "otp": "492013" }'Response 200
Section titled “Response 200”{ "agent_id": "agent_Q1x", "agent_key": "pk_agent_pkey_F9zK...", "key_prefix": "pk_agent_pkey_F9zK", "scopes": ["mailbox:create", "mailbox:read", "mailbox:send"], "address": "support@free.extrovertmail.com", "verified": true, "message": "Verified. The bootstrap key was revoked and the inbox is ready.", "mailbox_quickstart": { "inbox": "support@free.extrovertmail.com", "list_mail": { "tool": "read_messages", "arguments": { "inbox": "support@free.extrovertmail.com" } }, "read_message": { "tool": "get_message", "arguments": { "id": "<message_id from read_messages>", "format": "text", "variant": "extracted" } }, "wait_for_mail": { "tool": "wait_for_email", "arguments": { "inbox": "support@free.extrovertmail.com" } } }}The bootstrap key is revoked atomically when verification succeeds. address repeats the ready inbox,
and mailbox_quickstart provides copy-ready MCP calls for listing, reading, and waiting for mail.
Fresh signups may also return an org_claim_token for the human console handoff. The signed-in user
calls GET /v1/admin/me, selects the matching item from pending_signup_claims, then calls
POST /v1/admin/signup-claims/{claim_id}/accept. An invalid or expired code returns
400 otp_invalid.
Free-signup availability: GET /v1/signup-status
Section titled “Free-signup availability: GET /v1/signup-status”This unauthenticated, non-cacheable endpoint reports whether free agent signup and first-root console
bootstrap are available. Treat 503 signup_gate_unavailable, a timeout, or a malformed response as
disabled.
{ "free_signups_enabled": false }Inspect the current credential: GET /v1/auth/me
Section titled “Inspect the current credential: GET /v1/auth/me”Return the principal behind the current API credential. Legacy keys report their stored org/project
and key_tier; explicit connections also return auth_method: "connection" and a connection
record with identity, reach, actions, expiry, and creator provenance. A valid credential is required.
Hosted MCP exposes the same inspection through whoami in that host’s session.
curl -sS "$EXTROVERT_API_BASE_URL/v1/auth/me" \ -H "Authorization: Bearer $EXTROVERT_API_KEY"Response 200
Section titled “Response 200”{ "customer_id": "cust_8Vd", "org_id": "org_...", "project_id": "prj_...", "key_tier": "project", "auth_method": "agent_key", "inbox_scope": "agent_owned", "agent_id": "agent_Q1x", "key_id": "key_5dR", "scopes": ["mailbox:create", "mailbox:read", "mailbox:send"]}For a legacy agent key, key_tier is its ceiling (org / project / inbox); org_id and project_id are resolved from
the stored key and cannot be changed by the caller. There is no mutable active-project selector on the
legacy agent-key plane; whoami is how an agent reads its authority. To act in a project, name it in the path
(/v1/projects/{project_id}/...): a project key may name only its bound project (any other -> 404); an
org key may name any project in its subtree (or - for the org-wide wildcard).
For explicit connections, inspect the connection grant rather than inferring reach from the
legacy key example. See Connections and access for resource
selection, fixed expiry and separate revocation. A full-control connection may span organizations
and need not have one bound project.
- Enrollment tokens & scopes explains the capability model.
- Inboxes covers the first call to make with the new agent key.