Skip to content

Broadcasts

One-off emails to an audience, sent now or at a time you choose.

View as Markdown

List broadcasts

GET/projects/{project_id}/broadcasts

Newest first, without each email’s body; get one broadcast for its html.

Parameters

NameInTypeNotes
project_idpathstringRequired
statusquery"draft" | "scheduled" | "sending" | "sent" | "canceled" | nullOptional
cursorquerystring | nullOptional
limitqueryintegerOptional, default 50
Request
curl "https://sendocado.com/api/v1/projects/$PROJECT_ID/broadcasts?status=draft" \
  -H "Authorization: Bearer $SENDOCADO_API_KEY"
Response 200
{
  "broadcasts": [
    {
      "id": "06i8j76b2lajlj4h9du7794g9dpmrcg6",
      "project_id": "ujzde8gxd6ncf10epf91dhodzdoc9is0",
      "name": "October product update",
      "status": "draft",
      "filter_groups": [
        {
          "filters": [
            {
              "type": "tag",
              "op": "has",
              "tag": "customer"
            }
          ]
        }
      ],
      "subject": "What’s new in Acme this month",
      "preview_text": "Faster exports and a new dashboard.",
      "source_template_id": null,
      "scheduled_for": null,
      "has_open_sends": false,
      "created_at": 1790172800000
    }
  ],
  "next_cursor": null
}

Errors

StatusTypeExample message
400invalid_datalimit: Too big: expected number to be <=200
401unauthorizedSend a valid API key as "Authorization: Bearer sc_…", or sign in.
404project_not_foundProject not found.
429rate_limitedToo many requests. Try again in 12 seconds.

Create a broadcast

POST/projects/{project_id}/broadcasts

A draft. Its content is html, markdown, or a copy of a template. Send or schedule it with an update.

Parameters

NameInTypeNotes
project_idpathstringRequired

Body

Send JSON with every field; use null for a value you do not set.

FieldTypeNotes
namestringRequired
filter_groupsobject[]Required
filter_groups[].filtersobject[]Required
filter_groups[].filters[].type"tag"Required, when type is "tag"
filter_groups[].filters[].op"has" | "has_not"Required, when type is "tag"
filter_groups[].filters[].tagstringRequired, when type is "tag"
filter_groups[].filters[].type"attribute"Required, when type is "attribute"
filter_groups[].filters[].op"equals" | "not_equals"Required, when type is "attribute"
filter_groups[].filters[].keystringRequired, when type is "attribute"
filter_groups[].filters[].valuestringRequired, when type is "attribute"
filter_groups[].filters[].type"added_days_ago"Required, when type is "added_days_ago"
filter_groups[].filters[].op"less_than" | "more_than"Required, when type is "added_days_ago"
filter_groups[].filters[].daysintegerRequired, when type is "added_days_ago"
filter_groups[].filters[].type"opened" | "clicked"Required, when type is "opened" | "clicked"
filter_groups[].filters[].op"within" | "not_within"Required, when type is "opened" | "clicked"
filter_groups[].filters[].daysintegerRequired, when type is "opened" | "clicked"
filter_groups[].filters[].type"campaign"Required, when type is "campaign"
filter_groups[].filters[].op"is_enrolled" | "is_not_enrolled" | "was_enrolled" | "was_never_enrolled"Required, when type is "campaign"
filter_groups[].filters[].campaign_idstringRequired, when type is "campaign"
subjectstring | nullRequired
preview_textstring | nullRequired
htmlstring | nullRequired
markdownstring | nullRequired
template_idstring | nullRequired
Request
curl -X POST "https://sendocado.com/api/v1/projects/$PROJECT_ID/broadcasts" \
  -H "Authorization: Bearer $SENDOCADO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "October product update",
  "filter_groups": [
    {
      "filters": [
        {
          "type": "tag",
          "op": "has",
          "tag": "customer"
        }
      ]
    }
  ],
  "subject": "What’s new in Acme this month",
  "preview_text": "Faster exports and a new dashboard.",
  "html": null,
  "markdown": "# What’s new\n\nHi {{ first_name | there }}, exports are now twice as fast.",
  "template_id": null
}'
Response 201
{
  "id": "06i8j76b2lajlj4h9du7794g9dpmrcg6",
  "project_id": "ujzde8gxd6ncf10epf91dhodzdoc9is0",
  "name": "October product update",
  "status": "draft",
  "filter_groups": [
    {
      "filters": [
        {
          "type": "tag",
          "op": "has",
          "tag": "customer"
        }
      ]
    }
  ],
  "subject": "What’s new in Acme this month",
  "preview_text": "Faster exports and a new dashboard.",
  "html": "<h1>What’s new</h1>\n<p>Hi {{ first_name | there }}, exports are now twice as fast.</p>\n",
  "text": "What’s new\n\nHi {{ first_name | there }}, exports are now twice as fast.",
  "merge_tags": ["first_name"],
  "source_template_id": null,
  "scheduled_for": null,
  "has_open_sends": false,
  "created_at": 1790172800000
}

