# Telegram Channel Scraper: Messages & Search (`scrapingmonkey/telegram-scraper`) Actor

Scrape public Telegram channel profiles and 60-field messages with reactions, polls, media, keyword search, date filters, and incremental checkpoints.

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

## Pricing

from $0.48 / 1,000 channel messages

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/platform/actors/running/actors-in-store#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 - Channel Profiles, Messages and Keyword Search

Extract public Telegram channel information, message history, media metadata, reactions, polls, and keyword matches without a Telegram account, phone number, API ID, or bot token.

- Collect channel titles, descriptions, avatars, verification status, subscribers, media counters, and recent engagement metrics
- Scrape public message history with inclusive date filters and limits up to 5,000 messages per channel
- Search for keywords or phrases inside multiple public channels
- Resume recurring runs from a global or per-channel message ID checkpoint
- Preserve plain text and rich HTML, including formatting, mentions, hashtags, links, and custom emojis
- Extract views, standard reactions, custom reactions, paid Telegram Stars, polls, forwards, replies, albums, photos, videos, and stickers when exposed publicly
- Optionally store bounded public media files in the run Key-value store
- Process up to 100 public channel usernames or URLs in one run
- Process independent channels or channel/search-term pairs concurrently with a configurable limit
- Use mandatory built-in Apify Residential Proxy automatically; no proxy input is required
- Export results to JSON, CSV, Excel, XML, or access them through the Apify API

### What can you do with this Actor?

| Mode / action | Input | Output | Best for |
| --- | --- | --- | --- |
| `channelInfo` | Public channel usernames or URLs | One profile and recent-analytics row per channel | Channel discovery, audience sizing, and monitoring |
| `messages` | Public channels, message limit, optional date range | Public messages in newest-first order | Content archives, media research, and engagement analysis |
| `searchMessages` | Public channels and search terms | Messages matched by Telegram's channel search | Topic tracking, brand monitoring, and historical research |

One mode runs at a time. Use `channelInfo` to evaluate channels, then pass the same `channels` array to `messages` or `searchMessages` in another run.

### Quick start

1. Open the Actor and click **Try for free**.
2. Keep `channelInfo` selected and enter a public channel such as `durov`.
3. Add more usernames, `@usernames`, or `t.me` URLs if needed.
4. Click **Start**.
5. Preview the dataset or download it in your preferred format.

The default input requests the public profile for `@durov` and returns one useful dataset row.

### Input examples

#### Collect channel information

```json
{
  "mode": "channelInfo",
  "channels": [
    "durov",
    "@telegram",
    "https://t.me/telegramtips"
  ]
}
```

One dataset row represents one public channel. Recent analytics use the posts visible on the first public preview page.

#### Scrape public channel messages

```json
{
  "mode": "messages",
  "channels": ["durov"],
  "maxMessages": 1000,
  "dateFrom": "2025-01-01",
  "dateTo": "2025-12-31",
  "includeServiceMessages": true,
  "mediaMode": "urls",
  "maxConcurrency": 5
}
```

`maxMessages` applies separately to every channel. Messages are returned newest first after date filtering.

#### Search messages inside channels

```json
{
  "mode": "searchMessages",
  "channels": ["durov", "telegramtips"],
  "searchTerms": ["privacy", "Telegram"],
  "maxMessages": 200,
  "dateFrom": "2024-01-01",
  "includeServiceMessages": false,
  "mediaMode": "urls"
}
```

The limit applies independently to every channel and search-term pair. Two channels and two terms can therefore return up to 800 message rows.

#### Resume an incremental channel export

```json
{
  "mode": "messages",
  "channels": ["durov", "telegram"],
  "maxMessages": 1000,
  "afterMessageIds": {
    "durov": 518,
    "telegram": 429
  },
  "maxConcurrency": 5
}
```

