SonarSend API Endpoints Broadcasts

Broadcasts

Draft, schedule and control a one-off send.

16 endpoints Base URL · https://api.sonarsend.com Auth · X-API-Key
The Broadcast object 48 fields
archived_atdate-time · nullable

Set by POST /{id}/archive. Archived broadcasts are hidden from the list unless you ask for them.

contentobject · nullable

Subject, bodies and sender fields. Free-form: the send pipeline reads subject plus one of body_html / body_mjml / body_text, or a template binding. On the list endpoint this object is the LEAN form — the bodies and template_snapshot are replaced by has_body_html / has_body_mjml / has_body_text / has_template_snapshot booleans unless you pass ?full=true.

content_versioninteger

Bumped on every content edit. Each recipient is stamped with the version it received, so a sent email can still be reconstructed after the draft is changed.

created_atdate-time · nullable
delivery_window_datestring · nullable

Target completion day for by_date.

delivery_window_daysinteger · nullable
delivery_window_kindstring

now sends as fast as capacity allows; over_days spreads across delivery_window_days; by_date spreads to land by delivery_window_date.

email_type_iduuid

Session responses only — replaced by email_type_key for API-key callers.

email_type_keystring · nullable

API-key responses. The email type's public key; null when the type was deleted.

exclude_segment_idsuuid[]

Session responses only.

exclude_segment_keysstring[] · nullable

API-key responses. Subtracted from the audience.

failedinteger

Recipients the provider rejected. Present alongside total_recipients.

iduuid

Canonical broadcast ID. Every other endpoint in this group takes it.

include_segment_idsuuid[]

Session responses only.

include_segment_keysstring[] · nullable

API-key responses. The audience. An empty array means no segment is attached, which blocks scheduling.

kindstring

regular for a normal campaign. triggered marks a retired standing broadcast — those are excluded from GET /api/broadcasts entirely and are reachable only by ID.

last_seed_send_atdate-time · nullable
last_test_send_atdate-time · nullable
metadataobject · nullable

Your own workflow context. Flat only: up to 20 lowercase snake_case keys, values string/number/boolean or arrays of those, 8KB serialised. Filter the list by containment with ?metadata=.

mta_config_iduuid · nullable

Session responses only — replaced by mta_config_key for API-key callers.

mta_config_keystring · nullable

API-key responses. The sending provider's public key; null when none is set or the config was deleted.

namestring

Internal label. Never shown to recipients — the subject line lives on content.

pause_reasonstring · nullable

Why a paused broadcast is paused: user, or one of capacity_collapsed / domain_paused / tenant_paused when the platform paused it. Null in every other status.

progress_pctinteger

sent / total_recipients, rounded. Present alongside total_recipients.

scheduled_atdate-time · nullable

When the send was asked to start. Setting it does not schedule anything — POST /{id}/schedule does. Reporting calls this sent_at.

send_modestring · nullable

Only blast is accepted through the API today.

send_stateobject · nullable

Internal send bookkeeping (phase, estimated audience). Shape is not stable — read the reporting endpoints instead.

sending_identity_emailsstring[] · nullable

API-key responses. The from-addresses this broadcast may send from. Null means "no explicit pick" — the allocator chooses from the pool for the email type.

sending_identity_idsstring[] · nullable

Session responses only.

sending_lanesobject[] · nullable

Explicit (sending identity × provider) lanes, with optional weights. Null means each identity uses its preferred provider.

sends_per_hourinteger · nullable

Throttle. Null means unthrottled beyond the provider and warming caps.

sentinteger

Recipients dispatched so far. Present alongside total_recipients.

shortfall_behaviorstring

What happens when the audience does not fit the window. extend keeps sending past it; holdback sends only what fits today and drops the rest.

skipped_datesstring[]

YYYY-MM-DD days a spread send skips. Editable mid-flight through POST /{id}/reschedule, future days only.

split_test_configobject · nullable

Holdout split-test settings. Read-only through the API — sending it on create or update is rejected with ADVANCED_SPLIT_TEST_UNSUPPORTED.

