Skip to content
AISARAISAR
REST API

Broadcasts

Bulk message broadcasts to a segment of contacts across one or more channels (WhatsApp/Telegram/Instagram). The group covers the whole lifecycle: creating a draft, publishing and running it, A/B variants, throughput and send-window control, opt-outs, click tracking, run management, and re-sending to failed recipients. Every endpoint requires an active subscription and the plan's broadcasts feature.

Lifecycle and scheduling

A broadcast can be a draft (is_draft: true) and later launched in one of these ways:

  • immediately — schedule.mode = now;
  • scheduled — later + scheduledAt;
  • recurring — a recurrence rule.

Lifecycle: draft → publish → run(s); each run tracks its own progress (sent/delivered/read/failed/skipped).

Broadcast status (status): draftscheduled/activecompleted or failed (cancelled on cancellation). For a one-off broadcast (no recurring schedule), failed is set when its run finishes with every recipient either failed or skipped — the same value is accepted by the statuses[] filter on GET /broadcasts. Re-sending to failed recipients (retry-failed) resets such a broadcast back to scheduled.

A/B variants

Per-channel content supports A/B variants (2–3 per channel, content.channels[].variants): a variant is assigned to each recipient deterministically, with a per-variant stats breakdown in stats.byVariant.

Throughput and send window

Throughput is governed by schedule.rateControl (messagesPerMinute, jitter, dailyLimit, skipContactedWithinHours) and by the sendWindow (the smart mode delivers to each recipient during their historically active hour).

Opt-out

Contacts with opted_out_at are always excluded from the audience; an inbound stop-word message (stop/unsubscribe) auto-opts the contact out.

Click tracking

When content.settings.trackClicks = true, links are rewritten to tracking /r/{code} URLs — a public, unauthenticated redirect that records the click and 302-redirects to the original.

Managing runs

Runs can be paused/resumed/cancelled (pending recipients → skipped) and failed recipients can be re-sent (retry-failed).

Template validation before sending

For content.channels[] entries with type: "template", an approved version of the selected template on that channel is verified before sending starts. The check runs at three entry points: POST /broadcasts (when schedule.mode is "now"), POST /{broadcast}/run, and POST /{broadcast}/publish. If no approved template is found, the request is rejected with 422 and errors.content[0] reads Channel "..." has no approved template "..." (language ru). Approve this template for the channel or pick a channel where it is approved.

AI copy helper

The AI text helper (POST /broadcasts/assist, generate/improve) shares a 60/hour rate limit with the rest of the AI assist.

Endpoints

MethodPath
GET/v1/broadcasts/meta

Broadcast builder metadata (segments, variables, templates)

GET/v1/broadcasts

List broadcasts (paginated, filterable by status)

GET/v1/broadcasts/{broadcast}

Get a broadcast

POST/v1/broadcasts

Create a broadcast (supports is_draft)

PATCH/v1/broadcasts/{broadcast}

Update a broadcast

POST/v1/broadcasts/{broadcast}

Update a broadcast (POST alias of PATCH)

DELETE/v1/broadcasts/{broadcast}

Delete a broadcast

POST/v1/broadcasts/estimate

Estimate broadcast audience size

POST/v1/broadcasts/{broadcast}/duplicate

Duplicate a broadcast

POST/v1/broadcasts/{broadcast}/publish

Publish a draft (launch it or schedule it)

POST/v1/broadcasts/{broadcast}/cancel

Cancel a one-off broadcast

POST/v1/broadcasts/{broadcast}/run

Manually trigger a broadcast run

GET/v1/broadcasts/{broadcast}/messages

Run recipients (paginated, filterable by status)

GET/v1/broadcasts/{broadcast}/runs

List broadcast runs

GET/v1/broadcasts/{broadcast}/runs/{run}

Get a specific run

POST/v1/broadcasts/{broadcast}/pause-recurring

Pause a recurring schedule

POST/v1/broadcasts/{broadcast}/resume-recurring

Resume a recurring schedule

POST/v1/broadcasts/test

