# Telegram Channel Posts Scraper (`axiomworks/telegram-channel-posts-scraper`) Actor

Collect public Telegram channel posts with date, text, views, photos, video, links and reactions, plus optional channel profiles with subscriber counts. Reads the public t.me/s preview with no login or phone number; filter by date or keyword. Channels without a preview return a status row.

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

## Pricing

from $1.48 / 1,000 results

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

## Telegram Channel Posts Scraper

**Returns:** one row per public Telegram post with channel, postId, date, text, views, viewsText, and url; optional channel profile rows.
**Input:** `{"channels":["durov"],"maxPostsPerChannel":20}`
**Best for:** public channel monitoring, post archives, and view-count analysis.

```json
{"id":"telegram/460","channel":"telegram","postId":460,"date":"2026-08-26T19:12:34.000Z","views":1610000,"viewsText":"1.61M","url":"https://t.me/telegram/460"}
```

### What does Telegram Channel Posts Scraper do?

Telegram Channel Posts Scraper collects posts from public Telegram channels through their web preview pages. Give it one or more channel usernames or `t.me` URLs, and it returns structured post records in an Apify dataset. Each post has a channel, canonical URL, publication date, readable text when present, original text HTML, view count, media URLs, links, and visible reaction counts. You can also request a separate channel profile record for each channel.

The Actor uses the public `t.me/s/` preview. It does not sign in or read private channels, member lists, comments, or messages visible only in the Telegram app. A channel that does not expose public preview posts yields no post records; the run log reports its status. This is a batch Actor: it fetches pages, writes records, and exits. It processes up to two channels at once, paces requests within each channel, and retries block responses with backoff and fresh proxy sessions. If default datacenter proxy sessions are blocked, it tries a residential proxy when available.

The optional `enrich` setting classifies a post's main topic and sentiment. Enrichment sends post text to TypeSafe AI, a third-party AI processor. It costs extra per labelled post and is off by default. If the processor is unavailable or fails, the scraper continues and leaves the enrichment fields empty. Classification is an interpretation of the text, while the other post fields come from the Telegram preview.

### What data can you get?

The dataset contains three record types. A `post` record holds one Telegram post. A `channel-status` record is emitted when a channel has no web preview (private, group, not found), so the run never ends silently empty. A `channel` record holds the public profile summary, if `includeChannelInfo` is enabled. Both have a stable string `id` (`channel/postNumber`), `channel`, `url`, and `sourceUrl`. `sourceUrl` identifies the exact preview page from which the record came. The channel and post number are combined to keep dataset IDs distinct when two channels use the same post number.

| Field | Record | Meaning |
| --- | --- | --- |
| `id` | Both | Stable ID in the form `channel/postNumber`, unique across channels and pages. |
| `recordType` | Both | `post`, `channel` or `channel-status`. |
| `channel` | Both | Normalized public channel username. |
| `url` | Both | Canonical Telegram post or profile URL. |
| `sourceUrl` | Both | Preview page that supplied the record. |
| `postId` | Post | Telegram's post number within the channel. |
| `date` | Post | Publication timestamp in ISO 8601 format. |
| `text` | Post | Visible plain text with line breaks preserved as `\n`, when present. |
| `html` | Post | HTML of the visible text element with inline event handlers removed, when present. |
| `views` | Post | Numeric view count parsed from the displayed K, M, or B abbreviation. |
| `viewsText` | Post | Original view count displayed by Telegram, such as `1.61M`. |
| `photos` | Post | Available photo URLs shown in the preview. |
| `video` | Post | Available video URL shown in the preview. |
| `links` | Post | Links found in the post's text. |
| `linkPreview` | Post | Link preview URL when Telegram exposes one. |
| `linkPreviewTitle` | Post | Title on the link preview card, when present. |
| `linkPreviewDescription` | Post | Description on the link preview card, when present. |
| `linkPreviewSite` | Post | Site name on the link preview card, when present. |
| `videoThumbnail` | Post | Thumbnail image URL of the post video, when present. |
| `videoDuration` | Post | Video length in seconds, when shown. |
| `author` | Post | Author name shown on the post, when present. |
| `mediaType` | Post | `video`, `photo` or `link` for the main media, otherwise null. |
| `forwardedFrom` | Post | Visible attribution for a forwarded post, if shown. |
| `edited` | Post | Whether the preview marks the post edited. |
| `reactions` | Post | Reactions as text: the emoji (`⭐` for paid stars, `emoji:<id>` for custom emoji) followed by the count as displayed. |
| `topic` | Post | Optional AI classification: news, crypto, promo, tech, politics, entertainment, or other. |
| `sentiment` | Post | Optional AI classification: positive, neutral, or negative. |
| `title` | Channel profile | Public title of the channel. |
| `description` | Channel profile | Public channel description. |
| `subscribers` | Channel profile | Parsed subscriber count, when shown. |
| `photosCount`, `videosCount`, `linksCount` | Channel profile | Parsed public profile counters, when shown. |
| `status` | Channel profile, channel status | Whether public posts were found, or the channel status (`not_found`, `private`, `no_preview`). |
| `detail` | Channel status | Why no posts were returned (private, group, not found, redirect). |