After every run, read `RUN_SUMMARY.nextAfterMessageIds` and pass that object into the next scheduled run. Checkpoints are exclusive: a value of `518` returns only messages whose ID is greater than `518`.

#### Store public media files

```json
{
  "mode": "messages",
  "channels": ["durov"],
  "maxMessages": 50,
  "mediaMode": "urls",
  "downloadMedia": true,
  "maxMediaFiles": 25,
  "maxMediaSizeMb": 20
}
```

Successfully downloaded files are stored in the run's default Key-value store. Their record keys are returned in `media[].storeKey` and `mediaStoreKeys`. Signed source URLs remain in the dataset even when a file is skipped or cannot be stored.

### Complete output examples

The Actor always emits complete, stable shapes. Source fields that are not available use `null`; repeatable collections use empty arrays; boolean indicators remain explicit. Temporary public media URLs are returned only when `mediaMode` is `urls`.

#### Complete `channel` output - 30 top-level fields

```json
{
  "recordType": "channel",
  "channelId": "-1006503122",
  "username": "durov",
  "title": "Pavel Durov",
  "channelUrl": "https://t.me/durov",
  "previewUrl": "https://t.me/s/durov",
  "description": "Founder of Telegram.",
  "avatarUrl": "https://cdn4.telesco.pe/file/example-avatar.jpg",
  "verified": true,
  "subscriberCount": 11300000,
  "subscriberCountRaw": "11.3M",
  "subscriberCountApproximate": true,
  "photoCount": 101,
  "photoCountRaw": "101",
  "photoCountApproximate": false,
  "videoCount": 45,
  "videoCountRaw": "45",
  "videoCountApproximate": false,
  "linkCount": 194,
  "linkCountRaw": "194",
  "linkCountApproximate": false,
  "latestMessageId": 538,
  "latestMessageDate": "2026-08-07T14:35:57Z",
  "recentPostsSampleSize": 20,
  "recentAverageViews": 5372500.0,
  "recentMedianViews": 4910000.0,
  "recentAverageReactions": 64215.35,
  "recentEngagementRatePercent": 1.1953,
  "available": true,
  "scrapedAt": "2026-08-08T04:45:00Z"
}
```

#### Complete `message` output - 60 top-level fields

The same shape is used by `messages` and `searchMessages`. In regular message mode, `searchTerm` and `searchRank` are `null`.

