API · v1Production: https://send.blun.ai/api/v1OpenAPI: /api/v1/openapi.json
REST API reference.
JSON over HTTPS. Bearer-auth. Cursor pagination. POSTs are idempotent by header. EU-hosted, GDPR-by-default.
Authentication
All requests must include Authorization: Bearer <api_key>. Keys are workspace-scoped and carry one of three permission levels: read, write, or admin. Generate keys at /admin/settings#api.
Standard HTTP status codes are used. Failures return a JSON object with a stable error.code string (machine-readable) and an error.message (human-readable).
Status
code
When
400
invalid_request
Malformed body, missing required field, or schema violation.
401
unauthorized
No bearer token, expired key, or revoked key.
403
forbidden_scope
Key lacks the scope needed for the operation (e.g. send on a read-only key).
404
not_found
Resource does not exist in this workspace.
409
conflict
Idempotency-Key reuse with a different body, or duplicate resource.
422
unprocessable
Body parses but a downstream rule rejects it (e.g. unverified sender domain).
429
rate_limited
Quota exceeded. Retry-After header is set in seconds.
500
server_error
Generic upstream failure. Includes a request_id for support.
600 req/min/workspace — combined across all keys in a workspace.
60 req/min/key — per individual API key.
When exceeded, the API returns 429 rate_limited with a Retry-After header (seconds). Successful responses also include three informational headers: X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset (unix timestamp).
Sending throughput is metered separately and capped per plan; see pricing.
Idempotency
All POST requests must include an Idempotency-Key header (UUID v4 recommended). Replays of the same key return the original response — body and status — for 24 hours.
Reusing the same key with a different request body returns 409 conflict. This protects you from sending the same campaign twice when a network blip causes a retry.
List endpoints use cursor-based pagination. Pass ?limit= (1–200, default 50) and on subsequent calls ?cursor= using the value from the previous response. Cursors are opaque tokens — do not parse them.
Field
Type
Description
data
array
The page of results.
next_cursor
string|null
Pass to the next call, or null at the last page.
total_estimate
integer
Approximate total count (eventual consistency).
Versioning
The current major version is v1, encoded in the path. Breaking changes will ship under v2; v1 will continue to receive security fixes for at least 12 months after a successor is announced.
Non-breaking additions (new endpoints, optional fields) ship continuously and are documented in /changelog.
Lists
A list is a named collection of subscribers. Each subscriber belongs to exactly one list — to keep the same person across multiple lists, copy the address with the import endpoint.
Object schema
Field
Type
Description
id
string
Prefixed identifier, e.g. lst_8a7b3c.
name
string
Human-readable name.
subscribers
integer
Active subscriber count (excludes unsubscribed/bounced).
double_opt_in
boolean
Whether new subscribers receive a confirmation email.
default_from
object
{ "email", "name" } — used when a campaign omits a from.
Templates are reusable, parameterised email layouts assembled from blocks. A template declares its variables; rendering happens when a campaign is composed.
Edit blocks, name, or category in place. Existing campaigns continue using the snapshot they were composed against.
DELETE/api/v1/templates/:idadmin
Delete a template. Past campaigns retain their content snapshot.
Campaigns
A campaign is a draft envelope: subject, sender, list, and either a template id or an inline block array. Sending happens via a separate transition into a Send object.
Object schema
Field
Type
Description
id
string
e.g. cmp_61a8e9d3.
subject
string
Subject line. Personalisation tokens supported.
from
object
{ email, name } from a verified sender.
list_id
string
Audience list.
segment_id
string|null
Optional narrowing segment.
template_id
string|null
Set when composed from a template.
status
enum
draft | scheduled | sending | sent | cancelled.
scheduled_at
iso-8601|null
Set when scheduled.
sent_at
iso-8601|null
Set when fully dispatched.
analytics
object
Lazy-loaded summary; full report at /campaigns/:id/analytics.
GET/api/v1/campaignsread
List campaigns, filterable by status and a since-window.
POST/api/v1/sends/:id/cancelCancel a scheduled or in-flight send.
GET/api/v1/sends/:id/recipientsPer-recipient state (paginated).
Webhooks
Receive signed POSTs for every lifecycle event. Each request carries an X-Pulsemail-Signature header — HMAC-SHA256 of the raw body using your endpoint secret. Verify before processing.
GET/api/v1/webhooksList configured endpoints.
GET/api/v1/webhooks/:idRetrieve one endpoint + recent deliveries.
POST/api/v1/webhooksRegister an endpoint with subscribed event types.
PATCH/api/v1/webhooks/:idUpdate URL, secret, or subscribed events.
DELETE/api/v1/webhooks/:idRemove an endpoint.
POST/api/v1/webhooks/:id/replayReplay the last 24h of deliveries.
Events
The append-only stream of every action: opens, clicks, bounces, unsubscribes, spam complaints, send progress, and consent changes.
GET/api/v1/eventsCursor-paginated event feed.
GET/api/v1/events/:idRetrieve a single event.
GET/api/v1/campaigns/:id/eventsEvents scoped to one campaign.
GET/api/v1/subscribers/:id/eventsEngagement timeline for one subscriber.
Workspaces
A workspace is the top-level isolation boundary. Each key, sender domain, and audit trail is workspace-scoped.
GET/api/v1/workspaces/currentInspect the workspace this key belongs to.
PATCH/api/v1/workspaces/currentUpdate name and default region.
GET/api/v1/workspaces/current/usageCurrent-period send and storage counters.