> ## Documentation Index
> Fetch the complete documentation index at: https://docs.impellabs.tech/llms.txt
> Use this file to discover all available pages before exploring further.

# Campaigns and broadcasts

> Group contacts in campaigns and send broadcasts to them.

A campaign is a group of contacts with a goal. A broadcast is one message that ImpelLabs sends to an audience on a
channel. A broadcast belongs to a campaign: the people who get it join that campaign.

## Who can do what

* Every member sees the campaigns of the workspace and can change them. A member sees only the contacts that member
  may see, in the members, the audience and the recipients.
* Only the owner of a campaign, an owner or an admin can archive it.
* A member can create, send or change a broadcast only on a channel that the member owns. An owner or an admin can do
  it on any channel. Other members get a `403` with
  `Only the owner of this channel or an admin can send on it.`
* A campaign that a provider owns changes only its `status`, `goal` and `description` here.

Lists use the same paging as the rest of the API. Send `page`, `page_size` (1 to 100, default 20), `sort`, `order`
and `q`. The reply is `{results, count, page, page_size, pages}`. A `sort` or filter key the call does not list
returns `400`.

## Create a campaign

```bash theme={null}
curl -X POST https://api.impellabs.tech/api/v1/campaigns \
  -H "Authorization: Bearer imp_..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Spring offer",
    "goal": "Book viewings for the new towers",
    "sources": [{"kind": "website_form", "value": "FORM_ID"}]
  }'
```

The reply is the campaign, with status `201`.

```json theme={null}
{
  "id": "…",
  "name": "Spring offer",
  "goal": "Book viewings for the new towers",
  "description": "",
  "kind": "manual",
  "editable": true,
  "owner_id": "…",
  "status": "active",
  "start_at": null,
  "end_at": null,
  "sources": [{"id": "…", "kind": "website_form", "value": "FORM_ID", "filter": {}, "label": ""}],
  "workflows": [],
  "stats": {"members": 0, "new_7d": 0, "responded": 0, "converted": 0, "sent": 0, "delivered": 0, "read": 0, "replied": 0},
  "created_at": "…",
  "updated_at": "…"
}
```

| Field | Rule |
| - | - |
| `name` | Required. Up to 255 characters. |
| `kind` | `manual` (default), `broadcast` or `web`. |
| `goal` | Up to 500 characters. |
| `description` | Up to 4000 characters. |
| `status` | `active`, `paused` or `ended`. |
| `start_at`, `end_at` | A date and time. The end comes after the start. |
| `owner_id` | The id of an active member. |
| `sources` | Where members come from. Up to 50 items of `{kind, value?, filter?, label?}`. |