```json
{
  "recordType": "message",
  "channelId": "-1006503122",
  "channelUsername": "durov",
  "channelTitle": "Pavel Durov",
  "channelUrl": "https://t.me/durov",
  "channelSubscriberCount": 11300000,
  "channelSubscriberCountRaw": "11.3M",
  "messageId": 518,
  "messageUrl": "https://t.me/durov/518",
  "date": "2026-06-25T13:03:57Z",
  "displayedTime": "21:03",
  "searchTerm": "Telegram",
  "searchRank": 1,
  "text": "Telegram continues to improve privacy. Read the update and independent source. #privacy @telegram",
  "textHtml": "<tg-emoji emoji-id=\"5368324170671202286\"><i class=\"emoji\"><b>🔐</b></i></tg-emoji> <b>Telegram</b> continues to improve privacy. Read the <a href=\"https://telegram.org/blog/example\">update</a> and <a href=\"https://example.com/source\">independent source</a>. <a href=\"https://t.me/s/durov?q=%23privacy\">#privacy</a> <a href=\"https://t.me/telegram\">@telegram</a>",
  "isEdited": true,
  "isService": false,
  "serviceText": null,
  "authorName": "Pavel Durov",
  "authorUrl": "https://t.me/durov",
  "authorAvatarUrl": "https://cdn4.telesco.pe/file/example-author.jpg",
  "authorSignature": "Pavel Durov",
  "views": 5850000,
  "viewsRaw": "5.85M",
  "viewsApproximate": true,
  "reactions": [
    {
      "emoji": "👍",
      "emojiId": null,
      "emojiImageUrl": "https://telegram.org/img/emoji/40/F09F918D.png",
      "count": 219000,
      "countRaw": "219K",
      "isPaid": false,
      "isApproximate": true
    }
  ],
  "totalReactions": 412838,
  "paidReactions": 65900,
  "reactionsApproximate": true,
  "estimatedEngagementRatePercent": 7.0571,
  "estimatedViewsPerSubscriberPercent": 51.7699,
  "urls": [
    "https://telegram.org/blog/example",
    "https://example.com/source",
    "https://t.me/s/durov?q=%23privacy",
    "https://t.me/telegram"
  ],
  "externalUrls": [
    "https://example.com/source"
  ],
  "telegramUrls": [
    "https://telegram.org/blog/example",
    "https://t.me/s/durov?q=%23privacy",
    "https://t.me/telegram"
  ],
  "mentions": ["@telegram"],
  "hashtags": ["#privacy"],
  "customEmojis": [
    {
      "emojiId": "5368324170671202286",
      "emoji": "🔐",
      "imageUrl": "https://telegram.org/img/emoji/40/F09F9490.png"
    }
  ],
  "mediaType": "album",
  "media": [
    {
      "type": "photo",
      "url": "https://cdn1.telesco.pe/file/example-photo.jpg",
      "thumbnailUrl": "https://cdn1.telesco.pe/file/example-photo.jpg",
      "sourceUrl": "https://t.me/durov/518?single",
      "durationSeconds": null,
      "width": 600,
      "height": 800,
      "aspectRatio": 0.75,
      "fileName": null,
      "fileSizeText": null,
      "mimeType": "image/jpeg",
      "storeKey": "TG_MEDIA_durov_518_0_6f88af4a31897b67.jpg",
      "storeContentType": "image/jpeg",
      "storeSizeBytes": 184225
    }
  ],
  "mediaCount": 3,
  "isAlbum": true,
  "hasUnsupportedMedia": false,
  "photoUrls": [
    "https://cdn1.telesco.pe/file/example-photo.jpg"
  ],
  "videoUrl": "https://cdn1.telesco.pe/file/example-video.mp4",
  "videoThumbnailUrl": "https://cdn1.telesco.pe/file/example-video-thumbnail.jpg",
  "videoDurationSeconds": 28,
  "mediaWidth": 600,
  "mediaHeight": 800,
  "mediaAspectRatio": 0.75,
  "mediaStoredCount": 1,
  "mediaStoreKeys": [
    "TG_MEDIA_durov_518_0_6f88af4a31897b67.jpg"
  ],
  "linkPreview": {
    "url": "https://telegram.org/blog/example",
    "siteName": "Telegram",
    "title": "Telegram privacy update",
    "description": "New privacy and security controls are available.",
    "imageUrl": "https://cdn4.telesco.pe/file/example-preview.jpg",
    "videoUrl": null,
    "videoThumbnailUrl": null,
    "videoDurationSeconds": null
  },
  "forwardedFrom": {
    "name": "Telegram Geeks",
    "url": "https://t.me/geekschannel/1023"
  },
  "replyTo": {
    "messageId": 500,
    "messageUrl": "https://t.me/durov/500",
    "authorName": "Pavel Durov",
    "text": "Earlier message text",
    "isQuote": true
  },
  "poll": {
    "question": "Which topic should be covered next?",
    "type": "Anonymous Poll",
    "isMultiple": true,
    "totalVoters": 611000,
    "totalVotersRaw": "611K",
    "totalVotersApproximate": true,
    "optionsUrl": "https://t.me/durov/518",
    "options": [
      {
        "text": "Privacy",
        "percent": 63.0
      }
    ]
  },
  "commentsCount": 12500,
  "commentsCountRaw": "12.5K",
  "commentsCountApproximate": true,
  "commentsUrl": "https://t.me/durov/518?comment=100",
  "scrapedAt": "2026-08-08T04:45:00Z"
}
```

