Telegram Channel Data Without a Bot Token
Telegram publishes view counts no other platform gives you. They arrive as 926K and 24.2M, already rounded, and they are lifetime totals not velocity.

Copy this line to your agent to read a public Telegram channel.
set up https://monid.ai/SKILL.md and use tikhub /api/v1/telegram/web/fetch_channel_posts
Telegram publishes something no other major platform does: a view count on every public post, visible to anyone, with no account required. On 2026-09-08 we read twenty posts from one channel and the counts ranged from 926K to 24.2M. Both of those are strings, both are already rounded, and the larger one belongs to the older post. This guide runs through Monid, the OpenRouter for agent tools.
What can you read from a Telegram channel without an account?
More than the official Bot API will give you, because this is the public web preview rather than the messaging protocol.
The channel record
tikhub/api/v1/telegram/web/fetch_channel_info for durov returned:
{
"username": "durov",
"title": "Pavel Durov",
"verified": true,
"description": "Founder of Telegram.",
"subscribers": "10.8M",
"counters": { "subscribers": "10.8M", "photos": "102", "videos": "46", "links": "199" }
}
Four counters, a verification flag and a description. Note that photos, videos and links are counts of media the channel has posted, which together give you a rough content mix without pulling a single message.
The message record
fetch_channel_posts returns fifteen fields per message:
{
"id": 547,
"url": "https://t.me/durov/547",
"type": "text",
"date": "2026-09-06T17:40:39+00:00",
"text": "the plain message body",
"text_html": "the same body with entity markup",
"views": "926K",
"is_forwarded": false,
"forwarded_from": null,
"reply_to": null,
"link_preview": {},
"media": {},
"reactions": []
}
type is one of text, photo, video and a few others, and it is the field that tells you whether media will be populated before you go looking.
The two text fields are not duplicates
text is plain and text_html preserves the entity markup: bold spans, and crucially the outbound links with their hrefs. If you are tracking what a channel links to, the plain field has already thrown that away. Keep both, or keep the HTML one.
Why no token is needed
Every public Telegram channel has a web preview at t.me/s/ followed by its username, which renders recent posts as an HTML page for people who do not have the app. This route reads that. It works precisely because the content is already public, and it works for channels you do not administer, which the official Bot API does not.
📖 See also Channel Stats and Video Metadata From One Endpoint
Why is the view count a string?
Because the surface renders it for humans, and the rounding happens before you see it.
What actually came back
Across twenty messages: "926K", "1.89M", "3.31M", "5M", "6.48M", "24.2M". Every one is a string, and every one is rounded to three significant figures at most.
The rounding is lossy and irreversible
"1.89M" is somewhere between 1,885,000 and 1,894,999. That is a range of ten thousand views, already gone before the response left the server. "5M" is worse: it covers everything from 4,950,000 to 5,049,999, a hundred-thousand-view window.
So a parser like this is correct and still cannot give you an exact number:
SUFFIX = {"K": 1_000, "M": 1_000_000, "B": 1_000_000_000}
def views(raw):
if not raw: return None
raw = raw.strip()
if raw[-1] in SUFFIX:
return int(float(raw[:-1]) * SUFFIX[raw[-1]])
return int(raw.replace(",", ""))
Store the raw string alongside the parsed integer. When you later find a discontinuity in a series, the string is what tells you whether the number moved or the rounding bucket did.
What this rules out
Anything that needs small differences. A day-over-day change of half a percent on a post at "5M" is entirely inside the rounding, so growth tracking on a single large post is not available at this resolution. Comparisons across an order of magnitude are fine; comparisons within a rounding bucket are noise. The same distinction between a number and the confidence behind it runs through the engagement tracker post.
One field that carries nothing
forwards came back undefined on all twenty rows. It exists in the record shape and is empty on this surface. That is worth knowing before designing a dashboard around it: a field appearing in a schema is not a promise it is populated, and the only way to find out is to make the call. We hit exactly the same thing with viewCount on a Reddit endpoint, measured in the Reddit scraper comparison.
How do you pull a channel's posts?
Three steps, and discovery is free.
For agents
Grab an API key at app.monid.ai, then paste this to your agent and hand it the key:
set up https://monid.ai/SKILL.md
It learns the whole discover, inspect, run workflow itself. More in the agent quickstart.
For humans
npm install -g @monid-ai/cli
monid keys add -k <your-api-key> -l main
Step 1. Check the channel exists and is worth reading
What it does. Confirms the username resolves and gives you the size before you spend anything on messages.
The endpoints. tikhub/api/v1/telegram/web/fetch_channel_info, billed per call, requires channel.
The call.
monid run -p tikhub -e /api/v1/telegram/web/fetch_channel_info \
--query '{"channel": "durov"}'
What comes back. Title, verification flag, description, subscriber count and the photo, video and link counters.
What it costs. A fraction of a cent per call, and the charge does not move with channel size. Current figures at monid.ai/tools.
Step 2. Pull a page of messages
What it does. Returns recent posts with text, media type and views.
The endpoints. tikhub/api/v1/telegram/web/fetch_channel_posts, per call, requires channel, takes limit, before and after.
The call.
monid run -p tikhub -e /api/v1/telegram/web/fetch_channel_posts \
--query '{"channel": "durov", "limit": 20}'
What comes back. Twenty messages newest first, plus a pagination block: before_cursor, after_cursor, has_more_before and pages_fetched.
What it costs. The same per call, whether you take five messages or fifty. This is the shape that makes wide monitoring cheap and deep history slow.
Step 3. Walk backwards with the cursor, not with arithmetic
What it does. Gets you the next page without guessing at ids.
The call.
monid run -p tikhub -e /api/v1/telegram/web/fetch_channel_posts \
--query '{"channel": "durov", "limit": 20, "before": 527}'
before takes the before_cursor from the previous response. Do not compute the next id yourself, for the reason in the next section.
Give this to your agent![]()
Set up https://monid.ai/SKILL.md, and then use Monid to pull the last fifty posts from three Telegram channels I follow, parse the view strings into numbers, and show me which posts beat their channel's median.📖 See also What Is the Best API for Social Media Scraping in 2026?
Why do older posts have more views than newer ones?
Because a Telegram view count is a lifetime total that never resets, so it measures age at least as much as it measures reach.
The measurement
Twenty consecutive posts from one channel, on 2026-09-08:
| Posted | Views | Age at reading |
|---|---|---|
| 2026-09-06 | 926K | 2 days |
| 2026-08-31 | 1.89M | 8 days |
| 2026-08-18 | 3.31M | 21 days |
| 2026-07-21 | 6.48M | 49 days |
| 2026-06-15 | 24.2M | 85 days |
The ordering is almost perfectly inverted against recency. That is not a channel whose reach collapsed. It is a counter that has been running for eighty-five days on one post and two days on another.
What follows from it
Ranking a channel's posts by views ranks them by age. Any "top posts" list built on the raw field will be a list of old posts, and any dashboard that shows a downward trend on recent content is showing you the accumulation curve, not a decline.
The normalisation that fixes it
Divide by something. Views per day since posting is crude and works:
rate = views(m["views"]) / max((now - parse(m["date"])).days, 1)
On our twenty rows this reorders the list completely: the 926K post at two days old is running at roughly 460K per day, and the 24.2M post at eighty-five days is running at roughly 285K. The newest post is the stronger one, and the raw field said the opposite.
Better still, sample the same post twice and difference it, which gives you a real velocity rather than an average over a decaying curve. That requires storing history, which is the argument made in the price monitoring guide about keeping the whole record rather than the number you think you need.
The id gap that breaks naive pagination
Our twenty messages ran 547 down to 531, then 529, 528, 527. Id 530 is missing: deleted, or never public.
So twenty messages spanned twenty-one id slots. Message ids are a sequence with gaps, which means before and after are cursors and not offsets. Requesting ids 500 through 520 does not return twenty messages, and a loop that decrements by limit each iteration will silently skip or repeat depending on where the gaps fall. Use before_cursor from the response and let has_more_before tell you when to stop.
Which endpoint should I use for which job?
| Endpoint | What it does | Input | Output | Best for | Billing |
|---|---|---|---|---|---|
tikhub/api/v1/telegram/web/fetch_channel_info | Channel metadata | channel | Subscribers and content counters | Qualifying a channel before reading it | Per call |
tikhub/api/v1/telegram/web/fetch_channel_posts | A page of messages | channel, limit, before | Fifteen fields per message | The main monitoring loop | Per call |
tikhub/api/v1/telegram/web/fetch_post_detail | One message in full | Post reference | The single message record | Following up on one hit | Per call |
tikhub/api/v1/telegram/web/fetch_post_comments | Discussion under a post | Post reference | Comment records | Reaction to a specific announcement | Per call |
tikhub/api/v1/telegram/web/batch_channel_info | Many channels at once | Channel list | Info records | Building a watchlist in one go | Per call |
Every row was verified with monid inspect on 2026-09-08. The table gives billing shape rather than figures, because shape drives design and current numbers live on monid.ai/tools.
Every one of them bills per call, which makes the cost model unusually simple for social data: watching two hundred channels once a day is two hundred calls, regardless of how much any of them posted. Compare that to the per-result endpoints in the Reddit comparison, where a busy day costs more than a quiet one.
When is this the wrong route?
Four cases.
You administer the channel. If it is yours, the official Bot API and Telegram's own analytics give you exact numbers rather than rounded strings, plus subscriber growth and traffic sources that never appear on the public preview. Reading your own channel through a scraper throws away precision for no reason.
The channel is private or invite-only. This reads a public web page. A channel with no public preview has nothing here to read, and no amount of endpoint choice changes that. That is a boundary worth respecting rather than routing around.
You need exact numbers. The rounding described above is a hard floor on resolution. If your analysis turns on differences smaller than the rounding bucket, this surface cannot support it and no parser will recover what was discarded before the response was written.
You want to send messages. Nothing here writes. This is a read path over public content, and the messaging side of Telegram is the Bot API's job, with its own permissions model and its own rules about who you are allowed to contact.
And the disclosure: this is Monid's blog and we sell per-call access to these endpoints. The central argument here is that the headline field is misleading and needs dividing by age before it means anything, which is not a pitch for volume. The id gap and the empty forwards field are both things it costs us something to publish.
Conclusion
Telegram is unusual among social platforms in publishing a per-post view count to anyone who asks, and that field is the reason to read it. It is also a rounded string measuring a lifetime accumulation, so the two things you must do before using it are parse it carefully and divide it by age. Skip either and your top-performing posts will be your oldest ones.
The wider point is that this whole surface is a rendered web page rather than an API, and it behaves like one. Ids have gaps, counts are formatted for humans, one documented field is empty, and pagination is cursor-based because the underlying sequence is not dense. None of that is a defect. It is what reading a public page is, and a pipeline written as though it were a database will fail quietly rather than loudly.
Free next step: call fetch_channel_info on a channel you follow and compare its subscriber string to what the app shows you. It is one call, it costs a fraction of a cent, and it is the fastest way to see the rounding for yourself. Start at monid.ai.
FAQ
Why can the official Telegram Bot API not do this?
Because a bot can only read messages in chats and channels where it has been added, and being added requires an administrator to do it. That model is correct for automation inside communities you are part of, and it makes competitive or market monitoring impossible by design, since you are not going to be made an admin of a channel you want to watch. The public web preview exists for a different reason, to let non-users read public content, and reading it is the only route to channels you do not control.
Does this work on private or invite-only channels?
No. This reads the public preview page that Telegram publishes for channels marked public, so a private channel, an invite-only channel, or a public channel whose owner has disabled the web preview all return nothing. There is no configuration that changes this, and any tool claiming otherwise is describing account-based access with the risks that carries. Treat the absence of a preview as the channel owner's decision.
Can I get comments and discussion, or only the posts?
Both, through different endpoints. Channel posts come from fetch_channel_posts, and where a channel has a linked discussion group the replies to a given post are available through fetch_post_comments. The distinction matters because a Telegram channel is broadcast-only by default: the post carries views and reactions, and the actual conversation happens in the linked group, which is a separate object. If you are measuring sentiment rather than reach, the comments are where it lives.
How far back does the history go?
As far as the channel has public posts, walked one page at a time using the before cursor, with has_more_before telling you when to stop. Because each page is one billed call and pages are capped in size, deep history on an active channel is a real cost in time rather than a hard limit. Deleted messages leave gaps in the id sequence and simply do not appear, so a complete archive is not achievable and a complete-as-published one is.
Last updated September 2026.