split_test_resultobject · nullable

Winner and verdict, once a holdout test has been evaluated.

started_atdate-time · nullable

When dispatch actually began. Null until then, and it can differ from scheduled_at.

statusenum

Lifecycle state. Only draft accepts edits or deletion; only completed, failed and cancelled can be archived.

One of: draft, scheduled, sending, paused, completed, failed, cancelled
tagsstring[]

Free-text labels. Filter the list with ?tags=.

tenant_iduuid
test_notesstring · nullable
total_recipientsinteger

Materialised recipient rows. Present once a send has been prepared; absent on an untouched draft.

tracking_enabledboolean · nullable

Whether links are rewritten and the open pixel is injected.

updated_atdate-time · nullable
variant_countinteger

Lean list rows only, and only when the broadcast has variants.

variant_idsstring[]

Lean list rows only, and only when the broadcast has variants.

variantsobject[] · nullable

A/B arms: id, name, weight and content_overrides. Absent on the lean list rows, which carry variant_count and variant_ids instead.

view_modelobject · nullable

Derived UI state: available_actions (the lifecycle calls legal right now), schedule_readiness (per-guard reasons a draft cannot be scheduled yet), delivery_progress, failure_info. Derived, never stored.

List broadcasts

GET /t/{slug}/api/broadcasts/

Every broadcast in the account, newest first. Not paginated — the envelope's next_cursor is always null and has_more always false — so narrow with the filters rather than expecting pages.

Rows are lean by default. Each row drops body_html, body_mjml, body_text and template_snapshot (30–100KB each) and reports has_body_html / has_body_mjml / has_body_text / has_template_snapshot instead, and it replaces variants with variant_count + variant_ids. ?full=true returns everything, for every row — reach for GET /api/broadcasts/{id} first if you only need one.

Archived broadcasts are excluded unless you ask for them, and retired triggered broadcasts never appear here at all; fetch those by ID.

full string

Return the complete payload — rendered bodies and template snapshot — on every row. Defaults to the lean shape.

status string

Comma-separated: draft, scheduled, sending, paused, completed, failed, cancelled. Any other value rejects the whole request with INVALID_STATUS — it is not ignored.

tags string

Comma-separated tag names. Matches a broadcast whose tags overlap ANY of them.

metadata string

JSON object, matched by containment: a broadcast matches when its metadata contains every key/value given, arrays included. Must parse as a JSON object and stay under 8192 characters, or the request fails with INVALID_METADATA_FILTER.

since string

ISO 8601. Created at or after this instant.

before string

ISO 8601. Created strictly before this instant.

archived string

true adds archived broadcasts to the live ones; only returns archived ones alone. Omit to exclude them. false is not accepted — omit the parameter instead.

A page of Broadcast objects, under data.

200

Every matching broadcast, newest first. Unpaginated: next_cursor is null and has_more is false.

400

The request failed validation, or a referenced record was rejected.

404

The tenant slug, or the record the path names, does not exist.

Request
curl -X GET "https://api.sonarsend.com/t/acme/api/broadcasts" \
  -H "X-API-Key: $SONARSEND_API_KEY"
Response 200
{
  "data": [
    {
      "id": "9c21895d-57f0-4a15-a1df-4dc6835d4f80",
      "tenant_id": "9c21895d-57f0-4a15-a1df-4dc6835d4f80",
      "name": "Acme onboarding",
      "status": "draft",
      "kind": "string",
      "content_version": 0
    }
  ],
  "next_cursor": "eyJpZCI6IjljMjE4OTVkIn0",
  "has_more": true
}

Create a draft broadcast

POST /t/{slug}/api/broadcasts/

Creates a broadcast in draft. Nothing is sent — scheduling is a separate, separately-scoped call (POST /api/broadcasts/{id}/schedule, broadcasts:schedule).

