Cookbook
Four things worth building on the feed, each one run against the live API before it was written down.
The preamble all four share
Everything here runs on the feed route, /v1/peers/{peer_id}/history, with a single call to /v1/channels/{peer_id} where a member count is needed. Set your key once:
export RAPIDAPI_KEY="your key"
import os, requests
BASE = "https://telegram155.p.rapidapi.com"
H = {"X-RapidAPI-Key": os.environ["RAPIDAPI_KEY"],
"X-RapidAPI-Host": "telegram155.p.rapidapi.com"}
def history(peer, cursor=None, limit=50):
params = {"limit": limit}
if cursor:
params["cursor"] = cursor
return requests.get(f"{BASE}/v1/peers/{peer}/history",
headers=H, params=params, timeout=15).json()
next_page is the cursor. Pass it back to keep walking; when it stops coming, you are at the end.
Every output on this page was captured from the live API on 16 September 2026 at 06:46 UTC, against the public channel 1005640892. View counts keep climbing, so a run of your own will read a little higher.
1 · Engagement per post
Two divisions, no extra calls. Views against members tells you reach; forwards against views tells you whether it travelled.
full = requests.get(f"{BASE}/v1/channels/1005640892", headers=H, timeout=10).json()["full_chat"]
members = full["participants_count"]
for m in history(1005640892, limit=5)["messages"]:
views = m.get("views") or 0
fwd = m.get("forwards") or 0
print(f"{m['id']}: {views/members:.1%} of members, {fwd/views:.2%} forwarded")
460: 12.7% of members, 0.30% forwarded
459: 11.2% of members, 0.26% forwarded
458: 7.4% of members, 0.39% forwarded
On a channel of 9,542,316 members the latest post had reached 12.7% of them, and 0.30% of the people who saw it forwarded it on. Both numbers come out of one feed call and one channel call.
2 · Find the posts that actually travelled
A single post's forward rate means nothing until you know the channel's normal. Take the median across a hundred posts, then look for the multiples.
import statistics
def spikes(peer, pages=2, top=3):
posts, cursor = [], None
for _ in range(pages):
d = history(peer, cursor)
posts += d.get("messages") or []
cursor = d.get("next_page")
if not cursor:
break
posts = [m for m in posts if (m.get("views") or 0) > 0]
ratios = [(m.get("forwards") or 0) / m["views"] for m in posts]
median = statistics.median(ratios)
for m, r in sorted(zip(posts, ratios), key=lambda x: -x[1])[:top]:
print(f"post {m['id']}: {m['views']:,} views, {m.get('forwards') or 0:,} forwards, "
f"{r/median:.1f}x the channel median")
Across 100 posts of one channel, median forward rate 0.0022:
post 446: 1,490,705 views, 42,171 forwards, 13.1x the channel median
post 447: 1,689,586 views, 42,010 forwards, 11.5x the channel median
post 448: 1,843,818 views, 42,722 forwards, 10.7x the channel median
Three posts carrying thirteen times the channel's own norm, found in two calls. Run the same function on a smaller channel and the top multiples come out around 2.5x, tightly bunched — and that contrast is the useful part. A flat distribution means nothing broke out, and you learn it just as cheaply.
3 · What is new since last time
Message ids in a channel descend from the newest, and that is the whole trick. Store the highest id you have seen and compare.
def whats_new(peer, last_seen):
fresh, cursor = [], None
while True:
d = history(peer, cursor)
batch = d.get("messages") or []
fresh += [m for m in batch if m["id"] > last_seen]
if not batch or batch[-1]["id"] <= last_seen or not d.get("next_page"):
break
cursor = d["next_page"]
return fresh
Verified across 100 posts: the ids descend without a break in ordering, and asking for everything above 457 returned exactly [460, 459, 458]. The loop stops as soon as a page ends below your watermark, so watching a channel costs one call on a quiet day.
4 · Export a channel to CSV
import csv, datetime as dt
def export(peer, path, pages=2):
rows, cursor = [], None
for _ in range(pages):
d = history(peer, cursor)
for m in d.get("messages") or []:
rows.append({
"id": m["id"],
"date": dt.datetime.fromtimestamp(m["date"], dt.UTC).isoformat(),
"views": m.get("views") or 0,
"forwards": m.get("forwards") or 0,
"comments": (m.get("replies") or {}).get("replies") or 0,
"chars": len(m.get("message") or ""),
})
cursor = d.get("next_page")
if not cursor:
break
with open(path, "w", newline="") as f:
w = csv.DictWriter(f, fieldnames=list(rows[0]))
w.writeheader()
w.writerows(rows)
return len(rows)
Two pages came out as 100 rows in a 5,052-byte file:
id,date,views,forwards,comments,chars
460,2026-08-26T19:12:34+00:00,1207652,3662,0,206
459,2026-08-26T19:12:24+00:00,1073434,2806,0,143
The comments column comes from replies.replies, and it is zero when a post has comments turned off — reading the comments themselves takes one more route. The same feed call carries count, so you know the full length before you start: 430 posts for this channel, of which these 100 are the newest.
What this costs
Recipe 2 is two calls. The others are two or three each. All four live on /v1/peers/{peer_id}/history and /v1/channels/{peer_id}, and those two spend nothing but the monthly request count — measured route by route on 16 September 2026 at 07:22 UTC, the separate lookup counter is spent by the four search routes (/v1/usernames/{username}, /v1/channels/recommendations, /v1/contacts/search, /v1/messages/search) and by neither of the two above.
So the whole cookbook fits in one number. The free plan is 2,500 calls a month with no card, about 83 a day; a quiet-day watch costs one call per channel, which is a daily check over several dozen channels while you are still building. Pro at $14.99 raises the same loop to 80,000 calls a month — a few thousand channels a day — and the code above does not change a line.
Run these four on the free plan
2,500 calls a month, no card. No bot token and no phone number.