> For the complete documentation index, see [llms.txt](https://docs.graspil.com/en/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.graspil.com/en/api/broadcasts.md).

# Broadcasts

Methods for managing broadcasts: creating, editing, starting, stopping, and checking the status and delivery stats of a broadcast. These are the same actions available in the dashboard under [Broadcasts](/en/app/broadcast.md), but through the API — useful if you create and launch broadcasts automatically from your own system (CRM, internal service, etc.).

{% hint style="info" %}
Broadcast methods are only available on the **Premium** plan.
{% endhint %}

### How it works in a nutshell

1. You create a broadcast draft — the `create` method. You can specify just the message type, or pass the text and audience right away.
2. While the broadcast hasn't been launched, it stays in "draft" status — you can edit it as many times as you like via `save`.
3. If you need to set recipients as your own list instead of an audience by conditions — the simplest way is to put `chat_ids` directly in the audience via the `list` segment type (see below), no extra request needed. The `upload-segment` method is only needed for very large lists that don't fit in a single request body — it lets you upload the list in parts over several calls; the returned `segment_id` (type `custom`) only works **for the broadcast it was uploaded to**, it can't be used in a different broadcast.
4. Once everything is ready, launch the broadcast via `activate`. It's queued for sending.
5. Track status and delivery stats via `get` and `list`.
6. A broadcast that's already running can be stopped via `cancel`.

{% hint style="info" %}
For how to format the message text (bold, italics, links, emoji), see the [formatting guide](/en/app/broadcast/create-msg.md).
{% endhint %}

### Authentication

If your API key is tied to a single bot (a regular key from the "My Bots" section — see [Authorization](/en/api/auth.md)), you don't need to specify anything else — all methods apply to that bot.

If you're using a key from the [API Keys](https://app.graspil.com/api-keys) section that's been issued for several bots or all of your bots, every request must explicitly state which bot it's for — via the `Resource-Key` header, or the `resource_key` field (for `POST`) / query parameter (for `GET`). The value is the bot's public key: the part before the colon in the bot's API key, which you can see in the "My Bots" section next to the key icon (format `bot_key:api_key`).

### Response format

Successful response:

{% code overflow="wrap" %}

```json
{ "ok": true, "data": { ... } }
```

{% endcode %}

Error response:

{% code overflow="wrap" %}

```json
{ "ok": false, "error": "...", "error_code": 400 }
```

{% endcode %}

***

## The broadcast object

A broadcast (`task`) is what's returned by the `create`, `save`, `get`, `activate`, and `cancel` methods, and each item in `list`.

| Field                 | Type         | Description                                                                  |
| --------------------- | ------------ | ---------------------------------------------------------------------------- |
| `id`                  | int          | Broadcast ID                                                                 |
| `status`              | int          | Status — see the table below                                                 |
| `name`                | string       | Name (for your own reference, not shown to recipients)                       |
| `type`                | string       | Message type — `sendMessage`, `sendPhoto`, etc., see "Message content" below |
| `segmentations`       | object       | Audience — see "Broadcast audience" below                                    |
| `message`             | object       | Message text and media                                                       |
| `settings`            | object       | Sending settings — see "Sending settings" below                              |
| `date_send`           | string\|null | When the launch is scheduled                                                 |
| `date_finish`         | string\|null | When the broadcast finished or was stopped                                   |
| `count_recipients`    | int          | Total number of recipients                                                   |
| `count_delivered`     | int          | Number of messages delivered                                                 |
| `count_not_delivered` | int          | Number of messages not delivered (e.g. the user blocked the bot)             |

#### Broadcast statuses

| Code          | Meaning                                                                                     |
| ------------- | ------------------------------------------------------------------------------------------- |
| `0`           | Draft — can be edited                                                                       |
| `1`           | Scheduled, waiting to be processed                                                          |
| `2`           | Sending in progress                                                                         |
| `3`           | Finished                                                                                    |
| `4`           | Stopped                                                                                     |
| `5`           | Error                                                                                       |
| `6`, `7`, `8` | Intermediate technical statuses — the broadcast is being prepared for sending by the system |

{% hint style="info" %}
You can only edit (`save`) a broadcast in **draft** status (`0`). For any other status, `save` returns the broadcast unchanged — your edits are silently not applied.
{% endhint %}

***

## Broadcast audience

The `segmentations` field defines who the broadcast is sent to:

{% code overflow="wrap" %}

```json
{
  "include": [ { "type": "all", "config": {} } ],
  "exclude": [ { "type": "filter", "config": { "user_conditions": [...] } } ]
}
```

{% endcode %}

`include` and `exclude` are lists of segments, all sharing the same shape: `{ "type": ..., "config": ... }`. The final audience is the union of all `include` segments, minus the union of all `exclude` segments.

{% hint style="info" %}
If any segment in `include` has `type: "all"`, the other `include` segments are ignored — every user of the bot is included.
{% endhint %}

Available segment types:

| `type`      | `config`                | Description                                                                                                                                                                                                       |
| ----------- | ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `all`       | `{}`                    | All users of the bot                                                                                                                                                                                              |
| `list`      | `{ "chat_ids": [...] }` | A ready-made recipient list right in the request — no separate upload needed. Good for a one-off list; also works in `exclude`                                                                                    |
| `custom`    | `{ "segment_id": ... }` | A recipient list uploaded earlier via the `upload-segment` method (see below) or via the dashboard. The segment is locked to the broadcast it was uploaded to — it can't be referenced from a different broadcast |
| `channel`   | `{ "chat_ids": [...] }` | Specific users, chats, groups, or channels by `chat_id` (for groups and channels, `chat_id` is a negative number). Functionally the same as `list`                                                                |
| `from_task` | `{ "task_id": ... }`    | Recipients of another one of your broadcasts                                                                                                                                                                      |
| `filter`    | see below               | Audience by conditions — user fields and/or events performed                                                                                                                                                      |

### The `type: "filter"` segment

{% code overflow="wrap" %}

```json
{
  "user_conditions": ["and", ["=", "users.language_code", "ru"], ["=", "users.is_premium", 1]],
  "event_filters": [
    "and",
    ["performed_event", { "event_id": 42, "negation": false, "period_days": 30 }]
  ]
}
```

{% endcode %}

| Key               | Description                                 |
| ----------------- | ------------------------------------------- |
| `user_conditions` | A condition on user fields                  |
| `event_filters`   | A condition on events performed by the user |

**Condition format** (`user_conditions`) — a nested array `[operator, ...arguments]`:

* Logic: `["and", condition1, condition2, ...]`, `["or", ...]`, `["not", condition]`
* Comparison: `["=", "field", value]`, `["!=", ...]`, `[">", ...]`, `["<", ...]`, `[">=", ...]`, `["<=", ...]`
* Text: `["like", "field", "%substring%"]`, `["not like", ...]`
* Value sets: `["in", "field", [value1, value2, ...]]`, `["not in", ...]`
* Range: `["between", "field", from, to]`, `["not between", ...]`
* Null check: `["is", "field", null]`

Available fields: `users.user_id`, `users.full_name`, `users.username`, `users.first_name`, `users.last_name`, `users.date_create`, `users.date_last_active`, `users.gender`, `users.is_bot`, `users.user_status`, `users.is_premium`, `users.language_code`, `users.timezone`, as well as your custom fields — `users.custom_fields.<name>` / `users.addition_fields.<name>`.

**Example:** users with a Russian interface language who either had activity after June 1st, or have a custom `vip` flag set:

{% code overflow="wrap" %}

```json
["and",
  ["=", "users.language_code", "ru"],
  ["or",
    [">", "users.date_last_active", "2026-06-01"],
    ["=", "users.custom_fields.vip", "1"]
  ]
]
```

{% endcode %}

**`event_filters` format** — a tree of the same shape, but the leaves are `performed_event`:

* Group: `["and"|"or", node1, node2, ...]`
* Leaf: `["performed_event", { "event_id": ..., "negation": ..., "period_days": ... }]`

| Leaf field    | Description                                                                                                  |
| ------------- | ------------------------------------------------------------------------------------------------------------ |
| `event_id`    | Event ID (optional — if omitted, any event is checked)                                                       |
| `negation`    | `false` — "performed the event", `true` — "did not perform it"                                               |
| `period_days` | How many days back to check (`0` — all time)                                                                 |
| `conditions`  | Additional conditions on event properties, in the same format as `user_conditions`, but on `events.*` fields |

***

## Message content

The `message` field depends on the broadcast's `type`. All text fields support `{{user.first_name}}` variables and `{% raw %}{% if %}{% endraw %}` conditions — they're substituted individually for each recipient at send time.

| `type`           | `message` fields                                                                                                                                                                              |
| ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `sendMessage`    | `text` (required), `parse_mode` (`html` or `MarkdownV2`), `reply_markup` (Telegram inline keyboard), `disable_notification`, `protect_content`, `pin_message` (pin the message after sending) |
| `sendPhoto`      | `photo` (image URL or `file_id`), `caption`, `parse_mode`, `reply_markup`, `pin_message`                                                                                                      |
| `sendVideo`      | `video`, `caption`, `parse_mode`, `reply_markup`, `pin_message`                                                                                                                               |
| `sendDocument`   | `document`, `caption`, `parse_mode`                                                                                                                                                           |
| `sendAnimation`  | `animation`, `caption`, `parse_mode`                                                                                                                                                          |
| `sendAudio`      | `audio`, `caption`                                                                                                                                                                            |
| `sendVoice`      | `voice`, `caption`                                                                                                                                                                            |
| `sendMediaGroup` | `media` — an array of `{ "type": "photo"\|"video", "media": "<url>", "caption"?: "..." }` (an album of several photos/videos)                                                                 |

**Example for `sendMessage`:**

{% code overflow="wrap" %}

```json
{
  "text": "Hi, {{user.first_name}}! We have a special offer for you.",
  "parse_mode": "html",
  "reply_markup": {
    "inline_keyboard": [[{ "text": "Open", "url": "https://example.com" }]]
  }
}
```

{% endcode %}

`reply_markup` is a regular Telegram Bot API inline keyboard structure.

***

## Sending settings

The `settings` field is optional — every key has a default value:

{% code overflow="wrap" %}

```json
{
  "intervalCap": 10,
  "rateLimitOnError": { "stop": true, "extraDelay": 0 }
}
```

{% endcode %}

| Key                           | Type     | Default | Description                                                       |
| ----------------------------- | -------- | ------- | ----------------------------------------------------------------- |
| `intervalCap`                 | int      | `10`    | How many messages to send per second                              |
| `rateLimitOnError.stop`       | bool     | `true`  | Stop the broadcast if Telegram responds with a rate limit (`429`) |
| `rateLimitOnError.extraDelay` | int (ms) | `0`     | Extra delay between sends when the rate limit is hit              |

***

## Methods

### POST /v1/broadcast/create

Creates a new broadcast draft.

You can pass just `type` (an empty draft — same as creating a broadcast in the dashboard), or pass the content and audience right away and/or launch the broadcast — without separate `save`/`activate` calls.

#### Parameters

| Parameter       | Type   | Required                                  | Description                                              |
| --------------- | ------ | ----------------------------------------- | -------------------------------------------------------- |
| `resource_key`  | string | only for keys with access to several bots | See "Authentication" above                               |
| `type`          | string | yes                                       | Message type — `sendMessage`, `sendPhoto`, etc.          |
| `name`          | string | no                                        | Broadcast name                                           |
| `segmentations` | object | no                                        | Audience                                                 |
| `message`       | object | no                                        | Message text and media                                   |
| `settings`      | object | no                                        | Sending settings                                         |
| `date_send`     | string | no                                        | Launch date and time, format `YYYY-MM-DD HH:MM:SS`       |
| `activate`      | bool   | no                                        | `true` — launch the broadcast immediately after creation |

#### Example request

{% code overflow="wrap" %}

```json
POST /v1/broadcast/create
Api-Key: YOUR_API_KEY
Content-Type: application/json

{
  "type": "sendMessage",
  "name": "Promo announcement",
  "segmentations": { "include": [{ "type": "all", "config": {} }] },
  "message": { "text": "We have a special offer for you, {{user.first_name}}!" }
}
```

{% endcode %}

#### Response

{% code overflow="wrap" %}

```json
{ "ok": true, "data": { "task": { "id": 4821, "status": 0, "...": "..." } } }
```

{% endcode %}

***

### POST /v1/broadcast/save

Updates a broadcast — only works for a draft (status `0`).

#### Parameters

| Parameter       | Type   | Required                                  | Description                                        |
| --------------- | ------ | ----------------------------------------- | -------------------------------------------------- |
| `resource_key`  | string | only for keys with access to several bots | See "Authentication" above                         |
| `id`            | int    | yes                                       | Broadcast ID                                       |
| `name`          | string | no                                        | Name                                               |
| `segmentations` | object | no                                        | Audience                                           |
| `message`       | object | no                                        | Message text and media                             |
| `settings`      | object | no                                        | Sending settings                                   |
| `date_send`     | string | no                                        | Launch date and time, format `YYYY-MM-DD HH:MM:SS` |

#### Example request

{% code overflow="wrap" %}

```json
POST /v1/broadcast/save
Api-Key: YOUR_API_KEY
Content-Type: application/json

{
  "id": 4821,
  "message": { "text": "Special offer for you, {{user.first_name}}! Grab the discount now." }
}
```

{% endcode %}

#### Response

{% code overflow="wrap" %}

```json
{ "ok": true, "data": { "task": { "id": 4821, "status": 0, "...": "..." } } }
```

{% endcode %}

***

### GET /v1/broadcast/get

Returns a broadcast by ID — its current content, status, and delivery stats.

#### Parameters

| Parameter      | Type   | Required                                  | Description                |
| -------------- | ------ | ----------------------------------------- | -------------------------- |
| `resource_key` | string | only for keys with access to several bots | See "Authentication" above |
| `id`           | int    | yes                                       | Broadcast ID               |

#### Example request

{% code overflow="wrap" %}

```
GET /v1/broadcast/get?id=4821
```

{% endcode %}

#### Response

{% code overflow="wrap" %}

```json
{ "ok": true, "data": { "task": { "id": 4821, "status": 3, "count_recipients": 1200, "count_delivered": 1180, "count_not_delivered": 20, "...": "..." } } }
```

{% endcode %}

***

### GET /v1/broadcast/list

Returns a list of the resource's broadcasts with filters and sorting.

#### Parameters

| Parameter              | Type          | Required                                  | Description                                    |
| ---------------------- | ------------- | ----------------------------------------- | ---------------------------------------------- |
| `resource_key`         | string        | only for keys with access to several bots | See "Authentication" above                     |
| `filters[status]`      | int           | no                                        | Filter by status                               |
| `filters[name]`        | string        | no                                        | Filter by name (partial match)                 |
| `filters[date_create]` | string        | no                                        | Filter by creation date                        |
| `orders[<field>]`      | `asc`\|`desc` | no                                        | Sort by field, e.g. `orders[date_create]=desc` |
| `limit`                | int           | no                                        | Number of records, default `100`, max `500`    |
| `offset`               | int           | no                                        | Offset, default `0`                            |

#### Example request

{% code overflow="wrap" %}

```
GET /v1/broadcast/list?filters[status]=3&orders[date_create]=desc&limit=20
```

{% endcode %}

#### Response

{% code overflow="wrap" %}

```json
{
  "ok": true,
  "data": {
    "tasks": [
      { "id": 4821, "status": 3, "name": "Promo announcement", "...": "..." }
    ],
    "count": 1
  }
}
```

{% endcode %}

***

### POST /v1/broadcast/activate

Saves the passed fields (same as `save`) and launches the broadcast.

{% hint style="info" %}
If your plan's broadcast quota is exhausted, the request returns the error `error: "mailing_not_allowed"`. See the [pricing page](/en/other/pricing.md) for quota details.
{% endhint %}

#### Parameters

Same as the `save` method above.

#### Example request

{% code overflow="wrap" %}

```json
POST /v1/broadcast/activate
Api-Key: YOUR_API_KEY
Content-Type: application/json

{ "id": 4821 }
```

{% endcode %}

#### Response

{% code overflow="wrap" %}

```json
{ "ok": true, "data": { "task": { "id": 4821, "status": 1, "...": "..." } } }
```

{% endcode %}

***

### POST /v1/broadcast/cancel

Stops a broadcast. If it's already running, it moves to "stopped" status (`4`) and marks unsent recipients as cancelled (re-launching won't send them a duplicate message). If the broadcast hasn't started processing yet, it's simply returned to draft.

