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 has two authentication planes. Permissions come from the key on the agent plane and from the signed-in user plus the path on the admin plane.
- Agent plane (
/v1/..., excluding/v1/admin) uses a permissioned agent key (pk_agent_…). Thecustomer_id, org, and project are derived from the key, never from client input, so the API is tenant-scoped by construction. A broad key narrows to one project by path (/v1/projects/{project_id}/...), never a header or query. - Console / admin plane (
/v1/admin/...) uses a signed-in console session. Authority is derived from the identity’s membership in the org/project addressed in the path (/v1/admin/orgs/{org_id}/projects/{project_id}/...). There are no permission headers.
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.
Key tiers (agent plane)
Section titled “Key tiers (agent plane)”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).
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 |
|---|---|---|
| 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