Skip to content
Campaigns · API reference

Ship faster with Flaxxa Campaigns.

Drive Flaxxa Campaigns from your own software: a REST API with workspace keys, signed webhooks and idempotency keys.

No credit card
Free plan
Cancel anytime
flaxxa.com / product / campaigns / docs
cURLREST · JSON
# Flaxxa Campaigns · first request
curl https://flaxxa.com/api/v1/campaigns \
  -H "Authorization: Bearer $FLAXXA_KEY" \
  -H "Accept: application/json"

# webhooks: X-Flaxxa-Signature (HMAC-SHA256)
# retries: Idempotency-Key, 24 h
200 OKapplication/json
+ webhooks · HMAC-signed
REST
Requests per key by default
120/min
Idempotency window
24h
Quick start

How do I make my first API call?

Replace $FLAXXA_KEY with a key from your workspace. The first call returns a real id you can use for the next ones.

cURL
# 1. Add someone to a list (fields are keyed by the list's merge tags)
curl -X POST https://flaxxa.com/api/v1/campaigns/subscribers \
  -H "Authorization: Bearer flx_live_YOUR_KEY" \
  -H "Accept: application/json" -H "Content-Type: application/json" \
  -d '{ "list_id": 12, "email": "[email protected]",
        "fields": { "FIRST_NAME": "Ana" }, "tags": ["webinar-2026"] }'

# 2. Create a campaign, then give it a subject, content and an audience
curl -X POST https://flaxxa.com/api/v1/campaigns/email/campaigns \
  -H "Authorization: Bearer flx_live_YOUR_KEY" \
  -H "Accept: application/json" -H "Content-Type: application/json" \
  -d '{ "name": "November newsletter" }'

curl -X PUT https://flaxxa.com/api/v1/campaigns/email/campaigns/88 \
  -H "Authorization: Bearer flx_live_YOUR_KEY" \
  -H "Accept: application/json" -H "Content-Type: application/json" \
  -d '{ "subject": "What’s new in November", "from_email": "[email protected]",
        "html": "<p>Hi {FIRST_NAME}, …</p><p><a href=\"{UNSUBSCRIBE_URL}\">Unsubscribe</a></p>",
        "audiences": [{ "list_id": 12 }] }'

# 3. Check it can go, then schedule it
curl https://flaxxa.com/api/v1/campaigns/email/campaigns/88/preflight \
  -H "Authorization: Bearer flx_live_YOUR_KEY" -H "Accept: application/json"

curl -X POST https://flaxxa.com/api/v1/campaigns/email/campaigns/88/schedule \
  -H "Authorization: Bearer flx_live_YOUR_KEY" \
  -H "Accept: application/json" -H "Content-Type: application/json" \
  -d '{ "scheduled_at": "2026-11-10T09:00:00+05:30" }'
PHP
// Add every new customer to your newsletter list.
$subscriber = Http::withToken(env('FLAXXA_CAMPAIGNS_KEY'))->acceptJson()
    ->post('https://flaxxa.com/api/v1/campaigns/subscribers', [
        'list_id' => 12,
        'email'   => $customer->email,
        'fields'  => ['FIRST_NAME' => $customer->first_name],
        'tags'    => ['customer'],
    ])->throw()->json('data');   // $subscriber['id'], ['status']

