Ask AI about this page

REST API

Everything you can do in the SubsiMail browser UI — create campaigns, manage contacts and lists, enrol people, read stats and events, invite teammates — you can also do over a JSON REST API. It's designed to be driven from n8n, Make, Zapier (via an HTTP node), a cron job, or your own backend.

The API is a companion to webhooks: webhooks push events out to you as they happen; the API is how you pull data and push changes in.

Base URL & versioning

All endpoints live under /api/v1 on your own SubsiMail instance:

https://mail.yourcompany.com/api/v1

The v1 in the path is the API version. Breaking changes will ship as v2; v1 keeps working. All requests and responses are JSON (Content-Type: application/json).

Authentication

Every request must carry a personal API key as a bearer token:

curl https://mail.yourcompany.com/api/v1/me \
  -H "Authorization: Bearer sk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

A request with no key, a revoked key, or a malformed key gets 401 Unauthorized. There are no cookies, sessions, or CSRF tokens involved — the key is the whole story.

GET /api/v1/me is the simplest way to check a key works. It returns the owning user and the key id:

{
  "user_id": 4,
  "name": "Priya Shah",
  "email": "priya@yourcompany.com",
  "role": "admin",
  "api_key_id": 2
}

Creating & managing keys

Keys are created from the browser, per user. In your SubsiMail instance:

  1. Open Settings in the sidebar, then the API keys card (direct URL: /settings/api-keys).
  2. Give the key a name (e.g. "n8n automation") and click Create key.
  3. The full key (sk_live_…) is shown once, right after creation. Copy it now — only a hash is stored, so it can't be shown again. (Reloading the page won't bring it back, and won't create a second key.)
  4. Paste it into your integration as the bearer token.

Workspaces: a key belongs to the workspace that was active when you created it, and acts only inside that workspace — its contacts, lists, campaigns, mailboxes and everything else are that workspace's alone. To automate another client, switch to its workspace and create a key there. Keys created before 8.0 belong to the Default workspace.

Keys don't expire. Revoke one anytime from the same page — any integration using it stops working on the next request. Create a separate key per integration so you can revoke them independently. If a key leaks, revoke it and issue a new one; nothing else is affected.

The API keys page: a create-key form, the one-time key display with a Copy button, and a table of existing keys with Revoke buttons
Settings → API keys

What a key can do

A key acts as the user who created it, with exactly the same permissions that user has in the UI:

  • Workspace — the key sees and changes only its own workspace. Anything in another workspace returns 404, and team endpoints list and invite people for that workspace only. Your role is the one you hold in that workspace.
  • Role — a key owned by a manager can't reach admin-only endpoints (team management); a key owned by a view_only user can read but every write returns 403.
  • Sharing / visibility — if a campaign or contact has been scoped to specific people, a key only sees it when its owner is one of them (or is an admin). A resource you can't see returns 404, never 403 — the API never confirms that something exists but is off-limits.
  • Audit trail — anything the key creates or changes is recorded against its owner, along with the request's IP address, and shows in the "history" popover in the UI just like a manual change.

Making requests

Send a JSON body on POST and PATCH. PATCH is a partial update — include only the fields you want to change.

curl -X POST https://mail.yourcompany.com/api/v1/contacts \
  -H "Authorization: Bearer $SUBSIMAIL_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Jane Doe",
    "email": "jane@acme.com",
    "company": "Acme",
    "custom_fields": { "plan": "pro" },
    "list_ids": [3]
  }'
MethodUsed forSuccess status
GETRead a resource or list200
POSTCreate, or run an action (activate, archive, enrol…)201 create · 200 action
PATCHPartial update200
DELETESoft-delete (recoverable by an admin)204 (no body)

Lists & pagination

List endpoints return an object with data and pagination:

{
  "data": [ { "id": 42, "name": "Q3 outbound", ... }, ... ],
  "pagination": { "total": 137, "limit": 50, "offset": 0 }
}

Control the window with query parameters:

ParamDefaultNotes
limit501–200
offset0Skip this many rows

To page through everything, keep requesting with offset += limit until offset >= pagination.total. Small collections that are never large (a campaign's steps, a resource's share list, custom fields) skip pagination and just return { "data": [ ... ] }.

Most list endpoints also accept filters — ?archived=true, ?status=active, ?q=jane, ?campaign_id=42. These are documented per endpoint in the reference.

Errors

Every 4xx/5xx response has the same shape:

{
  "error": {
    "code": "invalid_request",
    "message": "account_id does not refer to a live account"
  }
}
StatuscodeMeaning
400invalid_requestMissing/invalid field, bad query param
401unauthorizedMissing, malformed, or revoked API key
403forbiddenKey's role isn't allowed to do this (e.g. view-only writing, non-admin hitting a team endpoint)
404not_foundNo such resource, or the key's owner can't see it
409conflictDuplicate — e.g. a contact with that email already exists
500internal_errorSomething broke on the server; safe to retry

Timestamps, IDs & conventions

  • IDs are integers, unique per resource type, stable forever.
  • Timestamps in responses are UTC, formatted YYYY-MM-DD HH:MM:SS. Timestamps you send (e.g. a campaign's starts_at, an event feed's since) must be RFC 3339, e.g. 2026-09-10T14:00:00Z.
  • Deletes are soft. A DELETE hides the resource everywhere and it stops functioning, but the row is retained and an admin can restore it. The same is true of archive.
  • There is no rate limit today. Be reasonable; a tight polling loop should sleep between passes.

Full endpoint reference →