Adsly.pro
Documentation pages All documentation

Manage campaigns

Change a campaign's text, link, bid and daily cap; show it only on chosen days and hours; pause and resume; add or withdraw budget; copy; delete — one campaign at a time or up to 1,000 with a bulk call.

View as Markdown

All calls here need a key with Manage campaigns and Manager (or owner) access to the cabinet. They act on one campaign, (account_id, ad_id): pass account_id in the body or query when the key reaches several cabinets. Every change appears in the campaign’s history in the panel marked 🔌 API.

CallWhat it doesAnswers
PATCH /v1/campaigns/{ad_id}change text, link, bid, cap, schedule, on/offright away
POST /v1/campaigns/{ad_id}/pauseswitch offright away
POST /v1/campaigns/{ad_id}/resumeswitch onright away
POST /v1/campaigns/{ad_id}/budgetadd or withdraw budgetright away
DELETE /v1/campaigns/{ad_id}deleteright away
POST /v1/campaigns/{ad_id}/copy1–100 copies202 + task
POST /v1/campaigns/bulkone action on up to 1,000 campaigns202 + task

Direct answers return the updated campaign object in data.

Edit

PATCH /v1/campaigns/{ad_id} — send only what changes.

curl -s -X PATCH https://app.adsly.pro/api/v1/campaigns/90412 \
  -H "X-API-Key: $ADSLY_API_KEY" -H "Content-Type: application/json" \
  -d '{ "account_id": 53, "cpm": 1.8, "text": "Daily market notes in 3 minutes." }'
FieldMeaning
title, text, promote_urlCreative and link.
cpmNew bid, greater than 0.
daily_budgetDaily cap.
views_per_user1–4.
activetrue / false — same as resume / pause.
scheduleDays and hours to show the ad; null removes it — see Schedule.
media, picture, button, website_nameAs in create.

What goes to Telegram’s review. Changing cpm, active or schedule doesn’t — they are applied at once and the campaign keeps running. Any other field (title, text, promote_url, media, picture, button, website_name, views_per_user, daily_budget) is saved through Telegram’s full ad form, and Telegram reviews the ad again: it shows In Review until approved. Send bid, on/off and schedule changes on their own, not together with those fields.

Targeting (channels, countries, …) can’t change on an existing ad — Telegram doesn’t allow it. Create a new campaign with the new targeting, or copy and adjust.

meta in the response:

FieldMeaning
status_warningThe fields saved, but Telegram didn’t apply the on/off switch (for example, no budget).
status_intent_onlyYour on/off choice is stored and applies when Telegram can (in review, no budget).
status_already_setThe campaign was already in the state you asked for.

A bid below Telegram’s minimum for this targeting returns 400 PLATFORM_REJECTED with param: "cpm", min_cpm and currency.

Pause and resume

curl -s -X POST "https://app.adsly.pro/api/v1/campaigns/90412/pause?account_id=53" -H "X-API-Key: $ADSLY_API_KEY"
curl -s -X POST "https://app.adsly.pro/api/v1/campaigns/90412/resume?account_id=53" -H "X-API-Key: $ADSLY_API_KEY"

meta.intent_only: true means Telegram can’t act on it yet — the campaign is in review, declined, or out of budget — and your choice is stored: it runs as soon as it can. When the status really changes, your status webhook fires.

Schedule

Show a campaign only on chosen weekdays and hours — for example weekdays 9:00–18:00, or evenings only. Outside those hours Telegram doesn’t show the ad. Setting, changing or removing a schedule doesn’t switch the campaign on or off — status and active stay as they are. It works on every cabinet type.

curl -s -X PATCH https://app.adsly.pro/api/v1/campaigns/90412 \
  -H "X-API-Key: $ADSLY_API_KEY" -H "Content-Type: application/json" \
  -d '{ "account_id": 53, "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] },
        "timezone": "+03:00" } }'
FieldMeaning
daysmon, tue, wed, thu, fri, sat, sun — each holds the hours (0–23) the ad runs that day. 9 means 09:00–09:59, so [9, …, 17] is 9:00–18:00. A day you leave out, or send as [], doesn’t run. At least one hour in total.
timezoneWhose clock the hours follow. "viewer" — each viewer’s own local time; only for audience targeting (target_type: "users"). Otherwise a UTC offset: "+03:00", "-05:00", "+05:30". The offsets Telegram offers: whole hours from -12:00 to +14:00, plus -09:30, -03:30, -02:30, +03:30, +04:30, +05:30, +05:45, +06:30, +08:45, +09:30, +10:30, +12:45, +13:45. "UTC+3" and "+3" are accepted too.

"schedule": null removes the schedule — the ad runs around the clock again.