The HTML preview does not guarantee every optional field on every post. Media-only posts can have empty text. Some previews do not expose a forwarded attribution or link preview. A numeric counter can be `null` when its visible text cannot be parsed. The Actor preserves available media URLs but does not download images or videos.

### How to use

Open the Actor's input form and enter public channel usernames, such as `durov`, or full `https://t.me/` links. Set `maxPostsPerChannel` to control how far the Actor pages back through each channel, and set `maxItems` for a hard cap over the whole dataset. A channel profile uses one item from that total cap when profiles are enabled. Then start the Actor and open the dataset when it finishes.

The newest preview page is requested first. If more posts are needed, the Actor requests earlier pages using the smallest post number from the previous page. It deduplicates posts by stable ID. Pagination ends when the post limit is met, a page has no posts, the post numbers stop decreasing, or the selected `dateFrom` boundary has been passed. `searchQuery` uses Telegram's public channel web search; its availability depends on Telegram's preview behavior. Set `dateTo` to exclude newer posts while paging back into a historical window. Set `afterPostIds` to collect only posts newer than the saved ID for each channel. A date-only `dateTo` includes the full UTC day.

For a quick sample, use `maxItems: 7`. If the channel has at least seven available records, the dataset contains exactly seven items, counting an included profile. To receive seven posts rather than six posts plus a profile, set `includeChannelInfo` to `false`. For longer runs across many channels, increase both the per-channel limit and the global cap to the amounts you need.

Only public channels with enabled web previews can return posts. A private link, a group, or a channel without preview access cannot be made accessible by this Actor. If Telegram redirects or rejects the preview, check the run log for the channel's status. The default Apify datacenter proxy can switch to residential sessions after repeated blocks when residential access is available. Requests to Telegram are paced to roughly one per second per channel, with at most two channels active at once.

### Input

| Input | Required | Default | Purpose |
| --- | --- | --- | --- |
| `channels` | Yes | Prefilled example | Public channel usernames or `t.me` URLs. |
| `maxPostsPerChannel` | No | 50 | Maximum post records for each channel, 1–1000. |
| `maxItems` | No | 1000 | Maximum total records, including profiles, 1–10000. |
| `dateFrom` | No | None | Earliest publication date in ISO format. |
| `dateTo` | No | None | Latest publication date in ISO format; date-only values include the UTC day. |
| `afterPostIds` | No | `{}` | Last seen numeric post ID for each channel; only newer posts are returned. |
| `searchQuery` | No | None | Query for Telegram's public channel search. |
| `includeChannelInfo` | No | `true` | Include one profile record per channel. |
| `enrich` | No | `false` | Request topic and sentiment classification. |
| `maxJevCalls` | No | 500 | Maximum AI requests per run, 1–500. |
| `proxyConfiguration` | No | Apify datacenter proxy | Apify Proxy settings for Telegram requests. |

Example input:

```json
{
  "channels": ["durov", "https://t.me/telegram"],
  "maxPostsPerChannel": 20,
  "maxItems": 42,
  "dateFrom": "2026-01-01",
  "dateTo": "2026-09-30",
  "afterPostIds": {"durov": 500},
  "includeChannelInfo": true,
  "enrich": false
}
```

The channel list is required in API calls. The Console form shows example channels as a prefill, but an API caller must supply its own `channels` value. Invalid usernames, unsupported URLs, empty arrays, invalid dates, and limits outside their documented ranges fail at the start of the run with a clear error. Duplicated channel names are processed once. A URL must point to `t.me`; other hosts are not accepted as channel input.

Enrichment uses our third-party AI processor (TypeSafe AI); no key is needed. Post text is sent to it only when `enrich` is on. If the service is unavailable, records are still produced without `topic` and `sentiment` values.

### Output

This channel profile record is from the local prefill run:

