Campaigns — list and read
The campaign object, its statuses, GET /v1/campaigns with filters by status, bot, website domain and ad type, GET /v1/campaigns/{ad_id} for one campaign with its targeting and schedule.
A campaign is one Telegram ad. It is identified by (account_id, ad_id) — Telegram reuses ad_id across cabinets, so ad_id alone is not unique.
The campaign object
GET /v1/campaigns/{ad_id} returns:
{
"ad_id": 90412,
"account_id": 53,
"title": "API test",
"text": "Join the channel for daily market notes",
"status": "Active",
"active": true,
"promote_url": "https://t.me/yourchannel",
"tme_path": "yourchannel",
"target_type": "channels",
"targets": ["durov"],
"cpm": "1.50",
"budget": "3.54100",
"spent": "1.45900",
"daily_budget": null,
"views": 1043,
"opens": 0,
"clicks": 12,
"actions": 4,
"ctr": "1.15",
"cvr": "33.33",
"cpc": "0.1215833333",
"cpa": "0.3647500000",
"views_per_user": 1,
"schedule": {
"days": { "mon": [9, 10, 11, 12, 13, 14, 15, 16, 17], "tue": [9, 10, 11, 12, 13, 14, 15, 16, 17],
"wed": [9, 10, 11, 12, 13, 14, 15, 16, 17], "thu": [9, 10, 11, 12, 13, 14, 15, 16, 17],
"fri": [9, 10, 11, 12, 13, 14, 15, 16, 17], "sat": [], "sun": [] },
"timezone": "+03:00"
},
"has_media": false,
"media_type": null,
"button": null,
"website_name": null,
"group_id": 7,
"auto_cpm_enabled": false,
"date": "1791364800",
"synced_at": "2026-10-08T10:29:41.000Z",
"status_changed_at": "2026-10-08T10:31:07.112Z"
}
| Field | Meaning |
|---|---|
title | Your internal name for the ad. |
text | The ad text people see. |
status | What Telegram is doing with the ad — see Statuses. |
active | What you asked for: true = should run. A campaign with active: true and status Stopped resumes as soon as it has budget again. |
promote_url / tme_path | What the ad leads to: a website URL, or a t.me/<tme_path> link (channel, bot, mini-app, post). |
target_type | channels, bots, search or users — see Create campaigns. |
targets | Usernames the ad targets (channels or bots), when known. |
cpm | Bid per 1,000 views, in the cabinet’s currency. |
budget | Money left on the campaign. budget + spent is everything it was given. |
spent | Money spent so far. |
daily_budget | Daily spending cap, if one is set. |
views, clicks, actions, opens | Lifetime counters. actions are platform actions (joins, bot starts) — not your conversions; for those see Conversions. |
ctr, cvr | Percentages: "1.15" means 1.15 %. |
cpc, cpa | Cost per click and per action. |
views_per_user | How many times one person can see the ad (1–4). |
schedule | Weekdays and hours the ad is shown, or null — it runs around the clock. See Schedule. |
group_id | The campaign’s group in the panel, if any. |
auto_cpm_enabled | Adsly’s Auto CPM manages the bid. |
date | When the ad was created: unix seconds, as a string. |
status_changed_at | Last status change. Not touched by stats updates — good for “what changed since”. |
synced_at | When the ad’s own settings — schedule, text, views_per_user, targeting — were last read from Telegram. See Freshness. |
Money fields are decimal strings (cpm, budget, spent, ctr, cvr, cpc, cpa) so decimals stay exact. Counters (views, clicks, actions, opens) are numbers. All money is in the cabinet’s currency (see Cabinets).
Statuses
| Status | Meaning | What you can do |
|---|---|---|
Active | Running. | Pause, change bid, add budget. |
In Review | Waiting for Telegram’s review; not shown yet. | Wait — a webhook tells you when it changes. |
Declined | Telegram rejected the ad. | Edit the text or link (resubmits), or create a new one. |
On Hold | Switched off — by you or an automation rule. | Resume. |
Stopped | Budget used up. | Add budget; it runs again if active is true. |
Paused | Paused by Telegram for the whole cabinet (rare). | Contact @adsly_pro. |
Deleted | Removed. Kept in history, hidden from lists. | — |
GET /v1/campaigns
Campaigns of every cabinet the key reaches.
curl -s "https://app.adsly.pro/api/v1/campaigns?account_id=53&status=Active,On%20Hold&limit=100&cursor=" \
-H "X-API-Key: $ADSLY_API_KEY"
| Parameter | Default | Meaning |
|---|---|---|
account_id | — | Only this cabinet. (accountId also works.) |
status | all but Deleted | One status or several, comma-separated. |
title | — | Title contains this text. |
bot_username | — | Ads of one bot or channel: matches the username in tme_path, case-insensitive, no @. foo does not match foobar. |
promote_domain | — | Website ads whose link is on this host. Accepts example.com or a full URL; www., scheme, path and port are ignored. |
promote_domain_mode | exact | suffix also matches subdomains (x.example.com). |
type | — | bot (bots and mini-apps), channel (channels, groups, posts) or site (external websites). |
limit | 100 | 1–500. |
cursor | — | Pagination — see Pagination. Recommended. |
offset, sortBy, sortOrder | — | Offset pagination (≤ 500 per cabinet). |
Filters combine with AND. An unknown bot or domain returns an empty list, not an error.
{
"success": true,
"data": [
{ "ad_id": 90412, "account_id": 53, "title": "API test", "text": "…", "status": "Active",
"tme_path": "yourchannel", "promote_url": null, "views": 1043, "opens": 0, "clicks": 12,
"actions": 4, "spent": "1.45900", "budget": "3.54100", "cpm": "1.50", "cpc": "0.12",
"cpa": "0.36", "ctr": "1.15", "cvr": "33.33", "date": "1791364800", "group_id": 7,
"status_changed_at": "2026-10-08T10:31:07.112Z" }
],
"meta": { "limit": 100, "cursor": null, "next_cursor": null, "hasMore": false }
}
Get one campaign
GET /v1/campaigns/{ad_id} — the full object above, including targeting, creative fields and schedule (the list endpoint leaves those out to stay fast). Deleted campaigns are returned too, with status: "Deleted".
curl -s "https://app.adsly.pro/api/v1/campaigns/90412?account_id=53" -H "X-API-Key: $ADSLY_API_KEY"
account_id is needed only when the same ad_id exists in several of your cabinets (400 ACCOUNT_ID_REQUIRED tells you).
Freshness
Counters and status are refreshed for every campaign every hour. The ad’s own settings — schedule, text, views_per_user, targeting — come from reading the ad itself, so:
GET /v1/campaigns/{ad_id}re-reads the ad from Telegram when our copy is older than 5 minutes, before answering. A change someone made directly in Telegram’s ad interface shows up here within that window.- Running campaigns (
Active,In Review,On Hold) are also re-read in the background about every 3 hours. - Changes made through Adsly — the panel or this API — are in the copy at once.
If Telegram doesn’t answer in time, you get the stored copy; synced_at tells you how old it is.
GET /v1/campaigns/{ad_id}/history
One campaign’s totals over a period — a single aggregate, not hourly points.
curl -s "https://app.adsly.pro/api/v1/campaigns/90412/history?account_id=53&period=7d" -H "X-API-Key: $ADSLY_API_KEY"
{ "success": true,
"data": { "views": 640, "clicks": 7, "spent": 0.96, "ctr": 1.09, "cpm": 1.5, "cpa": 0.32 },
"meta": { "adId": 90412, "accountId": 53, "period": "7d" } }
period: 24h (default), 7d, 30d or custom with startDate / endDate (YYYY-MM-DD, UTC) and optional startHour / endHour (0–23). No data for the period → 404 NOT_FOUND.
Keeping a local copy in sync
- Page through
GET /v1/campaigns?cursor=and upsert by(account_id, ad_id). - Subscribe to webhooks for status changes and hourly stats instead of polling every minute.
- Re-sync fully once a day to catch anything a webhook missed.
Updated 2026-10-08