API reference
Broadcasts
Create, list, schedule and cancel broadcasts to the subscribers of a topic.
A broadcast sends one message per eligible contact of a topic. A segment can narrow the recipients. A contact is eligible when all of these hold:
- its status is
subscribed; - its effective state for the topic is
subscribed(an explicit row, or the default opt-in of the topic); - it matches the segment;
- it is not on the suppression list.
Each message has kind: "broadcast.<topic>", so recipients get a one-click unsubscribe for that topic only.
SEND and FULL keys may use these endpoints. A domain-restricted API key can only create broadcasts from its domains, and it only sees those.
Create a broadcast
POST /api/v1/broadcasts
| Field | Type | Notes |
|---|---|---|
name |
string | Required. Internal label, ≤ 120 chars. |
from |
string | Required. email or Name <email> on a verified domain. |
topic |
string | Required. Topic slug. |
segment_id |
string | Optional. Must be one of the segments of the organization. |
template |
object | { "id" | "slug", "version"?: n | "latest" } — a published version. Mutually exclusive with subject/html/text. |
subject |
string | Required without template; 1–998 chars, no line breaks. |
html, text |
string | One required without template; ≤ 2 MB. |
subject_b |
string | Optional A/B subject: about half the recipients (by contact id) get this subject instead. Required when ab_test is set. |
ab_test |
object | Optional. { "percent": 10–50, "wait_minutes": 30–4320, "metric": "opens" | "clicks" }. The platform sends that share first, waits, then sends the rest with the winning subject. |
reply_to |
string | Optional. Reply-To address on every message of the broadcast. |
preview_text |
string | Optional, 1–256 chars, no line breaks. The inbox snippet next to the subject. The platform puts it in a hidden block at the top of the HTML part. It is not a header. The text part does not change. |
scheduled_at |
string | ISO 8601 with offset. Present → created as scheduled and queued for that time; absent → draft. |
The API rejects unknown fields. Content renders per contact with {{contact.email}}, {{contact.first_name}}, {{contact.last_name}} and {{contact.<property>}} (see Broadcasts in the Dashboard section).
curl -X POST https://api.f5send.com/api/v1/broadcasts \
-H "Authorization: Bearer $F5SEND_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "August newsletter",
"from": "Acme <news@example.com>",
"topic": "newsletter",
"template": { "slug": "newsletter", "version": "latest" },
"subject_b": "Hi {{contact.first_name}} — August news",
"reply_to": "hello@example.com",
"preview_text": "What changed in August"
}'
Broadcast object
{
"object": "broadcast",
"id": "cmg1…",
"name": "August newsletter",
"status": "draft",
"from": "Acme <news@example.com>",
"domain": "example.com",
"topic": "newsletter",
"segment_id": null,
"template": { "id": "cmt…", "slug": "newsletter", "version": 4 },
"subject": "August news",
"subject_b": "Hi {{contact.first_name}} — August news",
"ab_test": { "percent": 20, "wait_minutes": 240, "metric": "opens" },
"ab_winner": null,
"ab_decided_at": null,
"parent_id": null,
"reply_to": "hello@example.com",
"preview_text": "What changed in August",
"scheduled_at": null,
"started_at": null,
"finished_at": null,
"stats": null,
"created_at": "2026-08-17T14:03:11.120Z",
"updated_at": "2026-08-17T14:03:11.120Z"
}
status is draft → scheduled → sending → (testing → sending) → sent, or canceled.
ab_test is null when the broadcast does not pick a winner. ab_winner is A, B or null. ab_decided_at is the time of that choice, or null. parent_id names the parent broadcast on a resend. It is null on any other broadcast.
The worker writes stats: { "eligible", "suppressed", "queued", "skipped", "testShare"?, "reason"? }. eligible = matched minus suppressed. queued counts recipient rows in the queued state. In testing, that count includes recipients that wait for the winner.
testShare is the number of messages the test created. skipped counts per-contact render or API failures. reason is present when the worker ended the broadcast itself.
List broadcasts
GET /api/v1/broadcasts
Returns { "object": "list", "data": [ …up to 50 newest… ] }.
Retrieve a broadcast
GET /api/v1/broadcasts/:id
Send a broadcast
POST /api/v1/broadcasts/:id/send
The body is optional. {} or an empty body sends now. { "scheduled_at": "2026-09-01T09:00:00-04:00" } schedules the broadcast. The call works from draft or scheduled; a new schedule replaces the pending time. Any other status returns 409 not_schedulable. The call returns the broadcast object.
Cancel a broadcast
POST /api/v1/broadcasts/:id/cancel
The call works from any status except sent and canceled (409 not_cancelable). The platform removes a pending job. A broadcast that is already sending stops at its next batch of 500 recipients. Messages already in the queue still go out. Cancel during testing also removes the pending winner job.
Decide the winner
POST /api/v1/broadcasts/:id/decide
No body. The broadcast must be testing. Any other status returns 409 not_testing. The call queues the choice at once and returns the broadcast object. The status stays testing until the worker writes the winner. The worker then sends the rest of the audience.
Resend to non-openers
POST /api/v1/broadcasts/:id/resend
| Field | Type | Notes |
|---|---|---|
subject |
string | Optional on an inline broadcast. Omitted → the parent subject. On a template broadcast, a subject returns 422 subject_from_template. |
scheduled_at |
string | Optional ISO 8601 with offset. Present → the child is scheduled. Absent → draft. |
The parent must be sent and must not itself be a resend (409 not_resendable). A child that is not canceled blocks another resend (409 resend_exists). A send time less than 24 hours after the parent finished returns 409 resend_too_early. The message names the earliest time. 0 turns that wait off. The default is 24 hours.
The child copies the content, the topic and the segment. It has no subject B. The audience is the non-openers of the parent at fan-out time, not at creation time. The call returns the child broadcast. parent_id is the parent id.
Report
GET /api/v1/broadcasts/:id/report
SEND and FULL keys may read it. A TEST key may read it too. The call sends no mail. A domain-restricted key that does not own the From domain gets 404.
Every message count is distinct messages. recipients and skipped count recipient rows. opened counts a message with an open or a click. Rates are null when the denominator is 0. Open, click and unsubscribe rates use delivered messages. The bounce rate uses sent messages.
tracking_enabled is false when the sending domain has no tracking host. Opens and clicks are not collected in that case. variants is null when no recipient has a variant. winner is the A/B winner, or null.
{ "object": "broadcast_report", "broadcast_id": "…", "status": "sent", "tracking_enabled": true,
"totals": { "recipients": 0, "skipped": 0, "queued": 0, "sent": 0, "delivered": 0, "bounced": 0, "complained": 0, "opened": 0, "clicked": 0, "unsubscribed": 0 },
"rates": { "open": null, "click": null, "bounce": null, "unsubscribe": null },
"variants": [ { "variant": "A", "subject": "…", "recipients": 0, "sent": 0, "delivered": 0, "opened": 0, "clicked": 0, "open_rate": null, "click_rate": null } ],
"winner": null, "top_links": [ { "url": "https://…", "clicks": 0 } ], "generated_at": "2026-09-22T00:00:00.000Z" }