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

AreaEndpoints
AccountGET/PATCH /account, GET /account/storage, GET /account/webhook-secret, POST /account/webhook-secret/rotate
CreditsGET /credits/balance, POST /credits/topup. See Credits.
DomainsGET/POST /domains, PATCH/DELETE /domains/{id}, POST /domains/{id}/dns/check, POST /domains/{id}/verify, GET /domains/{id}/zone-file
Inbound emailsGET /emails, GET /emails/search, GET /emails/{id}, GET /emails/{id}/conversation, raw/attachments/discard/replay/reply actions. See Search.
ThreadsGET /threads/{id} — the full message chain an inbound email belongs to.
SendingPOST /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.
MemoriesPUT/GET/DELETE /memories, GET /memories/search. Durable JSON key-value state with org and Function scope. See Primitive Memories.
RoutingGET/POST /routes, POST /routes/reorder, POST /routes/simulate, PATCH/DELETE /routes/{id}. See Routing.
Endpointswebhook endpoint CRUD (GET/POST /endpoints, PATCH/DELETE /endpoints/{id}) plus POST /endpoints/{id}/test. See Endpoints.
Filtersallow/block filter CRUD (GET/POST /filters, PATCH/DELETE /filters/{id})
Functionsdeploy, update, delete, logs, tests, secrets, route binding
Webhook deliveriesGET /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/payments

Enumerate 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):

ParameterDescription
cursorOpaque pagination cursor from the previous page's meta.cursor.
limitPage size, 1–200 (default 50).
statusFilter by challenge status (e.g. settled, pending, failed, expired).
directionreceived or sent.
networkbase or base-sepolia.
created_afterISO-8601 datetime; inclusive lower bound on created_at.
created_beforeISO-8601 datetime; inclusive upper bound on created_at.

Each row:

FieldDescription
challenge_idThe challenge this payment belongs to.
directionreceived (this org is the payee) or sent (this org is the payer).
statusChallenge status.
amountToken base units, as a string.
assetToken contract address (checksummed).
networkbase or base-sepolia.
counterpartyThe other party: its org id when on-net, otherwise the counterparty email address (email-native payer/payee), otherwise null.
settle_txSettlement transaction hash, or null.
failure_reasonFailure detail when status is failed, otherwise null.
created_atISO-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/openapi

The 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.