### What Telegram data can you extract?

| Category | Fields |
| --- | --- |
| Row identity | `recordType`, `scrapedAt` |
| Channel identity | `channelId`, `username`, `title`, `channelUrl`, `previewUrl`, `description`, `avatarUrl`, `verified`, `available` |
| Channel audience and content counters | `subscriberCount`, `subscriberCountRaw`, `subscriberCountApproximate`, `photoCount`, `photoCountRaw`, `photoCountApproximate`, `videoCount`, `videoCountRaw`, `videoCountApproximate`, `linkCount`, `linkCountRaw`, `linkCountApproximate` |
| Recent channel analytics | `latestMessageId`, `latestMessageDate`, `recentPostsSampleSize`, `recentAverageViews`, `recentMedianViews`, `recentAverageReactions`, `recentEngagementRatePercent` |
| Message context | `channelUsername`, `channelTitle`, `channelSubscriberCount`, `channelSubscriberCountRaw`, `messageId`, `messageUrl`, `date`, `displayedTime`, `searchTerm`, `searchRank` |
| Message content and authorship | `text`, `textHtml`, `isEdited`, `isService`, `serviceText`, `authorName`, `authorUrl`, `authorAvatarUrl`, `authorSignature` |
| Views and engagement | `views`, `viewsRaw`, `viewsApproximate`, `reactions`, `totalReactions`, `paidReactions`, `reactionsApproximate`, `estimatedEngagementRatePercent`, `estimatedViewsPerSubscriberPercent` |
| Entities and links | `urls`, `externalUrls`, `telegramUrls`, `mentions`, `hashtags`, `customEmojis`, `linkPreview` |
| Media | `mediaType`, `media`, `mediaCount`, `isAlbum`, `hasUnsupportedMedia`, `photoUrls`, `videoUrl`, `videoThumbnailUrl`, `videoDurationSeconds`, `mediaWidth`, `mediaHeight`, `mediaAspectRatio`, `mediaStoredCount`, `mediaStoreKeys` |
| Message relationships | `forwardedFrom`, `replyTo`, `poll`, `commentsCount`, `commentsCountRaw`, `commentsCountApproximate`, `commentsUrl` |

Nested `reactions` include `emoji`, `emojiId`, `emojiImageUrl`, `count`, `countRaw`, `isPaid`, and `isApproximate`. Nested `media` include `type`, `url`, `thumbnailUrl`, `sourceUrl`, `durationSeconds`, `width`, `height`, `aspectRatio`, `fileName`, `fileSizeText`, `mimeType`, `storeKey`, `storeContentType`, and `storeSizeBytes`. Storage fields are `null` unless `downloadMedia` successfully stores that file. Nested objects use exactly the keys shown in the complete message example.

The default dataset has one `Results` view containing every documented channel and message field. Unavailable fields remain visible as `null` or empty arrays; no reduced “overview” hides the rest of the record.

### Input parameters

| Parameter | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `mode` | string | Yes | `channelInfo` | `channelInfo`, `messages`, or `searchMessages` |
| `channels` | string\[] | Yes | `["durov"]` | Up to 100 public usernames, `@usernames`, `t.me/name`, or `t.me/s/name` URLs |
| `searchTerms` | string\[] | In `searchMessages` | `["Telegram"]` | Up to 20 keywords or phrases, searched separately in every channel |
| `maxMessages` | integer | No | `100` | Maximum 1-5,000 messages per channel, or per channel and term in search mode |
| `maxConcurrency` | integer | No | `5` | Independent channels or channel-term pairs processed in parallel, from 1 to 20 |
| `afterMessageId` | integer | No | — | Exclusive global message-ID checkpoint used for incremental runs |
| `afterMessageIds` | object | No | — | Per-channel exclusive checkpoints; values override `afterMessageId` |
| `dateFrom` | string | No | `""` | Inclusive ISO 8601 start date or timestamp |
| `dateTo` | string | No | `""` | Inclusive ISO 8601 end date or timestamp |
| `includeServiceMessages` | boolean | No | `true` | Include channel service records when publicly visible |
| `mediaMode` | string | No | `urls` | `urls` extracts public media metadata; `none` returns empty media fields |
| `downloadMedia` | boolean | No | `false` | Store public message media files in the run Key-value store; requires `mediaMode: "urls"` |
| `maxMediaFiles` | integer | No | `100` | Run-wide stored-file limit from 1 to 5,000 |
| `maxMediaSizeMb` | integer | No | `25` | Maximum accepted size for one stored media file, from 1 to 100 MB |