Errors

StatusTypeExample message
400invalid_dataSend a JSON request body.
401unauthorizedSend a valid API key as "Authorization: Bearer sc_…", or sign in.
404project_not_foundProject not found.
422invalid_referenceNo template with that id in this project.
429rate_limitedToo many requests. Try again in 12 seconds.

Get a broadcast

GET/broadcasts/{broadcast_id}

has_open_sends is true while emails of the broadcast still wait for the provider’s answer.

Parameters

NameInTypeNotes
broadcast_idpathstringRequired
Request
curl "https://sendocado.com/api/v1/broadcasts/$BROADCAST_ID" \
  -H "Authorization: Bearer $SENDOCADO_API_KEY"
Response 200
{
  "id": "06i8j76b2lajlj4h9du7794g9dpmrcg6",
  "project_id": "ujzde8gxd6ncf10epf91dhodzdoc9is0",
  "name": "October product update",
  "status": "draft",
  "filter_groups": [
    {
      "filters": [
        {
          "type": "tag",
          "op": "has",
          "tag": "customer"
        }
      ]
    }
  ],
  "subject": "What’s new in Acme this month",
  "preview_text": "Faster exports and a new dashboard.",
  "html": "<h1>What’s new</h1>\n<p>Hi {{ first_name | there }}, exports are now twice as fast.</p>\n",
  "text": "What’s new\n\nHi {{ first_name | there }}, exports are now twice as fast.",
  "merge_tags": ["first_name"],
  "source_template_id": null,
  "scheduled_for": null,
  "has_open_sends": false,
  "created_at": 1790172800000
}

Errors

StatusTypeExample message
401unauthorizedSend a valid API key as "Authorization: Bearer sc_…", or sign in.
404broadcast_not_foundBroadcast not found.
429rate_limitedToo many requests. Try again in 12 seconds.

Update, schedule or send a broadcast

PUT/broadcasts/{broadcast_id}

Send every field, status included. draft → scheduled needs scheduled_for in the future; draft or scheduled → sending starts it within seconds; scheduled → draft unschedules; sending → canceled stops it (409 for any other move). Scheduling or sending first passes the readiness checks (422). Once it is sending, only the status can change.

Parameters

NameInTypeNotes
broadcast_idpathstringRequired

Body

Send JSON with every field; use null for a value you do not set.

FieldTypeNotes
namestringRequired
filter_groupsobject[]Required
filter_groups[].filtersobject[]Required
filter_groups[].filters[].type"tag"Required, when type is "tag"
filter_groups[].filters[].op"has" | "has_not"Required, when type is "tag"
filter_groups[].filters[].tagstringRequired, when type is "tag"
filter_groups[].filters[].type"attribute"Required, when type is "attribute"
filter_groups[].filters[].op"equals" | "not_equals"Required, when type is "attribute"
filter_groups[].filters[].keystringRequired, when type is "attribute"
filter_groups[].filters[].valuestringRequired, when type is "attribute"
filter_groups[].filters[].type"added_days_ago"Required, when type is "added_days_ago"
filter_groups[].filters[].op"less_than" | "more_than"Required, when type is "added_days_ago"
filter_groups[].filters[].daysintegerRequired, when type is "added_days_ago"
filter_groups[].filters[].type"opened" | "clicked"Required, when type is "opened" | "clicked"
filter_groups[].filters[].op"within" | "not_within"Required, when type is "opened" | "clicked"
filter_groups[].filters[].daysintegerRequired, when type is "opened" | "clicked"
filter_groups[].filters[].type"campaign"Required, when type is "campaign"
filter_groups[].filters[].op"is_enrolled" | "is_not_enrolled" | "was_enrolled" | "was_never_enrolled"Required, when type is "campaign"
filter_groups[].filters[].campaign_idstringRequired, when type is "campaign"
subjectstring | nullRequired
preview_textstring | nullRequired
htmlstring | nullRequired
markdownstring | nullRequired
template_idstring | nullRequired
status"draft" | "scheduled" | "sending" | "sent" | "canceled"Required
scheduled_forinteger | nullRequired
Request
curl -X PUT "https://sendocado.com/api/v1/broadcasts/$BROADCAST_ID" \
  -H "Authorization: Bearer $SENDOCADO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "October product update",
  "filter_groups": [
    {
      "filters": [
        {
          "type": "tag",
          "op": "has",
          "tag": "customer"
        }
      ]
    }
  ],
  "subject": "What’s new in Acme this month",
  "preview_text": "Faster exports and a new dashboard.",
  "html": "<h1>What’s new</h1>\n<p>Hi {{ first_name | there }}, exports are now twice as fast.</p>\n",
  "markdown": null,
  "template_id": null,
  "status": "scheduled",
  "scheduled_for": 1790259200000
}'
Response 200
{
  "id": "06i8j76b2lajlj4h9du7794g9dpmrcg6",
  "project_id": "ujzde8gxd6ncf10epf91dhodzdoc9is0",
  "name": "October product update",
  "status": "scheduled",
  "filter_groups": [
    {
      "filters": [
        {
          "type": "tag",
          "op": "has",
          "tag": "customer"
        }
      ]
    }
  ],
  "subject": "What’s new in Acme this month",
  "preview_text": "Faster exports and a new dashboard.",
  "html": "<h1>What’s new</h1>\n<p>Hi {{ first_name | there }}, exports are now twice as fast.</p>\n",
  "text": "What’s new\n\nHi {{ first_name | there }}, exports are now twice as fast.",
  "merge_tags": ["first_name"],
  "source_template_id": null,
  "scheduled_for": 1790259200000,
  "has_open_sends": false,
  "created_at": 1790172800000
}

