# Telegram Channel Scraper 📨 Messages, No Bot Token (`cleanfeed/telegram-channel-archive-scraper`) Actor

Scrape messages from any public Telegram channel. Pass channel names or t.me links; get message text, view count, date, message ID, photo and video flags and a direct link as JSON or CSV. No API key, bot token or phone number. Since date filter keeps runs cheap.

- **URL**: https://apify.com/cleanfeed/telegram-channel-archive-scraper.md
- **Developed by:** [Yaniv van der Stigchel](https://apify.com/cleanfeed) (community)
- **Categories:** Social media, AI, News
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 1 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.43 / 1,000 message returneds

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?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Telegram Channel Scraper — no API key, no bot token

Export messages from any public Telegram channel. Text, view counts, timestamps,
direct links and media flags. No bot token, no phone number, no login.

### Scrape public Telegram channels

- Export Telegram channel messages to JSON or CSV
- Download a Telegram channel's post history
- Monitor a Telegram channel for new messages
- Get Telegram post view counts and engagement
- Archive a public Telegram channel

Pass several channels at once. Every row is tagged with its source channel.

### Incremental mode — cheap scheduled runs

Set `since` to the date of your last run and the scraper stops as soon as it
reaches older messages. A daily monitoring job costs a fraction of a full
back-scrape, because it only returns what is new.

### What you get per message

Message text (HTML stripped, entities decoded), view count as a number, ISO
timestamp, message id, direct `t.me` link, and flags for photo, video and
forwarded content.

### Honest failure reporting

Private channels, deleted channels and misspelled names return a row with
`reason: "channel-unavailable"` — and **cost you nothing**. You are only charged
for messages actually delivered.

### Input

| Field | Required | Description |
|---|---|---|
| `channels` | yes | Names, `@handles`, or `t.me` URLs |
| `maxMessagesPerChannel` | no | Newest first. Default 200. Your cost ceiling. |
| `since` | no | ISO date. Only messages after it. |
| `proxy` | no | Optional — not needed for typical runs |

### Example input

```json
{
  "channels": ["@telegram", "durov"],
  "maxMessagesPerChannel": 500,
  "since": "2026-08-01"
}
```

### Output

Every row has the same fields whether it succeeded or failed, so you can
select columns without branching. Failed rows are never charged.

| Field | Type | Description |
|---|---|---|
| `success` | boolean | True when this row carries data. Failed rows are never charged. |
| `sourceChannel` | string | The channel input this row came from. |
| `messageUrl` | string | Public t.me link to the message. |
| `messageId` | integer | Sequential message number within the channel. |
| `text` | string | The message body as published. |
| `publishedAt` | string | ISO 8601 timestamp of publication. |
| `viewCount` | integer | Views at the time of the run. Null when Telegram does not expose it. |
| `hasPhoto` | boolean | True when the message includes a photo. |
| `hasVideo` | boolean | True when the message includes a video. |
| `isForwarded` | boolean | True when the message was forwarded from elsewhere. |
| `errorCode` | string | Machine-readable failure reason. Null on success. |
| `errorMessage` | string | Human-readable explanation of the failure. Null on success. |

#### Example — success

```json
{
  "success": true,
  "sourceChannel": "durov",
  "messageUrl": "https://t.me/durov/526",
  "messageId": 526,
  "text": "Telegram now supports gifting Premium subscriptions to any user.",
  "publishedAt": "2026-08-14T09:12:04+00:00",
  "viewCount": 1284000,
  "hasPhoto": true,
  "hasVideo": false,
  "isForwarded": false,
  "errorCode": null,
  "errorMessage": null
}
```

#### Example — failure

A failure carries the same fields, so nothing downstream has to branch.

```json
{
  "success": false,
  "sourceChannel": "private_example_channel",
  "messageUrl": null,
  "messageId": null,
  "text": null,
  "publishedAt": null,
  "viewCount": null,
  "hasPhoto": null,
  "hasVideo": null,
  "isForwarded": null,
  "errorCode": "channel-unavailable",
  "errorMessage": "No public Telegram channel exists at this name, or it is private."
}
```

#### Error codes

- `channel-unavailable`
- `unparseable-channel`
- `no-messages-found`
- `error`

### Use it for

- **Telegram channel messages** — full text history from public channels
- **Telegram channel archive** — a durable copy of a public channel
- **Public Telegram channel monitoring** — scheduled runs with a since filter
- **Telegram backup** — a durable local copy of a public channel's history
- OSINT, news tracking and market monitoring
- Research corpora and training datasets

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

This Actor is callable as a tool through the [Apify MCP server](https://docs.apify.com/platform/integrations/mcp), so Claude, ChatGPT, Cursor and VS Code can run it directly.

Add the server to your MCP client:

```json
{
  "mcpServers": {
    "apify": {
      "url": "https://mcp.apify.com",
      "headers": {
        "Authorization": "Bearer <YOUR_APIFY_TOKEN>"
      }
    }
  }
}
```

Then ask for what you want in plain language — for example *“get the recent messages from this public Telegram channel”* — and the agent calls `cleanfeed/telegram-channel-archive-scraper` with the right input. Every output field is described in the dataset schema, so the agent knows what it is getting back before it runs anything.

### Call it from code

#### Python

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")

run = client.actor("cleanfeed/telegram-channel-archive-scraper").call(run_input={
    "channels": ["durov"],
})

for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    if item["success"]:
        print(item)
```

#### JavaScript

```javascript
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });

