# Telegram Channel Scraper – Public Posts (`sfoln/telegram-channel-scraper`)

Scrape posts from public Telegram channels without login: text and markdown, date, views, reactions, links, media URLs (photos, videos, files), forwards and replies, plus the channel's title, description and subscriber count. $0.80 per 1,000 posts, channel info free.

- Family: Social media data
- Categories: Social media, News, Lead generation
- Availability: on request (private on Apify). Email contact@sfoln.com with subject "Access request: Telegram Channel Scraper – Public Posts (sfoln/telegram-channel-scraper)" before calling; calls fail until it is public.
- JSON: https://www.sfoln.com/actors/telegram-channel-scraper.json
- MCP: https://mcp.apify.com?tools=sfoln/telegram-channel-scraper
- REST (sync): POST https://api.apify.com/v2/actors/sfoln~telegram-channel-scraper/run-sync-get-dataset-items
- REST (async): POST https://api.apify.com/v2/actors/sfoln~telegram-channel-scraper/runs

Scrape posts from public Telegram channels without a Telegram account, phone number or API key. For each channel you get its newest posts as clean rows — text and markdown, date, views, reactions, links, hashtags, photo/video/file URLs, forwards and replies — plus the channel info (title, description, subscribers, photo, verified badge) for free. Built for news and crypto monitoring, brand and community tracking, research datasets and AI agents.

## Price per delivered record

| Event | Price | Per 1,000 | Charged for |
| --- | --- | --- | --- |
| Telegram post (`post`) | $0.0008 | $0.80 / 1,000 | Charged once per channel post delivered (text, date, views, reactions, links, media URLs). |

Pay-per-event, USD, billed by Apify; no platform usage charge. Only records with status `ok` are charged. Cap a run with `maxTotalChargeUsd`. Apify free plan: up to 10 per run.

## Run

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

MCP client configuration:

```json
{
  "mcpServers": {
    "apify": {
      "type": "http",
      "url": "https://mcp.apify.com?tools=sfoln/telegram-channel-scraper"
    }
  }
}
```

Example prompt: Use telegram-channel-scraper to get the last 20 posts of the telegram channel and summarize what was announced, with dates and view counts.

## Input

Public Telegram channels to read. Each channel delivers its newest posts first (one charged row per post) and one free summary record with the channel info.

| Field | Type | Default | Meaning |
| --- | --- | --- | --- |
| `channels` (required) | array | — | Channels: Public channel usernames or links, one per line: telegram, @telegram, https://t.me/telegram or https://t.me/s/telegram (a post link works too). Private channels and invite links (t.me/+…) have no public preview. Accounts on the Apify free plan can process up to 10 channels per run, with up to 20 posts each. |
| `maxPostsPerChannel` | integer | `20` | Max posts per channel: Newest posts to deliver per channel (Telegram serves 20 per page). |
| `postedAfter` | string | — | Posted after: Only posts on or after this date: YYYY-MM-DD, an ISO date-time, or a relative period such as "7 days". Paging stops at the first older post. |
| `beforePostId` | integer | — | Start before post ID: Start with the posts older than this post ID (e.g. the oldestPostIdSeen of an earlier run) to continue a backfill. |
| `maxChannels` | integer | `100` | Max channels: Safety cap on channels in this run; the rest are ignored and counted as inputItemsDropped in the OUTPUT record. |
| `concurrency` | integer | `2` | Concurrency: Channels read in parallel (requests to t.me are rate limited for the whole run). |
| `includeRaw` | boolean | `false` | Include raw HTML: Attach each post's HTML block from the public preview page (larger output). |

Example input:

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

## Output

One record per input item with `status` (`ok` is charged; `not_found`, `error`, `skipped_*` are free) and `charged`. The run stores an `OUTPUT` summary record.