### Use cases

#### Channel discovery and competitive research

Compare subscriber audiences, verification, content volume, posting recency, median views, reactions, and recent reaction-to-view rates before selecting channels for partnerships or research.

#### Brand and topic monitoring

Schedule `searchMessages` for product names, executives, campaign phrases, or industry terms. Send finished-run webhooks to Slack, Make, Zapier, or n8n.

#### Public content archives

Collect newest-first public posts with original timestamps, rich HTML, source links, media URLs, replies, forwards, and polls. Store recurring exports in a database or data warehouse.

#### Engagement and content analysis

Aggregate views, reactions, paid reactions, hashtags, external links, media types, and estimated engagement percentages in a spreadsheet or BI tool.

#### Editorial and research workflows

Search long-running channels for statements on a subject, retain direct message URLs for verification, and separate Telegram links from external sources.

### Performance and cost

`channelInfo` normally needs one proxied page request per channel. Message modes fetch the first page and then approximately one additional request per public page of older messages. Pages are parsed and saved incrementally instead of retaining a complete channel history in memory. Independent operations run concurrently up to `maxConcurrency`; pagination inside one channel remains sequential to preserve Telegram's cursor and sticky proxy session.

Date ranges far in the past may require paging through newer posts before reaching the requested period. An `afterMessageId` checkpoint can stop as soon as already-seen history is reached. Search pagination is controlled by Telegram and may stop before `maxMessages` is reached.

Actual run cost depends on the Actor's current Store pricing, Apify platform usage, residential proxy traffic, dataset writes, number of channels, and number of pages visited. Check the Actor's **Pricing** tab before a large run. No fixed speed or cost is claimed until repeatable Apify production measurements are available.

| Workload | Main request pattern | Cost consideration |
| --- | --- | --- |
| Channel profiles | About one public page per channel | Lowest request volume |
| Recent messages | Initial page plus older-message pages | Increases with requested messages |
| Historical date range | Pages newer than the range may also be visited | Can require substantially more proxy traffic |
| Keyword search | Separate search and pagination for every channel-term pair | Multiplies with channels and terms |
| Stored media | One proxied media download, KVS write, and `media-file` event per successful file | Keep disabled unless durable files are required |

#### Pay-per-event billing

Only successfully delivered data is billed. Failed channels, retries, empty searches, logs, and `RUN_SUMMARY` are not billable dataset results.

| Event | Charged when |
| --- | --- |
| `channel-info` | One public channel profile is successfully saved |
| `message` | One regular channel-history message is successfully saved |
| `search-result` | One native in-channel keyword match is successfully saved |
| `media-file` | One optional public media file is successfully stored in the Key-value store |

The Actor pricing configuration should not include `error`, `apify-actor-start`, or `apify-default-dataset-item` events. Check the Actor's **Pricing** tab for current tier prices before a large run.

### API usage

Replace `YOUR_USERNAME` with the Actor owner's Apify username after publication.

```bash
curl -X POST \
  "https://api.apify.com/v2/acts/YOUR_USERNAME~telegram-channel-scraper/runs?token=APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "mode": "searchMessages",
    "channels": ["durov"],
    "searchTerms": ["privacy"],
    "maxMessages": 50
  }'
```

