# Telegram Channel Scraper (`dami_studio/telegram-channel-scraper`) Actor

Telegram channel scraper API for posts from public channels. Each row holds the message text, exact time, views, author, media type and thumbnail, outbound link and post link, with the channel's subscriber count. Up to 10,000 posts per channel. $1.50 per 1,000 posts.

- **URL**: https://apify.com/dami_studio/telegram-channel-scraper.md
- **Developed by:** [Dami's Studio](https://apify.com/dami_studio) (community)
- **Categories:** Social media, News, Integrations
- **Features:** Limited permissions
- **Stats:** 8 total users, 0 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: 5.00 out of 5 stars

## Pricing

from $1.44 / 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

**Name a public Telegram channel and get its posts back as rows**: the message text, the exact
timestamp, the view count, whether media is attached and which kind, and a direct link to the post.
It pages back through the history until it has the number you asked for.

Public channels only, the ones with a web preview page. Private channels, invite links and groups
have no public page, so none of them work here. And views and subscriber counts arrive the way
Telegram prints them, `1.15M`, not as numbers.

| | |
|---|---|
| **Input** | Channel usernames, `@handles` or `t.me` links |
| **Output** | One row per post: text, time, views, author, media type and thumbnail, outbound link and post link, plus an optional summary row per channel |
| **Ceiling** | 10,000 posts per channel |
| **Account needed** | None, and no bot token |
| **Price** | $1.50 per 1,000 posts on the Free plan, a little less on paid plans. The free plan's $5 a month covers about 3,300 |

### 🔍 What Telegram Channel Scraper does

It reads a channel's public web preview and walks back through it until it hits your limit or
reaches the first message ever posted. Posts are deduped by message id inside a run.

Each post row carries the channel title and subscriber count alongside the message, so a run over
several channels stays sortable without joining anything. With the summary row switched on you also
get one row per channel with the description and the photo, video and link totals.

A channel that is private, deleted or has no public preview reports itself and does not stop the
others.

### 📋 What data you get from each Telegram post

| What you get | Field |
|---|---|
| The channel, the message number and the post link | `channel`, `messageId`, `postUrl` |
| The message text | `text` |
| The exact time it was posted | `date` |
| The view count, as Telegram prints it | `views` |
| Who signed it | `author` |
| Whether media is attached, which kind, and its thumbnail | `hasMedia`, `mediaType`, `mediaUrl` |
| The outbound link Telegram previewed under the post | `link` |
| The channel's title and subscriber count | `channelTitle`, `subscribers` |
| Summary rows: username, description, media and link totals, posts returned | `channelUsername`, `channelDescription`, `photosCount`, `videosCount`, `linksCount`, `postsReturned` |

### ▶️ How to scrape a Telegram channel

1. Open [Telegram Channel Scraper](https://apify.com/dami_studio/telegram-channel-scraper) and click
   **Try for free**.
2. Put one channel per line into **Channels**. A username like `durov` is enough.
3. Set **Max posts per channel**. Start around 20 to see the output shape.
4. Leave **Emit a channel info row** on if you want the subscriber and media totals.
5. Click **Start**, then download the dataset as JSON, CSV or Excel, or read it from the Apify API.

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

**$1.50 per 1,000 posts** on the Free plan. Paid plans pay a little less: $1.48 on Starter, $1.46 on Scale and $1.44 on Business. On the free plan, the $5 Apify
gives you each month covers about 3,300 posts.

Posts are what you pay for. Channel summary rows and diagnostic rows are free, duplicates within a
run are dropped before they reach you, and a channel that turns out to be private or empty costs you
nothing.

### 📥 What you give it

```json
{
  "channels": ["telegram", "@durov", "https://t.me/durov"],
  "maxItems": 100,
  "includeChannelInfoRow": true
}
```

| Field | Default | What it is |
|---|---|---|
| `channels` | none | One entry per channel. A bare username, an `@handle` or a full `t.me` link all resolve to the same thing. The form opens with `telegram` and `durov` filled in. |
| `maxItems` | `100` | Posts per channel, up to 10,000. Three channels at 100 is up to 300 post rows. |
| `includeChannelInfoRow` | `true` | Writes one summary row per channel before its posts. Turn it off for a clean posts-only dataset. |
| `notionConnector`, `notionParentId` | none | Optional. Write every post into your own Notion as well as the dataset. |
| `proxyConfiguration` | off | Optional, and off by default because a normal run does not need it. |

**Invite links do not work.** A `t.me/+abc123` or `t.me/joinchat/abc123` link points at a private
channel, and those have no public page. Use the public `t.me/{name}` form.

### 📤 What you get back

A real post row from a real run, with the message text cut short and its emoji removed:

```json
{
  "ok": true,
  "rowType": "post",
  "channel": "telegram",
  "messageId": 460,
  "postUrl": "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:\nhttps://telegram.org/blog/welcome-messages-buttons-TG-13",
  "date": "2026-08-26T19:12:34.000Z",
  "views": "1.15M",
  "author": "Telegram News",
  "hasMedia": false,
  "mediaType": null,
  "mediaUrl": null,
  "link": "https://telegram.org/blog/welcome-messages-buttons-TG-13",
  "channelTitle": "Telegram News",
  "subscribers": "9.56M"
}
```

| Field | How to read it |
|---|---|
| `views`, `subscribers` | **Display text, not numbers.** Parse them yourself if you need arithmetic. |
| `date` | A real ISO timestamp, straight off the post, not a relative "2 days ago". |
| `text` | `null` on a media-only post. Links inside the message keep their `t.me` form. |
| `mediaType` | `photo`, `video`, `roundvideo`, `voice`, `sticker`, `document` or `null`. |
| `mediaUrl` | The preview thumbnail, not the full-size file. |
| `messageId` | The channel's own message number. Stable, so use it as your dedupe key across runs. |

### 🧾 Reading the output

Three kinds of row can land in your dataset.

| Row | How to spot it | Billed |
|---|---|---|
| A post | `rowType: "post"` | yes |
| A channel summary | `rowType: "channel"` | no |
| A diagnostic | `ok: false` and an `errorCode` | no |

**Filter on `rowType`.** The summary row shares the dataset with posts and renders mostly blank in
the default table view, which is what a "why is there an empty line" question usually turns out to
be.

| Code | What it means |
|---|---|
| `BAD_INPUT` | No usable channel was given. Use a username, an `@handle` or a `t.me` link. |
| `NOT_FOUND` | The channel does not exist, is private, or has no public preview page. |
| `NO_RESULTS` | The channel resolved but has no public messages. |
| `RATE_LIMITED` | Telegram asked for a slower pace. Re-run with a smaller `maxItems`. |
| `SERVER_ERROR` | Telegram answered with a server error. Usually passes on its own. |
| `NOT_SERVED` | This post was not served this run. Re-run it later. Runs before 13 October 2026 wrote `BLOCKED` here, and runs from 13 to 24 October wrote `NETWORK`. |
| `NETWORK` | Telegram could not be reached. |
| `TIME_LIMIT` | The run stopped just before its time limit. The posts written before it are yours and charged; give the run more time to get the rest. When time runs short, a channel's summary row comes after its posts and counts only the posts written. |

One bad channel never stops the others, and a diagnostic row never fails the run. Even `TIME_LIMIT` leaves the run succeeded: when the time limit stopped it before any post came back, the run ends with an amber status message.

### 💡 What people use it for

- Archiving a channel's history once, then topping it up weekly with `messageId` as the dedupe key.
- Watching announcement channels for a product or a token and pulling the text into an alert.
- Measuring which posts in a channel actually travelled, using the view counts next to the dates.
- Collecting the outbound links a channel shares, which sit in `link` ready to follow.

From a call channel to the tokens it names, in three steps:

1. Run this actor on the channels you follow, with a small `maxItems`.
2. Pull the contract addresses out of the `text` of the posts.
3. Put them into [DexScreener Pair Price & Liquidity Scraper](https://apify.com/dami_studio/dexscreener-pairs-scraper)
   as `searchTerms` to get the price, liquidity and volume behind each one.

### 🚧 What it does not do

- **Public channels only.** No private channels, no invite links, no groups, no direct messages.
- **No reactions and no comments.** The preview page does not publish them.
- **`views` and `subscribers` are strings.** `1.15M` is what Telegram prints and what you get.
- **`mediaUrl` is a thumbnail.** Full-size photos, videos and documents are not downloaded.
- **No forwarding chain.** You get the post as the channel shows it, not where it was forwarded from.
- **No search.** You name channels, not keywords. Filter the rows afterwards.
- **A very old channel takes many pages.** Pulling thousands of posts takes a while, so give a large
  run enough time.
- **Media-only posts have no text**, which shows as `null` rather than an empty string.
- It is not an official Telegram tool. Dami's Studio is independent and is not affiliated with or endorsed by Telegram, or by any other company named on this page.

### 🧭 Which social scraper do you need?

| If you want | Use |
|---|---|
| Posts from public Telegram channels | This one |
| Price, liquidity and volume for the tokens a channel names | [DexScreener Pair Price & Liquidity Scraper](https://apify.com/dami_studio/dexscreener-pairs-scraper) |
| Posts from Bluesky accounts or a Bluesky search | [Bluesky Scraper](https://apify.com/dami_studio/bluesky-scraper) |

### ❓ Questions people ask

#### Do I need a Telegram account or a bot token?

No. Nothing to register and nothing to log in to.

#### Can I read a private channel I am a member of?

No. This reads the public preview page, and a private channel has none.

#### Why are views a string like 1.15M?

Because that is exactly what Telegram publishes on the page. The rounding is theirs, and the actor
cannot undo it.

#### How far back can it go?

To the first message, if you raise `maxItems` enough. It stops early when the channel runs out.

#### Can I call it from code or connect it to an AI assistant?

The [API tab](https://apify.com/dami_studio/telegram-channel-scraper/api/python) has code for Python, JavaScript and
the CLI. MCP clients like Claude and ChatGPT use
`https://mcp.apify.com/?tools=fetch-actor-details,dami_studio/telegram-channel-scraper`; coding assistants can load
[our skill
file](https://api.apify.com/v2/key-value-stores/BTBHU3DdiuoF4JOsK/records/apify-social-communities-SKILL.md). All
three bill your Apify account at the same price.

#### Is scraping Telegram channels legal?

These are public channels with public web pages. Posts can still contain personal data, which GDPR
and similar laws cover, so have a reason for collecting it. Apify's write-up on
[the legality of web scraping](https://blog.apify.com/is-web-scraping-legal/) is a good starting
point, and we are not lawyers.

### 🆘 If something breaks

Open the **Issues** tab on the actor page. Send the run ID and the exact channel entry you used. The
`errorCode` on the diagnostic row usually names the problem on its own.

# Actor input Schema

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

Public Telegram channels to scrape. Each entry can be a username (durov), an @handle (@durov), or a full link (https://t.me/durov). Only public channels with a web preview at t.me/s/{name} work, private channels and groups do not.

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

Maximum number of posts to collect for each channel. The actor pages back through older posts (about 16-20 per page) until it reaches this number or runs out of messages.

## `includeChannelInfoRow` (type: `boolean`):

When on, the actor pushes one summary row per channel (title, subscribers, counts) before its posts. Channel title and subscriber count are also attached to every post row regardless of this setting.

## `notionConnector` (type: `string`):

Optional. Write each post as a page into your Notion when the run finishes. Authorize a Notion connector once in Settings → API & Integrations → MCP connectors, then pick it here. Leave empty to skip (default), results are always saved to the dataset regardless.

## `notionParentId` (type: `string`):

Optional. The Notion data source ID of the database to write into (only used if a Notion connector is set). Leave empty to create the pages privately in your workspace instead.

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

Optional, and not needed for a normal run.

## Actor input object example

```json
{
  "channels": [
    "telegram",
    "durov"
  ],
  "maxItems": 100,
  "includeChannelInfoRow": true,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `results` (type: `string`):

Scraped rows are stored in the default dataset (one row per result). Empty or failed runs return a single uncharged diagnostic row instead.

# 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"
    ]
};

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

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

```

## MCP server setup

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