Skip to content

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.

Terminal window
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.

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_…). The customer_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.
Terminal window
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.

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.

TierPrefixReachCreated by
orgpk_agent_org_…The key’s org and its descendant orgs (the whole subtree).Console / admin only; never self-served.
projectpk_agent_…One bound project. This is the default.Enrollment redeem + console.
inboxpk_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.

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 returns 400 breadth_required; pick a breadth (see pagination).
  • /v1/projects/-/inboxes is the org-wide wildcard; an org key lists every inbox across its subtree. A non-org key on the wildcard is 403 forbidden_scope.
  • /v1/projects/{other}/... where {other} is outside the key’s ceiling is 404 not_found (never an existence oracle).

The interactive OpenAPI reference lists every operation with its request, response, parameter, and authentication schemas. These curated pages explain the common workflows.

AreaCurated pageWhat’s there
Identity & enrollmentEnrollment & identityenroll, sign-up, verify, /v1/auth/me
InboxesInboxescreate, list, get, update, credentials, delete, and send
Messages & threadsMessages & threadslist / get / raw / search / mark-read / reply / forward / threads / attachments
wait_for_emailwait_for_emailPOST /v1/inboxes/{inbox_id}/wait (+ legacy POST /v1/wait)
WebhooksWebhooksCRUD + HMAC delivery + signature verification
Review LoopAgent contractreviews, events, decisions, categories, and writing rules
DomainsInteractive referenceonboard, verify, list, inspect, and offboard domains
CommercePurchase approvalsquote domains and create, inspect, cancel, or poll human-controlled purchase and plan requests
SuppressionsUnsubscribes & suppressionprecheck, list, and revoke recipient opt-outs
JobsInteractive referencepoll asynchronous domain offboarding
ErrorsErrorsstatus codes and the problem+json envelope

The /v1/admin/* console plane is browser-authenticated and appears in the interactive reference and the OpenAPI spec.

TopicRule
Content typeRequests are application/json; error responses are application/problem+json.
IDsPrefixed 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 envelopeNewer 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 paginationCursor-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.
IdempotencyMutating 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.
VersioningSend 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.
TimestampsResource timestamps are ISO 8601 UTC (2026-06-18T18:04:11Z); message date is the original RFC5322 header value.
ErrorsRFC-9457 application/problem+json with a closed machine code enum. See Errors.

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_cursor is an opaque token. Do not parse it. Pass it back as ?cursor= to fetch the next page. It is null when 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 returns 400 breadth_required (the problem body names the next call), and /v1/projects/-/inboxes returns the org-wide list (rows labeled with their org_id + project_id).

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