Dataset results can be connected to Google Sheets, Make, Zapier, n8n, Airbyte, webhooks, Slack, or a data warehouse using standard Apify integrations.

### Best for / not for

**Best for:** public broadcast channels, channel profile monitoring, public message archives, topic search, media-link collection, engagement research, and scheduled monitoring.

**Not for:** private channels, invite-only chats, group message history, member lists, authenticated Telegram search, direct messages, comment-body collection, deleted posts, real-time streaming, or exact unrounded Telegram metrics.

### Limits and good to know

- Only one mode runs at a time.
- `maxMessages` is limited to 5,000 and applies per channel or per channel-term pair, not globally.
- The source must expose a public `https://t.me/s/username` preview. Public groups usually show only a join page and are not scraped as message feeds.
- Telegram decides how much public history and how many keyword matches are available. Deleted, restricted, age-gated, copyright-blocked, or unsupported posts may be absent.
- `dateFrom` and `dateTo` filter message timestamps, but reaching an old date can require paging through newer history.
- Telegram frequently displays `K`, `M`, or `B` counts. The Actor preserves the displayed value in `*Raw`, parses a numeric estimate, and marks rounded values with `*Approximate`.
- Public CDN media URLs can contain temporary tokens and may expire. Download or process them promptly when needed.
- `mediaMode: none` keeps the stable 60-field message shape but returns `media: []`, `photoUrls: []`, `mediaStoreKeys: []`, and `null` primary-media fields.
- Missing optional source data is represented by `null`, `false`, `0`, or an empty array according to field type; output fields are not silently removed.
- Failed channels are written to logs and `RUN_SUMMARY`, not mixed into the user dataset. Other inputs continue. If every operation fails because of network, proxy, blocking, or parsing errors, the run fails after recording diagnostics.
- All requests use built-in Apify Residential Proxy. Sticky sessions preserve continuity while paging through a channel; a new proxy session is selected after retryable errors. Direct Telegram traffic is disabled.
- `RUN_SUMMARY.nextAfterMessageIds` contains reusable per-channel checkpoints based only on successfully saved messages.
- Media downloads are optional, bounded by count and size, and may fail independently while the message row and original public URL remain available.

### Frequently asked questions

#### What input should I provide?

Provide a public username such as `durov`, `@durov`, `https://t.me/durov`, or `https://t.me/s/durov`. Private invite links such as `t.me/+...` are rejected.

#### Do I need a Telegram account, phone number, bot token, or API credentials?

No. The Actor reads Telegram's public channel preview pages and does not sign in to Telegram.

#### How many messages can I extract?

You can request 1-5,000 messages per channel or channel-term pair. The actual result can be smaller when Telegram exposes less public history, a date range removes messages, a search has fewer matches, or posts were deleted.

#### Does keyword search scan downloaded text locally?

No. `searchMessages` uses Telegram's public within-channel search and follows its server-provided pagination. This keeps matches consistent with the public website but also inherits its availability limits.

#### Why do some numeric values have an approximate flag?

Telegram displays large public metrics in abbreviated form, such as `11.3M` or `65.9K`. The Actor cannot recover precision that the public page does not provide, so it returns the raw label, a parsed estimate, and an approximation flag.

#### Can it scrape comments or public groups?

Channel posts may expose a comment count and discussion URL. Comment bodies and public-group message history are not reliably available through Telegram's public `/s/` channel pages and are not collected.

#### Can I process multiple channels and search terms?

Yes. Up to 100 channels can be processed in one run. Search mode accepts up to 20 terms and executes every channel-term combination independently.

#### Can I schedule recurring runs?

Yes. Use Apify schedules for recurring jobs and webhooks to notify another system when a run completes. Copy `RUN_SUMMARY.nextAfterMessageIds` into the next run's `afterMessageIds` to request only newer message IDs.

#### Can the Actor store Telegram media files?

