# Telegram Channel Scraper: Posts, Views, Media and Links (`omraydev/telegram-channel-posts`) Actor

Scrape posts from public Telegram channels by username or t.me link. Get text, date, views, media URLs, links, forwards and replies, filtered to an exact date range. No login needed.

- **URL**: https://apify.com/omraydev/telegram-channel-posts.md
- **Developed by:** [Om Ray](https://apify.com/omraydev) (community)
- **Categories:** Agents, Developer tools, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$5.00 / 1,000 posts

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Telegram Channel Scraper: Posts, Views, Media and Links

This actor saves posts from public Telegram channels. It reads the same public web preview anyone can open at `https://t.me/s/<channel>`, so it needs no Telegram account, no login and no phone number.

You give it a list of channels. For each one it saves posts newest first, going back page by page until it reaches your start date or your post limit.

### Pricing

You pay $0.005 per saved post, which is $5 per 1,000 posts. Rows that explain a problem with a channel are free. Posts outside your date range are skipped and never charged. A few posts can't be shown on the web preview at all (Telegram says "Please open Telegram to view this post"). Those are saved with `mediaType: "unsupported"` so you can see they exist, and they are free too.

If you set a spending limit on the run, the actor stops cleanly when it gets there and keeps everything saved so far.

### Input

| Field | What it does |
| --- | --- |
| `channels` | Usernames or links. `durov`, `@durov`, `t.me/durov` and `https://t.me/s/durov` all work. A link to one post also works, and the actor reads the whole channel it belongs to. |
| `fromDate` | Oldest post to include. An ISO date such as `2026-10-01` or `2026-10-01T09:00:00Z`, or a relative value such as `7 days`, `2 weeks` or `3 months`. |
| `toDate` | Newest post to include. Same formats. |
| `maxPostsPerChannel` | Stop a channel after this many paid posts. Default 100. Raise it for long date ranges. |
| `maxItems` | Stop the whole run after this many paid posts across all channels. Default 1000. |
| `maxRunSeconds` | Stop after this many seconds and keep what was saved. Default 600. |

All dates are in UTC. A date with no time means the start of that day for `fromDate` and the end of that day for `toDate`, so `fromDate: 2024-12-01` with `toDate: 2024-12-31` gives you all of December.

When you set `toDate` far in the past, the actor first jumps to posts near that date, so it skips loading every newer page. On a channel with about 80,000 posts, fetching one month from two years ago took 53 page loads and about 20 seconds.

Example input:

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

### Output

Each row is one post. This one is from a local run with the input above:

```json
{
  "rowType": "post",
  "channel": "telegram",
  "postId": 455,
  "url": "https://t.me/telegram/455",
  "date": "2026-08-26T19:11:05.000Z",
  "text": "Welcome Messages. Group and channel owners can now add messages that appear when a new member joins.\n\nThese messages are only visible to the person who joins the chat.\n\nAugust Features\n1 • 2 • 3 • 4 • 5 • More",
  "views": 1140000,
  "author": null,
  "edited": true,
  "forwarded": false,
  "forwardedFrom": null,
  "forwardedFromUrl": null,
  "replyToPostId": null,
  "replyToUrl": null,
  "mediaType": "video",
  "mediaUrls": [
    "https://cdn1.telesco.pe/file/566183f75b.mp4?token=ijr48MUlF07QPXZiVYe-xsg8_Al6VvVIgAn2MSLbB2G_BXedsizRE-DWgKPLrgwN4T8jFhr7Ypv5T9xvecWBxCfCCOtBpiKVazaY_uPVkyw4w_tvRM6RnsdfzAzDABNICborqC8f6aW2kwEC00pQwsWKggnohP-J_SKdcNht2PUV-uyxVp0CXUWR2o3KiKOcv-_gOaNbLZQ3cowr8kReR3OB33s5C2UUzeBxIGArE_ByMdYPFpRbVrvMr-Emc3kPdVLrq3KgwXnQnsGtL0iXWRinxs5VhnbbDJkRrJj3Sf0fjoo9p1Pd5OdFhUPv0cl7iVa5XrpfKQSCWsPZJEbT_Q"
  ],
  "media": [
    {
      "type": "video",
      "url": "https://cdn1.telesco.pe/file/566183f75b.mp4?token=ijr48MUlF07QPXZiVYe-xsg8_Al6VvVIgAn2MSLbB2G_BXedsizRE-DWgKPLrgwN4T8jFhr7Ypv5T9xvecWBxCfCCOtBpiKVazaY_uPVkyw4w_tvRM6RnsdfzAzDABNICborqC8f6aW2kwEC00pQwsWKggnohP-J_SKdcNht2PUV-uyxVp0CXUWR2o3KiKOcv-_gOaNbLZQ3cowr8kReR3OB33s5C2UUzeBxIGArE_ByMdYPFpRbVrvMr-Emc3kPdVLrq3KgwXnQnsGtL0iXWRinxs5VhnbbDJkRrJj3Sf0fjoo9p1Pd5OdFhUPv0cl7iVa5XrpfKQSCWsPZJEbT_Q",
      "thumbnailUrl": "https://cdn1.telesco.pe/file/YF8hrdQzWmsJi_cTgdEYGcQytFcrgv114c4v4O4MrSZyGykMoT7FJDSzbSEfUXsU1A_3yQqJrKPDBAfsFqbt-flCd0xpxWDCzQVecpyCV45xzypNpD1jlqdEVwwfKfKLFtNMc-6e-K7L9-XUtBxtEPcfPj0gn8MQuAU_7G6vpjt-mW2Zc256pC5tdgdKJ_ASFw6TMNwKeB0LRk6PpC1kCiHbyMunOk5pyph2MwEIeLyCRFGRbScBFOdm01wbyHSNPx1iGvffQfSfOaHVJbVCFklniwBemXNRqI3mUrdkSySKwHUJxxrwcft4Bheb4EyExm25Iu000nNAybyoYSyBDw",
      "name": null,
      "size": null,
      "duration": "0:10"
    }
  ],
  "links": [
    "https://t.me/telegram/456",
    "https://t.me/telegram/457",
    "https://t.me/telegram/458",
    "https://t.me/telegram/459",
    "https://t.me/telegram/460"
  ],
  "status": "ok",
  "statusReason": null,
  "input": "telegram"
}
```

Every row has the same fields:

| Field | Meaning |
| --- | --- |
| `rowType` | `post` for a post, `status` for a free row explaining a problem with a channel. |
| `channel` | Channel username as Telegram shows it. |
| `postId` | Post number inside the channel. |
| `url` | Link to the post. On status rows, the channel link. |
| `date` | Publish time, ISO 8601 in UTC. |
| `text` | Post text with line breaks kept. Empty when the post has no text. |
| `views` | View count as a whole number. Telegram rounds big counts, so `1.2K` becomes `1200`. |
| `author` | Author signature, if the channel signs posts. |
| `edited` | `true` when Telegram marks the post as edited. |
| `forwarded` | `true` when the post was forwarded. |
| `forwardedFrom` | Original channel or person, when Telegram shows the name. Telegram hides it for some sources. |
| `forwardedFromUrl` | Link to the original post, when it came from a public channel. |
| `replyToPostId`, `replyToUrl` | The post this one replies to. |
| `mediaType` | `photo`, `video`, `round_video`, `document`, `audio`, `voice`, `sticker`, `location`, `poll`, `mixed` for albums with more than one type, `unsupported` for posts the preview can't show, or `null` for text only. |
| `mediaUrls` | One link per attachment: the photo, video file, sticker or voice note. When Telegram shows only a preview image (a video too big to embed, a document thumbnail, a map), it is that image or map link. |
| `media` | One object per attachment with `type`, `url`, `thumbnailUrl`, `name` (file name or poll question), `size` and `duration`. |
| `links` | Links in the post text, plus the link preview URL. |
| `status`, `statusReason` | `ok` and `null` for posts. On status rows, a short code and a plain explanation. |
| `input` | The channel entry as you typed it. |

### When a channel gives no posts

The actor never ends with a silent empty dataset. If a channel can't be read, you get one free status row for it and the run moves on to the next channel.

| `status` | What happened |
| --- | --- |
| `not-found` | No public channel has this username. |
| `preview-disabled` | The channel exists, but its owner turned off the web preview. |
| `preview-unavailable` | Telegram lists a preview but redirected us away. This usually means the channel is blocked in the country the request came from. |
| `group`, `bot`, `user` | The username belongs to a group, a bot or a person. Only channels are read. |
| `invalid-input` | The entry is not a username or a public link, for example a private invite link. |
| `no-posts`, `no-posts-in-range` | The channel has no posts, or none in your date range. The reason says how far back it checked. |
| `partial`, `error` | Telegram kept failing after several retries. `partial` means some posts were saved first. |
| `duplicate` | The same channel appears twice in your list. Its posts are saved once. |
| `skipped` | The run hit your spending limit, `maxItems` or `maxRunSeconds` before reading this channel. |

### Run summary

The `OUTPUT` record in the key-value store has one entry per channel with its title, subscriber count, description, photo, the counters Telegram shows (photos, videos, files, links), how many posts were saved, the newest and oldest saved dates, how many pages were loaded and why it stopped. The channel details are fetched once per channel and are free.

### Limits

- Only public channels with a username work. Private channels, invite links, groups and direct chats are out of scope. The actor does not collect member lists, phone numbers or anything that needs a login.
- The web preview shows what a logged-out visitor sees. Comments, reaction lists and the full names of some forward sources are not on it.
- Telegram's CDN links for photos and videos contain a token that expires. Download media soon after the run if you need the files.
- View counts come rounded from Telegram, so they are accurate to about three significant figures.
- Date filtering assumes post numbers rise with time, which is how Telegram numbers posts.
- Pages load one at a time to stay clear of Telegram's rate limits. Each page holds about 20 posts, and a run loads a few pages per second.

If something breaks or looks wrong, open an issue on the Issues tab and it gets fixed fast, and if this actor helped you, an honest review on the Store helps other people find it.

# Actor input Schema

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

Public channel usernames or links. Any of these work: durov, @durov, t.me/durov, https://t.me/s/durov. A link to one post reads the whole channel it belongs to. Private invite links (t.me/+...) cannot be read.

## `fromDate` (type: `string`):

Only save posts published at or after this time. Use an ISO date like 2026-10-01 or 2026-10-01T09:00:00Z, or a relative value like 7 days, 2 weeks or 3 months. A date without a time means the start of that day in UTC. Paging stops as soon as posts get older than this, so you only pay for posts inside the range. Leave empty for no lower limit.

## `toDate` (type: `string`):

Only save posts published at or before this time. Same formats as From date. A date without a time means the end of that day in UTC. Newer posts are skipped for free. Leave empty for no upper limit.

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

Stop a channel after this many paid posts are saved. Posts are saved newest first. Raise this for long date ranges.

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

Stop the whole run after this many paid posts across all channels. Free rows do not count.

## `maxRunSeconds` (type: `integer`):

Stop paging after this many seconds and keep what was saved. Set the run timeout higher than this.

## Actor input object example

```json
{
  "channels": [
    "telegram",
    "durov"
  ],
  "maxPostsPerChannel": 20,
  "maxItems": 1000,
  "maxRunSeconds": 600
}
```

# Actor output Schema

## `dataset` (type: `string`):

No description

## `summary` (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": [
        "telegram",
        "durov"
    ],
    "maxPostsPerChannel": 20
};

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

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

```

## MCP server setup

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

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/Msgvi4MwITLGkqCPY/builds/v7b6r5WiwrOnIJJB9/openapi.json
