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 full-permission key.
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 key: GET /v1/auth/me
Section titled “Inspect the current key: GET /v1/auth/me”Return the principal behind the agent key, including its fixed org and project. This endpoint requires only a valid key, so an agent can always inspect its own authority.
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_…", "tier": "project", "agent_id": "agent_Q1x", "key_id": "key_5dR", "scopes": ["mailbox:create", "mailbox:read", "mailbox:send"]}tier is the key’s 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
agent 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).
- Enrollment tokens & scopes explains the capability model.
- Inboxes covers the first call to make with the new agent key.