Send a test message to a specific recipient

POST/v1/broadcasts/assist

AI helper for broadcast copy (generate/improve)

POST/v1/broadcasts/{broadcast}/runs/{run}/pause

Pause a run

POST/v1/broadcasts/{broadcast}/runs/{run}/resume

Resume a run

POST/v1/broadcasts/{broadcast}/runs/{run}/cancel

Cancel a run (pending recipients → skipped)

POST/v1/broadcasts/{broadcast}/runs/{run}/retry-failed

Re-send to failed recipients only

Examples

Create and immediately launch a broadcast

Request

bash
curl -X POST https://api.aisar.app/v1/broadcasts \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "February promo",
    "is_draft": false,
    "audience": { "segmentId": "12" },
    "channels": { "mode": "specific", "channelIds": [7] },
    "content": {
      "channels": [
        { "type": "text", "channelId": 7, "accountType": "whatsapp", "text": "Hi {{first_name}}, 20% off this week!" }
      ]
    },
    "schedule": { "mode": "now" }
  }'

Response

json
{
  "data": {
    "id": 88,
    "company_id": 1,
    "name": "February promo",
    "status": "active",
    "audience": {
      "conditions": [],
      "group_logic": "and"
    },
    "channels": [{ "id": 7, "mode": "specific" }],
    "content": [],
    "schedule": { "mode": "now", "scheduledAt": null },
    "recurring_schedule": null,
    "runs": [
      { "id": 201, "broadcast_id": 88, "status": "processing", "total": 340, "sent": 0, "failed": 0, "delivered": 0, "read": 0, "started_at": "2026-03-15T10:00:00.000000Z", "completed_at": null }
    ],
    "created_at": "2026-03-15T10:00:00.000000Z",
    "updated_at": "2026-03-15T10:00:00.000000Z"
  }
}

Estimate broadcast audience

Request

bash
curl -X POST https://api.aisar.app/v1/broadcasts/estimate \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "segment_id": 12,
    "channel_ids": [7]
  }'

Response

json
{
  "data": {
    "total_contacts": 340,
    "reachable_contacts": 312,
    "excluded_contacts": 28,
    "estimated_duration_seconds": 312,
    "estimated_completion": "2026-03-15T10:05:12.000Z"
  }
}

Draft with A/B variants, rate control and a send window

Request

bash
curl -X POST https://api.aisar.app/v1/broadcasts \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Spring reactivation",
    "is_draft": true,
    "audience": { "segmentId": "12" },
    "channels": { "mode": "specific", "channelIds": [7] },
    "content": {
      "channels": [
        {
          "type": "text",
          "channelId": 7,
          "accountType": "whatsapp",
          "text": "Hi {{first_name}}, we miss you!",
          "variants": [
            { "key": "A", "text": "Hi {{first_name}}, we miss you — here is 15% off." },
            { "key": "B", "text": "{{first_name}}, come back for 15% off this week." }
          ]
        }
      ],
      "settings": {
        "trackClicks": true,
        "includeOptOut": true,
        "optOutText": "Reply STOP to unsubscribe"
      }
    },
    "schedule": {
      "mode": "later",
      "scheduledAt": "2026-04-01T09:00:00Z",
      "rateControl": {
        "enabled": true,
        "messagesPerMinute": 30,
        "jitterMinSeconds": 2,
        "jitterMaxSeconds": 8,
        "dailyLimit": 5000,
        "skipContactedWithinHours": 48
      },
      "sendWindow": {
        "enabled": true,
        "mode": "smart",
        "startTime": "09:00",
        "endTime": "20:00",
        "timezone": "recipient"
      }
    }
  }'

Response

json
{
  "data": {
    "id": 89,
    "company_id": 1,
    "name": "Spring reactivation",
    "status": "draft",
    "audience": { "conditions": [], "group_logic": "and" },
    "channels": [{ "id": 7, "mode": "specific" }],
    "content": [],
    "schedule": { "mode": "later", "scheduledAt": "2026-04-01T09:00:00.000000Z" },
    "recurring_schedule": null,
    "runs": [],
    "created_at": "2026-03-20T12:00:00.000000Z",
    "updated_at": "2026-03-20T12:00:00.000000Z"
  }
}