Name the dependencies by public key, not by UUID. email_type_key, mta_config_key, include_segment_keys, exclude_segment_keys, sending_identity_emails and content.template_slug are resolved server-side; an unknown key is rejected (INVALID_EMAIL_TYPE, INVALID_MTA_CONFIG, INVALID_SEGMENT, INVALID_SENDING_IDENTITY, INVALID_TEMPLATE) rather than silently ignored. The *_id variants also exist but they are the browser session's form; keys survive a re-sync, UUIDs do not.

email_type_key is required for API-key callers even though the JSON Schema does not mark it so — omitting it returns 400 INVALID_EMAIL_TYPE. content.subject is required, and so is one of content.body_html / content.body_mjml, unless you are in template mode (content.template_slug + content.template_binding), where the server renders the bodies at save time.

Variants must number at least two, each with id, name, a positive weight and at least one content_overrides field, or the call fails with INVALID_VARIANTS. Per-variant sending_identity_id or mta_config_id is rejected (VARIANT_DELIVERY_OVERRIDE_UNSUPPORTED), as is any split_test_config (ADVANCED_SPLIT_TEST_UNSUPPORTED) — holdout/winner workflows are console-only.

Retries. Send an Idempotency-Key header and a replay returns the original 201 body with idempotency-replay: true instead of creating a second broadcast. The key must match ^[A-Za-z0-9_-]{8,128}$ and is remembered for 24 hours; a key that does not match the pattern is ignored silently, so a malformed key gives you no protection and no error.

contentobjectRequired

subject plus one of body_html / body_mjml / body_text, or template_slug + template_binding for template mode. Also carries from_name, reply_to, preheader.

namestringRequired

Internal label. Recipients never see it.

email_type_idstring

Session form of email_type_key.

email_type_keystring

Required for API-key callers. Determines subscription handling and the sender pool.

exclude_segment_idsstring[]

Session form of exclude_segment_keys.

exclude_segment_keysstring[]

Subtracted from the audience.

include_segment_idsstring[]

Session form of include_segment_keys.

include_segment_keysstring[]

The audience. At least one is needed before the draft can be scheduled.

metadataobject · nullable

Flat workflow context. Up to 20 lowercase snake_case keys; values string (≤256 chars), number, boolean, or arrays of those (≤50 items); 8KB serialised; no nested objects.

mta_config_idstring

Session form of mta_config_key.

mta_config_keystring

Sending provider. Required before the draft can be scheduled.

scheduled_atstring

ISO 8601 start time. Recorded only — it does not schedule anything until you call the schedule endpoint.

send_modeenum

blast is the only mode the API accepts.

One of: blast
sending_identity_emailsstring[] · nullable

From-addresses to send from. Omit to let the allocator pick from the email type's pool.

sending_identity_idsstring[] · nullable

Session form of sending_identity_emails.

sends_per_hourinteger · nullable

Throttle. Null for none beyond provider and warming caps.

split_test_configobject · nullable

Rejected for API-key callers. Holdout split tests are console-only.

tagsstring[]

Free-text labels for your own filtering.

tracking_enabledboolean

Rewrite links and inject the open pixel.

variantsany[]

A/B arms: id, name, weight, content_overrides. Two or more, or none.

The Broadcast object.

201

The draft was created — or, on an idempotent replay, the body the first call returned.

400

The request failed validation, or a referenced record was rejected.

404

The tenant slug, or the record the path names, does not exist.

Request
curl -X POST "https://api.sonarsend.com/t/acme/api/broadcasts" \
  -H "X-API-Key: $SONARSEND_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Acme onboarding",
    "content": {},
    "tags": [
      "string"
    ],
    "metadata": {},
    "include_segment_ids": [
      "string"
    ],
    "exclude_segment_ids": [
      "string"
    ]
  }'
Response 201
{
  "id": "9c21895d-57f0-4a15-a1df-4dc6835d4f80",
  "tenant_id": "9c21895d-57f0-4a15-a1df-4dc6835d4f80",
  "name": "Acme onboarding",
  "status": "draft",
  "kind": "string",
  "content_version": 0
}

Get a broadcast

GET /t/{slug}/api/broadcasts/{id}

The complete broadcast — always the full shape, never the list endpoint's lean form.