A source `kind` is `segment`, `meta_form`, `meta_campaign`, `utm_campaign`, `website_form` or `whatsapp_ad`. A
`segment` uses a `filter` and fills the campaign with the contacts that match. The other kinds match a lead by
`value`. A lead source adds a person to every active campaign that has a matching source. See
[forms and leads](https://docs.impellabs.tech/api/forms-and-leads.md). Setting `sources` in a `PATCH` replaces the list. Switching a campaign to
`active` fills it from its segments.

The audience filter takes these keys: `group`, `ungrouped`, `deliverable`, `tags`, `fields`, `channel`,
`reachable`, `owner`, `campaign`, `contact_ids`, `created_after`, `created_before`, `active_days`, `stage`,
`source`, `agent`, `opted_in`, `not_messaged_days`, `broadcast`, `reaction` and `exclude_ids`. An unknown key
returns `400`.

## Members

| Task | Call |
| - | - |
| List campaigns. Filters: `status`, `kind`, `q`. Sorts: `name`, `status`, `members`, `new`, `responded`, `converted`, `updated`. | `GET /api/v1/campaigns` |
| Read, change or archive a campaign. | `GET`, `PATCH`, `DELETE /api/v1/campaigns/{campaign_id}` |
| List members. Filters: `status`, `q`. Sorts: `name`, `status`, `stage`, `source`, `joined`. | `GET /api/v1/campaigns/{campaign_id}/members` |
| Add contacts by id. Body: `{"contact_ids": ["..."]}`. The reply is `{"added": n}`. | `POST /api/v1/campaigns/{campaign_id}/members` |
| Change a member. Body: `status` (`responded`, `converted`, `removed` or `member`) and `stage`. | `PATCH /api/v1/campaigns/{campaign_id}/members/{member_id}` |
| Read what happened to the members. Use `next` as `?before=`. | `GET /api/v1/campaigns/{campaign_id}/activity` |
| List the workflows that start on this campaign. Filter: `state=on` or `off`. | `GET /api/v1/campaigns/{campaign_id}/workflows` |
| Run a workflow for the members. Body: `{"workflow_id": "...", "member_ids"?: ["..."]}`. | `POST /api/v1/campaigns/{campaign_id}/run` |

A member has the status `member`, `responded`, `converted` or `removed`. A removed member comes back with the status
`member`. Only a member who has not converted can be marked as `responded`. A contact that opted out is skipped
when you run a workflow. The run reply is `{"started": n}`. When the workflow is off or has problems, the reply is
`400` with `detail` and `problems`.

ImpelLabs raises `campaign.member_joined`, `campaign.member_responded` and `campaign.member_converted` for these
changes. See [event types](https://docs.impellabs.tech/api/events.md).

## Build an audience

| Task | Call |
| - | - |
| Count the contacts a filter picks and read a page of them. Body: `{"filter": {...}, "channel_id"?: "..."}`. | `POST /api/v1/campaigns/audience` |
| List the tags on your contacts, most used first. | `GET /api/v1/campaigns/audience/tags` |
| Put contacts in a group. Body: `{"group": "...", "contact_ids": ["..."]}`. | `POST /api/v1/campaigns/audience/group` |
| Turn a small file into contacts. | `POST /api/v1/campaigns/audience/csv` |

The file call takes `multipart/form-data` with a `file` (CSV or `.xlsx`) and an optional `channel`. The first row
names the columns: `name`, and `phone` or `email`. The file can be 1 MB and up to 1000 rows. Use the contacts import
for more. Each row finds a contact by phone or email, or makes one that you own. The reply is
`{"contact_ids": [...], "added": n, "found": n, "skipped": [{"line": 2, "reason": "..."}]}`. Put `contact_ids` in
the `audience` of a broadcast.

A broadcast message can hold blanks that ImpelLabs fills with a detail of each contact. Read
`GET /api/v1/campaigns/variables` for the details a message can use, and
`POST /api/v1/campaigns/variables/values` with `{"contact_id": "...", "refs": [...]}` to preview them.

## Send a broadcast

```bash theme={null}
curl -X POST https://api.impellabs.tech/api/v1/broadcasts \
  -H "Authorization: Bearer imp_..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Spring offer, first message",
    "channel_id": "CHANNEL_ID",
    "message": {"kind": "text", "text": "Hello {name}, our spring offer starts today."},
    "audience": {"campaign": "CAMPAIGN_ID"},
    "per_minute": 60,
    "send": true
  }'
```

The reply is the broadcast, with status `201`. Without `"send": true` the broadcast is a draft. Its `status` is
`draft`, `scheduled`, `sending`, `paused`, `sent`, `failed` or `cancelled`.

| Field | Rule |
| - | - |
| `channel_id` | Required for a new broadcast. A connected channel that you may send on. |
| `message` | The message in the form the channel takes. A WhatsApp broadcast uses an approved template. The call returns `400` with a sentence when the message does not fit the channel. |
| `audience` | Required. An audience filter. Naming a `campaign` there also links the broadcast to that campaign. |
| `campaign_id` | Optional. The campaign the recipients join. Without it ImpelLabs makes one. |
| `channels` | Other channels of the same kind, for a rotation. Email only. Each one needs you as owner or an admin. |
| `scheduled_at` | A future time. Without it the broadcast starts at once. |
| `per_minute` | 1 to 1000. The channel limit caps it. |
| `quiet_start`, `quiet_end` | Clock times like `21:00`, in the zone of the workspace. Nothing goes between them. Sending goes on after. |

A broadcast that has started cannot change. A draft or a scheduled broadcast can be changed with `PATCH` or removed
with `DELETE`. A started broadcast returns `409`: stop it instead.

| Task | Call |
| - | - |
| List broadcasts. Filters: `campaign`, `status` (also `live` and `done`), `q`. Sorts: `name`, `status`, `when`, `created`. | `GET /api/v1/broadcasts` |
| Read, change or remove one. | `GET`, `PATCH`, `DELETE /api/v1/broadcasts/{broadcast_id}` |
| Send it. Body: `{"scheduled_at"?: "..."}`. | `POST /api/v1/broadcasts/{broadcast_id}/send` |
| Pause, resume or cancel it. | `POST /api/v1/broadcasts/{broadcast_id}/pause`, `/resume`, `/cancel` |
| Try the failed recipients again. | `POST /api/v1/broadcasts/{broadcast_id}/retry` |
| Send a test to your own number. Body: `{"to": "..."}`. | `POST /api/v1/broadcasts/{broadcast_id}/test` |
| List the recipients. Add `?as=csv` for a file. | `GET /api/v1/broadcasts/{broadcast_id}/recipients` |

Starting a broadcast fails with a sentence when the channel is not connected, when nobody in the audience can be
messaged, or when your credits do not cover everyone. A send that the rules block returns `400` with `detail` and
`check`. A recipient has the status `queued`, `sent`, `delivered`, `read`, `replied`, `failed` or `skipped`. A
status only moves forward. A `failed` or `skipped` recipient carries `error` and sometimes the provider `error_code`.
The broadcast `stats` count the recipients by what happened, and add `clicked` and `opted_out`.

## Email broadcasts: opens, clicks and unsubscribe

These behaviours apply to a broadcast that sends email.

* **Audience.** The audience keeps only people who have an email address that is not blocked. An address is
  blocked after a hard bounce, a complaint or an unsubscribe. When the workspace requires consent for email, a promotional email goes only to people who opted in.
* **Opens.** Each email has a one-pixel picture. When a mail app loads it, the recipient gets a `read` time. Some
  mail apps hold pictures back, so a missing open does not mean the person did not read the email.
* **Clicks.** Each link in the email points to an ImpelLabs address that holds a signed token. A follow of the link
  marks the recipient as `read` and `clicked`, then sends the person to the original link. A token that is not
  valid returns `404`. The token names the link, so nobody can use it to reach another address. The unsubscribe link
  is not changed.
* **Your own pages.** A link to a form page or a booking page of your workspace carries the broadcast id. A person who
  answers the form or books a time joins the campaign of the broadcast and counts as a click.
* **Unsubscribe.** Every promotional email has a footer link and the `List-Unsubscribe` headers with one-click
  support. The link opens a page that asks the person to confirm. A confirmed unsubscribe from a mailbox stops the
  marketing email from that mailbox to that address. Other mailboxes of the workspace can still send. When the
  rotation has other mailboxes, the broadcast sends from one the person did not leave. A recipient who left every
  mailbox of the broadcast is `skipped` with the error `Unsubscribed from these mailboxes.` A link without a mailbox
  stops the email of the whole workspace, withdraws the email consent and marks the contact as do not contact. A
  note goes on the contact timeline.
* **Report.** The broadcast `stats` of an email broadcast add `bounced`, `complained` and `unsubscribed`. The list
  `reasons` groups the failed and skipped recipients by error.

On a messaging channel, a tap on a button of the message counts as a click.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.