#### Parameters

| Parameter      | Type   | Required                                  | Description                |
| -------------- | ------ | ----------------------------------------- | -------------------------- |
| `resource_key` | string | only for keys with access to several bots | See "Authentication" above |
| `id`           | int    | yes                                       | Broadcast ID               |

#### Example request

{% code overflow="wrap" %}

```json
POST /v1/broadcast/cancel
Api-Key: YOUR_API_KEY
Content-Type: application/json

{ "id": 4821 }
```

{% endcode %}

#### Response

{% code overflow="wrap" %}

```json
{ "ok": true, "data": { "task": { "id": 4821, "status": 4, "...": "..." } } }
```

{% endcode %}

***

### POST /v1/broadcast/upload-segment

Uploads a recipient list for one specific broadcast (`task_id`) — the list is locked to that broadcast and can't be used by any other one. Only works for a broadcast in **draft** status (`0`). Recipients are passed as an array of Telegram IDs (negative numbers for groups and channels).

{% hint style="info" %}
In most cases you don't need this method — pass `chat_ids` directly in `segmentations` via the `list` segment type: `{ "include": [{ "type": "list", "config": { "chat_ids": [...] } }] }`. `upload-segment` is useful only for very large lists that need to be uploaded in parts over several calls, listing the resulting `segment_id`s together in `include`.
{% endhint %}

