# Telegram Channel Scraper: Posts, Comments & Media (`nice_dev/telegram-channel-scraper`) Actor

Scrape public Telegram channels without login: posts, views, reactions, media, links, comments, channel details. Search inside a channel and get only new posts for n8n or Make. Export to JSON, CSV or Excel.

- **URL**: https://apify.com/nice_dev/telegram-channel-scraper.md
- **Developed by:** [Nice Dev](https://apify.com/nice_dev) (community)
- **Categories:** Social media, Lead generation, MCP servers
- **Stats:** 7 total users, 3 monthly users, 86.4% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.07 / 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.
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

### 🔎 What is Telegram Channel Scraper?

**Telegram Channel Scraper** extracts **posts from public Telegram channels** ([t.me](https://t.me)): **text, date, views, reactions, photos, videos, links, forwards, polls**, plus the **comments** of each post and the **details of the channel** when you ask for them. It works as a **Telegram API alternative** for public channels: no account, no API key, no phone number. Run it on a schedule, call it from your code, or plug it into Make, Zapier or n8n.

Type the **channel names** (`durov`, `telegram`), click **Start**, and download the posts in JSON, CSV or Excel. Add a **keyword** to search inside each channel, or tick **Only new posts** to get, at each scheduled run, only the posts published since the last one. Speed: **about 1,000 posts of one channel in 4 minutes** with the included proxy (several channels are read side by side), and about 5 times faster without a proxy.

### 📋 What data can you extract from Telegram?

One item per post, comment or channel, 56 fields:

| Category | What you get |
| --- | --- |
| 📝 **Post** | text (plain, HTML and Markdown), publication date, edited mark, link, post number |
| 👀 **Reach** | views, every reaction with its emoji and count, reactions paid with Stars |
| 🖼️ **Media** | photo links, videos (thumbnail and duration), documents, audio files, polls, link cards, albums as one row |
| 🔗 **Links and mentions** | every link, hashtag, @mention, e-mail address and inline button of a post |
| ↪️ **Forwards and replies** | where a post was forwarded from, which message it replies to |
| 📣 **Channel** | title, username, numeric id, subscribers, verified badge, SCAM / FAKE label |
| 💬 **Comments** | author, text, date, reactions and reply-to of each public comment, with the commented post |
| 🧾 **Channel details** | exact subscriber count, description, photo, counts of photos, videos, files and links, last post date |
| 🔍 **Search** | the keyword that found a post |

Every field, with an example, is listed in the **Output** section below. Comments, channel details, e-mail addresses and saved media files are options: they are empty (or absent) unless you tick them.

### ✅ Why use Telegram Channel Scraper?

- 🔍 **Search inside the channels**: the keyword is searched by Telegram in each channel, older posts included, not only the latest ones.
- 🔔 **Monitoring built in**: tick **Only new posts**, schedule the Actor, and each run reads each channel only from the last post it delivered. Posts already delivered are not read again and not charged.
- 💬 **Comments without an account**: the public comments of each post, one row per comment, up to 1,000 per post (author, text, date, reactions, the comment it answers).
- 🧾 **A row for every channel it cannot read**: a closed, hidden, unknown or group channel gives a row that says why, never an empty run with no explanation.
- 🗂️ **Many channels in one run**: up to 1 000 channels, with a cap per channel so that a big one cannot use up the whole budget.
- 🔌 API, scheduling, monitoring, integrations (Make, Zapier, n8n, Google Sheets…), proxy and JSON/CSV/Excel export via the Apify platform.

### 🚀 How to scrape Telegram channels

1. Create a free Apify account.
2. Open **Telegram Channel Scraper** and list the **Channels** (`durov`, `@telegram`, `t.me/telegram`, or a full link).
3. Optional: a **Search inside the channels** keyword, a date range, the options you want (comments, channel details…).
4. Set **Max posts** (100 by default, 0 = no limit), then click **Start**.
5. Download the dataset in JSON, CSV, Excel or via API.

### 💰 How much does it cost to scrape Telegram?

This Actor uses **pay per event** pricing: you pay for each **post** saved, plus a small fee per run start. The options are charged only when you tick them: each **comment**, each **channel detail** row, each page of **comments** read, each **e-mail address** found and each **media file** saved. The exact prices are in the **Pricing** tab of the Actor. A run that saves nothing costs only the run start, plus the small fee for each post a filter drops if you use filters (see **Pricing**).

### ⚙️ Input

Channels, a cap, and nothing else:

```json
{
    "channels": ["durov", "telegram"],
    "maxItems": 100
}
```

Search a keyword inside a channel, from a date:

```json
{
    "channels": ["durov"],
    "query": "privacy",
    "postedAfter": "2026-01-01",
    "maxItems": 50
}
```

Monitoring: only the posts published since the last run, one memory per schedule:

```json
{
    "channels": ["durov", "telegram"],
    "onlyNew": true,
    "stateKey": "crypto-news",
    "maxItems": 0
}
```

With links, comments and channel details:

```json
{
    "startUrls": [{ "url": "https://t.me/s/durov?q=privacy" }, { "url": "https://t.me/durov/427" }],
    "includeChannelInfo": true,
    "includeComments": true,
    "maxCommentsPerPost": 20,
    "maxItems": 20
}
```

| Field | Notes |
| --- | --- |
| `channels` | Public channels, in any form: `durov`, `@durov`, `t.me/durov`, `https://t.me/s/durov`. A link of a channel page keeps its keyword and its page (`https://t.me/s/durov?q=privacy`), as in `startUrls`. Max 1 000 channels. A public group gives its details only. |
| `startUrls` | Channel pages, searches inside a channel (`https://t.me/s/durov?q=privacy`), single posts (`https://t.me/durov/427`), or a channel read from a post number (`https://t.me/s/durov?before=1000`). Max 1 000 URLs. |
| `query`, `searchQueries` | Keyword searched inside each channel, as the channel's own search box does (`privacy`); `searchQueries` adds more keywords: channels × keywords = one search each (max 500 searches per run). |
| `maxItems`, `maxItemsPerQuery` | Posts saved for the whole run (`100` by default, `0` = unlimited) and for each channel and keyword (`0` = no cap). Comments and channel rows are not counted. |
| `includeChannelInfo`, `channelInfoOnly` | One row per channel with its exact subscribers, description and counts; the second one returns only these rows, no post. Paid options, off by default. |
| `includeComments`, `maxCommentsPerPost` | The most recent public comments of each post whose discussion is open, one row per comment (the newest ones, written in the order they were posted), up to 1,000 per post (Telegram shows 100 per page, more are loaded 50 at a time). Paid option, off by default. |
| `includeEmails`, `downloadMedia` | E-mail addresses written in the posts and comments; photos and video thumbnails saved in the run's key-value store, their links in `mediaFiles` (they work as long as the run's storage is kept: the data retention of your plan). Paid options, off by default. |
| `postedAfter`, `postedBefore` | Publication date range: `2026-09-01`, or a period before now (`7 days`, `2 weeks`, `30 days`; via the API also `24 hours` or a full ISO date-time). A channel is read from the newest post back and stops at the first one too old. |
| `minViews`, `maxViews`, `minReactions` | Keep the posts seen at least / at most so many times, or with at least so many reactions (`0` = no bound). |
| `mediaOnly`, `withTextOnly`, `withLinksOnly`, `includeForwarded`, `includeServiceMessages` | Keep only the posts with any media (photo, video, file, audio, poll, sticker, location, or a post Telegram shows only in the app; use **Only these media types** to keep only some), with text, with a link; drop the forwarded posts; add the service messages (channel created, post pinned…). |
| `mediaTypes` | Keep only the posts holding one of these media kinds (`photo`, `video`, `document`, `audio`, `poll`, `sticker`, `location`). |
| `includeKeywords`, `excludeKeywords` | Keep only the posts containing one of these words, or drop those that do (`sale`); case and accents ignored. |
| `onlyNew`, `stateKey`, `resetState` | Monitoring: each channel is read from the last post a previous run delivered under this memory key (`crypto-news`); `resetState` forgets the memory. |
| Advanced | `proxyConfiguration` (Apify proxy by default, included in the price; the residential proxy is not available; for a proxy country, US works), `maxConcurrency`, `maxRequestsPerMinute`, `minRequestIntervalMs`, `maxRequestRetries`, `debugLog`. |

### 📦 Output

A post (shortened: the full row has 56 fields, the ones that do not apply are `null` or empty):

```json
{
    "type": "post",
    "id": "durov/427",
    "url": "https://t.me/durov/427",
    "channel": "durov",
    "channelTitle": "Pavel Durov",
    "channelUrl": "https://t.me/durov",
    "channelId": 1006503122,
    "channelSubscribers": 10600000,
    "channelVerified": true,
    "channelLabels": [],
    "postId": 427,
    "publishedAt": "2026-09-30T14:02:11.000Z",
    "edited": false,
    "text": "Telegram now has 1 billion monthly users.",
    "views": 18900000,
    "reactions": [{ "emoji": "👍", "emojiId": null, "count": 15400, "paid": false }],
    "reactionsCount": 52300,
    "photos": ["https://cdn4.telesco.pe/file/abc.jpg"],
    "mediaTypes": ["photo"],
    "mediaCount": 1,
    "links": ["https://telegram.org/blog"],
    "hashtags": [],
    "mentions": [],
    "searchQuery": null,
    "scrapedAt": "2026-10-07T12:00:00.000Z"
}
```

You can download the dataset in various formats such as JSON, HTML, CSV or Excel. A `comment` row and a `channel` row have the same columns; `type` tells them apart.

#### All 56 fields

| Fields | What you get |
| --- | --- |
| `type`, `id`, `url` | `post`, `comment` or `channel`; a unique row id (`durov/427`); the public link |
| `channel`, `channelTitle`, `channelUrl`, `channelId` | username, title, link, numeric id of the channel |
| `channelSubscribers`, `channelVerified`, `channelLabels` | subscribers (rounded by Telegram on a post, exact on a channel row, empty on a post pasted by its link), verified badge, SCAM / FAKE label |
| `postId`, `publishedAt`, `edited`, `serviceAction` | post number, ISO date, edited mark, what a service message says |
| `text`, `textHtml`, `textMarkdown` | the text, with its links and formatting |
| `views`, `reactions`, `reactionsCount`, `paidStars` | views, each reaction with its count, their total, reactions paid with Stars |
| `author`, `authorUrl`, `authorPhoto` | signature of a signed post, or the comment author and profile |
| `forwardedFrom`, `replyTo` | origin of a forwarded post, the message a post or comment replies to |
| `photos`, `videos`, `documents`, `audio` | media of the post |
| `poll`, `linkPreview`, `buttons` | poll question and results (no results on a post pasted by its link: its page shows none), link card, inline buttons |
| `mediaTypes`, `mediaCount`, `albumPostIds`, `mediaFiles` | kinds of media, how many, post numbers of an album, links of the files saved by **Download media** |
| `links`, `hashtags`, `mentions`, `emails` | every link, hashtag, @mention and e-mail address of the text |
| `commentsCount`, `relatedPostUrl` | comments of a post (0 = discussion closed); for a comment, the post it belongs to |
| `channelDescription`, `channelPhoto`, `isGroup`, `membersOnline` | channel and group rows: description, photo, group or channel, members online |
| `photosCount`, `videosCount`, `filesCount`, `linksCount` | channel rows: counts of what the channel published |
| `webPreview`, `lastPostId`, `lastPostAt` | channel rows: `open`, `closed`, `not_found` or `group`; the newest post and its date |
| `searchQuery`, `scrapedAt` | the keyword that found the post; ISO timestamp of the extraction |

### 💡 Tips

#### How to get more results

Set `maxItems` to `0` and `maxItemsPerQuery` to the number of posts you want from each channel. A channel is read from its newest post back, page after page, with no limit on its history.

#### How to reduce costs

The price is per post, so the levers are `maxItems`, `maxItemsPerQuery`, the date range (`postedAfter` stops the reading of a channel at the first post too old), the filters, and `onlyNew` for recurring runs (you never pay twice for the same post). Leave the options you do not need unticked.

#### Search inside a channel

Fill `query` (and `searchQueries` for several keywords): Telegram searches each channel and the Actor reads only the matching posts, however old. Each post carries the keyword that found it in `searchQuery`.

#### Several searches in one run

Fill `channels` and `searchQueries`: the Actor runs one search per channel × keyword (3 keywords × 4 channels = 12 searches, up to 500 per run). A post found by several keywords is saved — and charged — once. Set `maxItemsPerQuery` to give every channel its own cap: without it the first channels can use up the whole `maxItems`.

#### Monitoring: only the new posts

Tick **Only new posts** (`onlyNew`) and schedule the Actor. The first run returns the posts allowed by the caps and dates; each later run reads each channel only from the last post it delivered, so a delivered post is never read, saved or charged twice. The memory lives in a named key-value store of your account (`telegram-channel-scraper-seen`, up to 150,000 posts per key) and is only updated with posts that really reached the dataset: a post you paste by its link, or a page you paste with `?before=` / `?after=`, does not move where its channel stopped, and a run that was stopped before its end (its maximum cost, a site that changed) leaves the memory under the first post it did not deliver, so the next run delivers it. A post that `minViews` or `minReactions` dropped while it was among the newest (its counters can still grow) is checked again by the next run, with the filter fee, until it passes or a newer post is delivered. Give each schedule its own `stateKey`.

#### Comments

Tick **Comments** to get the public comments of each post whose discussion is open. A channel without comments is detected after a few posts and no more comment pages are asked for it.

### 🔌 Integrations and API

Call the Actor via the Apify API, the JavaScript or Python clients, or connect it with integrations and webhooks (Make, Zapier, n8n, Google Sheets, Slack, Airtable…). The dataset can be fetched as JSON or CSV from any tool.

### 🤖 Use with AI agents (MCP)

AI agents (Claude, ChatGPT, Cursor…) can find and run this Actor through the [Apify MCP server](https://mcp.apify.com), billed to their Apify account like any run. It returns one item per Telegram post. Actor id: `nice_dev/telegram-channel-scraper`; MCP server with this Actor only: `https://mcp.apify.com/?tools=fetch-actor-details,nice_dev/telegram-channel-scraper`.

Smallest input, for a cheap first call:

```json
{
    "channels": ["durov"],
    "maxItems": 10
}
```

Key output fields: `channel`, `postId`, `publishedAt`, `text`, `views`, `reactionsCount`, `url`.

Cost: see the pricing section above. Cap each call with `maxItems` and, through the API, with the run option `maxTotalChargeUsd`.

### ❓ FAQ

#### Is it legal to scrape Telegram?

The Actor only reads what Telegram shows publicly to any anonymous visitor on the web: the posts of public channels. It logs in to nothing. Posts and comments can contain personal data, which is protected by GDPR: do not store it without a legitimate reason. You are responsible for using the data in compliance with Telegram's Terms of Service and applicable law. This Actor is not affiliated with Telegram.

#### Does it need a login or a proxy?

No login. The proxy is included in the price: leave the default setting (the residential proxy is not available). Some channels are hidden on the web in some countries: pick another proxy country for them — US works; a country where Apify Proxy has no IP fails the run, with a message that says so. About 5 times faster without a proxy (untick **Apify Proxy**), but then a request Telegram turns away is asked again from the same address only, after a pause. A request Telegram turns away is retried at once on a new proxy session, up to 10 times on top of the retries (without a proxy, after a pause of 5 seconds, doubled at each retry up to 150 seconds).

#### Why does a channel give a row and no post?

A channel row with `webPreview` set to `closed` means the channel hides its posts on the web (some channels only show them in the app), `not_found` that the name belongs to nobody, `group` that it is a public group (its messages are not published on the web, only its details). These rows are free unless you ticked **Channel details**.

#### Is the data safe to open in Excel or to show on a web page?

Texts are the channels' own words, copied as they are. A text can begin with `-`, `+`, `=` or `@`: Excel and Google Sheets may read such a cell of a CSV file as a formula or as a number. The Actor leaves the text as it is, so that the JSON and the API give the real value: when you open a CSV, import these columns as text. On a web page, escape the text: `textHtml` is Telegram's own markup and the Actor does not sanitize it.

#### Known limitations

- Only public channels whose web preview is open give posts; public groups give their details only.
- Views, reactions and subscribers of a post are rounded by Telegram (`10.6M`); the exact subscriber count is on the channel row.
- Photo and video links of Telegram expire: tick **Download media** to save the files in the run's key-value store, where their links work as long as the run's storage is kept (the data retention of your plan): copy the files elsewhere to keep them for good.
- `onlyNew` remembers post numbers, not their content: a post that was edited is not returned again.
- Two runs sharing the same `stateKey` at the same time may both return the same new post.
- Comments: the most recent ones of each post, up to **Max comments per post** (1,000 at most).

**A run that reaches its timeout** stops itself about 45 seconds before it: no new page is asked, what it read is saved and, with `onlyNew`, remembered, and the run ends *Succeeded* with "Stopped before the run's timeout". Resurrect it to go on from there, or give the next run a longer timeout (Run options).

**A run the platform stops without warning** (out of memory)

- Resurrect it: it goes on from where it stood at most a minute before the stop. What it had read since is read again, and the posts already saved are skipped: none is delivered or charged twice, and `maxItems` still counts them.
- With `onlyNew`, the memory is saved after each write (at most once every 2 seconds): resurrect the stopped run and the posts it had saved meanwhile join the memory; leave it stopped for good, and the next run may return the posts of its last 2 seconds once more.

#### Something doesn't work?

The last line of the log counts the posts saved, those filtered out and the requests that failed after every retry. Those requests are listed, with the reason, in the `FAILED_REQUESTS` record of the run's key-value store. A run that saved nothing and had failed requests fails, and its last message gives the reason.

If Telegram changes its pages, you are told instead of paying for blank rows. If the first 20 posts read all lack their date, channel title or view count, the run saves nothing more, stops and fails, and its last message names the missing field: at most those first posts are charged. A post that `postedAfter` / `postedBefore` drops because it has no date at all counts among those 20. A run of fewer posts (a scheduled `onlyNew` run that finds a few new ones, a small **Max posts**) is checked at its end, from 2 posts (the view count from 5): its posts are already saved, and the run fails with the missing field instead of ending green.

### 🛟 Support

Open an issue in the **Issues** tab with a link to your run: the run log and the `FAILED_REQUESTS` record of the key-value store show exactly which URLs failed and why.

# Actor input Schema

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

Public Telegram channels, in any form: `durov`, `@durov`, `t.me/durov`, `https://t.me/s/durov`. A link of a channel page keeps its keyword and its page (`https://t.me/s/durov?q=privacy`). One source per channel; posts are read from the newest to the oldest. Public groups give their details only (their messages are not published on the web). Max 1 000 channels.

## `startUrls` (type: `array`):

Telegram links: channel pages (`https://t.me/durov`, `https://t.me/s/durov`), search pages inside a channel (`https://t.me/s/durov?q=privacy`) or single posts (`https://t.me/durov/427`). Added to **Channels**. Max 1 000 URLs.

## `query` (type: `string`):

Keyword searched by Telegram inside EACH channel above (the channel's own search box, `t.me/s/<channel>?q=`): only the matching posts are read, older ones included. Empty = every post.

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

Several keywords in one run: each keyword is searched in each channel (3 keywords × 4 channels = 12 searches, max 500). Added to **Search inside the channels**; a post found by several keywords is saved once.

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

Maximum number of posts to save for the whole run (after deduplication and filters). Comments and channel-detail rows are not counted. 0 = no limit.

## `maxItemsPerQuery` (type: `integer`):

Cap for EACH channel (and each keyword in it), so that a big first channel cannot use up the whole **Max posts** budget. 0 = no per-channel cap.

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

Add one row per channel (or public group): exact subscriber count (members and online for a group), description, photo, verified badge, counts of photos, videos, files and links, and whether its web preview is open. 1 extra page per channel.

## `channelInfoOnly` (type: `boolean`):

Return only the channel-detail rows (one per channel), without any post: the fastest way to check a list of channels or groups. Turns **Channel details** on.

## `includeComments` (type: `boolean`):

Add the public comments of each post whose discussion is open (one row per comment: author, text, date, reactions, reply-to), and the post's comment count. 1 extra page per post; channels without comments are detected and skipped.

## `maxCommentsPerPost` (type: `integer`):

The newest comments kept per post when **Comments** is on, written in the order they were posted, up to 1,000 (Telegram shows 100 per page, more are loaded 50 at a time).

## `includeEmails` (type: `boolean`):

Fill `emails` with the e-mail addresses written in each post or comment (paid per address found, see Pricing). Unticked: `emails` stays empty.

## `downloadMedia` (type: `boolean`):

Save the photos and video thumbnails of each post in the run's key-value store (Telegram links expire) and add their links to `mediaFiles`: they work as long as the run's storage is kept (the data retention of your plan). Charged per file saved.

## `postedAfter` (type: `string`):

Only posts published on or after this date: `2026-09-01`, or a period before now such as `7 days`, `2 weeks`, `1 month` (API: `24 hours` and full ISO date-times work too). Reading a channel stops at the first older post.

## `postedBefore` (type: `string`):

Only posts published on or before this date (the whole day is included), or older than a period such as `30 days`. The channel is read from that date backwards.

## `minViews` (type: `integer`):

Only posts seen at least this many times. 0 = no minimum.

## `maxViews` (type: `integer`):

Only posts seen at most this many times (e.g. to find the posts few people read). 0 = no maximum.

## `minReactions` (type: `integer`):

Only posts with at least this many reactions (all emojis and paid Stars added up). 0 = no minimum.

## `mediaOnly` (type: `boolean`):

Keep only the posts with any media: photo, video, file, audio, poll, sticker, location, or a post Telegram shows only in the app. Use **Only these media types** to keep only some.

## `withTextOnly` (type: `boolean`):

Drop the posts without any text or caption (a lone photo, sticker or voice message).

## `withLinksOnly` (type: `boolean`):

Keep only the posts whose text contains a link.

## `mediaTypes` (type: `array`):

Keep only the posts that contain at least one of these kinds of media. Empty = no media filter.

## `includeForwarded` (type: `boolean`):

Untick to drop the posts the channel forwarded from another channel or user.

## `includeServiceMessages` (type: `boolean`):

Also return the channel's service messages (channel created, photo changed, message pinned…), as rows with `serviceAction` filled. Off = only real posts.

## `includeKeywords` (type: `array`):

Keep only the posts whose text contains at least one of these words (case and accents ignored). Unlike **Search inside the channels**, Telegram is not asked: every post is read, then checked by the Actor.

## `excludeKeywords` (type: `array`):

Drop the posts whose text contains one of these words (case and accents ignored).

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

Read each channel only from the last post a previous run (same **Memory key**) delivered: older posts are not read, not saved and not charged. First run = the posts allowed by the caps and dates above.

## `stateKey` (type: `string`):

Name of the memory used by **Only new posts**. Give each schedule / task its own key (e.g. `crypto-news`) so that they do not share their memory. Letters, digits, `-` and `_`.

## `resetState` (type: `boolean`):

Forget everything remembered under this **Memory key** before the run: this run returns (and charges) every post again. Untick it afterwards.

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

Apify Proxy or your own proxies. Keep the default: it is included in the price. Some channels are hidden in some countries (e.g. in the EU): pick another proxy country for them — US works; a country where Apify Proxy has no IP fails the run, with a message that says so. About 5 times faster without a proxy, but a request Telegram turns away is then asked again from the same address only. The residential Apify proxy is not available in this Actor.

## `maxConcurrency` (type: `integer`):

Maximum number of requests processed in parallel.

## `maxRequestsPerMinute` (type: `integer`):

Most requests to Telegram in any 60 seconds, all of them counted: channel pages, comment pages, channel details. A budget, not an even pace. Lower it if Telegram answers HTTP 429 in the log.

## `minRequestIntervalMs` (type: `integer`):

Smallest gap between two requests, in milliseconds. Unlike the per-minute rate, this spreads the requests evenly instead of letting them go out in a burst. 0 = no gap.

## `maxRequestRetries` (type: `integer`):

Retries per request before it is marked as failed. Behind a proxy, a request Telegram turns away is also retried on a new proxy session up to 10 times without using up these retries.

## `debugLog` (type: `boolean`):

Include debug messages in the run log.

## Actor input object example

```json
{
  "channels": [
    "durov",
    "telegram"
  ],
  "startUrls": [],
  "searchQueries": [],
  "maxItems": 100,
  "maxItemsPerQuery": 0,
  "includeChannelInfo": false,
  "channelInfoOnly": false,
  "includeComments": false,
  "maxCommentsPerPost": 50,
  "includeEmails": false,
  "downloadMedia": false,
  "minViews": 0,
  "maxViews": 0,
  "minReactions": 0,
  "mediaOnly": false,
  "withTextOnly": false,
  "withLinksOnly": false,
  "mediaTypes": [],
  "includeForwarded": true,
  "includeServiceMessages": false,
  "includeKeywords": [],
  "excludeKeywords": [],
  "onlyNew": false,
  "stateKey": "default",
  "resetState": false,
  "proxyConfiguration": {
    "useApifyProxy": true
  },
  "maxConcurrency": 6,
  "maxRequestsPerMinute": 120,
  "minRequestIntervalMs": 0,
  "maxRequestRetries": 5,
  "debugLog": false
}
```

# Actor output Schema

## `results` (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": [
        "durov",
        "telegram"
    ],
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

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

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

```

## MCP server setup

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