> Adsly docs index: https://adsly.pro/docs/llms.txt · every page in one file: https://adsly.pro/docs/llms-full.txt · OpenAPI: https://adsly.pro/docs/api/openapi.yaml

# Create campaigns

POST /v1/campaigns — create one or up to 100 Telegram ads in a call, targeting channels, bots, search queries or audiences by country, language and topic; upload images and video with POST /v1/media.

`POST /v1/campaigns` creates campaigns in one cabinet. It needs a key with **Manage campaigns**, an `Idempotency-Key`, and returns `202` with a [task](https://adsly.pro/docs/api/tasks.md) — the campaigns appear when the task completes.

## A first example

```bash
curl -s -X POST https://app.adsly.pro/api/v1/campaigns \
  -H "X-API-Key: $ADSLY_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "account_id": 53,
    "title": "Crypto channels — notes",
    "text": "Daily market notes in 3 minutes. No noise.",
    "promote_url": "https://t.me/yourchannel",
    "cpm": 1.5,
    "budget": 20,
    "target_type": "channels",
    "channels": ["@durov", "t.me/telegram"]
  }'
```

```json
{ "success": true, "data": { "id": 81234, "type": "create", "status": "pending", "account_id": 53, "total": 1, "ad_ids": [] } }
```

## Many at once

Put the campaigns in `campaigns` (up to 100). `account_id` and the group fields stay at the top level and apply to all of them.

```json
{
  "account_id": 53,
  "new_group_name": "October test",
  "campaigns": [
    { "title": "A", "text": "Variant A", "promote_url": "https://t.me/yourchannel", "cpm": 1.5, "budget": 10,
      "target_type": "channels", "channels": ["@durov"] },
    { "title": "B", "text": "Variant B", "promote_url": "https://t.me/yourchannel", "cpm": 1.5, "budget": 10,
      "target_type": "channels", "channels": ["@telegram"] }
  ]
}
```

Campaigns are created one after another. If the first one fails, the task stops — fix the input rather than repeat the same error 100 times. Each failure is listed in the task's `errors` with its `index` in your array.

## Fields

| Field | Required | Meaning |
|---|---|---|
| `title` | yes | Internal name, shown in the panel. |
| `text` | yes | Ad text people see. Telegram's own length and content rules apply — a refusal comes back in the task's `errors`. |
| `promote_url` | yes | What the ad opens: `https://t.me/channel`, `t.me/bot?start=ref`, `@channel` or a website URL. |
| `cpm` | yes | Bid per 1,000 views in the cabinet's currency, greater than 0. Too low for the targeting → Telegram's minimum comes back in the error. |
| `budget` | yes | Money moved from the cabinet balance to the campaign: `0`, or at least the cabinet's `min_budget` ([Cabinets](https://adsly.pro/docs/api/accounts.md)). With `0` the ad is created but can't run until you add budget. |
| `target_type` | yes | `channels`, `bots`, `search` or `users` — see below. |
| `active` | no | `false` creates it switched off. Default `true`. |
| `daily_budget` | no | Daily spending cap. |
| `views_per_user` | no | How many times one person can see the ad, 1–4. |
| `media` | no | Image or video token from [`POST /v1/media`](#upload-media). |
| `picture` | no | `true` shows the promoted channel's avatar in the ad. |
| `button` | no | Button under the ad: `subscribe`, `view`, `read`, `learn_more`, `download`, `open`, `sign_up`, `buy`, `order`, `play`, `try`, `leave_request`. Only where the cabinet's `features.ad_button` is `true`. |
| `website_name` | no | Brand name shown above a website ad. |
| `schedule` | no | Show the ad only on chosen weekdays and hours, e.g. weekdays 9:00–18:00. Format: [Schedule](https://adsly.pro/docs/api/manage-campaigns.md). Without it the ad runs around the clock. |

Top level only: `account_id` (needed when the key reaches several cabinets), `group_id` (an existing [group](https://adsly.pro/docs/api/targeting.md)) or `new_group_name` (creates the group, or reuses one with that name).

Unknown fields are refused with `400 VALIDATION_ERROR` and the field name in `param` — a typo never silently drops a setting.

## Targeting

### `channels` — show the ad in specific channels

```json
{ "target_type": "channels", "channels": ["@durov", "t.me/telegram"],
  "languages": ["en"], "topics": [7, 26], "exclude_topics": [2], "add_similar_channels": true }
```

| Field | Meaning |
|---|---|
| `channels` | 1–100 public channels: `@name`, `name` or `t.me/name`. Required. |
| `languages`, `topics`, `exclude_topics` | Narrow by channel language and topic ([codes](https://adsly.pro/docs/api/targeting.md)). |
| `add_similar_channels` | Also add channels similar to these. |

### `bots` — show the ad in bots

```json
{ "target_type": "bots", "bots": ["@somebot"] }
```

`bots`: 1–100 public bots. Required.

### `search` — show the ad for Telegram search queries

```json
{ "target_type": "search", "search_keywords": ["crypto wallet", "bitcoin"] }
```

`search_keywords`: 1–100 phrases, up to 64 characters each. Required.

### `users` — show the ad to an audience

```json
{ "target_type": "users", "countries": ["DE", "AT"], "languages": ["de"],
  "topics": [26], "exclude_topics": [2], "audience_channels": ["@somechannel"] }
```

| Field | Meaning |
|---|---|
| `countries` | ISO country codes. |
| `languages` | Language codes. |
| `topics`, `exclude_topics` | Interests by topic id. |
| `audience_channels` | People who read these channels. |

At least one of these is required. Codes: [Targeting codes](https://adsly.pro/docs/api/targeting.md).

Channels, bots and keywords are resolved to Telegram's own ids when the task runs. Ones Telegram can't find are skipped; if none are found, that campaign fails — `No targets found: …` for channels and bots, `No valid search queries found` for keywords — and the task's `errors` says so.

## After creating

1. Poll [`GET /v1/tasks/{id}`](https://adsly.pro/docs/api/tasks.md) until `completed`. `ad_ids` holds the new campaigns.
2. Telegram reviews each new ad: it starts `In Review`, then becomes `Active` (or `Declined`). A [webhook](https://adsly.pro/docs/api/webhooks.md) tells you when.
3. Change, fund or pause them with [Manage campaigns](https://adsly.pro/docs/api/manage-campaigns.md).

## Upload media

`POST /v1/media` uploads an image or video into a cabinet and returns a `media` token for `POST /v1/campaigns`, `PATCH` and copy. The token belongs to that cabinet — upload into the cabinet you'll create in.

```bash
curl -s -X POST "https://app.adsly.pro/api/v1/media?account_id=53" \
  -H "X-API-Key: $ADSLY_API_KEY" \
  -F "file=@banner.jpg"
```

```json
{ "success": true, "data": { "media": "BQACAgIAAx0…", "account_id": 53 } }
```

- JPG, PNG, GIF, WEBP or MP4, up to 10 MB, sent as `multipart/form-data` in the field `file`.
- Some cabinets accept only 16:9 images at least 640 px wide; if a file doesn't fit, Telegram's reason comes back in `error`.

## Errors

| Code | When |
|---|---|
| `VALIDATION_ERROR` | A field is missing, unknown, of the wrong type, or doesn't belong to this `target_type` — `param` says which. |
| `IDEMPOTENCY_KEY_REQUIRED` | No `Idempotency-Key`. |
| `ACCOUNT_ID_REQUIRED` / `ACCOUNT_NOT_FOUND` | Say which cabinet / that cabinet isn't reachable. |
| `VIEW_ONLY_ACCESS` | You have Viewer access to this cabinet. |
| `QUEUE_FULL` | 5 creates already queued in this cabinet. |
| `CABINET_LOCKED`, `SUBSCRIPTION_REQUIRED` | The cabinet can't take manual changes right now. |

Individual campaign failures (Telegram refused a text, a bid, a link) don't fail the request — they appear in the task's `errors`.

---
Page: https://adsly.pro/docs/api/create-campaigns/ · Updated: 2026-10-08