A campaign with no budget can’t take a schedule. Telegram accepts a schedule only on a campaign that can run, so on a Stopped campaign with nothing left you get 400 PLATFORM_REJECTED with param: "schedule" and nothing is changed — add budget first, then set the schedule. A paused (On Hold) campaign takes it fine.

The campaign object returns the schedule in the same shape, always with all seven days and the offset written as "+03:00" — so you can read it, change a day and send it back. GET /v1/campaigns/{ad_id} gives the current schedule even if it was changed directly in Telegram — see Freshness. Hours outside 0–23, an unknown day, an empty schedule, "viewer" on a channel/bot/search ad or an offset Telegram doesn’t offer → 400 VALIDATION_ERROR with the exact field in param (for example schedule.days.mon).

Copies and recreated campaigns keep the schedule of the campaign they come from. To put one schedule on many campaigns at once, use the bulk action set_schedule.

Budget

POST /v1/campaigns/{ad_id}/budget moves money between the cabinet balance and the campaign. Requires an Idempotency-Key.

curl -s -X POST https://app.adsly.pro/api/v1/campaigns/90412/budget \
  -H "X-API-Key: $ADSLY_API_KEY" -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{ "account_id": 53, "action": "add", "amount": 10 }'
actionamountEffect
addrequiredCabinet balance → campaign.
withdrawrequiredCampaign → cabinet balance. More than the campaign has → 400 INSUFFICIENT_AD_BALANCE.
withdraw_all—Everything left on the campaign → cabinet balance.

meta.moved is what actually moved; meta.partial is true when less than asked could be withdrawn. Money never leaves the cabinet through the API.

Copy

POST /v1/campaigns/{ad_id}/copy creates copies with the same targeting; anything you send overrides the original. Requires an Idempotency-Key. Returns a task whose ad_ids are the copies.

{ "account_id": 53, "copies": 3, "text": "Variant B — shorter", "cpm": 2, "budget": 5 }
FieldMeaning
copies1–100, default 1.
title, text, promote_url, cpm, budget, media, picture, views_per_userOverrides for every copy. budget: 0 or at least the cabinet’s min_budget.
scheduleCopies keep the original’s schedule; send one to replace it, or null for none.
group_id / new_group_namePut the copies in a group.

Delete

curl -s -X DELETE "https://app.adsly.pro/api/v1/campaigns/90412?account_id=53" -H "X-API-Key: $ADSLY_API_KEY"
{ "success": true, "data": { "ad_id": 90412, "account_id": 53, "deleted": true } }

A deleted campaign that had views stays in your stats with status: "Deleted"; one that never ran is removed. Deleting again returns 404 CAMPAIGN_NOT_FOUND.

Bulk

POST /v1/campaigns/bulk applies one action to up to 1,000 campaigns of one cabinet. Requires an Idempotency-Key. Returns a task.

curl -s -X POST https://app.adsly.pro/api/v1/campaigns/bulk \
  -H "X-API-Key: $ADSLY_API_KEY" -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{ "account_id": 53, "action": "set_cpm", "cpm": 1.2, "ad_ids": [90412, 90413, 90414] }'
actionExtra fieldEffect
pause—switch off
resume—switch on
add_budgetamountadd to each campaign
withdraw_budgetamountwithdraw from each campaign
withdraw_all_budget—withdraw everything from each campaign
set_cpmcpmset the same bid on each
set_schedulescheduleset the same schedule on each; "schedule": null removes it
delete—delete each

Every ad_id must be a live campaign of that cabinet; otherwise nothing is queued and you get 400 UNKNOWN_AD_IDS with the offenders in missing. Duplicates are ignored. The task’s errors lists campaigns Telegram refused — with set_schedule, also campaigns with no budget and campaigns where "timezone": "viewer" can’t apply because they don’t target an audience.

Recipe: pause campaigns whose cost per action is too high

import os, uuid, requests

API = "https://app.adsly.pro/api/v1"
H = {"X-API-Key": os.environ["ADSLY_API_KEY"]}
ACCOUNT, MAX_CPA = 53, 4.0

stats = requests.get(f"{API}/stats", params={"period": "1d"}, headers=H, timeout=60).json()["data"]
# cost per action = spend / actions, computed here so the rule means exactly that
expensive = [int(ad_id) for ad_id, s in stats.get(str(ACCOUNT), {}).items()
             if s["actions"] > 0 and s["spent"] / s["actions"] > MAX_CPA]

if expensive:
    r = requests.post(f"{API}/campaigns/bulk",
                      headers={**H, "Idempotency-Key": str(uuid.uuid4())},
                      json={"account_id": ACCOUNT, "action": "pause", "ad_ids": expensive},
                      timeout=60)
    print(r.status_code, r.json()["data"]["id"])

Updated 2026-10-08

Discuss your project