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 full-permission key. 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 }

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.

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_…",
"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).