Skip to content

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.

{
"token": "pk_enroll_...", // required
"agent_handle": "support-bot", // optional; idempotent binding key
"agent_name": "Support Bot" // optional
}
FieldTypeNotes
tokenstringThe pk_enroll_... enrollment token. Validated against its stored hash.
agent_handlestring?Client-chosen agent identifier. Same handle -> same agent (idempotent).
agent_namestring?Friendly display name for the agent.
Terminal window
curl -sS -X POST "$EXTROVERT_API_BASE_URL/v1/enroll" \
-H "Content-Type: application/json" \
-d '{ "token": "pk_enroll_...", "agent_handle": "support-bot" }'
{
"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.

The redeem is rejected when the token is unknown, revoked, expired, or exhausted. See Errors for the envelope and status codes.

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.

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.

{
"human_email": "you@example.com", // required; where the code is sent
"username": "support" // optional; local part for the first inbox
}
Terminal window
curl -sS -X POST "$EXTROVERT_API_BASE_URL/v1/agent/sign-up" \
-H "Content-Type: application/json" \
-d '{ "human_email": "you@example.com" }'
{
"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.

{ "otp": "492013" }
Terminal window
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" }'
{
"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.

response 200
{ "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.

Terminal window
curl -sS "$EXTROVERT_API_BASE_URL/v1/auth/me" \
-H "Authorization: Bearer $EXTROVERT_API_KEY"
{
"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.