$customer->update(['flaxxa_subscriber_id' => $subscriber['id']]);
Node
const res = await fetch("https://flaxxa.com/api/v1/campaigns/subscribers", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.FLAXXA_CAMPAIGNS_KEY}`,
    Accept: "application/json",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ list_id: 12, email: "[email protected]", fields: { FIRST_NAME: "Ana" } }),
});
if (!res.ok) throw new Error((await res.json()).message);
const { data } = await res.json();
console.log(data.id, data.status); // -> 5031 "subscribed"
Python
import requests
r = requests.post("https://flaxxa.com/api/v1/campaigns/subscribers",
    headers={"Authorization": f"Bearer {KEY}", "Accept": "application/json"},
    json={"list_id": 12, "email": "[email protected]", "fields": {"FIRST_NAME": "Ana"}})
r.raise_for_status()
sub = r.json()["data"]
print(sub["id"], sub["status"])  # -> 5031 subscribed
Request
POST /api/v1/campaigns/subscribers HTTP/1.1
Host: flaxxa.com
Authorization: Bearer flx_live_·············
Content-Type: application/json

{
  "list_id": 12,
  "email":   "[email protected]",
  "fields":  { "FIRST_NAME": "Ana" },
  "tags":    ["webinar-2026"]
}
Response · 201
HTTP/1.1 201 Created
Content-Type: application/json

{
  "data": {
    "id": 5031, "list_id": 12,
    "email": "[email protected]", "status": "subscribed",
    "fields": { "FIRST_NAME": "Ana" }, "tags": ["webinar-2026"],
    "source": "manual",
    "created_at": "2026-11-06T10:00:00+00:00",
    "subscribed_at": "2026-11-06T10:00:00+00:00"
  }
}
Authentication

How do I authenticate?

Every call requires Authorization: Bearer <key>. Rotate from workspace settings — old keys are revoked instantly. Server-side only; never ship a live key to the browser.

Base URLhttps://flaxxa.com/api/v1/campaigns
Lists & fields

Lists & fields API

A list holds subscribers and its own fields. Each field has a merge tag (`FIRST_NAME`, …) — the key you use in a subscriber's `fields` and as `{FIRST_NAME}` in an email. Scopes: `campaigns.audience.view` to read, `campaigns.audience.manage` to change.

GET/lists

List lists

Every list with its subscriber counts and open / click rates.

POST/lists

Create list

{ name, description?, from_name?, from_email?, reply_to?, double_opt_in?, company? }. Blank sender fields take the workspace defaults.

GET/lists/{id}

Retrieve list

The list and its settings. /lists/{id}/overview adds its growth and engagement.

PUT/lists/{id}

Update list

Same body as create.

POST/lists/{id}/copy

Copy list

A new list with the same fields and settings.

DELETE/lists/{id}

Delete list

Deletes the list.

GET/lists/{id}/fields

List fields

Each field's label, tag, type and whether it's required.

POST/lists/{id}/fields

Add field

Add a custom field; PUT / DELETE /lists/{id}/fields/{field} to change or remove one, POST …/fields/reorder to reorder.

Subscribers

Subscribers API

People on a list. Status is one of `subscribed`, `unconfirmed` (waiting on double opt-in), `unsubscribed`, `bounced` or `complained`. An address on the workspace blacklist is refused.

GET/subscribers

List subscribers

Filter: list_id, segment_id, status, tag, q; sort: newest, oldest, email, email_desc, updated; per_page ≤ 100. Paginated {data: {data, current_page, last_page, total}}.

POST/subscribers

Add subscriber

{ list_id, email, status?, fields?, tags? }. Without status, a double opt-in list makes them unconfirmed, otherwise subscribed. 422 if the address is already on the list.

GET/subscribers/{id}

Retrieve subscriber

The subscriber, their list's fields and their activity timeline.

PUT/subscribers/{id}

Update subscriber

{ email?, status?, fields?, tags? }.

DELETE/subscribers/{id}

Delete subscriber

Removes them from the list.

POST/subscribers/bulk

Bulk action

{ action: subscribe|unsubscribe|delete|add_tag|remove_tag|copy|move, ids[] (≤ 1000) or all: true with the list filters }.

GET/subscribers/export

Export CSV

The filtered subscribers as a CSV file.

GET/subscribers/tags

Tags in use

Each tag with how many subscribers carry it (?list_id= for one list).

CSV imports

CSV imports API

Bring a list in from a CSV in two calls: upload it, then map its columns and start. The import runs in the background — poll it for progress.

POST/lists/{id}/imports

Upload CSV

multipart file (csv, up to 50 MB). Returns the import with its column headers and sample rows.

POST/imports/{id}/start

Map and start

{ mapping: { "0": "email", "1": "field:FIRST_NAME", "2": "tags" }, update_existing? }. Column index → email, tags or field:<TAG>.

GET/imports/{id}

Import progress

Status and counts: imported, updated, skipped.

Segments

Segments API

A saved filter on one list (by field, tag, status, activity). Send a campaign to a segment instead of the whole list.

GET/segments

List segments

Every segment with its list and size.

GET/lists/{id}/segment-options

Condition options

The fields and operators a segment on that list can use.

POST/segments/preview

Preview

{ list_id, match: all|any, conditions[] } → how many match, without saving.

POST/segments

Create segment

{ list_id, name, match: all|any, conditions[] (≤ 50) }.

GET/segments/{id}

Retrieve segment

Its conditions and size. PUT to change, DELETE to remove.

Signup forms

Signup forms API

Embedded, popup and hosted forms that add people to a list. Scopes as for lists.

GET/forms

List forms

Every form with its list, type and status.

POST/forms

Create form

{ name, list_id, type, design? } — design is blank or one of GET /forms/designs.

GET/forms/{id}/stats

Form stats

Views, signups and conversion.

GET/forms/{id}/embed

Embed code

The snippet / link to put the form on your site.

POST/forms/{id}/publish

Publish

Make it live; /unpublish takes it down, /duplicate copies it.

Email campaigns

Email campaigns API

A campaign starts as a `draft`. Give it a subject, a from address, content (`html` or `plain`) and one or more audiences (`{ list_id, segment_id? }`), then send or schedule it. Statuses: draft, scheduled, sending, paused, sent, failed, cancelled. Scopes: `campaigns.campaign.view` / `.manage`, and `campaigns.campaign.send` to send.

GET/email/campaigns

List campaigns

Filter: status, q; per_page ≤ 100.

POST/email/campaigns

Create campaign

{ name?, type?: regular|plain }. Starts from the workspace's sender and tracking settings.

GET/email/campaigns/{id}

Retrieve campaign

Everything about it, with its audiences.

PUT/email/campaigns/{id}

Update campaign

name, subject, preheader, from_name, from_email, reply_to, html, plain, template_id, track_opens, track_clicks, audiences[]. Only while draft, scheduled or paused; audiences are fixed once sending starts.

POST/email/recipient-count

Count recipients

{ audiences[] } → the deduplicated number of subscribed people.

GET/email/campaigns/{id}/preflight

Preflight

{ ready, recipients, checks[] } — provider, recipients, subject, from address, content, unsubscribe link, spam words.

POST/email/campaigns/{id}/test

Send a test

{ emails } — up to 5 addresses.

POST/email/campaigns/{id}/send

Send now

Starts sending. 422 with the failing check if preflight isn't ready.

POST/email/campaigns/{id}/schedule

Schedule

{ scheduled_at } — a time in the future (ISO 8601).

POST/email/campaigns/{id}/pause

Pause / resume / cancel

Pause a sending campaign; …/resume carries on; …/cancel unschedules a scheduled one or stops a sending one for good.

POST/email/campaigns/{id}/duplicate

Duplicate

A new draft with the same content and audiences.

DELETE/email/campaigns/{id}

Delete campaign

Pause or cancel a sending campaign first.

Reports

Reports API

Read how a campaign did, and who it went to.

GET/email/campaigns/{id}/report

Campaign report

Sent, delivered, opened, clicked, bounced, complained and unsubscribed, with rates.

GET/email/campaigns/{id}/recipients

Recipients

Who it went to and what each did; /recipients/export as CSV.

Automations

Automations API

Emails that go out on their own when something happens on a list — someone subscribes, a date comes round, and so on. Build the flow, then activate it. Scopes: `campaigns.automation.view` / `.manage`.

GET/automations

List automations

Each with its list, trigger, status and counts.

GET/automations/options

Builder options

The triggers and steps available.

POST/automations

Create automation

{ name, list_id, trigger, flow? }.

PUT/automations/{id}/flow

Save flow

{ flow: { steps[] } } — the emails, waits and conditions.

POST/automations/{id}/activate

Activate / pause

Turn it on; …/pause stops new runs.

GET/automations/{id}/stats

Stats and runs

Per-step numbers; /runs lists who went through it.

Templates

Templates API

Saved email designs to start campaigns from. Scopes: `campaigns.template.view` / `.manage`.

GET/templates

List templates

?tab=mine|gallery|all, category, q, per_page.

GET/templates/{id}

Retrieve

The template with its HTML.

POST/templates

Create template

Save a design; PUT /templates/{id} to change it.

POST/templates/{id}/duplicate

Duplicate

A copy in your templates.

DELETE/templates/{id}

Delete

Removes it.

Domains & blacklist

Domains & blacklist API

Your sending and tracking domains, and the addresses and domains never to email. Scopes: `campaigns.sending.view` / `.manage`.

GET/sending/domains

Sending domains

Each domain with its DNS records and verification state.

POST/sending/domains

Add domain

Returns the DNS records to publish; POST …/domains/{id}/verify once they're live.

GET/sending/tracking-domains

Tracking domains

Branded open / click links; POST to add, …/verify, …/default.

GET/sending/blacklist

Blacklist

Blocked addresses (q, per_page).

POST/sending/blacklist

Block addresses

Add addresses; POST /sending/blacklist/delete to unblock.

Webhooks

Webhooks API

POST /api/v1/webhooks { url, subscribed_events[] } — `app.campaigns.*` subscribes to every Campaigns event. Each delivery carries X-Flaxxa-Event and X-Flaxxa-Signature (HMAC-SHA256 of the raw body with your endpoint secret). The payload: tenant_id, message_id, campaign_id or automation_id, subscriber_id, list_id, contact_id, email, occurred_at.

POST/../webhooks

Subscribe

Scope workspace.webhooks.manage. The secret is returned once.

DELETE/../webhooks/{id}

Unsubscribe

Stops delivery.

Webhook events

Which webhook events can I subscribe to?

Subscribe via POST /webhooks. Verify X-Flaxxa-Signature on every payload.

  • app.campaigns.email.delivered

    An email from a campaign or automation was handed to the mail server.

  • app.campaigns.email.opened

    A recipient opened it (once per email).

  • app.campaigns.email.clicked

    A recipient clicked a link (once per email); the payload carries the url.

  • app.campaigns.email.bounced

    It bounced; bounce_type is hard or soft. A hard bounce blacklists the address.

  • app.campaigns.email.complained

    The recipient marked it as spam.

  • app.campaigns.email.unsubscribed

    The recipient unsubscribed from its link.

Errors

What errors can the API return?

All errors return a JSON envelope: { error: { code, message, details } }. Retry idempotent calls on 5xx with exponential backoff.

CodeNameWhen it fires
401unauthenticatedAPI key missing, expired or revoked.
403insufficient_scopeThe key lacks the scope — e.g. campaigns.audience.manage to add subscribers, campaigns.campaign.send to send.
404not_foundNo list, subscriber or campaign with that id in this workspace.
422validation_failed{ message, errors } — e.g. the address is already on the list, it's blacklisted, or a campaign isn't ready to send.
429rate_limited120 requests a minute per key.
Conventions

Which rules apply to every request?

Every Flaxxa app's API follows the same authentication, rate limits, idempotency and webhook signing.

Bearer auth

Every API call uses a Bearer token. Keys belong to a workspace and are scoped per app. Create and revoke them from workspace API settings.

Rate limits

Each key can make 120 requests a minute unless the app sets its own limit. Going over returns 429 with a Retry-After header.

Idempotency

Send an Idempotency-Key header on a POST and a retry with the same key within 24 hours returns the first response instead of running again.

Signed webhooks

Every webhook payload carries an X-Flaxxa-Signature HMAC-SHA256 header made with the secret you get when you subscribe. Verify it before processing.

API FAQ

Flaxxa Campaigns API: common questions

Frequently asked questions

Yes. A REST API at https://flaxxa.com/api/v1/campaigns, called with a workspace API key scoped to Flaxxa Campaigns.

Get started in minutes

Ship faster with Flaxxa Campaigns.

Create a workspace, make an API key in its settings, and send your first request.

Free plan · No credit card · Cancel anytime