```json
{
  "id": "durov/0",
  "recordType": "channel",
  "channel": "durov",
  "url": "https://t.me/durov",
  "sourceUrl": "https://t.me/s/durov",
  "title": "Pavel Durov",
  "description": "Founder of Telegram.",
  "subscribers": 10500000,
  "photosCount": 102,
  "videosCount": 46,
  "linksCount": 200,
  "status": "ok"
}
```

A post from the local prefill run has these values:

```json
{
  "id": "telegram/460",
  "recordType": "post",
  "channel": "telegram",
  "postId": 460,
  "date": "2026-08-26T19:12:34.000Z",
  "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\n\nAugust Features\n1 • 2 • 3 • 4 • 5 • More",
  "views": 1610000,
  "viewsText": "1.61M",
  "url": "https://t.me/telegram/460",
  "sourceUrl": "https://t.me/s/telegram"
}
```

The sample post is shown with the fields most useful for a quick review; the Actor also stores its available HTML, media, links, reactions, and enrichment fields. Values can change as Telegram updates view and reaction counts.

### How much does it cost?

Pricing is per result. Each post is charged as one result. Each channel profile record is charged as one result. Channel-status records are not charged. A post is charged an additional enrichment result only if both topic and sentiment enrichment succeeds. The final charge depends on the number of records and whether enrichment was used. See the Actor's Pricing tab for current event prices; they are intentionally not copied into this README.

Use `maxItems` to place a clear ceiling on the total records in a run. The `maxPostsPerChannel` setting controls the work for each channel, and `dateFrom` can stop older-page requests early. Failed AI calls do not trigger the enrichment event. Compute and proxy costs are included in the event prices.

### Use with AI agents (MCP)

Apify's MCP server can expose this Actor as a tool. A client configuration can use the server URL below:

```json
{
  "mcpServers": {
    "apify": {
      "url": "https://mcp.apify.com/?tools=axiomworks/telegram-channel-posts-scraper"
    }
  }
}
```

An agent can then ask, “Collect the latest 10 posts from the public `durov` channel and summarize their dates and view counts.” Another prompt is, “Get public posts from `telegram` since 2026-01-01, and include the channel profile.” The agent should provide `channels` explicitly, because the Console prefill is an example rather than a value supplied to API callers. The output schema helps the agent distinguish post rows from channel profile rows.

For topic-based tasks, ask the agent to set `enrich` only when topic or sentiment is needed, because it costs extra per labelled post. Classification consumes an external AI service and may be incomplete. The original text, date, URL, and source URL remain available for checking a summary against its source.

### Integrations and API

You can start the Actor through Apify's API and read its dataset through standard SDKs. Set your Apify API token in the `APIFY_TOKEN` environment variable and keep it in your application's secret store. The examples show a small public scrape; adjust the limits to suit your workflow.

Python:

```python
import os
from apify_client import ApifyClient

client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("axiomworks/telegram-channel-posts-scraper").call(run_input={
    "channels": ["durov"],
    "maxPostsPerChannel": 10,
    "maxItems": 10,
    "includeChannelInfo": False,
})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item["url"], item.get("date"))
```

