Control plane
Docs / Control plane

Control plane API

The control plane is the part that decides and records — not the part that delivers. Send is only half of it. These endpoints let your own software list and write the same resources the panel manages. Auth is the same Bearer spw_… key. Abilities are chosen at mint, never widenable, no wildcard. messages:send and messages:read-content still may not share a key.

Resources

ResourceEndpointsReadWritePage
Send a messagePOST /v1/messages—messages:sendPOST /v1/messages
Message metadataGET /v1/messages, GET /v1/messages/{id}messages:read—List and show
Message bodiesGET /v1/messages/{id}/contentmessages:read-content—Content
TemplatesGET /v1/templates, GET /v1/templates/{id}, GET /v1/templates/by-slug/{slug}templates:readtemplates:manage grants the same reads. No write API.Templates
TransportsGET /v1/transports, GET /v1/transports/{id}transports:readtransports:manage grants the same reads. No write API.Transports
SuppressionsGET /v1/suppressions, GET /v1/suppressions/lookup, GET /v1/suppressions/{id}, POST /v1/suppressions, DELETE /v1/suppressions/{id}suppressions:readsuppressions:writeSuppressions
ApplicationsGET /v1/applications, GET /v1/applications/{id}, POST /v1/applicationsapplications:readapplications:writeApplications
DomainsGET /v1/domains, GET /v1/domains/{id}, GET /v1/dmarc-reports, POST /v1/dmarc-reportsdomains:readNo write ability. The DMARC POST uses domains:read.Domains
UsageGET /v1/usageusage:read—Usage

messages:send and messages:read-content may not share a key. Message metadata also has GET /v1/messages/{id}.

Abilities

A key without the ability for a surface gets 403 with type: forbidden_ability and the required_ability it lacks. templates:manage and transports:manage also grant the matching read. Neither is a write on this API.

Pagination

Lists paginate like messages: ?limit= (1–100, default 25) and ?cursor= (keyset on integer id — pass the last id from the previous page). DMARC reports are the exception: their cursor is an opaque string naming range_end and id, and an integer is 422 invalid_cursor. Response shape is always { data, next_cursor }. See .

Tenancy fails closed

Cross-account ids return 404, not 403. Errors share the same { message } envelope as .

What this is not

HONESTY NOTE — WHAT THIS API WILL NOT DOPublishing a template stays in the panel, or a git push to the repository you connected. Testing transports, verifying domains, minting API keys and changing plan stay in the panel. Batch send, bulk suppression import and webhook CRUD are not on v1 yet.