# Telegram Scraper — Channel Messages, Views & Channel Info (`innolabs/telegram-scraper`) Actor

Telegram scraper and API: public channel messages with views, reactions, media, links and exact subscriber counts. Keyword search, date range, only-new mode. No login.

- **URL**: https://apify.com/innolabs/telegram-scraper.md
- **Developed by:** [Tristan Leroux](https://apify.com/innolabs) (community)
- **Categories:** Social media, News
- **Features:** Limited permissions
- **Stats:** 3 total users, 0 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.21 / 1,000 messages

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Telegram Scraper

Scrape **public Telegram channels** without a Telegram account, phone number or API key:
every message with its text, date, views, reactions, photos and videos, files, polls,
link previews, hashtags, mentions and links, plus a channel record with the exact
subscriber count, description, verified badge and media counters. Page back through a
channel's whole history, keep a date window or keywords, or run it on a schedule and get
only the new posts. Export as JSON, CSV or Excel, or pull the data through the API.

### What does Telegram Scraper do?

Telegram Scraper reads the public web preview of Telegram channels (the page anyone sees
at `t.me/s/<channel>`) and turns it into clean records:

- **Channel messages** — enter `durov`, `@durov` or a `t.me` link and get the channel's
  messages, newest first, paged back through its history down to the very first post.
- **Message links** — paste `https://t.me/durov/455` and get exactly that message.
- **Channel info** — title, username, numeric channel id, description, **exact subscriber
  count** (e.g. 10,099,107, not "10.1M"), photo / video / file / link counters, verified,
  scam and fake badges, avatar, and the date of the latest post. Groups, bots and users
  get a profile record with their member count or type.
- **Date window** — only messages newer than and/or older than a date (`2026-01-31` or
  relative values like `7 days`). Old windows are reached in a few requests, without
  paging through the newer posts.
- **Keywords** — keep only messages whose text contains your words, or run Telegram's
  own in-channel search.
- **Only new messages** — for scheduled runs: each message is delivered once; the next run
  returns only what was posted since.

Every message has its id, URL, ISO date, text (plain and HTML), view count as a number,
author signature, forwarded-from source, reply-to message, edited flag, media (photo URLs,
video URL, thumbnail and duration, documents and audio with name and size, stickers),
poll question, options and percentages, link preview (URL, site, title, description,
image), reactions with counts (including paid Telegram Stars), inline buttons, hashtags,
mentions, links and e-mail addresses.

### Use cases

- **News and media monitoring** — follow news, government and company channels and send
  every new post to Slack, Google Sheets or your database.
- **Crypto and finance intelligence** — track token announcements, exchange listings and
  trading channels with views and reactions as attention signals.
- **Brand and competitor research** — see what competitors and communities publish, how
  often, and which posts get the most views and reactions.
- **OSINT and journalism** — archive public channels around an event with exact
  timestamps, forwards and reply chains, including posts from years ago.
- **Marketing and influencer analysis** — compare channels by exact subscriber count, post
  frequency and average views per post before buying an ad.
- **AI and data teams** — build text datasets from public channels for sentiment analysis,
  translation, summarisation or RAG pipelines.

### How much does it cost?

You pay only for the results you get (pay-per-event). Duplicates, messages removed by your
filters and items over your `maxItems` limit are never charged.

| Event | What you pay for | Free plan | Paid plans (from) |
| --- | --- | --- | --- |
| `message` | One Telegram message in the dataset | $0.31 per 1,000 | $0.21 per 1,000 |
| `channel` | One channel record (title, exact subscribers, counters, description) | $0.31 per 1,000 | $0.21 per 1,000 |

"Paid plans (from)" is the Gold-and-above price; Bronze and Silver plans sit in between.
Apify also charges a tiny actor-start fee ($0.00005 per run). Channel records can be
switched off with **Include channel info**.

Worked examples (Free plan prices):

1. **Channel archive** — 10 channels × 1,000 messages = 10,000 messages + 10 channel
   records: 10,000 × $0.31 / 1,000 + 10 × $0.31 / 1,000 = $3.10 + $0.0031 = **$3.10**.
2. **Daily monitoring** — 20 news channels with **Only new messages**, about 30 new posts
   per channel per day: 600 messages × $0.31 / 1,000 = **$0.186 per day** (plus $0.006 if
   you keep the daily channel records).

The Apify Free plan includes $5 monthly credit, enough for about 16,000 messages every
month.

### How to use Telegram Scraper

In Apify Console:

1. Open Telegram Scraper and click **Try for free**.
2. Enter **Channels or message links**: usernames (`durov`), `@handles` or links
   (`https://t.me/durov`, `https://t.me/s/durov`, `https://t.me/durov/455`). The form is
   prefilled with two channels.
3. Set **Max messages per channel**. **Max items** caps the whole run.
4. Optionally set a date window (**Only messages newer than / older than**), **Keep
   messages containing** keywords, or **Telegram search in each channel**.
5. For schedules, switch on **Only new messages** (and give each schedule its own
   **State name**).
6. Click **Start**. The prefilled input (2 channels × 20 messages) takes about 5 seconds;
   2,500 messages from 10 channels take about a minute.
7. Open the **Output** tab (Messages and Channels views) and export as JSON, CSV, Excel
   or HTML.

Through the API:

1. Get your API token in Apify Console → Settings → Integrations.
2. Call the actor with your input JSON (see [Integrations and API](#integrations-and-api)).
3. Read the dataset items from the run.

### Input

| Field | Type | Default | Description |
| --- | --- | --- | --- |
| `channels` | array | – | Required. Usernames, `@handles`, `t.me` / `t.me/s/` links, or message links such as `https://t.me/durov/455` (that one message). |
| `maxMessagesPerChannel` | integer | `100` | Messages per channel, newest first (up to 100,000). With `searchQueries`, the cap per keyword. `0` = channel info only. |
| `includeChannelInfo` | boolean | `true` | One `channel` record per channel. |
| `newerThan` | string | – | Stop at messages older than this: `YYYY-MM-DD`, ISO date-time (UTC) or relative (`7 days`, `12 hours`). |
| `olderThan` | string | – | Skip messages newer than this (same formats). With `newerThan` it makes a window. |
| `keywords` | array | – | Keep messages whose text, link-preview title or poll question contains any of these words (case-insensitive). |
| `searchQueries` | array | – | Telegram's in-channel search, run per keyword instead of the plain feed. |
| `onlyNewMessages` | boolean | `false` | Deliver only messages newer than the newest one a previous run delivered (per channel and keyword). |
| `stateName` | string | `default` | Separate "already delivered" memory per schedule. Change it to start fresh. |
| `maxItems` | integer | `1000` | Hard cap on all items (messages and channel records) in the run. |
| `proxyConfiguration` | object | Apify Proxy | Proxy settings. The default datacenter proxy works for Telegram. |
| `debug` | boolean | `false` | Log every page fetched. |

Example — three channels, the last 500 messages each, no channel records:

```json
{
    "channels": ["durov", "telegram", "https://t.me/bloomberg"],
    "maxMessagesPerChannel": 500,
    "includeChannelInfo": false,
    "maxItems": 1500
}
```

Example — everything a channel posted in March 2023:

```json
{
    "channels": ["meduzalive"],
    "newerThan": "2023-03-01",
    "olderThan": "2023-03-31",
    "maxMessagesPerChannel": 5000
}
```

Example — daily schedule: only new posts that mention a keyword:

```json
{
    "channels": ["cointelegraph", "binance_announcements", "whale_alert_io"],
    "keywords": ["bitcoin", "ETF"],
    "onlyNewMessages": true,
    "stateName": "crypto-daily",
    "includeChannelInfo": false
}
```

### Output

Every item has `type` (`message` or `channel`) and `sourceInput` (the channel or link it
came from). Examples from a real run on 2026-11-07 (long CDN links and texts shortened).

#### Message

```json
{
    "type": "message",
    "id": 552,
    "url": "https://t.me/durov/552",
    "channelUsername": "durov",
    "channelTitle": "Pavel Durov",
    "channelId": 1006503122,
    "date": "2026-10-11T14:52:40Z",
    "text": "✨ Designers × AI\n\n🆕 The mockup era is over. Today’s AI tools give every designer a superpower…",
    "textHtml": "✨ <b>Designers × AI<br><br></b>🆕 The mockup era is over…",
    "views": 2090000,
    "viewsText": "2.09M",
    "author": "Pavel Durov",
    "senderName": "Pavel Durov",
    "senderUrl": "https://t.me/durov",
    "viaBot": null,
    "isEdited": false,
    "isForwarded": true,
    "forwardedFrom": { "name": null, "author": null, "url": null, "username": null, "messageId": null },
    "replyTo": null,
    "mediaType": "video",
    "media": [
        {
            "type": "video",
            "url": null,
            "thumbnailUrl": "https://cdn4.telesco.pe/file/…",
            "duration": "1:15",
            "durationSec": 75,
            "title": null,
            "size": null,
            "messageUrl": "https://t.me/durov/552"
        }
    ],
    "mediaCount": 1,
    "poll": null,
    "linkPreview": null,
    "location": null,
    "contact": null,
    "buttons": [],
    "reactions": [
        { "emoji": "⭐", "emojiId": null, "isPaid": true, "count": 10600, "countText": "10.6K" },
        { "emoji": null, "emojiId": "5936157098181135162", "isPaid": false, "count": 19000, "countText": "19K" }
    ],
    "totalReactions": 44090,
    "hashtags": [],
    "mentions": ["designers"],
    "links": ["https://t.me/designers"],
    "emails": [],
    "isContentHidden": false,
    "groupedId": null,
    "scrapedAt": "2026-11-07T11:29:32Z",
    "sourceInput": "durov",
    "searchQuery": null
}
```

#### Channel

```json
{
    "type": "channel",
    "id": 1006503122,
    "username": "durov",
    "url": "https://t.me/durov",
    "title": "Pavel Durov",
    "description": "Founder of Telegram.",
    "descriptionHtml": "Founder of Telegram.",
    "entityType": "channel",
    "subscribers": 10099107,
    "subscribersText": "10.1M",
    "members": null,
    "online": null,
    "photos": 102,
    "videos": 47,
    "audios": null,
    "files": null,
    "links": 200,
    "verified": true,
    "scam": false,
    "fake": false,
    "avatarUrl": "https://cdn4.telesco.pe/file/…",
    "hasPublicPreview": true,
    "lastMessageId": 552,
    "lastMessageDate": "2026-10-11T14:52:40Z",
    "sourceInput": "durov",
    "scrapedAt": "2026-11-07T11:29:32Z"
}
```

Field notes:

- `mediaType` is `text`, `photo`, `video`, `gif`, `roundVideo`, `voice`, `audio`,
  `document`, `sticker`, `album` (several items in `media`), `poll`, `location`,
  `contact`, `linkPreview` or `unsupported` (a post type the web preview does not show).
- An album is one message: `media` lists every photo/video with its own `messageUrl`, and
  `groupedId` marks the album.
- `views` and reaction `count` are numbers parsed from Telegram's rounded display
  (`2.09M` → 2,090,000); the original text is kept in `viewsText` / `countText`.
- Custom-emoji reactions have `emojiId` and no `emoji` character; paid Telegram Stars
  reactions have `isPaid: true`.
- `author` is the post signature when the channel signs posts; `senderName` is the
  channel (or the group member in discussion groups).
- `forwardedFrom.name` is empty when the original author hides forwards; `url`,
  `username` and `messageId` point to the source post when it is a public channel.
- `subscribers` on the channel record is the exact count from the channel page;
  `subscribersText` is the rounded figure shown in the preview.
- Dates are UTC (ISO 8601).

### Integrations and API

Run the actor and get the items back in one call:

```bash
curl -X POST "https://api.apify.com/v2/acts/innolabs~telegram-scraper/run-sync-get-dataset-items?token=YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"channels": ["durov"], "maxMessagesPerChannel": 50}'
```

Python with [apify-client](https://docs.apify.com/api/client/python):

```python
from apify_client import ApifyClient

client = ApifyClient("YOUR_TOKEN")
run = client.actor("innolabs/telegram-scraper").call(
    run_input={
        "channels": ["telegram", "durov"],
        "maxMessagesPerChannel": 200,
        "newerThan": "30 days",
    }
)
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    if item["type"] == "message":
        print(item["date"], item["views"], item["text"][:80])
```

- **Make, Zapier, n8n** — use the Apify app/node to start the actor and send new messages
  to Google Sheets, Airtable, Notion, Slack or Discord.
- **Scheduling** — create a schedule in Apify Console (e.g. every hour) with **Only new
  messages** on to monitor channels.
- **Webhooks** — trigger your pipeline when a run finishes.
- **AI agents (MCP)** — connect Claude, Cursor or other MCP clients through the Apify MCP
  server at https://mcp.apify.com and let your agent read Telegram channels on demand.

### FAQ

#### Do I need a Telegram account, API key or phone number?

No. The scraper reads the public web preview that Telegram shows to any visitor, so it
needs no login, no `api_id`/`api_hash` and no phone number. The default Apify Proxy
setting works; rate-limited requests are retried automatically on a fresh connection.

#### Is it legal to scrape Telegram?

The scraper only collects what Telegram publishes openly on `t.me` for public channels:
messages, view counts and channel details. It cannot see private channels, private
groups or chats. Messages can contain personal data protected by laws such as GDPR, so
make sure you have a legitimate reason to store it and that your use complies with
Telegram's terms.

#### How many messages can I get from a channel?

The whole public history: the scraper pages back 20 messages per request until the first
post of the channel (tested: complete histories of two channels, and 1,500 messages from
March 2023 of a channel with 150,000 posts). Use
`maxMessagesPerChannel` and `maxItems` to cap a run, and a date window to fetch only a
period.

#### How does "Only new messages" work?

The scraper remembers the newest message id it delivered per channel (and per search
keyword) in a named key-value store in your Apify account. The next run stops as soon as
it reaches that message, so you pay only for new posts. The first run works normally and
saves the state. Use a different **State name** per schedule; change it to start over.
Each run delivers up to `maxMessagesPerChannel` new messages per channel.

#### What is the difference between "Keep messages containing" and "Telegram search"?

**Keep messages containing** pages through the channel and keeps every message whose text
contains one of your words, so it finds matches deep in the history. **Telegram search**
uses Telegram's own search box, which returns only about the 20 newest matches per
keyword.

#### Why are some channels or messages missing?

Groups, bots, users and private channels have no public message preview; for them you get
the profile record (type, member count) and a note in the run's `SUMMARY`. Some channels
are hidden by Telegram in certain countries or turn their web preview off. Each channel
is processed on its own: one failing channel never stops the run, and you are not charged
for it. If every input fails, the run is marked as failed with the reason.

#### Can I download the photos and videos?

The dataset contains direct photo URLs, video URLs (for videos the preview plays), video
thumbnails and durations. The links come from Telegram's CDN and expire after a while, so
download them soon after the run. Large videos, documents and audio files are only listed
with their name, size and duration: Telegram does not offer them for download to
logged-out visitors.

### Limits

- Public channels only. Groups, bots and users return a profile record without messages;
  private channels and invite links (`t.me/+…`) are not supported.
- Comments under posts (discussion groups) are not collected.
- Telegram rounds views and reaction counts in the preview (e.g. `2.09M`); the numbers are
  as precise as that display. Channel subscriber counts are exact.
- Telegram's in-channel search returns about 20 newest matches per keyword.
- Some post types (e.g. stories, paid media, some animated stickers) are not shown in the
  web preview; such messages have `mediaType: "unsupported"` and `isContentHidden: true`
  with their date, views and URL.
- Large videos, documents and audio are listed without a download URL.
- Media URLs are temporary.
- `Only new messages` delivers at most `maxMessagesPerChannel` new posts per channel and
  run; set it high enough for your schedule interval.

# Changelog

This Actor's version history is a separate document: https://apify.com/innolabs/telegram-scraper/changelog.md

# Actor input Schema

## `channels` (type: `array`):

Public Telegram channels: durov, @durov, https://t.me/durov, https://t.me/s/durov. A message link such as https://t.me/durov/455 returns just that message. Duplicates are scraped once.

## `maxMessagesPerChannel` (type: `integer`):

How many messages to collect from each channel, newest first, paging back through its history. With search keywords this is also the cap per keyword. 0 returns channel info only.

## `includeChannelInfo` (type: `boolean`):

Add one channel record per channel: title, description, exact subscriber count, photo/video/file/link counters, verified badge, avatar.

## `newerThan` (type: `string`):

Stop at messages older than this date: YYYY-MM-DD, an ISO date-time in UTC, or a relative value such as '7 days' (counted back from now).

## `olderThan` (type: `string`):

Skip messages newer than this date (same formats). Combine with 'newer than' for a window, e.g. a past month.

## `keywords` (type: `array`):

Case-insensitive words or phrases; a message is kept when its text, link-preview title or poll question contains any of them. The scraper pages through the channel history until it has enough matches.

## `searchQueries` (type: `array`):

Runs Telegram's own in-channel search for each keyword (like the search box on t.me/s/channel). Telegram returns only the ~20 newest matches per keyword; use 'Keep messages containing' for deeper keyword scans.

## `onlyNewMessages` (type: `boolean`):

Push only messages newer than the newest one previous runs delivered for the same channel (and search keyword). The state is kept in a named key-value store, so a daily schedule outputs just the new posts. The first run works normally and saves the state.

## `stateName` (type: `string`):

Separate 'already delivered' memory for different schedules or tasks. Letters, digits, '-' and '\_' only. Change it to start fresh.

## `maxItems` (type: `integer`):

Hard cap on dataset items (messages and channel records) for the whole run. The run stops cleanly when reached.

## `proxyConfiguration` (type: `object`):

Apify Proxy settings. The default datacenter proxy works for Telegram; switch the country if a channel is restricted in some regions.

## `debug` (type: `boolean`):

Log every page fetched.

## Actor input object example

```json
{
  "channels": [
    "durov",
    "telegram"
  ],
  "maxMessagesPerChannel": 20,
  "includeChannelInfo": true,
  "onlyNewMessages": false,
  "stateName": "default",
  "maxItems": 1000,
  "proxyConfiguration": {
    "useApifyProxy": true
  },
  "debug": false
}
```

# Actor output Schema

## `dataset` (type: `string`):

All items produced by the run, one JSON object per record.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "channels": [
        "durov",
        "telegram"
    ],
    "maxMessagesPerChannel": 20,
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("innolabs/telegram-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "channels": [
        "durov",
        "telegram",
    ],
    "maxMessagesPerChannel": 20,
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("innolabs/telegram-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "channels": [
    "durov",
    "telegram"
  ],
  "maxMessagesPerChannel": 20,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call innolabs/telegram-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,innolabs/telegram-scraper"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/qcaXDIrJAsaGl7vUD/builds/FxF6Oq2kVuEL52FOr/openapi.json