Errors

StatusTypeExample message
400invalid_dataSend a JSON request body.
401unauthorizedSend a valid API key as "Authorization: Bearer sc_…", or sign in.
402plan_limit_reachedYour plan allows 5,000 contacts. Upgrade the plan to add more.
404broadcast_not_foundBroadcast not found.
409invalid_status_transitionA draft campaign cannot become paused. Set it live first.
409conflictThis campaign has enrollments, so it cannot be deleted. Pause it instead.
422campaign_incompleteAdd at least one email with a subject and content before going live.
422sender_not_readyVerify the domain of your from address before you send email.
422postal_address_missingAdd a postal address to the project before you send marketing email.
422invalid_referenceNo template with that id in this project.
429rate_limitedToo many requests. Try again in 12 seconds.

Delete a broadcast

DELETE/broadcasts/{broadcast_id}

Only a draft can be deleted (409 otherwise); a sent broadcast stays as the record of what went out.

Parameters

NameInTypeNotes
broadcast_idpathstringRequired
Request
curl -X DELETE "https://sendocado.com/api/v1/broadcasts/$BROADCAST_ID" \
  -H "Authorization: Bearer $SENDOCADO_API_KEY"

Answers 204 with no body.

Errors

StatusTypeExample message
401unauthorizedSend a valid API key as "Authorization: Bearer sc_…", or sign in.
404broadcast_not_foundBroadcast not found.
409conflictThis campaign has enrollments, so it cannot be deleted. Pause it instead.
429rate_limitedToo many requests. Try again in 12 seconds.

Check whether a broadcast can be sent

GET/broadcasts/{broadcast_id}/readiness

What would refuse scheduling or sending this broadcast right now. An empty list means it can go.

Parameters

NameInTypeNotes
broadcast_idpathstringRequired
Request
curl "https://sendocado.com/api/v1/broadcasts/$BROADCAST_ID/readiness" \
  -H "Authorization: Bearer $SENDOCADO_API_KEY"
Response 200
{
  "problems": []
}

Errors

StatusTypeExample message
401unauthorizedSend a valid API key as "Authorization: Bearer sc_…", or sign in.
404broadcast_not_foundBroadcast not found.
429rate_limitedToo many requests. Try again in 12 seconds.

Get broadcast stats

GET/broadcasts/{broadcast_id}/stats

Every day the broadcast sent on. Rates are fractions of sent_count, null before anything was sent; numbers trail events by about a minute.

Parameters

NameInTypeNotes
broadcast_idpathstringRequired
Request
curl "https://sendocado.com/api/v1/broadcasts/$BROADCAST_ID/stats" \
  -H "Authorization: Bearer $SENDOCADO_API_KEY"
Response 200
{
  "totals": {
    "sent_count": 1240,
    "delivered_count": 1221,
    "opened_count": 642,
    "clicked_count": 187,
    "replied_count": 41,
    "bounced_count": 6,
    "complained_count": 1,
    "unsubscribed_count": 9,
    "failed_count": 2
  },
  "rates": {
    "delivery_rate": 0.9847,
    "open_rate": 0.5177,
    "click_rate": 0.1508,
    "reply_rate": 0.0331,
    "bounce_rate": 0.0048,
    "complaint_rate": 0.0008,
    "unsubscribe_rate": 0.0073
  },
  "daily": [
    {
      "sent_count": 1240,
      "delivered_count": 1221,
      "opened_count": 642,
      "clicked_count": 187,
      "replied_count": 41,
      "bounced_count": 6,
      "complained_count": 1,
      "unsubscribed_count": 9,
      "failed_count": 2,
      "day": "2026-09-21"
    }
  ],
  "links": [
    {
      "url": "https://acme.example/changelog",
      "click_count": 203,
      "unique_click_count": 187
    }
  ]
}

Errors

StatusTypeExample message
401unauthorizedSend a valid API key as "Authorization: Bearer sc_…", or sign in.
404broadcast_not_foundBroadcast not found.
429rate_limitedToo many requests. Try again in 12 seconds.