SonarSend API Endpoints Reporting

Reporting

Per-broadcast performance, bounces, engagement and timing.

3 endpoints Base URL · https://api.sonarsend.com Auth · X-API-Key

Broadcast performance summary

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

The headline numbers for one send. Start here; the other reporting endpoints break these down.

Rates are over total_recipients — the intended audience — not over what the provider accepted. That keeps them comparable with the list page and means a send with bounces reads slightly lower than a provider-side dashboard would show.

Three open counts, deliberately. apparent_opened is every contact with any open event, which is the number other tools report. verified_opened is the headline: non-bot openers plus anyone who clicked, since a person cannot click without opening — this is what guarantees open rate ≥ click rate. confirmed_opened is the strict high-confidence floor. total_opened and total_clicked are high-confidence only, so they already exclude bots.

pending_opened and pending_clicked are engagement the bot classifier has not judged yet, and they are forced to 0 once the send is no longer live — a late open on a finished campaign is organic tail, not a scanner flood, so do not read a zero as "nothing waiting". classifier_last_run_at and classifier_interval_ms are likewise only populated while the send is live; they are null otherwise.

id string Required

The broadcast ID.

apparent_open_ratenumber
apparent_openedinteger

Every contact with any open event, bots included.

bot_clickedinteger
bot_openedinteger

Contacts whose open was classified as a machine.

bounce_ratenumber
broadcast_iduuid
classifier_interval_msinteger · nullable

Only populated while the send is live.

classifier_last_run_atstring · nullable

Only populated while the send is live.

click_ratenumber
confirmed_open_ratenumber
confirmed_openedinteger

Strict high-confidence openers only.

last_send_atdate-time · nullable

Most recent recipient dispatched. Advances per spread batch. Null until the first send.

namestring
open_ratenumber

total_opened / total_recipients, to 4 decimal places.

pending_clickedinteger

Unclassified clicks. Zero once the send is no longer live.

pending_openedinteger

Unclassified opens, held behind the verifying window. Zero once the send is no longer live.

sent_atdate-time · nullable

The broadcast's scheduled_at — when the send was asked to start, not when it finished.

statusstring
total_bouncedinteger
total_clickedinteger

High-confidence unique clickers.

total_complainedinteger
total_deliveredinteger
total_failedinteger
total_openedinteger

High-confidence unique openers — bots already excluded.

total_recipientsinteger

The intended audience, and the denominator for every rate here.

total_sentinteger
total_unsubscribedinteger
verified_open_ratenumber
verified_openedinteger

Headline openers: non-bot opens ∪ verified clickers.

200

Headline metrics for the send.

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/report" \
  -H "X-API-Key: $SONARSEND_API_KEY"
Response 200
{
  "broadcast_id": "9c21895d-57f0-4a15-a1df-4dc6835d4f80",
  "name": "Acme onboarding",
  "status": "active",
  "sent_at": "2026-01-15T09:30:00.000Z",
  "last_send_at": "2026-01-15T09:30:00.000Z",
  "total_recipients": 0
}

KPIs for every broadcast, in one call

GET /t/{slug}/api/broadcasts/metrics-bulk

Headline metrics for all of the account's broadcasts at once — the endpoint to build a dashboard on, instead of calling /report in a loop.

Served from a cache with a long TTL. For a send that just started, or after an action you expect to have moved the numbers, pass ?refresh=true to invalidate and recompute before reading. Without it a freshly-started send can read stale for as long as the TTL.

Leaner than /report: opened and clicked here are the verified (bot-filtered) counts, there is no apparent/confirmed split, and there is no total_failed. sparkline is the first 6 hourly open counts from the broadcast's first open, for a trend glyph.

refresh is not declared as a parameter on purpose — this route has never had a querystring schema, and adding one would change what Fastify does with the request. It must be exactly true; any other value, 1 included, is ignored.

No documented fields.

200

One entry per broadcast: broadcast_id, total_recipients, delivered, opened, clicked, bounced, the matching *_rate fields, sparkline, pending_opened, pending_clicked, last_send_at.

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/metrics-bulk" \
  -H "X-API-Key: $SONARSEND_API_KEY"
Response 200
[
  {}
]