JavaScript:

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

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('axiomworks/telegram-channel-posts-scraper').call({
  channels: ['durov'],
  maxPostsPerChannel: 10,
  maxItems: 10,
  includeChannelInfo: false,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
for (const item of items) process.stdout.write(`${item.url}\n`);
```

cURL:

```bash
curl -X POST "https://api.apify.com/v2/acts/axiomworks~telegram-channel-posts-scraper/run-sync-get-dataset-items" \
  -H "Authorization: Bearer $APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"channels":["durov"],"maxPostsPerChannel":10,"maxItems":10,"includeChannelInfo":false}'
```

The synchronous endpoint is convenient for a short sample. For larger runs, start the Actor asynchronously, wait for completion, then fetch the default dataset. Dataset items can be sent onward to spreadsheets, databases, analysis jobs, or an agent. Keep `id` as the deduplication key if you collect the same channel repeatedly; view counts can change while that ID stays stable.

### FAQ

**Can this read private channels or groups?** No. The Actor reads only public Telegram channel pages that expose a web preview. It has no account session and does not try to bypass login or access controls.

**What happens when a channel has no public posts?** The run still ends successfully. The dataset gets a `channel-status` record (`private`, `not_found` or `no_preview`) and the run status message says what to change, for example using a public channel username.

**Why are there fewer posts than my limit?** The public preview may have fewer accessible posts, `dateFrom` may exclude older posts, the channel may redirect, or Telegram may respond with an error. The Actor logs a per-channel status. The maximum is a ceiling, not a promise that a channel contains that many accessible posts.

**Why does `maxItems` include profiles?** A profile is a dataset record with its own stable ID and source URL. Counting it makes the global item limit exact. Disable `includeChannelInfo` when your limit should apply only to posts.

**Are images downloaded?** No. Photo and video fields are URLs observed in the preview. Some media URLs may be temporary or may stop working later. The Actor does not open those URLs or store binary media.

**How are views and subscribers counted?** Telegram displays abbreviated values such as `1.2K` or `3.4M`. The Actor converts them to approximate integers using decimal multipliers. The source display value can change after the run.

**What happens if AI enrichment fails?** Scraping continues. The relevant post is still returned, `topic` and `sentiment` remain empty, and no successful enrichment event is charged for it.

### Is it legal to scrape Telegram?

This Actor is designed for publicly visible channel previews. Whether a particular use is permitted depends on your location, the content, and your intended use. Review applicable law and Telegram's terms for your case. Respect copyright, privacy, and any obligations that apply to your downstream storage or publication. The Actor does not provide access to private data or require a Telegram login. It is an independent tool and is not affiliated with Telegram.

### Feedback

If a public channel's preview changes or a field stops appearing, report the channel URL, a post URL if available, the run time, and which field was missing. Avoid including secret keys or private data in a report. A reproducible public example helps identify selector changes. You can also suggest additional fields that are actually visible on the public preview. Check the run log first for redirects, rate limits, or an empty public preview.

# Actor input Schema

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

Public Telegram channels to scrape, as usernames or t.me URLs. Required, at least one. Example: \["durov", "@telegram", "https://t.me/bbcnews"]. Private channels, groups and invite links return a channel-status record instead of posts.

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

Maximum post records collected per channel, integer 1-1000, default 50. Example: 20. Newest posts come first.

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

Hard cap on total records across all channels, including channel profile records, integer 1-10000, default 1000. Example: 100.

## `dateFrom` (type: `string`):

Only posts on or after this date. ISO date or timestamp, for example 2026-01-01 or 2026-01-01T12:00:00Z. Leave empty for no lower bound.

## `dateTo` (type: `string`):

Only posts on or before this date. ISO date or timestamp, for example 2026-09-30 (a date alone includes the whole UTC day). Leave empty for no upper bound.

## `afterPostIds` (type: `object`):

Monitoring checkpoints: object mapping channel to the last post ID you already have, for example {"durov": 525}. Only posts with a higher ID are returned. Keys accept usernames, @handles or t.me URLs. Default empty.

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

Keyword or phrase to search inside each channel via Telegram web search, at most 200 characters, for example 'bitcoin'. Leave empty to get the most recent posts.

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

true adds one channel profile record per channel (title, description, subscribers, photo/video/link counts). false returns posts only. Default true.

## `enrich` (type: `boolean`):

true adds AI-classified topic and sentiment to each post via the third-party TypeSafe AI processor, billed as an extra enriched-result event per labelled post. If unavailable, topic and sentiment stay null and nothing extra is charged. Default false.

## `maxJevCalls` (type: `integer`):

Cap on posts sent for AI labelling in one run, integer 1-500, default 500. Only used when enrich is true.

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

Apify Proxy settings, for example {"useApifyProxy": true}. Default datacenter proxy; blocked requests retry on fresh sessions and may fall back to residential. Disable only for local testing.

## Actor input object example

```json
{
  "channels": [
    "durov",
    "telegram"
  ],
  "maxPostsPerChannel": 20,
  "maxItems": 50,
  "afterPostIds": {},
  "includeChannelInfo": true,
  "enrich": false,
  "maxJevCalls": 500,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

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

Dataset with post and channel profile records.

# 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,
    "maxItems": 50,
    "includeChannelInfo": true
};

// Run the Actor and wait for it to finish
const run = await client.actor("axiomworks/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 = {
    "channels": [
        "durov",
        "telegram",
    ],
    "maxPostsPerChannel": 20,
    "maxItems": 50,
    "includeChannelInfo": True,
}

# Run the Actor and wait for it to finish
run = client.actor("axiomworks/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 '{
  "channels": [
    "durov",
    "telegram"
  ],
  "maxPostsPerChannel": 20,
  "maxItems": 50,
  "includeChannelInfo": true
}' |
apify call axiomworks/telegram-channel-posts-scraper --silent --output-dataset

```

## MCP server setup

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