const run = await client.actor('cleanfeed/telegram-channel-archive-scraper').call({
    channels: ["durov"],
});

const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items.filter((i) => i.success));
```

#### cURL

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

### Related actors

| If you need | Use |
|---|---|
| Posts from public Bluesky accounts | [Bluesky Posts Scraper](https://apify.com/cleanfeed/bluesky-posts-extractor) |
| Posts from public Threads profiles | [Threads Posts Scraper](https://apify.com/cleanfeed/meta-threads-posts-scraper) |

### Limitations

- **Public channels only.** Private channels, groups and direct messages are not accessible and never will be by this route.
- **No bot token or phone number is used**, so there is no account to get restricted.
- `viewCount` is null when Telegram does not publish it for a message.
- Use the `since` filter for scheduled runs so each run only collects and charges for what is new.

### FAQ

#### Do I need a bot token or phone number?

Neither. Public channel pages are read directly, so there is no API key, no bot and no account to get restricted.

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

No. Only public channels are accessible.

#### What channel formats work?

A channel name such as `durov`, an `@durov` handle, or a `t.me/...` link.

#### How do I keep scheduled runs cheap?

Set `since` to an ISO date. Each run then collects and charges only for messages published after it.

#### Why is viewCount sometimes null?

Telegram does not publish a view count for every message. The field is null rather than guessed.

### Notes

Only public channels and publicly visible content. No bot token, no user
account, no private groups, no personal data beyond what the channel publishes.

# Actor input Schema

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

Public Telegram channels to fetch messages from: channel names such as durov, @durov, or t.me links. Private channels and groups are not accessible.

## `maxMessagesPerChannel` (type: `integer`):

Newest first. You are charged per message returned, so this is your cost ceiling.

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

ISO date, e.g. 2026-08-01. Leave empty for the most recent messages. Scraping stops as soon as older messages appear, so scheduled runs stay cheap.

## `proxy` (type: `object`):

Optional. Telegram's public preview does not block datacenter IPs, so no proxy is needed for typical runs.

## Actor input object example

```json
{
  "channels": [
    "@telegram"
  ],
  "maxMessagesPerChannel": 200,
  "proxy": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `messages` (type: `string`):

One row per message: text, view count, timestamp, direct URL, and flags for photo, video and forwarded content.

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

Channels processed and messages delivered.

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

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

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

```

## MCP server setup

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