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.
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 currently authorize full Primitive API access for the selected organization. Fine-grained OAuth scopes 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}, GET /outbound/status. send-permissions lists Primitive-managed domains, your domains, known addresses, and member emails. |
| 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.