Creating ads¶
createAd, editAd, submitAdForReview, deleteAd, getAdsById, getAdsList.
Create¶
from telead import InputAdTargetChannels
ad = client.create_ad(
title="Autumn launch — tech channels",
text="Everything you need to know about the launch, in one channel.",
promote_url="https://t.me/mychannel",
cpm=2.5,
placement="channel_post",
target=InputAdTargetChannels(language_codes=["en"], topic_ids=[12, 31]),
initial_budget=50,
)
Required¶
| Argument | |
|---|---|
title |
Your internal name, 1–128 bytes of UTF-8. Never shown to users. |
text |
What people see, 1–160 characters. Required for every target type except search. |
promote_url |
The link the button opens: a channel, post, bot or website. Up to 256 characters. |
cpm |
Price per 1000 impressions, in the account's currency. |
target |
Who sees it — one of the four target types. |
placement |
channel_post, bot_banner, search_result or video_banner. |
Always pass placement
The API still accepts an ad without placement and infers it from the target type, but its docs say that may stop working. The library does not guess it for you.
Optional¶
| Argument | |
|---|---|
photo_id / video_id |
Media from upload_ad_photo / upload_ad_video. One or the other. Channels and users targeting only. |
show_userpic |
Show the promoted channel's or bot's picture. |
impression_frequency |
Times one user may see the ad per day, 1–4. Default 1. |
website_name, website_photo_id |
For an external link: a name of 1–40 characters (required then) and a photo. |
button |
For an external link: subscribe, view, read, learn_more, download, open, sign_up, buy, order, play, try or leave_request. Omit for "Open Website". |
conversion_event_id |
A pixel event that counts as a conversion. External links only; cannot be changed once set. |
additional_info |
Up to 64 characters users can see — only if the account has a legal_name. Cannot be changed once set. |
initial_budget |
Moved from the account budget into the ad. |
daily_budget_limit |
Cap on spend per day; 0 means none. |
is_paused |
Create it on hold. |
activate_date, deactivate_date |
When to start and stop — see Start and stop dates. |
schedule |
Hours of the week — see Schedules. |
return_target |
Include target in the returned Ad. |
idempotency_key |
Your own key; one is generated otherwise. See idempotency. |
account_id |
Create it in a related account. |
The result is an Ad.
Review¶
An ad with money in its budget is submitted for review automatically when it is created or edited. An ad without one is not; it can wait in ready_for_review until you submit it.
status |
Meaning |
|---|---|
ready_for_review |
Not submitted — call submit_ad_for_review(ad_id). |
in_review |
Waiting for review. |
active |
Approved and running. |
declined |
Rejected; decline_reason.text and decline_reason.description_html say why. |
on_hold |
On hold — not running for now. |
stopped |
Stopped. |
ad = client.get_ad(ad_id)
if ad.needs_review_submission:
ad = client.submit_ad_for_review(ad_id)
elif ad.is_declined:
print(ad.decline_reason.text)
Edit¶
edit_ad changes only what you pass:
- A new
photo_idorvideo_idreplaces the current media. conversion_event_idandadditional_infocannot be changed once set.daily_budget_limit=0removes the daily limit;schedule=Falseremoves the schedule.- Editing an ad that has a budget sends it back to review.
Pause and resume¶
client.pause_ad(ad_id) # edit_ad(ad_id, is_paused=True)
client.resume_ad(ad_id) # edit_ad(ad_id, is_paused=False)
Pausing or resuming this way clears any activate_date and deactivate_date.
Start and stop dates¶
activate_date and deactivate_date take Unix seconds, or a datetime (naive ones are treated as UTC). They are rounded to the minute, and must be in the future but no more than 365 days ahead.
from datetime import datetime, timezone
client.create_ad(
...,
activate_date=datetime(2026, 11, 1, 9, 0, tzinfo=timezone.utc),
deactivate_date=datetime(2026, 11, 30, tzinfo=timezone.utc),
)
They interact with is_paused:
activate_datein the future → the ad is held until then.deactivate_datealone, in the future → the ad runs until then.- Either one set →
is_pausedis ignored.
Schedules¶
A schedule limits the hours of the week an ad runs. Build one from hours rather than bitmasks:
from telead import InputAdSchedule
# 09:00–18:00 every day, in UTC+3
InputAdSchedule.every_day(range(9, 18), timezone=3 * 3600)
# Weekday evenings and Saturday mornings, in UTC
InputAdSchedule.weekly(
{"mon": range(18, 23), "tue": range(18, 23), "wed": range(18, 23),
"thu": range(18, 23), "fri": range(18, 23), "sat": range(8, 12)},
timezone=0,
)
# 18:00–23:00 in each viewer's own time zone — users targeting only
InputAdSchedule.every_day(range(18, 23), use_viewer_timezone=True)
range(9, 18) covers the hours starting 09:00 through 17:00, so the ad runs 09:00–18:00. The time zone must be one of the offsets the API accepts (telead.limits.SCHEDULE_TIMEZONES); whole hours from UTC−12 to UTC+12 are all there, plus the common half- and quarter-hour zones. See Targets and schedules.
To remove a schedule:
Read¶
ad = client.get_ad(42) # one ad, or None
ads = client.get_ads_by_id([42, 43, 44]) # several
page = client.get_ads_list(limit=50) # one page: page.ads, page.next_offset
for ad in client.iter_ads(): # all of them
...
Add return_target=True to any of these to get ad.target.
Delete¶
The ad must have been inactive for at least 10 minutes. To take its remaining budget back first, use decrease_ad_budget.