| Field | Type | Meaning |
| --- | --- | --- |
| `input` | string | object | Input: The channel this record belongs to (canonical username). |
| `recordType` | string | Record type: post (charged) or summary (one per channel, with the channel info; free). |
| `position` | integer | Position: Post number within its channel in this run (1 = newest). |
| `status` | string | Status: ok \| not_found \| error \| skipped_invalid \| skipped_budget \| skipped_free_plan \| skipped_unmetered |
| `charged` | boolean | Charged: Whether this record incurred a pay-per-event charge. |
| `chargedEvent` | string | Charged event: Pay-per-event event charged for this record (present only when charged). |
| `chargedCount` | integer | Events charged: Events actually charged when the charge came back short of the requested count (present only then; a warning explains it). |
| `source` | string | Source: Upstream data source. |
| `fetchedAt` | string | Fetched at: ISO timestamp. |
| `error` | object | Error: { code, message, provider?, status? } when status is not ok. Codes set by the runtime: budget_exhausted, free_plan_limit, unmetered, aborted, charge_failed, record_too_large, invalid_record, dataset_push_failed; upstream codes: rate_limit, timeout, network, server, client, invalid_response, not_found; this Actor: invalid_input. |
| `warnings` | array | Warnings: Notes about this record, e.g. a cap or the budget that stopped a channel early. |
| `channel` | string | Channel: Channel username (lower case). |
| `postId` | integer | Post ID: Message id within the channel. |
| `url` | string | Post URL: https://t.me/<channel>/<postId> |
| `date` | string | null | Date: Publication time, ISO 8601 UTC. |
| `edited` | boolean | Edited: The post was edited after publication. |
| `author` | string | null | Author signature: Author name shown on the post, when the channel signs posts. |
| `text` | string | null | Text: Plain text of the post or caption. |
| `markdown` | string | null | Markdown: The same text with bold, italic, links, code and quotes as markdown. |
| `views` | integer | null | Views: View count (approximate when Telegram abbreviates it). |
| `viewsText` | string | null | Views (as shown): The view count as Telegram shows it, e.g. 1.56M. |
| `reactions` | array | Reactions: [{ emoji, count }]: the emoji, paid (Telegram Stars) or custom:<id> for a custom emoji. |
| `reactionsTotal` | integer | null | Reactions total: Sum of all reaction counts. |
| `forwardedFrom` | object | null | Forwarded from: { name, url, username } of the original sender of a forwarded post (fields are null when hidden). |
| `replyTo` | object | null | Reply to: { postId, url, author, text } of the post this one replies to. |
| `links` | array | Links: Links in the text and the link preview. |
| `hashtags` | array | Hashtags: #hashtags in the text. |
| `mentions` | array | Mentions: @mentions in the text. |
| `linkPreview` | object | null | Link preview: { url, siteName, title, description, imageUrl } of the attached link preview. |
| `poll` | object | null | Poll: { question, type, options: [{ text, percent }] }. |
| `media` | array | Media: [{ type, url, thumbnailUrl, postUrl, durationSecs, title, details }]; type: photo, video, round_video, document, audio, voice, sticker, location. |
| `mediaTypes` | array | Media types: Distinct media types of the post. |
| `mediaNotSupported` | boolean | Media only in the app: Telegram shows this post's media only in its apps. |
| `channelTitle` | string | null | Channel title: The channel's display name. |
| `raw` | string | Raw HTML: The post's HTML block from the preview page (only with includeRaw). |
| `channelDescription` | string | null | Channel description: Summary records: the channel's description. |
| `subscribers` | integer | null | Subscribers: Summary records: subscriber count (approximate when abbreviated). |
| `subscribersText` | string | null | Subscribers (as shown): Summary records: e.g. 9.33M. |
| `photoUrl` | string | null | Channel photo: Summary records: channel photo URL. |
| `verified` | boolean | Verified: Summary records: the channel has Telegram's verified badge. |
| `counters` | object | Counters: Summary records: other header counters (photos, videos, files, links). |
| `channelUrl` | string | null | Channel URL: Summary records: https://t.me/<channel>. |
| `oldestPostIdSeen` | integer | null | Oldest post ID seen: Summary records: the oldest post ID this run delivered (null when none); pass it as beforePostId to continue further back. |
| `resultsDelivered` | integer | Posts delivered: Summary records: charged post rows of this channel. |
| `stopReason` | string | Stop reason: Summary records: limit \| free_plan \| end \| budget \| aborted \| deadline \| empty_pages \| max_pages \| error. |

Example record:

```json
[
  {
    "input": "synthetic_news",
    "recordType": "post",
    "position": 1,
    "channel": "synthetic_news",
    "postId": 101,
    "url": "https://t.me/synthetic_news/101",
    "date": "2026-10-10T08:00:00.000Z",
    "edited": true,
    "author": "Synthetic Editor",
    "text": "✨ Big update is out: read the release notes.",
    "markdown": "✨ **Big update** is out: read the [release notes](https://example.com/blog/update).",
    "views": 15300,
    "viewsText": "15.3K",
    "reactions": [
      {
        "emoji": "👍",
        "count": 1200
      },
      {
        "emoji": "paid",
        "count": 12
      }
    ],
    "reactionsTotal": 1212,
    "forwardedFrom": null,
    "replyTo": null,
    "links": [
      "https://example.com/blog/update"
    ],
    "hashtags": [
      "#release"
    ],
    "mentions": [],
    "linkPreview": {
      "url": "https://example.com/blog/update",
      "siteName": "Example Blog",
      "title": "Release notes",
      "description": "Everything new in this release.",
      "imageUrl": "https://cdn4.example-cdn.test/file/preview.jpg"
    },
    "poll": null,
    "media": [
      {
        "type": "photo",
        "url": null,
        "thumbnailUrl": "https://cdn4.example-cdn.test/file/photo-101.jpg",
        "postUrl": "https://t.me/synthetic_news/101",
        "durationSecs": null,
        "title": null,
        "details": null
      }
    ],
    "mediaTypes": [
      "photo"
    ],
    "mediaNotSupported": false,
    "channelTitle": "Synthetic News",
    "status": "ok",
    "charged": true,
    "chargedEvent": "post",
    "source": "telegram",
    "fetchedAt": "2026-10-14T12:00:00.000Z"
  },
  {
    "input": "synthetic_news",
    "channel": "synthetic_news",
    "channelTitle": "Synthetic News",
    "channelDescription": "Test headlines for a synthetic channel.",
    "subscribers": 1250000,
    "subscribersText": "1.25M",
    "photoUrl": "https://cdn1.example-cdn.test/file/channel-photo.jpg",
    "verified": true,
    "counters": {
      "photos": 412,
      "videos": 38,
      "links": 2100
    },
    "channelUrl": "https://t.me/synthetic_news",
    "oldestPostIdSeen": 82,
    "recordType": "summary",
    "resultsDelivered": 20,
    "stopReason": "limit",
    "status": "ok",
    "charged": false,
    "source": "telegram",
    "fetchedAt": "2026-10-14T12:00:02.000Z"
  }
]
```

## FAQ

**Do I need a Telegram account, phone number or API key?**

No. The Actor reads the public web preview Telegram shows for every public channel at `t.me/s/<channel>`; no login is involved.

**Which channels work?**

Public channels with a username. Private channels, invite links (t.me/+…), groups, bots and user accounts have no public preview and come back as not_found (free).

**Are view and subscriber counts exact?**

Telegram rounds them on the preview (for example 1.56M). The Actor returns the number it shows (views: 1560000) next to the original text (viewsText: "1.56M").

**How do I scrape only new posts every day?**

Schedule the Actor with postedAfter set to a relative period such as "1 day", or keep the oldestPostIdSeen of a run and pass it as beforePostId to continue a backfill.


## Related

- [Hacker News Scraper – Stories & Comments](https://www.sfoln.com/actors/hacker-news-scraper.md) (`sfoln/hacker-news-scraper`)
- [Substack Scraper – Newsletter Posts](https://www.sfoln.com/actors/substack-scraper.md) (`sfoln/substack-scraper`)
- [Reddit Posts Scraper (Subreddits)](https://www.sfoln.com/actors/reddit-subreddit-posts.md) (`sfoln/reddit-subreddit-posts`)
- [Brand Monitoring – X, Reddit, News & Bluesky](https://www.sfoln.com/actors/brand-mentions-monitor.md) (`sfoln/brand-mentions-monitor`)
- [YouTube Channel Videos Scraper](https://www.sfoln.com/actors/youtube-channel-videos.md) (`sfoln/youtube-channel-videos`)
- [URL to Markdown & Article Extractor](https://www.sfoln.com/actors/url-to-markdown-summary.md) (`sfoln/url-to-markdown-summary`)

Publisher: SFOLN LLC, contact@sfoln.com. Catalogue: https://www.sfoln.com/catalog.json. Agent guide: https://www.sfoln.com/agents.md.
