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 |
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.
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.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,
"clicksByLink": [
{
"url": "https://example.com",
"clicks": 42
}
],
"clicksOverTime": [
{
"at": "2026-08-23T11:00:00.000Z",
"clicks": 12
}
]
}
}