After uploading, use the returned `segment_id` in the broadcast's `segmentations`:

{% code overflow="wrap" %}

```json
{ "include": [{ "type": "custom", "config": { "segment_id": 17 } }] }
```

{% endcode %}

#### Parameters

| Parameter      | Type         | Required                                  | Description                                 |
| -------------- | ------------ | ----------------------------------------- | ------------------------------------------- |
| `resource_key` | string       | only for keys with access to several bots | See "Authentication" above                  |
| `task_id`      | int          | yes                                       | ID of the broadcast the list is attached to |
| `chat_ids`     | array of int | yes, non-empty                            | Telegram IDs of the recipients              |

#### Example request

{% code overflow="wrap" %}

```json
POST /v1/broadcast/upload-segment
Api-Key: YOUR_API_KEY
Content-Type: application/json

{
  "task_id": 4821,
  "chat_ids": [123456789, 987654321, -1001670520580]
}
```

{% endcode %}

#### Response

{% code overflow="wrap" %}

```json
{ "ok": true, "data": { "segment_id": 17, "count_list": 3, "duplicates": 0 } }
```

{% endcode %}

| Field        | Description                                            |
| ------------ | ------------------------------------------------------ |
| `segment_id` | ID of the uploaded list — use it in `segmentations`    |
| `count_list` | Number of recipients saved (after removing duplicates) |
| `duplicates` | Number of duplicates removed from the submitted list   |

***

## Possible errors

| Code  | When it occurs                                                                                           |
| ----- | -------------------------------------------------------------------------------------------------------- |
| `400` | A required parameter is missing, or a parameter has an invalid format                                    |
| `401` | The API key is missing or invalid                                                                        |
| `403` | The method isn't available on your plan (Premium required), or the resource isn't accessible to this key |
| `404` | No broadcast was found with the given `id`                                                               |
| `500` | Internal server error — retry the request later                                                          |

**Example error:**

{% code overflow="wrap" %}

```json
{ "ok": false, "error": { "errors": "Premium tariff required" }, "error_code": 403 }
```

{% endcode %}
