# Telegram Channel Scraper: Posts, Views, Media & Reactions (`localwire/telegram-channels`) Actor

Telegram channel scraper: posts from any public Telegram channel with text, date, views, photos, videos, polls, forwards, link previews and reactions, plus keyword and date filters and new-post monitoring. $1 per 1,000 posts, no start fee, no login. No members or user profiles.

- **URL**: https://apify.com/localwire/telegram-channels.md
- **Developed by:** [Local Wire](https://apify.com/localwire) (community)
- **Categories:** Social media, News
- **Features:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$1.00 / 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, views, media and reactions from public channels

Telegram channel scraper: the posts of any public Telegram channel, read from the public preview pages Telegram publishes (`t.me/s/<channel>`), with text, date, view count, photos, videos, polls, forwards, link previews and reactions. **$1 per 1,000 posts** ($0.001 each), no start fee, no login, no Telegram account or API keys.

Give channel usernames or `t.me` links, optionally keywords and a date window. In **monitor mode** each run returns only the posts that are new since the last one. Because the Actor reads what a channel broadcasts, it never collects members, subscribers, commenters or user profiles.

### What you get

One record per post, newest first. The dataset has **Posts** and **Links and forwards** views.

```json
{
  "id": "telegram:telegram:460",
  "type": "post",
  "channel": "telegram",
  "channelTitle": "Telegram News",
  "channelUrl": "https://t.me/telegram",
  "channelSubscribers": 9170000,
  "channelVerified": true,
  "postId": 460,
  "url": "https://t.me/telegram/460",
  "text": "For all the info from today's update and a look back at 13 years of Telegram features — check out our blog: https://telegram.org/blog/welcome-messages-buttons-TG-13",
  "textHtml": null,
  "postedAt": "2026-08-26T19:12:34.000Z",
  "edited": false,
  "views": 2380000,
  "authorSignature": null,
  "forwardedFromTitle": null,
  "forwardedFromUrl": null,
  "replyToUrl": null,
  "hasMedia": false,
  "mediaTypes": [],
  "media": [],
  "linkPreview": {
    "url": "https://telegram.org/blog/welcome-messages-buttons-TG-13",
    "siteName": "Telegram",
    "title": "Welcome Messages, Buttons and More",
    "description": "…"
  },
  "links": ["https://telegram.org/blog/welcome-messages-buttons-TG-13"],
  "poll": null,
  "reactions": { "👍": 703, "❤": 120 },
  "reactionsTotal": 823,
  "scrapedAt": "2026-11-07T21:30:00.000Z"
}
```

| Field | Meaning |
|---|---|
| `id` | Stable (`telegram:<channel>:<postId>`), so you can de-duplicate or upsert. |
| `channel`, `channelTitle`, `channelUrl`, `channelSubscribers`, `channelVerified` | Channel facts on every post, so you do not need a second call. |
| `postId`, `url` | The post's number in the channel and its `t.me` link. |
| `text`, `textHtml` | The post text, plain, and as HTML when `includeHtml` is on. E-mail addresses and phone numbers are removed. |
| `postedAt`, `edited` | Posting time in UTC, and whether the post was edited afterwards. |
| `views` | View count as Telegram shows it, so rounded above 1,000 ("2.38M" becomes 2380000). |
| `forwardedFromTitle`, `forwardedFromUrl` | Filled only when a post was forwarded from another **channel**. Forwards from people are not attributed. |
| `replyToUrl` | The post this one replies to, when it is a reply. |
| `media[]`, `mediaTypes`, `hasMedia` | Each photo, video, round video, document, audio or voice note, sticker, poll or location: `type`, `url` (the file, where the page exposes it), `thumbUrl`, `title`, `duration`. |
| `linkPreview`, `links` | The link preview card (`url`, `siteName`, `title`, `description`) and every link in the post. |
| `poll` | The question, the options with their percentages, and the vote count text. |
| `reactions`, `reactionsTotal` | Counts per emoji. Custom emoji appear as `custom:<id>` and paid Star reactions as `stars`. |
| `authorSignature` | The admin signature some channels add to posts. `null` unless `includeAuthorSignature` is on. |

The run's key-value store also gets an `OUTPUT` record with, per channel, the title, subscriber count, pages read and posts read and returned, plus the channels that had no public preview.

### Quick start

The form opens with two large public channels and 20 posts each. Press **Start** for about 40 posts at $0.04:

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

A channel can be a username (`durov`, `@telegram`) or any `t.me` link to the channel or one of its posts.

The last week of three channels, posts mentioning a product:

```json
{
  "channels": ["telegram", "durov", "https://t.me/nexta_live"],
  "keywords": ["Stars", "Premium"],
  "postedWithinDays": 7,
  "maxPostsPerChannel": 200
}
```

An hourly feed of new posts (save as a Task and add a Schedule):

```json
{
  "channels": ["nexta_live", "telegram"],
  "maxPostsPerChannel": 40,
  "onlyNew": true,
  "monitorKey": "news-feed"
}
```

### Input reference

Only `channels` is required.

| Field | Type | Default | What it does |
|---|---|---|---|
| `channels` (required) | string\[] | | Usernames or `t.me` links of public channels, up to 200 per run. |
| `maxPostsPerChannel` | integer | 100 | How many of each channel's newest posts to read (1 to 10,000). Keyword and date filters apply within these. |
| `keywords` | string\[] | | Keep only posts whose text or link preview contains ANY of these words or phrases (case-insensitive). |
| `postedWithinDays` | integer | | Stop at posts older than this many days. |
| `postedSince` | string | | Stop at posts older than this date (`2026-11-01`). Overrides `postedWithinDays`. |
| `includeHtml` | boolean | false | Also return each post's text as HTML (`textHtml`), keeping bold, links and line breaks. |
| `includeAuthorSignature` | boolean | false | Return the admin's signature where a channel signs its posts. |
| `maxResults` | integer | 1000 | Stop the run after this many posts in total (1 to 100,000). |
| `onlyNew` | boolean | false | Monitor mode: return, and charge for, only posts not returned by earlier runs. |
| `monitorKey` | string | | A name for the monitor's memory. By default each set of channels and keywords has its own. |

### Use cases

- **News and OSINT monitoring.** Follow channels on a topic and send new posts to Slack, e-mail, Sheets or a webhook on a schedule.
- **Brand and keyword tracking.** Filter a watchlist of channels by product names, tickers or places.
- **Crypto, trading and deal channels.** Capture announcements with their timestamps and view counts.
- **Research and archiving.** Pull a channel's history, with links, forwards and reactions, into a dataset.
- **Content analytics.** Compare views and reactions per post across channels, with subscriber counts for context.
- **Media and fact-checking desks.** Track how a story moves between channels through `forwardedFromUrl`.
- **AI agents.** "What did these channels post about the listing this week?" answered from the posts themselves.

### Monitor mode

With `onlyNew` on, the Actor remembers the posts it has returned for this set of channels and keywords (or under your `monitorKey`) in a key-value store named `telegram-channels-monitor` in your account. It stops reading a channel as soon as it reaches posts it has already returned, and returns only the new ones. Use one monitor per channel set; changing the keywords starts a new memory unless you give the same `monitorKey`.

Save the input as a **Task**, add a **Schedule**, and connect an **integration** (Slack, e-mail, Google Sheets, webhook). Each run then delivers only the new posts.

### Pricing

Pay per result: **$0.001 per post returned ($1 per 1,000)**. No start fee, no monthly fee. Posts filtered out by keywords or dates, posts already returned in monitor mode, and channels that cannot be read cost nothing.

| Run | Posts returned | Cost |
|---|---|---|
| The quick-start run: 2 channels × 20 posts | about 40 | about **$0.04** |
| The newest 100 posts of 10 channels | 1,000 | **$1.00** |
| A full 10,000-post history of one channel | 10,000 | **$10.00** |
| An hourly monitor on 20 channels that finds 150 new posts a day | 150 a day | $0.15 a day, about **$4.50 a month** |

To cap spending, set `maxResults` or a maximum cost per run in the run options (`maxTotalChargeUsd` in the API). The Actor stops cleanly at the limit.

### Use it from the API

```bash
curl -X POST "https://api.apify.com/v2/acts/localwire~telegram-channels/run-sync-get-dataset-items?token=<YOUR_APIFY_TOKEN>" \
  -H "content-type: application/json" \
  -d '{"channels": ["durov"], "maxPostsPerChannel": 20}'
```

With the official clients:

```js
import { ApifyClient } from "apify-client";

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor("localwire/telegram-channels").call({ channels: ["telegram"], postedWithinDays: 30 });
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("localwire/telegram-channels").call(run_input={"channels": ["telegram", "durov"], "keywords": ["update"], "maxPostsPerChannel": 200})
items = client.dataset(run["defaultDatasetId"]).list_items().items
```

### Use it from an AI agent (MCP)

Apify hosts an MCP server at `https://mcp.apify.com`. Add it to an MCP client (Claude, ChatGPT, Cursor, VS Code and others) and sign in with your Apify account, or send your API token as an `Authorization: Bearer` header. To expose just this Actor as a tool:

```
https://mcp.apify.com?tools=localwire/telegram-channels
```

Then ask "what did these channels post about the listing this week?". Notes for agents: `channels` is required. `maxPostsPerChannel` defaults to 100 per channel, so pass a smaller number (10 to 20) for a quick look, and `postedWithinDays` to bound the time window.

### Integrations

Apify connects runs to Make, n8n, Zapier, LangChain, webhooks and other tools, and datasets export as JSON, CSV, Excel, XML or RSS. Typical setups: new posts from a watchlist to a Slack channel; keyword matches to e-mail; a channel archive appended to Google Sheets or loaded into a database.

### FAQ

#### Is there a free Telegram channel API?

Telegram has two official APIs, both free. The Bot API only sees channels where your bot is an administrator. The full Telegram API (MTProto) can read public channels but needs a Telegram account, a phone number and an application id. For public channels there is also the preview page at `t.me/s/<channel>`, which anyone can open without an account; this Actor reads that page and returns structured records.

#### How do I scrape a Telegram channel without an account or a phone number?

Put the channel's username or `t.me` link in `channels` and run. No Telegram login, API key or phone number is involved at any point.

#### Can I scrape private channels or groups?

No. Only public channels with a public preview can be read. Private channels, invite links (`t.me/+…`), `t.me/c/…` links, groups and bots have no preview; they are reported in `OUTPUT` and cost nothing.

#### Does it return channel members, subscribers or commenters?

No. The preview page shows only what the channel broadcast, and this Actor does not look for anything else. You get the subscriber **count**, never a list of people.

#### How do I export Telegram channel posts to CSV, Excel or JSON?

Open the run's dataset and choose a format, or request `https://api.apify.com/v2/datasets/<DATASET_ID>/items?format=csv&view=overview`.

#### How do I monitor a Telegram channel for keywords?

Set `channels` and `keywords`, turn on `onlyNew`, schedule the task and add an integration. Each matching post is delivered once.

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

Each media item has the link Telegram's page exposes: the file for photos, small videos and stickers, and a thumbnail for large videos. The Actor returns the links; it does not download the files. Links point at Telegram's CDN and can expire, so fetch what you need soon after the run.

#### How far back can I go?

Up to 10,000 posts per channel in one run, read 20 at a time. At one request every 1.5 seconds that takes about 15 minutes.

#### Are view counts exact?

Above 1,000 they are rounded, as Telegram displays them ("2.38M"). The same applies to subscriber counts.

#### How do I check whether a channel can be read?

Open `https://t.me/s/<channel>` in a browser. If you see the channel's posts, the Actor can read them.

### Limitations

- **Public channels only.** A channel needs a public username and previews turned on (most news, project and brand channels have both).
- **No comments, members or profiles**, and no search across Telegram: you name the channels.
- **Order and paging.** Posts come newest first, 20 per page, at most 10,000 per channel per run and 200 channels per run.
- **Counts are rounded** above 1,000.
- **Media links** can expire, and large videos have a thumbnail but no file link.
- **Keyword filters** match the post text and link preview within the posts read; they are not a Telegram-wide search.
- **Rate limits.** One ordinary request every 1.5 seconds, no proxies. If Telegram answers with a rate limit or a block, the Actor waits once, then stops and keeps what it has. Run less often rather than more aggressively.

### Data and privacy

Channel posts are broadcasts a publisher makes to the public; this Actor reads the same preview pages anyone can open without an account. It never logs in, never collects subscriber, member or commenter data, does not attribute forwards from people, removes e-mail addresses and phone numbers from text, and leaves out author signatures unless you turn them on. You are responsible for how you use the data, including Telegram's terms and the rights of the channels' authors.

This Actor is not affiliated with or endorsed by Telegram.

### Feedback

Found a channel that does not work, or need a field that is missing? Open an issue on the Actor's **Issues** tab.

### More from this developer

- [Google Trends Trending Now](https://apify.com/localwire/google-trends-trending-now)
- [Hacker News](https://apify.com/localwire/hacker-news)
- [Eventbrite Events](https://apify.com/localwire/eventbrite-events)
- [Meetup Events & Groups](https://apify.com/localwire/meetup-events)
- [Gumtree Listings](https://apify.com/localwire/gumtree-listings)
- [UK & EU public tenders](https://apify.com/localwire/uk-public-tenders)
- [Career Site Jobs API](https://apify.com/localwire/ats-jobs)
- [Workday Jobs](https://apify.com/localwire/workday-jobs)
- [Reed Jobs](https://apify.com/localwire/reed-jobs)
- [Wellfound Jobs](https://apify.com/localwire/wellfound-jobs)
- [App Store Reviews, Details & Top Charts](https://apify.com/localwire/app-store-reviews)
- [Flashscore results and fixtures](https://apify.com/localwire/flashscore-sports)
- [ESPN scores and standings](https://apify.com/localwire/espn-sports)

# Actor input Schema

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

One channel per line: a username (durov, @telegram) or a t.me link (https://t.me/durov, t.me/s/durov, or any post link such as https://t.me/durov/536). Private channels, invite links (t.me/+…) and t.me/c/… links have no public preview.

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

How many of each channel's newest posts to read (100 if left out; 20 per page, one request per page). Keyword and date filters apply within these. Each post returned costs $0.001, so 20 posts from 2 channels cost about $0.04.

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

Keep only posts whose text (or link preview) contains ANY of these words or phrases, case-insensitive. Leave empty for every post.

## `postedWithinDays` (type: `integer`):

Stop at posts older than this many days.

## `postedSince` (type: `string`):

Stop at posts older than this date, e.g. 2026-11-01. Overrides 'Posted in the last N days'.

## `includeHtml` (type: `boolean`):

Also return each post's text as HTML (textHtml), keeping bold, links and line breaks.

## `includeAuthorSignature` (type: `boolean`):

Channels can sign posts with an admin's name. Off by default; turn on to return it as authorSignature.

## `maxResults` (type: `integer`):

Stop the run after this many posts across all channels (1,000 if left out). You can also cap spending with the run's maximum cost.

## `onlyNew` (type: `boolean`):

Remember the posts returned by earlier runs of the same channels and keywords, and return (and charge for) only new ones. Schedule the Actor to get a feed of new posts.

## `monitorKey` (type: `string`):

Optional name for this monitor's memory. By default each set of channels and keywords gets its own memory.

## Actor input object example

```json
{
  "channels": [
    "telegram",
    "https://t.me/durov"
  ],
  "maxPostsPerChannel": 20,
  "includeHtml": false,
  "includeAuthorSignature": false,
  "maxResults": 1000,
  "onlyNew": false
}
```

# Actor output Schema

## `overview` (type: `string`):

Each post with its date, views, text and media.

## `links` (type: `string`):

Posts that link out or forward from another channel.

## `allResults` (type: `string`):

Every record in the run's default dataset, all fields.

# 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",
        "https://t.me/durov"
    ],
    "maxPostsPerChannel": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("localwire/telegram-channels").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",
        "https://t.me/durov",
    ],
    "maxPostsPerChannel": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("localwire/telegram-channels").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",
    "https://t.me/durov"
  ],
  "maxPostsPerChannel": 20
}' |
apify call localwire/telegram-channels --silent --output-dataset

```

## MCP server setup

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

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/UpOmy6zW0O13hFEOq/builds/SVJXfkiQtqLDW7kNr/openapi.json