Beyond the stored row this adds live send progress (total_recipients, sent, failed, progress_pct) and view_model, whose available_actions tells you which lifecycle calls are legal right now and whose schedule_readiness names, guard by guard, what a draft is still missing. Prefer reading those over inferring legality from status.

id string Required

The broadcast ID.

The Broadcast object.

200

The broadcast, with send progress and the derived action/readiness view model.

400

The request failed validation, or a referenced record was rejected.

404

The tenant slug, or the record the path names, does not exist.

Request
curl -X GET "https://api.sonarsend.com/t/acme/api/broadcasts/9c21895d-57f0-4a15-a1df-4dc6835d4f80" \
  -H "X-API-Key: $SONARSEND_API_KEY"
Response 200
{
  "id": "9c21895d-57f0-4a15-a1df-4dc6835d4f80",
  "tenant_id": "9c21895d-57f0-4a15-a1df-4dc6835d4f80",
  "name": "Acme onboarding",
  "status": "draft",
  "kind": "string",
  "content_version": 0
}

Update a draft

PUT /t/{slug}/api/broadcasts/{id}

Drafts only. Once a broadcast has been scheduled the row is frozen and this returns 409 NOT_DRAFT; call POST /{id}/unschedule first to bring it back to draft.

A partial update: fields you omit are left alone. Fields you send REPLACE what is stored — tags and metadata are overwritten wholesale, not merged, which is the opposite of the contact upsert's behaviour. The same public-key reference fields work here as on create.

Sending sending_identity_ids: [] is rejected with NO_IDENTITY; send null to hand identity selection back to the allocator. split_test_config and per-variant delivery overrides are rejected here too.

id string Required

The broadcast ID.

The Broadcast object.

200

The updated broadcast.

400

The request failed validation, or a referenced record was rejected.

404

The tenant slug, or the record the path names, does not exist.

Request
curl -X PUT "https://api.sonarsend.com/t/acme/api/broadcasts/9c21895d-57f0-4a15-a1df-4dc6835d4f80" \
  -H "X-API-Key: $SONARSEND_API_KEY"
Response 200
{
  "id": "9c21895d-57f0-4a15-a1df-4dc6835d4f80",
  "tenant_id": "9c21895d-57f0-4a15-a1df-4dc6835d4f80",
  "name": "Acme onboarding",
  "status": "draft",
  "kind": "string",
  "content_version": 0
}

Delete a draft

DELETE /t/{slug}/api/broadcasts/{id}

Permanently removes the broadcast row. Irreversible — there is no undelete and no trash.

Drafts only: anything that has been scheduled or sent returns 409 NOT_DRAFT, deliberately, so a send's record survives for reporting. To get a finished broadcast out of the way use POST /{id}/archive, which is reversible.

id string Required

The broadcast ID.

204

The draft was deleted.

400

The request failed validation, or a referenced record was rejected.

404

The tenant slug, or the record the path names, does not exist.

Request
curl -X DELETE "https://api.sonarsend.com/t/acme/api/broadcasts/9c21895d-57f0-4a15-a1df-4dc6835d4f80" \
  -H "X-API-Key: $SONARSEND_API_KEY"

Schedule a broadcast

POST /t/{slug}/api/broadcasts/{id}/schedule

Moves the draft to scheduled and commits it to send. This is the call that causes mail to go out, which is why it needs broadcasts:schedule rather than broadcasts:write.

Legal from draft only; anything else is 409 INVALID_TRANSITION. Four guards must pass first — at least one include segment, an MTA config, a content.subject, and a valid split-test config — and a failure names the guard. GET /api/broadcasts/{id} reports the same checks up front in view_model.schedule_readiness.

Three failures worth handling separately: 422 TRACKING_NOT_LIVE when a sending identity's tracking domain has not gone live (its unsubscribe and click links would be dead), 422 when the delivery window cannot be satisfied by the warming capacity available, and 402 when the account's plan does not permit sending yet. None of them are retryable without changing something first.

Set scheduled_at before you call this. Scheduling does not supply a default: the dispatcher picks up broadcasts whose scheduled_at has passed, and a null one never matches that comparison — the broadcast sits in scheduled indefinitely. A scheduled_at already in the past starts on the next tick.

