TelegramAnswer

Get a Telegram channel's messages as JSON

Telegram's APIs need a bot in the channel or a phone-number login. Our API returns a public channel's posts as JSON: Durov's 430 posts took 25 calls.

5 min read

Short answer

  • Telegram's Bot API only delivers new posts to a bot added to the channel. Reading old posts through Telegram's own API means a user-account client tied to your phone number.
  • ToolzerHub returns a public channel's posts as JSON, up to 20 a call with text, views and media links, and pages back to the channel's first line.
  • A script saved the 430 messages the preview shows for Pavel Durov's channel in 25 calls and 45 seconds. A call costs 1 credit.
On this page

Telegram's APIs give you a channel's history only through a client logged in with your phone number; a bot sees just the new posts in a channel it was added to. Our API reads a public channel's web preview instead and returns its posts as JSON, up to 20 a call, back to the first one.

What Telegram's own APIs let you read

The Bot API delivers a channel's posts as channel_post updates to a bot in the channel, and undelivered updates "will not be kept longer than 24 hours". It has no method for posts from before your bot was there. The full history is in Telegram's main API (MTProto), which libraries such as Telethon wrap: you create an api_id tied to a phone number and log in as that account. If you need a private channel or a group you belong to, that is the better pick.

Get a channel's posts with ToolzerHub

We make ToolzerHub. Our Telegram API has one route, /v1/telegram/channel, for public channels. It takes a name, @name or a t.me link. Each call is a GET with the x-api-key header and costs 1 credit.

bash
export TOOLZERHUB_API_KEY=your_key
curl -H "x-api-key: $TOOLZERHUB_API_KEY" \
  "https://api.toolzerhub.com/v1/telegram/channel?channel=durov"

One of the 20 posts it returns, with some fields left out and the text shortened:

json
{
  "data": {
    "title": "Pavel Durov",
    "username": "durov",
    "subscribers_display": "10.3M",
    "messages": [
      {
        "message_number": 548,
        "url": "https://t.me/durov/548",
        "published_at": "2026-09-11T16:04:02+00:00",
        "views_display": "3.56M",
        "text": "🤝 Telegram has become the sponsor of Codeforces ...",
        "photo_url": null,
        "video_url": null
      }
    ],
    "next_before": 531,
    "exhausted": false
  }
}

Views come as the rounded figure Telegram shows, such as 3.56M, not an exact count. video_url and photo_url are links to Telegram's media servers, so download media soon after the call.

Send next_before as before for the posts before those, and stop when exhausted is true. The newest page arrives first and each page lists its posts oldest to newest. To catch new posts later, send the newest_message_number you stored as after, and follow next_after if more than one page has appeared.

Export a channel to CSV

The script pages through a public channel until its first post, or until a page limit, and writes one row per post.

python
import csv
import os
import sys
import time

import requests

URL = "https://api.toolzerhub.com/v1/telegram/channel"
HEADERS = {"x-api-key": os.environ.get("TOOLZERHUB_API_KEY", "")}
FIELDS = ["message_number", "published_at", "views", "text", "photo_url", "video_url", "url"]


def page(channel, before=None, tries=3):
    # 429, 5xx and dropped connections are retried; failed calls cost no credits.
    params = {"channel": channel, **({"before": before} if before else {})}
    for attempt in range(tries):
        last = attempt == tries - 1
        try:
            r = requests.get(URL, headers=HEADERS, params=params, timeout=90)
        except requests.RequestException:
            if last:
                raise
            time.sleep(10 * (attempt + 1))
            continue
        if r.status_code in (429, 500, 502, 503, 504) and not last:
            time.sleep(10 * (attempt + 1))
            continue
        r.raise_for_status()
        return r.json()["data"]


def main(channel, max_pages="1000", out=None):
    data, pages, seen = page(channel), 1, set()
    if data is None:  # private channels, groups and unknown names
        sys.exit(f"{channel}: no public channel with that name")
    out = out or f"{data['channel']}.csv"  # also for @name or t.me links
    with open(out, "w", newline="", encoding="utf-8-sig") as f:  # -sig: Excel reads UTF-8
        writer = csv.DictWriter(f, fieldnames=FIELDS)
        writer.writeheader()
        while True:
            for m in data["messages"]:  # up to 20 per page, newest page first
                if m["message_number"] in seen:
                    continue
                seen.add(m["message_number"])
                writer.writerow({
                    "message_number": m["message_number"], "published_at": m["published_at"],
                    "views": m["views_display"], "text": m["text"],
                    "photo_url": m["photo_url"], "video_url": m["video_url"], "url": m["url"],
                })
            if data["exhausted"] or not data["messages"] or pages >= int(max_pages):
                break
            data = page(channel, data["next_before"])
            pages += 1
            if data is None:
                sys.exit(f"page {pages} came back empty; {len(seen)} messages saved so far")
    print(f"{len(seen)} messages from {pages} calls written to {out}")


if __name__ == "__main__":
    if len(sys.argv) < 2:
        sys.exit("usage: python tg_export.py durov [max_pages] [file.csv]")
    if not HEADERS["x-api-key"]:
        sys.exit("set TOOLZERHUB_API_KEY first")
    main(*sys.argv[1:])

python tg_export.py https://t.me/durov printed 430 messages from 25 calls written to durov.csv after 45 seconds. Message numbers have gaps, so 430 messages came back for a channel numbered up to 552, and the first is Telegram's "Channel created" line. The pages held 6 to 20 posts, 17.2 on average.

  • Durov posted 11 times in 2015 and 80 times in 2026 so far, with 77 in 2024 and 72 in 2025.
  • The most viewed post, at 42.6M views, was the one about the TapSwap mini app.

What it costs

Each call is 1 credit and returns up to 20 posts, so 1,000 posts is about 58 calls and Durov's whole channel is 25. The 100 free credits a month cover the first few channels; $19 buys 10,000 credits that never expire, and plans for higher volume cover more channels. Set your key and the first channel is a CSV in under a minute.

The same job on other networks: our Reddit search API guide searches comments, and the Twitter search API guide pages X posts to a CSV.

We are not affiliated with Telegram.

Get this data from your own code

The Telegram API returns it as JSON with one x-api-key header. 100 free credits monthly.