Publish a draft (draft → scheduled)

Request

bash
curl -X POST https://api.aisar.app/v1/broadcasts/89/publish \
  -H "Authorization: Bearer YOUR_API_TOKEN"

Response

json
{
  "data": {
    "id": 89,
    "status": "scheduled",
    "schedule": { "mode": "later", "scheduledAt": "2026-04-01T09:00:00.000000Z" },
    "runs": [],
    "updated_at": "2026-03-20T12:05:00.000000Z"
  }
}

Error: no approved template on the channel

Request

bash
curl -X POST https://api.aisar.app/v1/broadcasts/90/run \
  -H "Authorization: Bearer YOUR_API_TOKEN"

Response

json
{
  "data": {
    "message": "Channel \"WhatsApp Main\" has no approved template \"spring_promo\" (language ru). Approve this template for the channel or pick a channel where it is approved.",
    "errors": {
      "content": [
        "Channel \"WhatsApp Main\" has no approved template \"spring_promo\" (language ru). Approve this template for the channel or pick a channel where it is approved."
      ]
    }
  }
}

Test message before launching

Request

bash
curl -X POST https://api.aisar.app/v1/broadcasts/test \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "channel_id": 7,
    "recipient": "+77000000001",
    "content": { "type": "text", "text": "Test: 15% off this week!" }
  }'

Response

json
{
  "data": {
    "message": "Test message queued.",
    "message_id": "b1f2c3d4-EXAMPLE"
  }
}

AI helper for broadcast copy

Request

bash
curl -X POST https://api.aisar.app/v1/broadcasts/assist \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "action": "improve",
    "text": "hey buy our stuff now big sale",
    "locale": "en"
  }'

Response

json
{
  "data": {
    "text": "Big news, {{first_name}} — our best sale of the season is live. Save 20% this week only.",
    "action": "improve",
    "tokens_used": 128
  }
}

Run recipients (filtered by failed status)

Request

bash
curl "https://api.aisar.app/v1/broadcasts/89/messages?status=failed&per_page=2" \
  -H "Authorization: Bearer YOUR_API_TOKEN"

Response

json
{
  "data": [
    {
      "id": 7001,
      "contact_id": 123,
      "identifier": "+7700000XXXX",
      "status": "failed",
      "error_message": "recipient_not_on_whatsapp",
      "attempts": 2,
      "variant_key": "A",
      "delivered_at": null,
      "read_at": null,
      "replied_at": null,
      "clicked_at": null,
      "contact": { "id": 123, "display_name": "Aylin Weber", "first_name": "Aylin", "last_name": "Weber" },
      "channel": { "id": 7, "name": "WhatsApp Main" }
    },
    {
      "id": 7002,
      "contact_id": 124,
      "identifier": "+7700000YYYY",
      "status": "failed",
      "error_message": "message_send_timeout",
      "attempts": 3,
      "variant_key": "B",
      "delivered_at": null,
      "read_at": null,
      "replied_at": null,
      "clicked_at": null,
      "contact": { "id": 124, "display_name": "Marco Ricci", "first_name": "Marco", "last_name": "Ricci" },
      "channel": { "id": 7, "name": "WhatsApp Main" }
    }
  ],
  "meta": { "current_page": 1, "last_page": 1, "per_page": 2, "total": 2 }
}

Re-send to failed recipients

Request

bash
curl -X POST https://api.aisar.app/v1/broadcasts/89/runs/201/retry-failed \
  -H "Authorization: Bearer YOUR_API_TOKEN"

Response

json
{
  "data": {
    "id": 201,
    "broadcast_id": 89,
    "status": "processing",
    "total": 340,
    "sent": 300,
    "failed": 0,
    "delivered": 280,
    "read": 190,
    "started_at": "2026-04-01T09:00:00.000000Z",
    "completed_at": null
  }
}