id string Required
statusstring

Always scheduled. The action succeeded; re-read the broadcast for the full state.

200

The broadcast is scheduled.

400

The request failed validation, or a referenced record was rejected.

404

The tenant slug, or the record the path names, does not exist.

Request
curl -X POST "https://api.sonarsend.com/t/acme/api/broadcasts/9c21895d-57f0-4a15-a1df-4dc6835d4f80/schedule" \
  -H "X-API-Key: $SONARSEND_API_KEY"
Response 200
{
  "status": "active"
}

Edit the calendar of a running spread send

POST /t/{slug}/api/broadcasts/{id}/reschedule

Rewrites which days a multi-day spread send skips, then re-projects the remaining schedule immediately.

Despite the name this is not "change the send time". It only takes skipped_dates, and it applies only to a broadcast that is actively sending on a spread plan: any other status is 409 NOT_SENDING, and a send with no spread plan is 409 NO_SPREAD_PLAN. To move a send that has not started, unschedule it, set scheduled_at, and schedule it again.

skipped_dates is the complete replacement list, not a delta — days you leave out become sending days again. Only FUTURE days can be newly skipped; adding today or a past day returns 400 INVALID_SKIP. Un-skipping is always allowed. "Today" is the tenant's local day, not UTC.

id string Required

The broadcast ID.

skipped_datesstring[]Required

The full set of YYYY-MM-DD days to skip. Anything longer is truncated to the first 10 characters, and duplicates are collapsed.

batchesobject[]

One entry per sending day. Past and in-flight days are unchanged; only the tail is re-projected.

planobject

The spread plan after reconciliation.

200

The re-projected delivery plan and its per-day batches.

400

The request failed validation, or a referenced record was rejected.

404

The tenant slug, or the record the path names, does not exist.

Request
curl -X POST "https://api.sonarsend.com/t/acme/api/broadcasts/9c21895d-57f0-4a15-a1df-4dc6835d4f80/reschedule" \
  -H "X-API-Key: $SONARSEND_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "skipped_dates": [
      "string"
    ]
  }'
Response 200
{
  "plan": {},
  "batches": [
    {}
  ]
}

Pause a send

POST /t/{slug}/api/broadcasts/{id}/pause

Stops dispatching. Legal from sending only — 409 INVALID_TRANSITION otherwise, including from scheduled (use unschedule for one that has not started).

Queued jobs are drained, claimed recipients go back to pending, and a spread plan is held. Mail already handed to the provider is gone and cannot be recalled; pausing only affects what has not been dispatched. Warming day-bookings are released, so a long pause can mean the resumed send has to compete for capacity again.

A split test in its evaluation dwell phase cannot be paused.

id string Required
statusstring

Always paused. The action succeeded; re-read the broadcast for the full state.

200

The send is paused.

400

The request failed validation, or a referenced record was rejected.

404

The tenant slug, or the record the path names, does not exist.

Request
curl -X POST "https://api.sonarsend.com/t/acme/api/broadcasts/9c21895d-57f0-4a15-a1df-4dc6835d4f80/pause" \
  -H "X-API-Key: $SONARSEND_API_KEY"
Response 200
{
  "status": "active"
}

Resume a paused send

POST /t/{slug}/api/broadcasts/{id}/resume

Returns the broadcast to sending and re-enqueues everyone still pending. Legal from paused only.

Only a plan held because you paused is released; a plan the platform held for collapsed capacity or a paused domain stays held and recovers on its own once the underlying condition clears. If re-enqueueing fails the broadcast is rolled back to paused and the call errors, so a failed resume never leaves it stranded mid-state.

id string Required
statusstring

Always sending. The action succeeded; re-read the broadcast for the full state.

200

The send is running again.

400

The request failed validation, or a referenced record was rejected.

404

The tenant slug, or the record the path names, does not exist.

