REST API
The Primitive API lives under https://api.primitive.dev/v1. Use Bearer auth with either an organization-scoped API key or an OAuth access token.
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:
- 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.
The v1 REST API documented here is the member-level surface: sending, receiving, domains, functions, webhook endpoints, filters, and routing are all available to members, so any valid credential can call every endpoint on this page. Organization administration that is not part of the REST API — billing and organization settings — requires an owner or admin and is performed in the dashboard.
Fine-grained per-key scopes (for example a send-only or read-only key) are on the roadmap. Today the role model above is the unit of access control, and issuing an API key is the way to grant a machine identity member-level access without sharing a user's OAuth 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/topup. 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}/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, delete, logs, tests, secrets, route binding |
| Webhook deliveries | GET /webhooks/deliveries, GET /webhooks/deliveries/{id}, POST /webhooks/deliveries/{id}/replay |
| Commerce (ACP) | POST /checkout_sessions, GET/POST /checkout_sessions/{id}, POST /checkout_sessions/{id}/complete, POST /checkout_sessions/{id}/cancel, POST /agentic_commerce/delegate_payment. Agentic Commerce Protocol checkout for credit purchases; see Collecting Payments. |
| Payments (x402) | POST/GET /x402/payout-addresses, POST /x402/challenges, POST /x402/email-challenges, GET /x402/challenges/{id}, POST /x402/challenges/{id}/pay, GET /x402/payments, GET /x402/declined-payments, GET/PUT /x402/spend-policy. Invite-only soft launch; see Collecting Payments and x402 over Email. |
For conceptual guides see Authentication, Search, Primitive Memories, Endpoints, Credits, Rate limits, Routing, Collecting Payments, and x402 over Email.
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}/test GET /v1/functions/{id}/logs 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} replaces code and optionally accepts sourceMap for source-mapped logs.
List payments (x402)
GET /v1/x402/paymentsEnumerate this org's x402 payments for programmatic reconciliation. Returns the combined ledger of payments received (challenges this org issued, as the payee) and sent (challenges this org is the payer on). Cursor-paginated, newest first.
Query parameters (all optional):
| Parameter | Description |
|---|---|
cursor | Opaque pagination cursor from the previous page's meta.cursor. |
limit | Page size, 1–200 (default 50). |
status | Filter by challenge status (e.g. settled, pending, failed, expired). |
direction | received or sent. |
network | base or base-sepolia. |
created_after | ISO-8601 datetime; inclusive lower bound on created_at. |
created_before | ISO-8601 datetime; inclusive upper bound on created_at. |
Each row:
| Field | Description |
|---|---|
challenge_id | The challenge this payment belongs to. |
direction | received (this org is the payee) or sent (this org is the payer). |
status | Challenge status. |
amount | Token base units, as a string. |
asset | Token contract address (checksummed). |
network | base or base-sepolia. |
counterparty | The other party: its org id when on-net, otherwise the counterparty email address (email-native payer/payee), otherwise null. |
settle_tx | Settlement transaction hash, or null. |
failure_reason | Failure detail when status is failed, otherwise null. |
created_at | ISO-8601 timestamp. |
A self-pay challenge (this org is both the payee and the payer) yields two rows, one received and one sent, so it reconciles from both sides. Because of this, limit bounds the number of challenges per page, not the number of rows: a page can return more rows than limit when self-pay challenges are present. Use meta.cursor === null as the end-of-page signal rather than comparing the row count to limit.
CSV export
Send Accept: text/csv to download the same ledger as a CSV file for month-end reconciliation:
curl -H "Authorization: Bearer $PRIMITIVE_API_KEY" \ -H "Accept: text/csv" \ "https://api.primitive.dev/v1/x402/payments?created_after=2026-06-01T00:00:00Z&created_before=2026-07-01T00:00:00Z" \ -o x402-payments.csv
The CSV applies the same status, direction, network, and date filters as the JSON response and is RLS-scoped identically. It returns the full filtered set (not one page) as an RFC 4180 document with a header row and columns created_at, challenge_id, direction, status, amount, asset, network, counterparty, settle_tx, failure_reason. The response sets Content-Disposition: attachment so a browser downloads it.
The export is bounded to 10,000 challenges. If the result hits that cap it is not silently truncated: the response carries an X-Truncated: true header. Narrow the window with created_after / created_before (or page the JSON response) to retrieve everything. cursor is not supported with Accept: text/csv and returns a 400.
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 and as YAML at https://api.primitive.dev/v1/openapi.yaml. https://api.primitive.dev/v1/openapi is the canonical machine URL.
Agents should inspect the OpenAPI document or use primitive describe <operation> before guessing request shapes.