Use the documented endpoints, not the legacy ones
A small number of older endpoints — for example, status/generic/allml — were used historically for performance data but have been deprecated and may return invalid or partial data. If you have code that still hits these endpoints, migrate it. The current canonical statistics endpoints are described below; if you need something not covered here, contact Piano Support before assuming a legacy endpoint still works.
Campaign-vs-sendbucket: the key distinction
When you set up a campaign in ESP, the campaign carries the configuration — sending mode, templates, sources (RSS feeds), and attached mailing lists. Each time that campaign actually runs, it produces a sendbucket: a concrete instance that captures when the send happened, who received it (the active subscriber list at that moment), and what content went out (the stories picked up at that time).
Stats APIs come in two flavours: aggregated by campaign (sum across all of its sendbuckets in a date range) or per sendbucket (one specific send). Choose based on whether you want, e.g., "month-over-month performance of the daily newsletter campaign" (campaign-level) or "how did yesterday's 9am send specifically do" (sendbucket-level).
Sendbucket IDs can be retrieved from Piano Support if you need to use the sendbucket endpoints.
Email campaign statistics
Full campaign statistics by date
This is the workhorse endpoint and the right starting point for most reporting integrations. It returns the same set of metrics as the analytics page in the ESP dashboard: deliverability counters (sent, soft/hard bounce, spam, deferral, reject, retry), performance (open, click, promo_click), and break-downs by device, email client, location, and device-by-location.
GET http(s)://api-esp.piano.io/stats/campaigns/full/<campids>?date_start=<YYYY-MM-DD>&date_end=<YYYY-MM-DD>&api_key=<api_key>
-
is a comma-separated list of campaign IDs (e.g.
5,9,34). If you pass multiple IDs, the response is aggregated across all listed campaigns, not separated per campaign. -
and define the window.
Full campaign statistics by sendbucket
For single-send analysis (or aggregate stats across a specific set of sends):
GET http(s)://api-esp.piano.io/stats/campaign/<campid>/sb/<sbids>?api_key=<api_key>
-
is the campaign ID.
-
is a comma-separated list of sendbucket IDs. Multiple IDs produce an aggregated response, not one entry per sendbucket.
The response shape is the same as the by-date endpoint.
Advanced click stats
If you need clicks broken down by block, order, and feed within a campaign:
GET /stats/advancedclicks/<campids>/<date_start>/<date_end>?api_key=<api_key>
The response includes clicks and advertisement_clicks per block / order / feed combination. An optional performance_unique=true query parameter aggregates promo clicks as unique. Note: although the endpoint accepts multiple campaign IDs, block IDs and order numbers are not comparable across campaigns, so multi-campaign calls are rarely meaningful in practice.
Most clicked stories
GET /stats/topstories/<campids>/<date_start>/<date_end>?api_key=<api_key>
Returns per-URL click counts with the story title and link.
Mailing list statistics
GET /stats/squads/full/<sqids>?date_start=<YYYY-MM-DD>&date_end=<YYYY-MM-DD>&api_key=<api_key>
Returns subscription activity (subAdded, subRemoved) and detail break-downs of both subscribes and unsubscribes by source: userInitiated, apiInitiated, dashInitiated, igSync, testEmail, directSub, plus reasons such as hardBounce and spam on the unsubscribe side. Multiple produce an aggregated response.
Total mailing list subscribers
For point-in-time counts of total and active subscribers per list:
POST /publisher/pub/<PUB_ID>/sq/subscribers?api_key=<API_KEY>
Content-Type: application/json
{ "sqIds": [1, 2, 3] }
Returns squad_users (total) and squad_users_active (active subscribers) per list.
Push campaign and push list statistics
Push notifications have their own statistics endpoints with payloads tailored to push semantics (push_sent, received, click, click_to_button_1, prompt outcomes such as prompt_was_accepted / prompt_was_declined):
GET /stats/push/campaigns/full/<campids>?date_start=<...>&date_end=<...>&api_key=<api_key> GET /stats/push/campaign/<campid>/sb/<sbids>?api_key=<api_key> GET /stats/push/squads/full/<sqids>?date_start=<...>&date_end=<...>&api_key=<api_key>
These mirror the email-side endpoints structurally but return push-specific metrics.
Unsubscribe and contact-form feedback
Any message a recipient submits via the contact or feedback link in a newsletter footer — including reason codes and free-text feedback from the default unsubscribe form — is stored in the ESP backend and retrievable via:
GET /publicpages/feedback/<PUB_ID>?event=<event_type>&campid=<camp_id>&sbid=<sb_id>&userid=<user_id>&date_start=<YYYY-MM-DD>&date_end=<YYYY-MM-DD>&api_key=<api_key>
Path and query parameters:
-
(required) — the publisher / Site ID, as shown in the ESP dashboard.
-
event_type(required) —unsubscribeorfaq. -
camp_id,sb_id,user_id(optional) — filter by campaign, sendbucket, or ESP user ID. -
date_start,date_end(required) — the period inYYYY-MM-DD. -
api_key(required) — your ESP API key, available from Setup → Integrations in the ESP dashboard. If you need it provisioned, give Piano Support both your Application ID (AID) and Site ID.
The maximum window is 90 days per call. A request spanning more than 90 days returns HTTP 400. Loop in 90-day chunks if you need a longer history.
A successful response returns up to 1,000,000 documents within the requested period, each with added timestamp, publisherId, campaignId, sbId, userId, an array of reasons (numeric reason codes from the unsubscribe form), and any freeFeedback text the user typed.
Previewing the most recent send of a campaign
ESP does not expose a permanent public URL that always points at "the most recent email from campaign X". The "Copy HTML" button in the dashboard is a UI convenience and is not directly callable from outside. To embed a live preview on a public page, build a small server-side fetch that mirrors what the dashboard does:
-
Make sure you have your ESP API key (Setup → Integrations in the ESP UI) and the campaign ID you want to preview.
-
Query the send history for the campaign to get the most recent sendbucket ID. The campaign-stats-by-sendbucket and feedback endpoints can both serve as ways to identify recent sends; coordinate with Piano Support if you need a dedicated "list sends" endpoint enabled for your Site.
-
Identify the newest send in the response by date and grab its sendbucket ID.
-
Fetch the rendered HTML for that sendbucket via the message-body endpoint. Use the generic "seed" version of the send rather than a personalized one, so the returned HTML does not contain unresolved per-user placeholders.
-
Cache or proxy the HTML to the preview location. Caching for a few hours is recommended both for performance and to avoid hitting API rate limits.
Security considerations
-
Never expose your ESP API key in client-side code. Always route the calls through your own server. Treat the key like a database password.
-
Always request the seed (non-personalized) HTML for previews unless you specifically need the personalized version for a known recipient.
Alternatives to live retrieval
If you do not need on-demand "show the latest send" behavior, two simpler patterns work:
-
Capture-at-send-time. When your team triggers a production send, also call the API once and store the returned HTML to your own storage / CMS, then serve from there.
-
On-demand server-side fetch. Do the same retrieval flow above each time the preview page is requested, with a short server-side cache.
Quick reference
|
Need |
Endpoint |
|---|---|
|
Full email campaign stats over a date range |
|
|
Stats for a specific send |
|
|
Clicks by block / order / feed |
|
|
Top stories by clicks |
|
|
Mailing list subscribe / unsubscribe activity |
|
|
Current subscriber counts per list |
|
|
Push campaign stats |
|
|
Push list stats |
|
|
Unsubscribe / contact feedback (max 90-day window) |
|
|
Render HTML of a specific send |
Server-side flow above using the message-body endpoint |