Skip to content
View as Markdown

List blasts ​

GET /v1/blasts

Returns your campaigns' email blasts with their main stats, newest first.

Scope: blasts:read

Blasts are ordered by created_at (then id), newest first, and rows are never repeated or skipped. The first page fixes the window: a blast created while you page does not join the walk; it heads the next list you start without a cursor.

List rows carry the main stats. opened, clicked and the day-by-day breakdown are only on GET /v1/blasts/{blast_id}.

Without campaign_id the list covers every campaign whose blasts the key can read; a campaign_id must be one of them, otherwise 404.

Query parameters ​

NameTypeRequiredDescription
campaign_idUUIDNoOnly this campaign.
statusstringNoOnly blasts with this status. One of draft, scheduled, sending, paused, completed, cancelled, failed.
sincetimestampNoOnly blasts created at or after this time. ISO 8601; a time without an offset is read as UTC (URL-encode + as %2B).
limitintegerNoBlasts per page. Every blast's stats are counted on each request, so pages are small. 1 to 20. Default: 20.
cursorstringNoThe next_cursor of the previous page, unchanged. It carries the filters: send it alone, or with exactly the filters of the first page. Up to 4,096 characters.

Response ​

Status: 200 OK

NameTypeDescription
dataarray of objectsBlasts, newest first.
data.idUUIDThe blast.
data.namestringIts name in DialEcho.
data.campaign_idUUIDThe blast's campaign.
data.statusstringThe blast status. One of draft, scheduled, sending, paused, completed, cancelled, failed.
data.created_attimestampWhen it was created (RFC 3339, UTC).
data.scheduled_attimestamp | nullWhen it is or was scheduled to start; null when it was not scheduled.
data.subject_templatestring | nullThe subject as written, merge tags included. Each send's subject is the subject its recipient got.
data.statsobjectThe main stats.
data.stats.recipients_totalintegerEvery recipient of the blast, in any status.
data.stats.deliveredintegerDelivered sends: status delivered, plus sent through any sender but SendGrid, whose sends count once SendGrid confirms delivery.
data.stats.bouncedintegerSends that bounced.
data.stats.failedintegerSends that failed.
data.stats.suppressedintegerRecipients not emailed because the address is suppressed.
data.stats.pendingintegerRecipients not sent yet (pending or processing). Cancelled and skipped recipients count only in recipients_total.
data.stats.repliedintegerDelivered recipients who replied: a reply DialEcho matched to their send.
data.stats.opt_outsintegerRecipients who opted out: their matched reply was an opt-out, or the provider reported an unsubscribe. Unsubscribe-link clicks cannot be tied to a blast and are not counted.
data.stats.bounce_ratenumber | nullbounced / (delivered + bounced), rounded to 4 decimals; null while both are 0.
data.stats.reply_ratenumber | nullreplied / delivered, rounded to 4 decimals and never above 1; null while nothing was delivered.
next_cursorstring | nullPass it back as cursor for the next page; null when there is none.
has_morebooleantrue while next_cursor is set.

Example request ​

curl

bash
curl "https://development.agencydevworks.ai/v1/blasts?campaign_id=cccccccc-0000-4000-8000-000000000001&limit=20" \
  -H "Authorization: Bearer dei_your_key"

JavaScript

js
const response = await fetch("https://development.agencydevworks.ai/v1/blasts?campaign_id=cccccccc-0000-4000-8000-000000000001&limit=20", {
  headers: {
    "Authorization": "Bearer dei_your_key",
  },
});
const data = await response.json();

Example response ​

Status: 200 OK

json
{
  "data": [
    {
      "id": "22222222-0000-4000-8000-000000000001",
      "name": "Fall re-engagement",
      "campaign_id": "cccccccc-0000-4000-8000-000000000001",
      "status": "completed",
      "created_at": "2026-11-02T14:00:00Z",
      "scheduled_at": "2026-11-03T15:00:00Z",
      "subject_template": "{{first_name}}, your solar quote is ready",
      "stats": {
        "recipients_total": 1200,
        "delivered": 1100,
        "bounced": 30,
        "failed": 5,
        "suppressed": 40,
        "pending": 20,
        "replied": 25,
        "opt_outs": 4,
        "bounce_rate": 0.0265,
        "reply_rate": 0.0227
      }
    }
  ],
  "next_cursor": null,
  "has_more": false
}

Errors ​

HTTPCodeWhen
403insufficient_scopeThe key does not have blasts:read. Checked before the query is validated.
404not_foundcampaign_id is not a campaign whose blasts the key can read.
409resync_requiredThe cursor expired (after 30 days) or the key's or its member's access changed since it was issued. Start again without a cursor.
422invalid_cursorThe cursor was not issued to this key for this list, was changed, or was sent with different filters.
422invalid_requestA parameter is invalid. error.fields names the first invalid filter (campaign_id, status, since), or query.limit (over 20) or query.cursor.
503dependency_unavailableBlast data or the access check is temporarily unavailable. Retry with backoff.

Every endpoint can also answer 401 invalid_api_key, 429 rate_limited and 503 dependency_unavailable. See Errors in the overview.