# Telegram Channel Scraper - Posts & Views, No Login (`cliqtomedia/telegram-channel-scraper`) Actor

Scrape posts from public Telegram channels without login or API keys: text, date, views, media, links, forwards, reactions and channel info. Search inside channels, filter by date and get only new posts on a schedule.

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

## Pricing

from $0.40 / 1,000 post rows

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

Scrape posts from public Telegram channels with no login, no phone number and no API keys, and get clean JSON rows with text, date, view count as a number, media links, reactions, forwards and channel info with the exact subscriber count.

Use it for news and crypto monitoring, research archives or AI pipelines: the smallest run is one channel name, such as `durov`, and two channels with 20 posts each finish in about 5 seconds for about $0.08 on the Free plan.

![Telegram Channel Scraper: channel names, @handles or t.me links in; every public post with views, media and reactions out as clean rows. No login.](https://api.apify.com/v2/key-value-stores/MJ6QSFO3n3AsCSLEK/records/README-BANNER-20261014-v2.png)

### Table of contents

- [What it does](#what-it-does)
- [Quick start](#quick-start)
- [Input](#input)
- [Output](#output)
- [Pricing](#pricing)
- [Use cases](#use-cases)
- [Scheduling and monitoring](#scheduling-and-monitoring)
- [Switching from another Telegram scraper](#switching-from-another-telegram-scraper)
- [API, export and integrations](#api-export-and-integrations)
- [Limits and partial results](#limits-and-partial-results)
- [Troubleshooting](#troubleshooting)
- [FAQ](#faq)
- [Learn more](#learn-more)
- [Support](#support)

### What it does

The Actor reads the public web preview of a Telegram channel (`t.me/s/<channel>`), the page that anyone can open in a browser without a Telegram account. For each channel you give, it saves the newest posts (or the posts that match your search), plus one row about the channel. It can stop at a date, search inside the channel with Telegram's own search, and on a schedule return only posts that are new since the last run. Every channel gets a clear status, so a wrong name is never mixed up with a quiet channel.

#### Good fit

- Follow what a list of public news, crypto or brand channels posted today.
- Build a dataset of posts with text, views, reactions, media and links.
- Find posts about a keyword or #hashtag inside many channels at once.
- Get subscriber counts and descriptions of many channels.
- Send only new posts to a spreadsheet, a chat or an AI agent every hour.

#### Not a fit / limits

- Only public channels that have a web preview. Private channels, invite links (`t.me/+...`), groups, chats, users and bots are not supported.
- No comments, no member or subscriber lists, no DMs, no global Telegram search: only posts of the channels you give.
- Telegram rounds view and reaction counts on the web (for example `830K`). The Actor turns them into numbers (`830000`); it cannot make them more exact than Telegram shows.
- Videos: the web preview usually gives only a preview image, not the video file. Photo links work, but all media links are temporary.
- One run reads about 1,800 posts in full; at about 4.5 minutes it stops and keeps what it has (see [Limits](#limits-and-partial-results)).

### Quick start

1. Open the **Input** tab.
2. Add one or more channels to **Channels** (`channels`), for example `durov` and `telegram` (this is the prefill).
3. Keep **Max posts per channel** (`maxPostsPerChannel`) at 20, or set a higher number.
4. Click **Start**.
5. Open the **Output** tab: the **Posts** view shows the post columns (channel, date, text, views, media); the **Channels** view shows the channel columns (status, title, subscribers). Both views list all rows; the **Row type** column tells posts and channels apart.

Expected result: 20 posts per channel, newest first, and one channel row each, in about 5 seconds.

### Input

#### Input fields

| Field | Plain name | Type | Required | Default / prefill | Allowed values | Effect | Example | Recommendation |
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
| `channels` | Channels | array of strings | yes | prefill `["durov", "telegram"]` | 1–100 items: `name`, `@name`, `https://t.me/name` or `https://t.me/s/name` | which channels to read | `["bbcrussian", "@telegram"]` | Repeats in any spelling are read once. Invalid values are skipped and counted in the run message; the rest still run. |
| `maxPostsPerChannel` | Max posts per channel | integer | no | default 20, prefill 20 | 1–1000 | how many posts per channel, newest first | `200` | Each post row is charged; keep it small for frequent runs. |
| `since` | Only posts since | string | no | empty (no date limit) | date `2026-10-01`, date-time `2026-10-01T12:00:00Z`, or `30 minutes`, `24 hours`, `7 days`, `2 weeks`, `1 month` | skips older posts and stops reading the channel there | `"7 days"` | The best way to keep runs short and cheap. |
| `searchQuery` | Search inside channels | string | no | empty | up to 200 characters; a word, phrase or `#hashtag`, any language | returns only posts that Telegram's channel search matches | `"bitcoin"` | Leave empty to get all posts. |
| `includeChannelInfo` | Exact subscriber count | boolean | no | `true` | true / false | one extra request per channel for the exact number in `subscribers` | `false` | Turn off when the rounded `subscribersText` (`10.4M`) is enough. |
| `onlyNew` | Only new posts since the last run | boolean | no | `false` | true / false | remembers the newest post per channel and next time returns only newer posts | `true` | For schedules; see [Scheduling and monitoring](#scheduling-and-monitoring). |
| `stateKey` | Monitoring name | string | no | `default` | 1–40 letters, digits, `-` or `_` | keeps separate memory for different schedules | `"crypto-news"` | Use one name per schedule. |

If no value in `channels` is a valid channel name, the run fails with `Invalid input` and only the start fee is charged.

#### Interacting settings

| When | Input A | Input B | Effective behavior | Example | Cost/result effect |
| --- | --- | --- | --- | --- | --- |
| Both set | `maxPostsPerChannel` | `since` | reading stops at whichever comes first | 200 and `"1 day"` | fewer posts and requests |
| Search and date | `searchQuery` | `since` | matches newer than `since`, newest first | `"#news"` and `"7 days"` | fewer posts |
| Monitoring | `onlyNew` | `maxPostsPerChannel` | at most this many new posts per run, oldest new post first; the rest come next run | `true` and 50 | no gaps between runs |
| Monitoring and search | `onlyNew` | `searchQuery` | memory is kept per channel and search; the newest matches come first | `true` and `"ton"` | set `maxPostsPerChannel` above the expected number of new matches |
| Monitoring and date | `onlyNew` | `since` | both apply; posts older than `since` are skipped | `true` and `"1 day"` | — |

#### Input examples

##### Latest posts of two channels

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

Expected behavior: about 4 requests, about 5 seconds.
Expected output: 40 post rows and 2 channel rows with `subscribers`.
Cost: $0.0841 on the Free plan.

##### Last 7 days of several channels, search for a word

```json
{
  "channels": ["bbcrussian", "meduzalive", "nexta_live"],
  "since": "7 days",
  "searchQuery": "Россия",
  "maxPostsPerChannel": 100
}
```

Expected behavior: only posts that Telegram's search matches, newer than 7 days.
Expected output: post rows with `searchQuery` set; channel rows with status.

##### Hourly monitoring

```json
{
  "channels": ["durov", "telegram"],
  "maxPostsPerChannel": 50,
  "onlyNew": true,
  "stateKey": "telegram-news",
  "includeChannelInfo": false
}
```

Expected behavior: the first run returns the newest posts and remembers them; every next run returns only posts published after that.
Expected output: new post rows only; with nothing new, the run ends as `NO_RESULTS` with 2 channel rows.

### Output

Results go to the default **Dataset**. Each row has `recordType`: `post` or `channel`. The dataset has two views, **Posts** and **Channels**. A view only picks columns, it does not hide rows: post rows and channel rows are in both views. To keep only posts, filter by `recordType = post` (for example in Excel, Google Sheets or your code). A run summary is saved as `RUN_SUMMARY` in the key-value store. The **Output** tab links all three.

#### Output example

```json
{
  "recordType": "post",
  "channel": "durov",
  "channelTitle": "Pavel Durov",
  "postId": 552,
  "url": "https://t.me/durov/552",
  "date": "2026-10-11T14:52:40+00:00",
  "edited": false,
  "text": "✨ Designers × AI\n\n🆕 The mockup era is over. …",
  "author": "Pavel Durov",
  "views": 830000,
  "viewsText": "830K",
  "forwardedFrom": "Telegram Designers",
  "forwardedFromUrl": "https://t.me/designers/266",
  "replyToPostId": null,
  "replyToUrl": null,
  "mediaType": "video",
  "mediaUrl": "https://cdn4.telesco.pe/file/…",
  "media": [
    {
      "type": "video",
      "url": null,
      "thumbnailUrl": "https://cdn4.telesco.pe/file/…",
      "durationText": "1:15",
      "title": null
    }
  ],
  "linkPreview": null,
  "poll": null,
  "links": ["https://t.me/designers"],
  "hashtags": [],
  "reactions": [
    { "emoji": "⭐", "paid": true, "count": 5280, "countText": "5.28K" },
    { "emoji": "custom:5936157098181135162", "paid": false, "count": 10700, "countText": "10.7K" }
  ],
  "searchQuery": null,
  "sourceUrl": "https://t.me/s/durov",
  "fetchedAt": "2026-10-14T15:01:48.976Z"
}
```

This post row is from the first input example; the text, media links and reactions are shortened. `null` means Telegram shows no value for this post (not a reply, no link preview, no poll). For this video the web preview has only a preview image, so `media[0].url` is `null`.

#### Post fields

| Field | Meaning | Type | Can be empty? | Example / enum | Notes |
| --- | --- | --- | --- | --- | --- |
| `recordType` | row type | string | no | `post` | |
| `channel` | channel username | string | no | `durov` | as in the channel link |
| `channelTitle` | channel name | string | yes | `Pavel Durov` | |
| `postId` | post number in the channel | integer | no | `552` | with `channel`, a stable key to remove duplicates |
| `url` | link to the post | string | no | `https://t.me/durov/552` | |
| `date` | when it was posted | string | yes | `2026-10-11T14:52:40+00:00` | ISO 8601, UTC |
| `edited` | the post was edited | boolean | no | `false` | |
| `text` | post text | string | can be `""` | `✨ Designers × AI…` | line breaks kept; `""` for media-only posts |
| `author` | signature shown under the post | string | yes | `Pavel Durov` | only if the channel signs posts |
| `views` | views as a number | integer | yes | `830000` | from `viewsText`; rounded by Telegram |
| `viewsText` | views as Telegram shows them | string | yes | `830K` | |
| `forwardedFrom`, `forwardedFromUrl` | original channel or person of a forwarded post | string | yes | `Telegram Designers` | null if not forwarded |
| `replyToPostId`, `replyToUrl` | the post this one replies to | integer, string | yes | `null` | |
| `mediaType` | main media type | string | yes | `photo`, `video`, `round_video`, `document`, `audio`, `voice`, `sticker`, `poll`, `location`, `album`, `unsupported` | null for text-only posts |
| `mediaUrl` | first media link | string | yes | `https://cdn4.telesco.pe/file/…` | photo image or video preview image; temporary |
| `media` | all media: `type`, `url`, `thumbnailUrl`, `durationText`, `title` | array | can be `[]` | see example | `url` is null when the preview has no file link |
| `linkPreview` | link card: `url`, `siteName`, `title`, `description`, `imageUrl` | object | yes | | null if the post has no link card |
| `poll` | `question`, `type`, `votersText`, `options[]` (`text`, `percent`) | object | yes | | null if no poll |
| `links` | links in the text | array | can be `[]` | `["https://t.me/designers"]` | |
| `hashtags` | hashtags | array | can be `[]` | `["#news"]` | |
| `reactions` | `emoji`, `paid` (Telegram Stars), `count`, `countText` | array | can be `[]` | see example | custom emoji appear as `custom:<id>` |
| `searchQuery` | the search that found this post | string | yes | `null` | null when no search was used |
| `sourceUrl` | page the post was read from | string | no | `https://t.me/s/durov` | |
| `fetchedAt` | when the run read it | string | no | `2026-10-14T15:01:48.976Z` | views and reactions are as of this time |

#### Channel fields

One channel row per valid channel, also when the channel is not found. If the run stops in the middle of a channel (time or request limit), that channel gets one more row with `status: capped`; this row is free.

| Field | Meaning | Type | Can be empty? | Example / enum |
| --- | --- | --- | --- | --- |
| `recordType` | row type | string | no | `channel` |
| `status` | result for this channel | string | no | `complete`, `not_found`, `error`, `capped` |
| `statusCode` | why, when not complete | string | yes | `NOT_FOUND`, `NO_PUBLIC_PREVIEW`, or an error code |
| `statusReason` | plain explanation | string | yes | `"telegram" exists on Telegram but has no public channel preview…` |
| `channel`, `url` | username and link | string | no | `durov`, `https://t.me/durov` |
| `title`, `description` | channel name and description | string | yes | `Pavel Durov`, `Founder of Telegram.` |
| `verified` | verified badge | boolean | yes | `true` |
| `avatarUrl` | channel photo (temporary link) | string | yes | |
| `subscribers` | exact subscriber count | integer | yes | `10386608` — null when `includeChannelInfo` is off |
| `subscribersText` | subscribers as Telegram shows them | string | yes | `10.4M` |
| `photos`, `videos`, `files`, `linksShared` | media counters from the channel page | integer | yes | `102`, `47`, `null`, `200` |
| `postsReturned` | posts saved for this channel | integer | yes | |
| `sourceUrl`, `fetchedAt` | page and time | string | no | |

#### Run summary (`RUN_SUMMARY`)

| Field | Meaning |
| --- | --- |
| `status` | `COMPLETE`, `PARTIAL` (some channels not found or failed), `NOT_FOUND` (no channel found), `NO_RESULTS` (channels found, no posts matched), `CAPPED` (stopped at a limit, rows kept), `SOURCE_ERROR`, `ERROR`, `INVALID_INPUT` |
| `message` | the same text as the run status message |
| `requested` | the settings used |
| `counts` | `channels`, `postRows`, `channelRows`, `notFound`, `errors`, `chargedPostRows`, `chargedChannelRows`, `invalidChannels`, `unprocessedChannels` |
| `channels[]` | one entry per channel: `channel`, `outcome` (`complete`, `no_posts`, `not_found`, `no_public_preview`, `error`, `capped`, `unprocessed`), `posts`, `pages`, `newestPostId`, `oldestPostId`, `watermarkBefore`, `watermarkAfter` (monitoring), `reason` |
| `warnings`, `http`, `source`, `timing` | warnings, request counters, data source note, start and finish time |

#### Output entities and surfaces

| Situation | Where | Result shape | User action |
| --- | --- | --- | --- |
| Normal | Dataset, **Posts** and **Channels** views | post rows + one channel row per channel | open, export or call the API |
| Channel does not exist | **Channels** view, `RUN_SUMMARY` | `status: not_found`, `statusCode: NOT_FOUND` | check the name |
| Group, user, bot or preview turned off | **Channels** view | `not_found` / `NO_PUBLIC_PREVIEW` | only public channels work |
| No posts in the period or no search match | `RUN_SUMMARY` | `NO_RESULTS`, channel rows `complete`, outcome `no_posts` | widen `since` or change the search |
| Stopped at a limit | Dataset + `RUN_SUMMARY` | rows kept, `CAPPED`, cut channel `status: capped`, unread channels `unprocessed` | split the list into smaller runs |
| Telegram not answering | `RUN_SUMMARY`, run `FAILED` | `SOURCE_ERROR` (rows kept) or `ERROR` | try again later |

Runs with `COMPLETE`, `PARTIAL`, `NOT_FOUND`, `NO_RESULTS` or `CAPPED` end as `SUCCEEDED`; `SOURCE_ERROR`, `ERROR` and `INVALID_INPUT` end as `FAILED`.

### Pricing

#### Pricing unit

You pay per event. Prices per 1,000 events:

| Event | Free | Bronze | Silver | Gold | Platinum | Diamond |
| --- | ---: | ---: | ---: | ---: | ---: | ---: |
| **Post** — one saved post row | $2.00 | $1.60 | $1.30 | $0.40 | $0.40 | $0.40 |
| **Channel** — one channel row (found or not found) | $2.00 | $1.80 | $1.50 | $1.00 | $1.00 | $1.00 |

Each run also has a start fee of $0.0001. Platform usage is included.

- A channel that Telegram answered about is charged once: found (`complete`) or not found (`not_found`). A channel row with `status: error` is free.
- Invalid input values are not charged. A run with no valid channel pays only the start fee.
- A run with no matching posts, or a monitoring run with nothing new, pays only for the channel rows and the start fee.
- If you set a maximum cost per run, the Actor stops before it goes over it and keeps the rows already saved.

#### Cost examples (Free plan prices)

| Scenario | Input scope | Events | Charge | Time |
| --- | --- | ---: | ---: | --- |
| Prefill | 2 channels × 20 posts | 2 channels + 40 posts | $0.0841 | about 5 s |
| Medium | 5 channels × 100 posts | 5 + 500 | $1.0101 | about 1 minute |
| Large | 10 channels × 180 posts | 10 + 1,800 | $3.6201 | about 3.5 minutes |
| Nothing new | 2 channels, `onlyNew`, no new posts | 2 channels | $0.0041 | a few seconds |
| Wrong names | 3 channels not found | 3 channels | $0.0061 | a few seconds |

On the Gold, Platinum and Diamond plans the prefill run costs $0.0181.

### Use cases

| Buyer job | Input path | Output entity | Key fields | Next integration |
| --- | --- | --- | --- | --- |
| Hourly news or crypto monitoring | `channels` + `onlyNew` | post | `date`, `text`, `url`, `views` | Schedule + webhook, Google Sheets, Slack |
| Post engagement research | `channels` + `maxPostsPerChannel` | post | `views`, `reactions`, `forwardedFrom` | CSV / Excel export |
| Keyword tracking | `channels` + `searchQuery` + `since` | post | `text`, `date`, `searchQuery` | API, spreadsheet |
| Channel directory | `channels` + `includeChannelInfo` | channel | `subscribers`, `title`, `description`, `verified` | API, spreadsheet |
| AI agent context | `channels` (small) | post | `text`, `date`, `url` | MCP |

### Scheduling and monitoring

Create a schedule (for example every hour) with `onlyNew: true` and a `stateKey` such as `telegram-news`. The first run returns the newest posts and remembers the newest post of each channel. Every next run returns only posts published after it, oldest first, without gaps: if more posts arrived than `maxPostsPerChannel`, the rest come in the next run. The memory is kept in your account, in the key-value store `telegram-channel-scraper-state`; use a different `stateKey` for each schedule. A run with nothing new ends as `NO_RESULTS` and costs only the channel rows. Add a webhook or an integration to send new rows to Slack, a sheet or your own API when the run finishes.

Without `onlyNew`, use `since: "1 day"` and remove duplicates by `channel` + `postId`.

### Switching from another Telegram scraper

Some Telegram scrapers return rows like `username`, `fullName`, `followers` and a `message` object, and give views as text. This Actor uses flat fields and numbers:

| Other scraper | This Actor |
| --- | --- |
| `username` | `channel` |
| `fullName` | `channelTitle` (post row), `title` (channel row) |
| `followers` | `subscribers` (exact) and `subscribersText` |
| `message.fulldate` | `date` (ISO 8601, UTC) |
| `message.views` (`1.16M` as text) | `views` (`1160000`) and `viewsText` |
| `message.description` | `text` |
| `message.link` | `url` |
| `preview` | `linkPreview` |
| `image`, `video` | `media[]` with `type`, `url`, `thumbnailUrl` |

Channels that are not found get their own row with `status: not_found` and a reason, not an empty row.

### API, export and integrations

- **API:** start a run with `POST https://api.apify.com/v2/acts/cliqtomedia~telegram-channel-scraper/runs` and the input JSON; read rows from the run's Dataset and keep the rows with the `recordType` you need (`?view=posts` or `?view=channels` only picks the columns), and read the summary from the `RUN_SUMMARY` record.
- **Export:** JSON, CSV, Excel, XML or RSS from the Dataset.
- **Webhooks / no-code:** Apify integrations (Make, Zapier, Google Sheets, Slack) can send new rows when a run finishes.

#### Use with AI agents (MCP)

- MCP server with only this Actor: `https://mcp.apify.com?tools=cliqtomedia/telegram-channel-scraper`.
- Smallest useful input: `{"channels": ["durov", "telegram"], "maxPostsPerChannel": 20}`.
- Key fields: `text` (what was posted), `date` (when), `url` (link to cite), `channel` (where), `views` (reach as a number).
- Two channels with 20 posts take about 5 seconds and cost about $0.08 on the Free plan. One run reads up to about 1,800 posts; use `since` or `searchQuery` to keep answers short.

### Limits and partial results

- Up to 100 channels per run and 1,000 posts per channel. Telegram's web preview gives about 20 posts per page, and the Actor reads about one page per second.
- Each run stops reading Telegram after about 4.5 minutes, whatever the run timeout is. That is enough for about 1,800 posts. If the time runs out, saved rows stay, the run is `CAPPED`, the channel being read is `capped` and the rest are `unprocessed`. Split large lists into several runs.
- If you set a maximum cost per run, the Actor stops before going over it: rows already saved stay, the run is `SUCCEEDED` with `CAPPED` in `RUN_SUMMARY`.
- View, reaction and subscriber texts are rounded by Telegram; only `subscribers` with `includeChannelInfo` is exact.
- Media links (`telesco.pe`) expire after some time. Download the files soon if you need them.

### Troubleshooting

#### A channel returns no posts

Check its channel row (`recordType = channel`) in the **Channels** view: `not_found` / `NOT_FOUND` (wrong name), `not_found` / `NO_PUBLIC_PREVIEW` (a group, user, bot or a channel with the web preview turned off), or `complete` with outcome `no_posts` in `RUN_SUMMARY` (nothing matched `since` or `searchQuery`).

#### The run ends as CAPPED

Read `RUN_SUMMARY.message`: either the maximum cost you set was reached, or the run reached its 4.5-minute reading limit. Saved rows stay in the Dataset. Lower `maxPostsPerChannel`, use `since`, or split the channel list.

#### Some of my values were skipped as invalid

Only channel names (3–32 letters, digits or `_`, starting with a letter), `@name` and `t.me` links work. Invite links (`t.me/+...`) and other websites are skipped; the run message says how many.

#### The run fails with SOURCE_ERROR or ERROR

Telegram did not answer or asked the Actor to slow down several times. Rows saved before the error stay in the Dataset. Try again later.

### FAQ

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

No. The Actor reads only the public web preview that Telegram shows to anyone, without login.

#### Why are views like 830000 and not exact?

Telegram shows rounded counts on the web (`830K`). The Actor gives you the number (`views`) and the original text (`viewsText`). Exact view counts are not public.

#### Can I get comments, members or posts from private channels?

No. These are not in the public web preview.

#### Are the counts live?

Views, reactions and subscribers are as they were at `fetchedAt`. Run again to refresh them.

#### Is scraping Telegram allowed?

The Actor reads only public pages that Telegram shows without login, at a slow rate, and does not bypass any login or CAPTCHA. You are responsible for how you collect, store and reuse the content, including personal data and authors' rights, under Telegram's terms and the laws that apply to you.

### Learn more

- [Telegram channel web preview example](https://t.me/s/telegram): the page the Actor reads.
- [Telegram Terms of Service](https://telegram.org/tos).
- [Apify pay-per-event pricing](https://docs.apify.com/platform/actors/running/actors-in-store#pay-per-event).
- [Apify schedules](https://docs.apify.com/platform/schedules).

### Support

Use the **Issues** tab of this Actor for bugs, questions and feature requests. Include the run ID, the input and the error text. Never post tokens, cookies or private data.

# Changelog

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

# Actor input Schema

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

Up to 100 public Telegram channels: a username (durov), @username, or a link (https://t.me/durov, https://t.me/s/durov). Invite links (t.me/+...) are skipped as invalid; private channels, groups, users and bots are reported as not_found.

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

How many posts to return per channel, newest first (oldest first when onlyNew continues from a previous run). 1–1000. Each post row is charged.

## `since` (type: `string`):

Skip posts older than this: a date (2026-10-01), an ISO timestamp (2026-10-01T12:00:00Z), or a relative period (24 hours, 7 days, 2 weeks). Older pages are not fetched. Leave empty for no date limit.

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

Optional keyword or #hashtag. Returns only posts that Telegram's own channel search matches, newest first. Leave empty to get all posts.

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

Also open the channel page to get the exact subscriber count (one extra request per channel). Without it the channel row still has title, description, verified badge, rounded subscribers and media counters.

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

For scheduled monitoring: remember the newest post per channel and on the next run return only posts published after it, oldest first, without gaps. The first run returns the newest posts and sets the starting point. Memory is kept in your account (key-value store telegram-channel-scraper-state).

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

Separate memory for different schedules or tasks, e.g. crypto-news. 1–40 letters, digits, - or \_. Default: default.

## Actor input object example

```json
{
  "channels": [
    "durov",
    "telegram"
  ],
  "maxPostsPerChannel": 20,
  "includeChannelInfo": true,
  "onlyNew": false
}
```

# Actor output Schema

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

One row per post.

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

Channel info and per-channel status rows.

## `runSummary` (type: `string`):

Status, per-channel outcome, counts and request statistics.

# 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"
    ],
    "maxPostsPerChannel": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("cliqtomedia/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",
    ],
    "maxPostsPerChannel": 20,
}

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

```

## MCP server setup

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