GET /api/v1/campaigns/{campaignId}/stats
How the campaign performed — the same figures the Stats screen shows.
| Field | Description |
|---|---|
sent | Emails actually dispatched |
delivered | sent minus bounced |
bounced | Sends rejected by the receiving side |
uniqueOpens / openRate | Contacts who opened at least once, and their share of delivered |
uniqueClicks / clickRate | Contacts who clicked at least once, and their share of delivered |
unsubscribed / unsubscribeRate | Unsubscribes attributed to this campaign, and their share of delivered |
topicOptOuts | Recipients who turned off one topic through this campaign — they stay subscribed |
clicksByLink | Per URL: {url, uniqueClicks, totalClicks} |
clicksOverTime | Per day: {date, clicks}, date as YYYY-MM-DD |
opensOverTime | Per day: {date, opens}, date as YYYY-MM-DD |
clicksByHour / opensByHour | Per hour since sending: {hour, clicks} / {hour, opens}, hour from 0 to 47 |
sentAt and always carries all 48 slots, quiet hours included.
Opens count first opens, one per recipient at most.
unsubscribed and topicOptOuts count two different departures and never overlap. Someone who
unsubscribes stops receiving everything from you; someone who turns off a topic keeps receiving the
rest. Reading them together tells you whether a campaign lost an audience or only lost a subject.
topicOptOuts covers only campaigns sent since topic opt-outs started being recorded — older ones
read 0.
Rates are shares of delivered, not of sent — an address that bounced never had the chance to
open. While nothing is delivered yet the denominator is zero and every rate reads 0, never NaN:
poll progress on the campaign itself to know whether the send is over before reading anything into
them. A campaign of another organization answers 404, like an id that does not exist.
Authorization
| Role in the organization | This endpoint |
|---|---|
OWNER | Allowed |
ADMIN | Allowed |
MEMBER | Allowed |
Example
bash
curl -H "x-api-key: $AGENTMAIL_API_KEY" \
"https://www.agentsmail.io/api/v1/campaigns/$CAMPAIGN_ID/stats"Response
200 OK— the call succeeded.401 Unauthorized— missing or invalid key, or its owner left the organization.404 Not Found— no such resource, or it belongs to another organization. The API never confirms that an id exists to a caller who has no right to it.429 Too Many Requests— over 120 requests in a minute for this key.
Response fields
| Field | Type | Description |
|---|---|---|
success | boolean | Indicates if the operation was successful |
data.sent | number | Emails handed to the provider |
data.delivered | number | Accepted by the receiving server |
data.bounced | number | Permanently rejected — the address goes to the suppression list |
data.uniqueOpens | number | Recipients who loaded the tracking pixel at least once |
data.openRate | number | Computed on delivered, not on sent |
data.uniqueClicks | number | Recipients who followed at least one tracked link |
data.clickRate | number | Same basis |
data.unsubscribed | number | Recipients who left through this campaign |
data.unsubscribeRate | number | Same basis |
data.topicOptOuts | number | Recipients who turned off a topic through this campaign, still subscribed |
data.clicksByLink | array | One entry per tracked URL |
data.clicksOverTime | array | Clicks bucketed by time |
Example response
json
{
"success": true,
"data": {
"sent": 12,
"delivered": 12,
"bounced": 12,
"uniqueOpens": 12,
"openRate": 12,
"uniqueClicks": 12,
"clickRate": 12,
"unsubscribed": 12,
"unsubscribeRate": 12,
"topicOptOuts": 3,
"clicksByLink": [
{
"url": "https://example.com",
"clicks": 42
}
],
"clicksOverTime": [
{
"at": "2026-08-23T11:00:00.000Z",
"clicks": 12
}
]
}
}