Request
curl -X POST "https://api.sonarsend.com/t/acme/api/broadcasts/9c21895d-57f0-4a15-a1df-4dc6835d4f80/resume" \
  -H "X-API-Key: $SONARSEND_API_KEY"
Response 200
{
  "status": "active"
}

Cancel a send

POST /t/{slug}/api/broadcasts/{id}/cancel

Stops the send for good. Legal from scheduled, sending or paused; a draft has nothing to cancel (delete it instead) and a completed broadcast cannot be undone.

This is the call that stops a send already in flight: it drains queued jobs, releases claimed recipients, and drops every spread batch that has not fired. Batches that already went out are kept for reporting. Mail the provider has already accepted is on its way and cannot be recalled.

A cancelled broadcast can be returned to draft from the console, but only while no recipient has been sent or failed — after the first send that door is closed.

id string Required
statusstring

Always cancelled. The action succeeded; re-read the broadcast for the full state.

200

The send is cancelled.

400

The request failed validation, or a referenced record was rejected.

404

The tenant slug, or the record the path names, does not exist.

Request
curl -X POST "https://api.sonarsend.com/t/acme/api/broadcasts/9c21895d-57f0-4a15-a1df-4dc6835d4f80/cancel" \
  -H "X-API-Key: $SONARSEND_API_KEY"
Response 200
{
  "status": "active"
}

Return a scheduled broadcast to draft

POST /t/{slug}/api/broadcasts/{id}/unschedule

Takes a broadcast out of the send queue and makes it editable again.

Legal from scheduled only. A broadcast that has started sending returns 409 INVALID_TRANSITION — this is not the call for stopping a live send; cancel is.

The preview delivery plan is discarded and warming day-bookings are released, so re-scheduling recomputes the audience and the day plan from scratch against whatever capacity is free then. A broadcast unscheduled and re-scheduled is not guaranteed the same delivery calendar.

id string Required
statusstring

Always draft. The action succeeded; re-read the broadcast for the full state.

200

The broadcast is a draft again.

400

The request failed validation, or a referenced record was rejected.

404

The tenant slug, or the record the path names, does not exist.

Request
curl -X POST "https://api.sonarsend.com/t/acme/api/broadcasts/9c21895d-57f0-4a15-a1df-4dc6835d4f80/unschedule" \
  -H "X-API-Key: $SONARSEND_API_KEY"
Response 200
{
  "status": "active"
}

Archive a broadcast

POST /t/{slug}/api/broadcasts/{id}/archive

Hides a finished broadcast from the default list. Housekeeping only — nothing is deleted, reporting keeps working, and POST /{id}/unarchive puts it back.

Only completed, failed and cancelled broadcasts can be archived (409 NOT_ARCHIVABLE); archiving one that already is returns 409 ALREADY_ARCHIVED. To see archived rows again pass ?archived=true or ?archived=only on the list endpoint.

id string Required
statusstring

Always archived. The action succeeded; re-read the broadcast for the full state.

200

The broadcast is archived.

400

The request failed validation, or a referenced record was rejected.

404

The tenant slug, or the record the path names, does not exist.

Request
curl -X POST "https://api.sonarsend.com/t/acme/api/broadcasts/9c21895d-57f0-4a15-a1df-4dc6835d4f80/archive" \
  -H "X-API-Key: $SONARSEND_API_KEY"
Response 200
{
  "status": "active"
}

Restore an archived broadcast

POST /t/{slug}/api/broadcasts/{id}/unarchive

Clears the archive flag and returns the broadcast to the default list. The lifecycle status is untouched — an unarchived broadcast is still completed or cancelled, not a draft. A broadcast that is not archived returns 409 NOT_ARCHIVED.

id string Required
statusstring

Always unarchived. The action succeeded; re-read the broadcast for the full state.

200

The broadcast is back in the default list.

400

The request failed validation, or a referenced record was rejected.

404

The tenant slug, or the record the path names, does not exist.

Request
curl -X POST "https://api.sonarsend.com/t/acme/api/broadcasts/9c21895d-57f0-4a15-a1df-4dc6835d4f80/unarchive" \
  -H "X-API-Key: $SONARSEND_API_KEY"
