# Telegram Posts Scraper - Channel History, Views & Media (`scrapersdelight/telegram-channel-posts-scraper`) Actor

From $0.30 per 1,000 posts, no start fee. Scrape the full public history of any Telegram channel: post text, view count, publish date, photos, videos, outbound links, forwards and replies — plus channel subscribers and media counts. Date ranges, incremental runs, no login.

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

## Pricing

$0.30 / 1,000 per post returneds

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 Posts Scraper — Channel History, Views & Media

Exports the **full public post history** of any Telegram channel: post text, view count, publish
date, photos, videos, outbound links, forwards and replies — plus the channel's own subscriber and
media counts on every row.

No login. No phone number. No API key. No Telegram account of any kind.

***

### What one row looks like

```json
{
  "postId": 443,
  "channel": "telegram",
  "postUrl": "https://t.me/telegram/443",
  "datetime": "2026-05-14T16:08:31+00:00",
  "text": "Telegram's new update brings…",
  "textLength": 219,
  "views": 1730000,
  "isForwarded": false,
  "isReply": false,
  "hasLinkPreview": true,
  "photoCount": 0,
  "videoCount": 1,
  "hasMedia": true,
  "videoThumbUrls": ["https://cdn1.telesco.pe/file/…"],
  "links": ["https://telegram.org/blog/…"],
  "channelTitle": "Telegram News",
  "subscribers": 9540000,
  "videosCount": 228
}
```

| field | what it is |
|---|---|
| `postId` | Telegram's own sequential id — the stable key, and the cursor for incremental runs |
| `datetime` | Publish time, ISO 8601 with timezone |
| `views` | **A real number.** Telegram renders `"9.55M"`; this expands it to `9550000` |
| `text` · `textLength` | Post text with markup stripped and emoji kept. `null` on a media-only post |
| `photoCount` · `videoCount` · `hasMedia` | Always present, whether or not you ask for media URLs |
| `photoUrls` · `videoUrls` · `videoThumbUrls` | CDN links, when `includeMedia` is on |
| `links` | Outbound URLs inside the post body |
| `isForwarded` · `forwardedFrom` · `isReply` · `isEdited` | Post provenance |
| `subscribers` · `photosCount` · `videosCount` · `linksCount` | Channel totals — **free**, read off the same page, no extra request |

### Input

| input | default | what it does |
|---|---|---|
| `channels` | `["telegram"]` | Handles or links — `telegram`, `@telegram`, `https://t.me/telegram` all work |
| `maxPosts` | `200` | Cap across all channels on delivered — and charged — posts |
| `maxPostsPerChannel` | `0` | Per-channel cap, for an even sample across many channels |
| `maxPagesPerChannel` | `0` | Telegram serves 20 per page; a safety stop for very long channels |
| `sinceDate` · `untilDate` | — | ISO dates. Paging stops once a page is entirely older than `sinceDate`, so a narrow window is cheap |
| `afterPostId` | `0` | Fetch only posts newer than an id you already hold |
| `onlyNewSinceLastRun` | `false` | Remembers the highest id per channel between runs — a monitor without you tracking ids |
| `includeMedia` | `true` | Adds media URLs. Counts are included either way |
| `includeChannelInfo` | `true` | Channel title, description and counts on every row |
| `includeRawHtmlSnippet` | `false` | Original markup, capped at 20 KB per post |

### Public channels only

This reads `t.me/s/<channel>`, the preview Telegram serves to anyone with a browser. If a channel
has no public preview — private, or preview disabled by its owner — the run says so and charges
nothing for it. Groups, direct messages and member lists are **not** accessible this way and this
Actor does not attempt them.

### Why a run either completes or fails

A page that isn't HTTP 200 is treated as a **transport failure, not the end of the channel**. That
distinction is the difference between an honest result and an expensive lie: a scraper that stops
"when a page returns nothing" will end a third of the way through a long channel, report success,
and bill you for a partial history.

The Actor also checks its own parsing. Telegram serves exactly 20 posts per page, and if the number
of posts decoded doesn't match the number of message blocks on the page, the run **fails** rather
than delivering a quietly short history.

### Pricing

Pay per event: **$0.0003 per post**, and **no run-start fee**.

The start fee is where this category hides its cost. A 100-post run here is **$0.03**. The same run
is about **$0.045** at one competitor ($0.00035/post + $0.01 start) and about **$0.05** at another
($0.00045/post + $0.005 start). Channel details ride along on every row at no extra charge.

Filters — date range, post id, per-channel caps — are applied **before** delivery, so posts they
exclude are never charged, and a run that returns nothing costs nothing.

Every run writes a `RUN_SUMMARY` with, per channel: subscriber count, pages read, posts delivered,
the highest post id reached, and exactly why paging stopped.

### Source

`t.me/s/<channel>` is Telegram's own public web preview, served to any visitor. The host publishes
no `robots.txt` at all. Posts in a public channel are published by the channel owner to be read
publicly; you are responsible for what you do with them, which for outreach means the relevant
marketing and data-protection rules wherever you are sending from.

# Actor input Schema

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

Channel handles or t.me links. `telegram`, `@telegram` and `https://t.me/telegram` all work. One row per post; a post is never delivered twice in a run.

## `maxPosts` (type: `integer`):

Hard cap on delivered — and therefore charged — posts across every channel. 0 means no cap.

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

Per-channel cap, useful when you pass many channels and want an even sample. 0 means no per-channel cap.

## `maxPagesPerChannel` (type: `integer`):

Telegram serves 20 posts per page, so 10 pages is about 200 posts. A safety stop for very long channels. 0 means page to the end.

## `sinceDate` (type: `string`):

ISO date, e.g. 2026-01-01. Paging stops once a whole page is older than this, so a narrow window is cheap.

## `untilDate` (type: `string`):

ISO date, e.g. 2026-06-30.

## `afterPostId` (type: `integer`):

Telegram numbers posts sequentially per channel. Pass the highest id you already have to fetch only what is new.

## `onlyNewSinceLastRun` (type: `boolean`):

Remembers the highest post id per channel in a named key-value store and starts from there next time. Turns this Actor into a monitor without you tracking ids yourself.

## `includeMedia` (type: `boolean`):

Adds photo, video and video-thumbnail URLs. Counts (photoCount, videoCount, hasMedia) are always included whether this is on or off.

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

Channel title, description, subscriber count and media counts. Free — it is read from the same page as the posts, with no extra request.

## `includeRawHtmlSnippet` (type: `boolean`):

The original markup, capped at 20 KB per post. For anyone parsing formatting we do not model.

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

Apify Proxy. Datacenter is sufficient — measured 200 OK with 20 posts per page and no cookie.

## Actor input object example

```json
{
  "channels": [
    "telegram"
  ],
  "maxPosts": 200,
  "maxPostsPerChannel": 0,
  "maxPagesPerChannel": 0,
  "afterPostId": 0,
  "onlyNewSinceLastRun": false,
  "includeMedia": true,
  "includeChannelInfo": true,
  "includeRawHtmlSnippet": false,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `items` (type: `string`):

One row per public post: id, date, text, view count, media counts and URLs, outbound links, forward and reply flags, plus the channel's own title, description and subscriber count.

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

RUN\_SUMMARY: per channel the subscriber count, pages read, posts delivered, highest post id reached and why paging stopped; then delivered vs charged.

# 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 = {};

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

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

```

## MCP server setup

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