Yes. Set `downloadMedia: true` with `mediaMode: "urls"`. Successfully stored files appear in the run Key-value store, and their record keys are returned in `media[].storeKey` and `mediaStoreKeys`. File storage is bounded by `maxMediaFiles` and `maxMediaSizeMb` and is billed separately from message rows.

#### Where are failed inputs reported?

Failures are logged and summarized in the `RUN_SUMMARY` Key-value store record. They are deliberately excluded from the `Results` dataset so exported data contains only usable channel and message records.

#### Do I need to configure a proxy?

No proxy field is exposed. The Actor always creates and verifies an Apify Residential Proxy configuration internally and refuses to send direct traffic.

### Responsible use

This Actor extracts publicly available Telegram channel information. Users are responsible for complying with applicable laws, privacy regulations, contractual obligations, and Telegram's terms. Avoid collecting or redistributing personal data without a lawful purpose.

Telegram is a trademark of its respective owner. This Actor is independent and is not affiliated with, endorsed by, or sponsored by Telegram.

### Support

If you encounter a problem, create an issue in the Actor's **Issues** tab. Include the Apify run ID, the mode, a non-sensitive public channel input, and what you expected to receive. Do not post private invite links, credentials, tokens, or personal data.

### Local development

```bash
pip install -r requirements.txt
python -m unittest discover -s tests -v
python -m my_actor
```

Local Actor execution still requires a working Apify Proxy configuration. The scraper intentionally has no direct-network fallback.

# Actor input Schema

## `mode` (type: `string`):

Choose whether to return channel profiles, message history, or messages matching keywords.

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

Public Telegram usernames, @usernames, or t.me URLs. Up to 100 channels per run.

## `searchTerms` (type: `array`):

Keywords or phrases required in searchMessages mode. Each term is searched separately in every channel.

## `maxMessages` (type: `integer`):

Maximum messages per channel in messages mode, or per channel and search-term pair in searchMessages mode.

## `maxConcurrency` (type: `integer`):

Number of independent channels or channel/search-term pairs processed in parallel.

## `afterMessageId` (type: `integer`):

Optional exclusive global checkpoint. Only messages with a larger Telegram message ID are saved.

## `afterMessageIds` (type: `object`):

Optional JSON object mapping channel usernames or URLs to exclusive last-seen message IDs. Per-channel values override the global checkpoint.

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

Optional inclusive ISO 8601 date or timestamp, for example 2025-01-01.

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

Optional inclusive ISO 8601 date or timestamp, for example 2025-12-31.

## `includeServiceMessages` (type: `boolean`):

Include public service records such as channel-created or channel-name-changed messages.

## `mediaMode` (type: `string`):

Return public media URLs and metadata, or omit media extraction for smaller records.

## `downloadMedia` (type: `boolean`):

Download public message media through Apify Proxy and save successful files to the run Key-value store. Each saved file is a separate paid event.

## `maxMediaFiles` (type: `integer`):

Run-wide safety limit used only when Store media files is enabled.

## `maxMediaSizeMb` (type: `integer`):

Skip individual public media files larger than this limit.

## Actor input object example

```json
{
  "mode": "channelInfo",
  "channels": [
    "durov"
  ],
  "searchTerms": [
    "Telegram"
  ],
  "maxMessages": 100,
  "maxConcurrency": 5,
  "dateFrom": "",
  "dateTo": "",
  "includeServiceMessages": true,
  "mediaMode": "urls",
  "downloadMedia": false,
  "maxMediaFiles": 100,
  "maxMediaSizeMb": 25
}
```

# Actor output Schema

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

Public Telegram channel profiles, message records, or in-channel keyword matches.

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

Operation counts, failures, stored media totals, and reusable afterMessageIds checkpoints.

# 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("scrapingmonkey/telegram-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("scrapingmonkey/telegram-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 scrapingmonkey/telegram-scraper --silent --output-dataset

```

## MCP server setup

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