# Telegram Channel Scraper \[USD 0.25/1K]: Posts & Messages (`usefulapi/telegram-channel-scraper`) Actor

Scrape public Telegram channels without login: posts and messages with views, reactions, media links, forwards, plus exact subscriber counts. Bulk channels, date range, keyword filter, only-new-posts mode. USD 0.25 per 1,000 posts.

- **URL**: https://apify.com/usefulapi/telegram-channel-scraper.md
- **Developed by:** [Useful API](https://apify.com/usefulapi) (community)
- **Categories:** Social media, News, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.25 / 1,000 posts

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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 Channel Scraper: posts, messages, views and media from public channels

Scrape **public Telegram channels** without a login, a phone number or a bot token. Give it channel usernames or
t.me links and get every post as a clean row: text (plain and HTML), date, views, reactions, photo and video links,
link previews, polls, links, hashtags, forwards and replies, plus one row per channel with the **exact subscriber
count**. Export to JSON, CSV or Excel, or call it as an API.

**USD 0.25 per 1,000 posts.** No monthly fee. Channel details USD 1.00 per 1,000 channels.

### What this Telegram scraper does

- **Bulk channels:** up to 5,000 channels in one run, 4 read side by side. Two names for the same channel (aliases)
  are read once.
- **Newest posts first**, up to the number you set per channel (or all of them).
- **Date range that jumps:** ask for one month from two years ago and the scraper goes straight there (binary search on
  post ids) instead of paging through every newer post. In a news channel with 348,113 post ids, the last post of
  31 January 2025 was found with 16 page requests in 7 seconds (measured October 2026).
- **Keyword filter:** keep only posts that mention your words (in the text, link preview, poll or file names). Posts it
  reads and leaves out cost USD 0.02 per 1,000, not the full post price.
- **Only new posts since the last run:** for scheduled monitoring. The scraper remembers the last post per channel
  and next time returns only newer posts, oldest first. If a run stops early (cost limit, timeout), the next run
  continues from the last saved post, so no post is skipped and none is billed twice.
- **Exact subscriber counts:** the channel preview only shows "10.4M"; the channel row reads Telegram's channel card
  for the exact number (10,352,107).
- **A clear row for every channel that cannot be read:** not found, preview turned off (you still get title,
  description and exact subscribers), a group, a bot, a personal account, a private invite link. The run goes on.
- **Telegram's own search** (fast, also for hashtags) as an option.

### Input examples

Newest 100 posts from three channels:

```json
{ "channels": ["telegram", "https://t.me/durov", "@bbcrussian"], "maxPostsPerChannel": 100 }
```

Everything a channel posted in September 2026:

```json
{ "channels": ["rian_ru"], "fromDate": "2026-09-01", "toDate": "2026-09-30", "maxPostsPerChannel": 100000 }
```

Brand monitoring across many channels, last 7 days, only posts that mention you:

```json
{ "channels": ["cointelegraph", "whale_alert_io", "binance_announcements"], "fromDate": "7 days", "keywords": ["bitcoin ETF", "BlackRock"], "maxPostsPerChannel": 1000 }
```

Scheduled every hour, only new posts (set it up once in Apify Schedules):

```json
{ "channels": ["nytimes", "bloomberg", "ajenews_official"], "onlyNewPosts": true, "includeChannelInfo": false, "maxPostsPerChannel": 500 }
```

Channel details only (title, exact subscribers, description, verified, photo), no posts:

```json
{ "channels": ["durov", "telegram", "tginfo"], "maxPostsPerChannel": 0 }
```

| Field | What it does |
|---|---|
| `channels` | Usernames, @names, t.me or t.me/s links, post links. Up to 5,000. |
| `maxPostsPerChannel` | Posts per channel, newest first (default 100). 0 = channel details only. |
| `fromDate`, `toDate` | `2026-10-01`, an ISO date-time, or relative (`7 days`, `24 hours`). A bare `toDate` includes the whole day (UTC). |
| `keywords` | Keep only posts containing any of these words or phrases (not case-sensitive). |
| `searchQuery` | Telegram's own in-channel search. Telegram returns at most the 22 newest matches per channel. |
| `onlyNewPosts`, `stateName` | Only posts newer than the last run's; `stateName` keeps separate lists for separate tasks. |
| `includeChannelInfo` | One row per channel (default on). |
| `exactSubscriberCount` | Read the exact subscriber number (default on, one small extra page per channel). |
| `proxyConfiguration` | Not needed. Apify datacenter proxy or your own proxy URLs accepted. |

### Output

One dataset with two kinds of rows (`recordType`): `post` and `channel`. Three ready views: Posts, Media and links,
Channels.

A post row (real, from t.me/s/bloomberg, shortened):

```json
{
  "recordType": "post",
  "channel": "bloomberg",
  "channelTitle": "Bloomberg",
  "channelSubscribers": 185000,
  "postId": 3206,
  "url": "https://t.me/bloomberg/3206",
  "date": "2026-01-09T07:51:56.000Z",
  "edited": false,
  "text": "US Commerce Secretary Howard Lutnick says a trade deal with India did not materialize last year because Prime Minister Narendra Modi did not call President Donald Trump. https://bloom.bg/49ncFHr\n\n📷: Prakash Singh/Bloomberg",
  "textHtml": "US Commerce Secretary ... <a href=\"https://bloom.bg/49ncFHr\">https://bloom.bg/49ncFHr</a><br><br>📷: Prakash Singh/Bloomberg",
  "views": 63500,
  "viewsText": "63.5K",
  "reactions": [{ "type": "emoji", "emoji": "😁", "count": 93, "countText": "93" }, { "type": "emoji", "emoji": "❤", "count": 44, "countText": "44" }],
  "reactionsTotal": 311,
  "mediaType": "photo",
  "media": [{ "type": "photo", "postId": 3206, "url": "https://cdn5.telesco.pe/file/...jpg", "thumbnailUrl": null }],
  "poll": null,
  "linkPreview": null,
  "links": ["https://bloom.bg/49ncFHr"],
  "hashtags": [],
  "cashtags": [],
  "mentions": [],
  "forwarded": false,
  "forwardedFromChannel": null,
  "replyToPostId": null,
  "viaBot": null,
  "channelId": 1488156064,
  "scrapedAt": "2026-10-17T05:00:00.000Z"
}
```

Post fields: `postId`, `url`, `date` (UTC), `edited`, `isServiceMessage`, `text`, `textHtml`, `views` and `viewsText`,
`reactions` (emoji, custom emoji id or paid star, count), `reactionsTotal`, `mediaType` (photo, video, gif, album,
document, audio, voice, video_note, sticker, location, poll, link_preview), `media` (one entry per item: type, item
post id, file URL, thumbnail, duration, file name and size, title and performer for audio, sticker format), `poll` (question, type, options with percent, voters),
`linkPreview` (url, site, title, description, image), `links`, `buttons` (inline buttons under the post: text and
link), `hashtags`, `cashtags`, `mentions`, `forwarded` with `forwardedFromChannel`, `forwardedFromPostId`,
`forwardedFromUrl`, `forwardedFromTitle` (or `forwardSourceNotShown` when Telegram's page leaves the source out),
`replyToPostId`, `replyToUrl`,
`viaBot`, `contentNotShownOnWeb`, `channelId`, `channelTitle`, `channelSubscribers`.

Channel fields: `status` (ok, no_public_preview, not_found, not_a_channel, group_not_supported, duplicate_channel,
private_link_not_supported, invalid_input, fetch_failed), `error` (plain-English reason), `input` (the name as you
gave it; `channel` is Telegram's own spelling, the same as on the post rows), `title`, `description`,
`descriptionHtml`, `subscribers`, `subscribersExact`, `subscribersText`, `verified`, `scam`, `fake`, `photoUrl`,
`photosCount`, `videosCount`, `filesCount`, `linksCount`, `channelId`, `latestPostId`, `latestPostDate`.

A run summary (posts saved per channel, statuses, billed events, why a run stopped early) is in the key-value store
record `OUTPUT`.

### Pricing

Pay per event, no subscription:

| Event | Price |
|---|---|
| Post (a post row) | USD 0.25 per 1,000 |
| Channel (a channel row: details of a channel that exists) | USD 1.00 per 1,000 |
| Channel checked (a name that is not a readable channel, or Telegram did not answer) | USD 0.30 per 1,000 |
| Post checked (read but left out by your keyword filter, no row) | USD 0.02 per 1,000 |
| Run start | USD 0.002 |

Examples: 1,000 posts from one channel = USD 0.253 in total. 100 channels x 50 posts = USD 1.35. Set "Maximum cost per
run" and the scraper stops by itself at that amount, keeping every row it saved.

### Good to know

- **Views and reactions are as rounded as Telegram shows them** on its public page ("989K" becomes 989000; the shown
  text is kept in `viewsText` and `countText`). Exact numbers are not public.
- **Forward counts and comment counts are not on the public page**, so they are not in the output. Comments are never
  collected.
- **Telegram's search (`searchQuery`) stops at 22 results per channel** (measured October 2026). Use `keywords` for
  complete matching. With `searchQuery` and `onlyNewPosts` together, a run returns at most `maxPostsPerChannel` new
  matches and the next run starts after the newest one.
- **Large videos** ("Media is too big" on the web) come with a thumbnail and duration but no file link
  (`tooBigForWeb: true`). Media links point to Telegram's file servers; download what you need soon after the run.
- **Public channels only.** Private channels, invite links and groups need a login and are not read. Personal accounts
  are never read: a username that belongs to a person gets a status row and nothing else. Post author signatures are
  left out, and a forward's source is named only when it is a channel post.
- Speed measured on Apify in October 2026: 5 channels x 1,000 posts in about 3.5 minutes; 2,000 posts from one
  month of an old date range in about 1 minute.

### Use it as an API

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

Python:

```python
from apify_client import ApifyClient
client = ApifyClient("YOUR_TOKEN")
run = client.actor("usefulapi/telegram-channel-scraper").call(run_input={"channels": ["telegram"], "maxPostsPerChannel": 50})
for row in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(row["recordType"], row.get("postId"), row.get("text", "")[:80])
```

Works with Apify Schedules, webhooks, Make, Zapier, n8n and Google Sheets exports.

### FAQ

**Do I need a Telegram account, API id or bot token?** No. It reads the public preview Telegram publishes at
t.me/s/<channel>.

**Can it read private channels or groups?** No. Only public channels with a username and the web preview turned on.

**Can it get members, phone numbers or user profiles?** No, and it never will.

**Is it legal?** It reads pages Telegram shows to anyone without a login. You are responsible for how you use the
data: respect copyright, privacy law and Telegram's terms, which among other things forbid using Telegram data to
train AI models.

**Something is wrong or missing?** Open an issue on the Issues tab; we answer quickly.

# Actor input Schema

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

Public channel usernames or links, one per line: <code>telegram</code>, <code>@durov</code>, <code>https://t.me/bbcnews</code>, <code>t.me/s/channel</code> or a post link. Private invite links (t.me/+...) cannot be read without a login and get a row that says so. Up to 5,000 channels per run.

## `maxPostsPerChannel` (type: `integer`):

Newest posts first. 0 = channel details only (title, exact subscriber count, description, photo), no posts.

## `fromDate` (type: `string`):

Only posts on or after this date: <code>2026-10-01</code>, an ISO date-time, or relative like <code>7 days</code> or <code>24 hours</code>.

## `toDate` (type: `string`):

Only posts on or before this date (a bare date includes the whole day, UTC). The scraper jumps straight to it, so an old date range in a big channel does not page through every newer post.

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

Keeps posts that contain any of these words or phrases (not case-sensitive) in the text, link preview, poll or file names. Every post in range is read; posts left out cost USD 0.02 per 1,000 instead of the full post price.

## `searchQuery` (type: `string`):

Uses Telegram's own in-channel search (also hashtags like <code>#news</code>). Telegram returns at most the 22 newest matching posts per channel (measured October 2026). For complete matching use Keywords.

## `onlyNewPosts` (type: `boolean`):

Remembers the last post saved per channel (in a key-value store on your own account) and returns only newer posts next time, oldest first. If a run stops early, the next run continues from the last saved post, so nothing is skipped. The first run returns the newest posts.

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

Keeps separate "only new posts" positions for different tasks or schedules. Letters, digits, dot, dash, underscore.

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

One row per channel with title, exact subscriber count, description, verified flag, photo and counters. Switch off to get posts only (a channel that cannot be read always gets a row).

## `exactSubscriberCount` (type: `boolean`):

The channel preview shows a rounded count (10.4M); this reads Telegram's channel card for the exact number (10,352,107). One small extra page per channel.

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

Not needed: Telegram's public preview answered plain requests in every test. Apify datacenter proxy or your own proxy URLs are accepted; residential proxy is not.

## Actor input object example

```json
{
  "channels": [
    "telegram",
    "durov"
  ],
  "maxPostsPerChannel": 20,
  "onlyNewPosts": false,
  "stateName": "default",
  "includeChannelInfo": true,
  "exactSubscriberCount": true,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `posts` (type: `string`):

No description

## `channels` (type: `string`):

No description

## `summary` (type: `string`):

No description

# 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": [
        "telegram",
        "durov"
    ],
    "maxPostsPerChannel": 20,
    "proxyConfiguration": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("usefulapi/telegram-channel-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": [
        "telegram",
        "durov",
    ],
    "maxPostsPerChannel": 20,
    "proxyConfiguration": { "useApifyProxy": False },
}

# Run the Actor and wait for it to finish
run = client.actor("usefulapi/telegram-channel-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": [
    "telegram",
    "durov"
  ],
  "maxPostsPerChannel": 20,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}' |
apify call usefulapi/telegram-channel-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,usefulapi/telegram-channel-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/p1agLljKlfQ3DPlS1/builds/qhNjgjEoprFWlL8lJ/openapi.json
