Primitive REST API reference
The Primitive API lives under https://api.primitive.dev/v1. Use Bearer auth with an organization API key, OAuth access token, or restricted agent connection key.
curl https://api.primitive.dev/v1/account \ -H "Authorization: Bearer $PRIMITIVE_AUTH_TOKEN"
Set PRIMITIVE_AUTH_TOKEN to an API key (prim_...) or OAuth access token (prim_oat_...).
Authentication
Send the credential in the Authorization header:
Authorization: Bearer prim_... Authorization: Bearer prim_oat_...
Use API keys for server-to-server integrations and CI. Use OAuth for CLI, desktop, local-agent, and third-party app access. OAuth access tokens are short-lived and can be revoked from Settings -> Connected Apps.
Never put API keys, OAuth access tokens, or OAuth refresh tokens in client-side code, browser local storage, screenshots, or logs.
Roles and permissions
Access is governed by organization role-based access control. Every member of an organization holds one of three roles — owner, admin, or member — and a credential inherits a role:
- Organization API keys always act with member-level access, regardless of the role of the user who created them. An API key can never perform owner- or admin-only actions.
- OAuth access tokens act with the authorizing user's current organization role, resolved on each request — so if that user's role later changes, the token's access follows it.
Organization API keys and OAuth tokens can call the member-level REST surface. Connection management requires an organization owner or admin session or OAuth token. Agent connection keys (pconn_) permit only their bound address mail operations, reads and writes of their own address notes, and reads of other organization agents' AGENT_WORKING, AGENT_RUNTIME and AGENT_INFO notes. All other operations are denied. Billing reads and organization settings are person-only operations (see below).
Mailbox access
Mailbox access says who may read, draft and send for each mailbox or domain. It can be read and changed through the API, with one exception to the rule above: an organization-wide API key may manage it.
GET /v1/mailbox-accesslists every access entry.GET /v1/groupslists the groups an entry can name.PUT /v1/mailboxes/{address}/accessreplaces the access list of one mailbox, or of a domain (@example.com).POST /v1/groups,PATCH /v1/groups/{id}andDELETE /v1/groups/{id}manage groups. Through an API key or the CLI, only groups without a role.
These accept a dashboard session, the person's own Primitive CLI, or an organization-wide API key. A key acts for the person it belongs to, with that person's current access, so it can do here exactly what that person can: reading needs members:read (every member has it), and changing mailbox access needs delegations:manage (owners and admins), so a key of someone demoted to member can still read but no longer change anything. A key whose person has left the organization is refused. Narrowed keys, Function keys and agent connection keys are refused. Any other OAuth app gets step_up_required on a change until the person confirms it in the dashboard. Nobody can grant access beyond their own, and an API key cannot give access to a member's personal mailbox. Every change is recorded in the credential audit log.
Roles and members
Roles, member roles, removing a member, the mailbox access mode, the auto-join setting, break-glass, transfers and access suspensions are in the API reference under "Roles and members". Reads such as GET /v1/roles and GET /v1/access-mode are open to any person. Every change is person only: a dashboard session, or the person's own OAuth or CLI token after they confirm the change in the dashboard. A request without that confirmation answers step_up_required with the purpose and the confirmation link; send the token it shows in the Primitive-Step-Up header. API keys cannot use these routes.
Agent connection keys provide a fixed address-scoped grant through POST /v1/agent-connections and a one-use claim. Arbitrary per-key scopes, such as configurable send-only or read-only grants, are not exposed. See agent connection authentication.
Person-only operations
Some operations act on the organization's people and credentials rather than its mail, so only a person can call them. API keys, agent connection keys and agents are refused. Each is marked in the OpenAPI document by x-primitive-person-only and says who may call it:
- Dashboard session: a person signed in to the dashboard.
- Session or the person's OAuth token: the dashboard, or the person's own OAuth token (the CLI or an app they connected).
- Session or the person's CLI: the dashboard, or the person's own Primitive CLI token; no other OAuth app.
- Step-up: the dashboard, or the person's own OAuth token carrying a step-up token in the
Primitive-Step-Upheader. Without one the API answers403 step_up_required, anderror.details.step_up_urlopens the dashboard page that issues a single-use token for that purpose, valid for five minutes. The token works only with the app or CLI it was confirmed for, and is never accepted with a token issued for the MCP endpoint or with an API key. An operation whose marker also carriescli_without_step_uptakes the person's own Primitive CLI login with no step-up, unless the organization's credential policy setscli_writes_require_approval.
The person's role must still hold the operation's permission (x-primitive-permission).
Dashboard only
A few things stay in the dashboard on purpose, because they prove a person is present in a browser or cannot be undone:
- Issuing a step-up token, and approving a CLI sign-in, a Primitive app sign-in or an OAuth app's consent.
- Confirming or discarding a request that waits for an owner's confirmation.
- Deleting the organization.
- Changing the plan or the payment method (checkout and the billing portal). Billing can be read through the API.
- Onboarding steps, terms acceptance and similar dashboard flows that are not product settings.
Revoking every credential at once (POST /credentials/revoke-all) is in the API but needs an owner in a dashboard session.
OAuth
Primitive supports OAuth Authorization Code + PKCE for public clients. Dynamic Client Registration is available so local tools can obtain a client_id without manual setup.
Discovery and token endpoints:
GET /.well-known/oauth-authorization-server POST /oauth/register GET /oauth/authorize POST /oauth/token POST /oauth/revoke
OAuth grants authorize the selected organization at the authorizing user's current role (see Roles and permissions), resolved on each request: a granted token can call every REST API operation available to that user's role. Fine-grained per-scope OAuth grants — narrowing a token to a subset of operations — are not exposed yet.
Response Envelope
Successful responses use:
{ "success": true, "data": {}, "meta": { "total": 1, "limit": 50, "cursor": null } }
Errors use:
{ "success": false, "error": { "code": "not_found", "message": "Email not found", "request_id": "req_..." } }
Branch on stable error.code values rather than human-readable messages.
Pagination
List endpoints use cursor pagination. Pass limit and cursor query parameters. When meta.cursor is null, there are no more pages.
Endpoint Inventory
| Area | Endpoints |
|---|---|
| Account | GET/PATCH /account, GET /account/storage, GET /account/webhook-secret, POST /account/webhook-secret/rotate |
| Credits | GET /credits/balance, POST /credits/redeem. See Credits. |
| Domains | GET/POST /domains, PATCH/DELETE /domains/{id}, POST /domains/{id}/dns/check, POST /domains/{id}/verify, GET /domains/{id}/zone-file |
| Inbound emails | GET /emails, GET /emails/search, GET /emails/{id}, GET /emails/{id}/readable (what an email says, as paged plain text), GET /emails/{id}/conversation, raw/attachments/discard/replay/reply actions. See Search. |
| Threads | GET /threads/{id} — the full message chain an inbound email belongs to. |
| Sending | POST /send-mail, POST /send-mail/batch, POST /sendability, GET /send-permissions, GET /sent-emails, GET /sent-emails/{id}, PATCH /sent-emails/{id} (reschedule a scheduled send), POST /sent-emails/{id}/cancel, GET /outbound/status. send-permissions lists Primitive-managed domains, your domains, known addresses, and member emails. See Sending. |
| Simple Email | POST /simple-email/emails, POST /simple-email/emails/batch, GET/PATCH /simple-email/emails/{id}, POST /simple-email/emails/{id}/cancel. A drop-in send surface compatible with a widely used JavaScript email SDK's wire format; it does not use the envelope described on this page. See Simple Email. |
| Memories | PUT/GET/DELETE /memories, GET /memories/search. Durable JSON key-value state with org and Function scope. See Primitive Memories. |
| Routing | GET/POST /routes, POST /routes/reorder, POST /routes/simulate, PATCH/DELETE /routes/{id}. See Routing. |
| Endpoints | webhook endpoint CRUD (GET/POST /endpoints, PATCH/DELETE /endpoints/{id}) plus POST /endpoints/{id}/test. See Endpoints. |
| Filters | allow/block filter CRUD (GET/POST /filters, PATCH/DELETE /filters/{id}) |
| Functions | deploy, update, redeploy, delete, tags, logs, tests, secrets, route binding, deployments, source, invocations, activity, usage and runtime keys. See Functions. |
| Credentials | Person only. GET/POST /keys, GET/PATCH /keys/{id}, POST /keys/{id}/rotate, GET /keys/{id}/suggestion, GET /keys/builder, GET /credentials, POST /credentials/revoke, POST /credentials/revoke-all, GET /credential-events (JSON or CSV), GET/PATCH /credential-policy. Creating, rotating and changing a key accept a keys.create step-up. |
| Organization settings | Person only. GET /me/grants, POST /me/grants/{id}/revoke (your own connected apps and CLI logins), GET /oauth-grants, POST /oauth-grants/{id}/revoke, GET/PATCH /organization, PUT/DELETE /organization/logo, GET/PUT /search-settings. Billing is in the dashboard. Name, logo and semantic search changes accept an org_settings.write step-up. |
| Webhook deliveries | GET /webhooks/deliveries, GET /webhooks/deliveries/{id}, POST /webhooks/deliveries/{id}/replay |
| Interactions | GET /interactions (filter by state, protocol, wire_id or the email_id of a carrier email), GET /interactions/{id} (with its steps), POST /interactions (start one), POST /interactions/{id}/steps (send the next step). Typed multi-step protocols carried over email; reading needs interactions:read and organization-wide mail scope. |
For conceptual guides see Authentication, Search, Primitive Memories, Endpoints, Credits, Rate limits, and Routing.
Function-specific endpoints include:
GET /v1/functions POST /v1/functions GET /v1/functions/{id} PUT /v1/functions/{id} DELETE /v1/functions/{id} POST /v1/functions/{id}/redeploy POST /v1/functions/{id}/test GET /v1/functions/{id}/logs GET /v1/functions/{id}/invocations GET /v1/functions/{id}/secrets POST /v1/functions/{id}/secrets PUT /v1/functions/{id}/secrets/{key} DELETE /v1/functions/{id}/secrets/{key} PUT /v1/functions/{id}/route
The full list, including deployments, usage, routing and runtime keys, is in Functions.
PUT /v1/functions/{id} replaces code and optionally accepts sourceMap for source-mapped logs.
OpenAPI
Fetch the machine-readable spec from:
curl https://api.primitive.dev/v1/openapiThe same document is also served as JSON at https://api.primitive.dev/v1/openapi.json; the YAML form lives at https://www.primitive.dev/openapi.yaml (requests to https://api.primitive.dev/v1/openapi.yaml redirect there). https://api.primitive.dev/v1/openapi is the canonical machine URL.
The full document covers every operation. To load only one area, fetch the per-tag index at https://api.primitive.dev/v1/openapi/tags. Each entry links a complete OpenAPI document for that tag's operations and the components they reference, for example https://api.primitive.dev/v1/openapi/tags/domains. The full document links the same index through its top-level x-tag-specs field and each tag's x-openapi-spec field.
Agents should inspect the OpenAPI document or use primitive describe <operation> before guessing request shapes.