API reference
Extrovert exposes a JSON REST API under /v1. The SDKs and MCP server use the same agent-plane
contract, so their operations are also available over HTTP.
Base URL
Section titled “Base URL”export EXTROVERT_API_BASE_URL="https://api.extrovert.dev"Set EXTROVERT_API_BASE_URL to override this URL in the SDK, MCP server, or your own client. Use
mock with the TypeScript SDK to run against local fixtures.
Authentication
Section titled “Authentication”The API accepts scoped agent keys and explicitly consented connections. Inspect GET /v1/auth/me
for the credential actually in use; identity, resource reach, and actions are separate.
- Agent plane (
/v1/..., excluding/v1/admin) accepts a permissionedpk_agent_...key, API-audience connection token, or independentev_credential_...credential. Legacy keys retain their stored tenant/ownership ceiling. Connections use the reach and actions granted at consent. A project path narrows the request within that authority; knowing an ID never grants access. - Console / admin plane (
/v1/admin/...) accepts a signed-in human session or an explicitly authorized API-audience Full account control connection. Current human roles and addressed org/project still bound every operation. Delegated activity records the connection and authorizer. Ordinary agent keys and mail scopes do not authorize administration.
Hosted MCP OAuth has its own audience. Do not copy that token into a direct API client. See Connections and access for consent, the 24-hour full-control setup option, independent credentials, handoff, and revocation. Never give an agent a human session.
curl -H "Authorization: Bearer pk_agent_..." "$EXTROVERT_API_BASE_URL/v1/inboxes"POST /v1/enroll and POST /v1/agent/sign-up are the unauthenticated bootstrap endpoints. See
Authentication & keys and Enrollment & identity.
Legacy agent-key tiers
Section titled “Legacy agent-key tiers”An agent key carries a ceiling tier in its authenticated server record. The tier is the maximum reach of the key. A call may narrow within it by path but never above it.
| Tier | Prefix | Reach | Created by |
|---|---|---|---|
| org | pk_agent_org_... | The key’s org and its descendant orgs (the whole subtree). | Console / admin only; never self-served. |
| project | pk_agent_... | One bound project. This is the default. | Enrollment redeem + console. |
| inbox | pk_agent_inbox_... | One bound inbox in one project. | Console / admin only. |
Project keys use the bare pk_agent_... prefix. A key cannot issue a child key
with broader authority than its own.
Addressing projects by path
Section titled “Addressing projects by path”The canonical agent path puts the project (and inbox) in the URL:
/v1/projects/{project_id}/inboxes/{inbox_id}/v1/inboxes(bare) is curl sugar that resolves to the project key’s bound project. For an org key a bare list returns400 breadth_required; pick a breadth (see pagination)./v1/projects/-/inboxesis the org-wide wildcard; an org key lists every inbox across its subtree. A non-org key on the wildcard is403 forbidden_scope./v1/projects/{other}/...where{other}is outside the key’s ceiling is404 not_found(never an existence oracle).
Explicit connections can select a project inside their granted boundary. Their bare inbox list
uses the consented reach, including selected IDs or multiple projects; do not apply legacy
agent-owned-only assumptions. Check whoami.connection before choosing breadth.
Endpoints
Section titled “Endpoints”The interactive OpenAPI reference lists every operation with its request, response, parameter, and authentication schemas. These curated pages explain the common workflows.
| Area | Curated page | What’s there |
|---|---|---|
| Connections & administration | Connections and access | consent, resource/action choices, administrative discovery, worker handoff and separate revocation |
| Identity & enrollment | Enrollment & identity | enroll, sign-up, verify, /v1/auth/me |
| Inboxes | Inboxes | create, list, get, update, credentials, delete, and send |
| Messages & threads | Messages & threads | list / get / raw / search / mark-read / reply / forward / threads / attachments |
| wait_for_email | wait_for_email | POST /v1/inboxes/{inbox_id}/wait (+ legacy POST /v1/wait) |
| Webhooks | Webhooks | CRUD + HMAC delivery + signature verification |
| Review Loop | Agent contract | reviews, events, decisions, categories, and writing rules |
| Domains | Interactive reference | onboard, verify, list, inspect, and offboard domains |
| Commerce | Purchase approvals | quote domains and create, inspect, cancel, or poll human-controlled purchase and plan requests |
| Suppressions | Unsubscribes & suppression | precheck, list, and revoke recipient opt-outs |
| Jobs | Interactive reference | poll asynchronous domain offboarding |
| Errors | Errors | status codes and the problem+json envelope |
The /v1/admin/* console plane is browser-authenticated and appears in the
interactive reference and the OpenAPI spec.
Conventions
Section titled “Conventions”| Topic | Rule |
|---|---|
| Content type | Requests are application/json; error responses are application/problem+json. |
| IDs | Prefixed and opaque: msg_..., thread_..., agent_..., wh_..., and the inbox id (pmbx_...). Treat every id as an opaque string. An inbox’s canonical key is its opaque id ({inbox_id}); its email address is accepted in the same path slot as a within-project alias (URL-encode it: agent7%40extrovertmail.com). |
| Resource envelope | Newer resources include an object discriminator, scope ids, and timestamps. Legacy message, thread, and attachment shapes vary; use the schema for the resource you are reading. |
| List pagination | Cursor-paginated collections return the one list envelope { object: "list", data, has_more, next_cursor }. next_cursor is opaque; pass it back verbatim as ?cursor=. Use ?limit= (1-100, default 50). See List pagination & cursors. |
| Idempotency | Mutating operations that document Idempotency-Key replay the original response for the same key and body. Reusing a key with a different body returns 409 idempotency_conflict. The key is scoped per tenant, agent, and project. |
| Versioning | Send a dated Extrovert-Version header (e.g. 2026-06-23) to pin a snapshot; omit it for the latest. A request-time shim transforms older pinned versions to the current shape. |
| Expansion | ?include= opt-in expands relations (per-resource allowlist, depth <= 2); each join re-applies the same ceiling/ownership filter. A disallowed or too-deep path is 400 bad_request. |
| Timestamps | Resource timestamps are ISO 8601 UTC (2026-06-18T18:04:11Z); message date is the original RFC5322 header value. |
| Errors | RFC-9457 application/problem+json with a closed machine code enum. See Errors. |
List pagination & cursors
Section titled “List pagination & cursors”Cursor-paginated collections (e.g. /v1/projects/{project_id}/inboxes) return the one envelope:
{ "object": "list", "data": [ /* ... resource objects ... */ ], "has_more": true, "next_cursor": "eyJvIjo1MCwidiI6MX0"}next_cursoris an opaque token. Do not parse it. Pass it back as?cursor=to fetch the next page. It isnullwhen there are no more rows.?limit=bounds the page size to[1, 100](default 50).- Breadth follows the key tier. A project/inbox key on the bare
/v1/inboxes(or/v1/projects/{own}/inboxes) lists that project’s inboxes. An org key must pick a breadth: the bare list returns400 breadth_required(the problem body names the next call), and/v1/projects/-/inboxesreturns the org-wide list (rows labeled with theirorg_id+project_id).
Rate limits
Section titled “Rate limits”Per-key limits are published and designed not to punish fan-out across many inboxes. A 429 carries a
Retry-After header. See Rate limits & quotas. fan-out friendly
Per-endpoint reference
Section titled “Per-endpoint reference”- Interactive OpenAPI reference: every operation generated from the spec
- Enrollment & identity: enroll, sign-up, verify, and
/v1/auth/me - Inboxes: create, list, get, update, credentials, delete, and send
- Messages & threads: list, search, read, mark, reply, forward, attachments, and threads
- wait_for_email:
POST /v1/inboxes/{inbox_id}/wait - Webhooks: CRUD and signature verification
- Errors: status codes and problem+json
codes