SonarSend API Reporting & examples Reporting

Reporting

Three read-only views over broadcast performance. All three require:

  • reports:read
EndpointAnswers
GET /api/broadcasts/{broadcast_id}/reportHow did this send do?
GET /api/broadcasts/{broadcast_id}/linksWhich link did the work?
GET /api/broadcasts/metrics-bulkThe headline numbers for many sends at once.

These are the aggregate figures a dashboard, a warehouse job, or a weekly digest needs. The deeper analysis SonarSend runs on a send — bounce and failure breakdowns, engaged-contact lists, variant significance testing, delivery timelines, receiving-provider and device mix, and a single contact’s engagement history — is read in the app on the broadcast’s report page, and is not part of the API.

Common reporting workflow#

  1. store broadcast IDs from the broadcast API as you create sends
  2. poll metrics-bulk for the KPI row of everything you track
  3. call /report for the full numbers on one send, and /links when the question is which call to action worked

Broadcast summary#

GET /api/broadcasts/:broadcast_id/report

Use this as the primary broadcast performance snapshot.

Rates use total_recipients — the intended audience — as the denominator, not what the provider accepted, so they stay comparable with the list page and read slightly lower than a provider-side dashboard.

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

pending_opened and pending_clicked are forced to 0 once the send is no longer live, so a zero there does not mean nothing is waiting on the classifier. classifier_last_run_at and classifier_interval_ms are likewise only populated while the send is live.

Engagement here reflects the settled confidence tier rather than raw opens and clicks: a click is recorded immediately and classified shortly after, so a report read minutes into a send can still move. Expect the numbers to be lower — and truer — than a raw count of engagement.

GET /api/broadcasts/:broadcast_id/links

Every tracked destination with its click counts, busiest first. Links that were never clicked still appear, with zeroes and null timestamps — the universe comes from what was sent, not from what was clicked, which is what makes this usable as a “which call to action failed” view. click_rate is per link over the whole audience, so the link rates do not sum to the broadcast’s click rate.

Bulk metrics for many broadcasts#

GET /api/broadcasts/metrics-bulk

This is useful for dashboards that need high-level KPIs for many broadcasts at once.

It is 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. It must be exactly true; 1 is ignored.

Leaner than /report: opened and clicked here are the verified (bot-filtered) counts, with no apparent/confirmed split and no total_failed.

Integration guidance#

  • Treat these as read-only analytical views.
  • Start with metrics-bulk and drop to /report only for the sends whose numbers you display in full. A dashboard covering fifty broadcasts is one request, not fifty.
  • Poll at a sensible cadence. Nothing here changes second by second once a send has finished.
  • Store broadcast IDs from the creation flow so reporting calls stay simple later.
  • For contact-level data, use a contact export rather than a reporting call: one export produces one CSV for a whole audience, and can include engagement rollup columns.