Response 200
{
  "status": "active"
}

Archive many broadcasts

POST /t/{slug}/api/broadcasts/bulk-archive

Archives up to 100 broadcasts in one call.

Partial success is the normal outcome, and it is reported as 200, not an error. Each ID is attempted independently; any that fails — wrong status, already archived, does not exist, belongs to another account — lands in skipped with no reason attached. Read the response arrays, not the status code, and archive individually if you need to know why something was refused.

idsuuid[]Required

Broadcast IDs. More than 100, or any non-UUID entry, rejects the whole request.

archivedstring[]

IDs now archived.

skippedstring[]

IDs that failed, for any reason. The reason is not reported.

200

What happened to each ID. Both arrays together always account for every ID you sent.

400

The request failed validation, or a referenced record was rejected.

404

The tenant slug, or the record the path names, does not exist.

Request
curl -X POST "https://api.sonarsend.com/t/acme/api/broadcasts/bulk-archive" \
  -H "X-API-Key: $SONARSEND_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "ids": [
      "9c21895d-57f0-4a15-a1df-4dc6835d4f80"
    ]
  }'
Response 200
{
  "archived": [
    "string"
  ],
  "skipped": [
    "string"
  ]
}

Preview a template rebase

POST /t/{slug}/api/broadcasts/{id}/rebase-preview

Replays the broadcast's stored template binding against the template's CURRENT schema and returns what the binding would become. Nothing is written — despite being a POST this is a pure read. To keep the result, send merged_binding back through PUT /api/broadcasts/{id}.

Use it when a template has changed since the draft was authored and you need to know which parameters, sections or slots were added, removed or retyped before re-saving.

Only applies to template-mode broadcasts: one authored with inline HTML returns 400 NOT_TEMPLATE_MODE.

id string Required

The broadcast ID.

diffany

What the rebase changed, by binding element. Shape follows the template schema, so it is not enumerated here.

merged_bindingobject

The binding reconciled against the template's current schema. Save this.

merged_snapshotobject

The template snapshot the merged binding was resolved against.

200

The rebased binding, its snapshot, and what changed.

400

The request failed validation, or a referenced record was rejected.

404

The tenant slug, or the record the path names, does not exist.

Request
curl -X POST "https://api.sonarsend.com/t/acme/api/broadcasts/9c21895d-57f0-4a15-a1df-4dc6835d4f80/rebase-preview" \
  -H "X-API-Key: $SONARSEND_API_KEY"
Response 200
{
  "merged_binding": {},
  "merged_snapshot": {},
  "diff": null
}

Send a proof to yourself

POST /t/{slug}/api/broadcasts/{id}/test-send

Sends the broadcast to a handful of your own addresses so a human can read it before it goes out. Real mail, hence broadcasts:schedule.

The cap is on addresses, not on messages. Every address receives one copy per variant per sending identity, so five addresses across three variants and two senders is thirty emails. Each subject is prefixed [TEST: <variant> · <sender>] so the copies are distinguishable in one inbox. That is the difference from seed-send, which sends a single version to a seed list for placement testing.

Like a seed send, it publishes no events and creates no recipient rows, so it never shows up in the broadcast's report.

id string Required

The broadcast ID.

recipientsemail[]Required

Addresses to proof to, 1–10. They do not need to be contacts in the account.

errorsstring[]

One message per failure. Empty when everything went out.

sentinteger

Messages the provider accepted — one per address × variant × sending identity, the count from the description above, not the number of addresses.

200

Per-message outcome. A non-empty errors still returns 200 — check it.

400

The request failed validation, or a referenced record was rejected.

404

The tenant slug, or the record the path names, does not exist.

Request
curl -X POST "https://api.sonarsend.com/t/acme/api/broadcasts/9c21895d-57f0-4a15-a1df-4dc6835d4f80/test-send" \
  -H "X-API-Key: $SONARSEND_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "recipients": [
      "jamie@example.com"
    ]
  }'
Response 200
{
  "sent": 0,
  "errors": [
    "string"
  ]
}