Telegram Channel Scraper — New, Edited & Deleted Posts
Pricing
from $1.00 / 1,000 post events
Telegram Channel Scraper — New, Edited & Deleted Posts
Scrape public Telegram channels without an account and get only the changes: new posts, edited posts, deleted posts. Keyless. JSON output with text, media URLs and timestamps for feeds, alerts and OSINT pipelines.
Pricing
from $1.00 / 1,000 post events
Rating
0.0
(0)
Developer
Heim AI
Maintained by CommunityActor stats
1
Bookmarked
2
Total users
1
Monthly active users
19 days ago
Last modified
Categories
Share
Scrape public Telegram channels and get only the changes: new posts, edited posts, and deleted posts. Keyless — reads https://t.me/s/<channel> public preview HTML. No Telegram account, no user-level PII (no members, commenters, phones).
Built for feeds, alerts, OSINT pipelines, and agent workflows that need a stable delta stream instead of re-scraping full timelines.
What it does
Each run polls the channels you list, diffs them against a persistent snapshot keyed by monitorId, and emits:
- new — post id appeared since last poll (or all visible posts on first seen, if
emitOnFirstSeenis true) - edited — same id, content hash changed (text / media type / media count; view ticks and CDN URL churn ignored)
- deleted — id was inside the visible window and has been missing for two consecutive polls that could observe its position (a poll whose window does not reach that far neither confirms nor counts; a transient Telegram page glitch never produces a false deletion; the row carries last-known text). The poll row's
pendingDeletionCountshows first-miss candidates awaiting confirmation.
Plus one poll summary row per successfully checked channel.
Quick start (Console)
- Open the Actor → Input
- Keep the prefill (
telegram,durov) or paste your public handles - Set a stable
monitorId(e.g.prod-alerts) - Run → first run emits baseline
newposts (unlessemitOnFirstSeen: false) - Schedule every 15 minutes with the same
monitorIdto receive only real deltas
Input
| Field | Type | Default | Description |
|---|---|---|---|
channels | string[] | — (required) | Handles: telegram, @durov, t.me/x, https://t.me/s/x. Max 50/run. |
monitorId | string | default | Snapshot namespace. ^[A-Za-z0-9_-]{1,64}$ |
scanDepth | integer | 20 | Posts fetched per channel (20–100). Deletion detection covers this window only. |
emitOnFirstSeen | boolean | true | First poll: emit all visible as new, or silently baseline. |
Output row types
Every row includes type, monitorId, and detectedAt (ISO).
type: "post" (charged post-event)
{"type": "post","event": "new","monitorId": "prod-alerts","channel": "telegram","postId": 28421,"postUrl": "https://t.me/telegram/28421","text": "Hello from Telegram","mediaType": "photo","mediaUrls": ["https://cdn4.telegram-cdn.org/..."],"views": 1200000,"viewsRaw": "1.2M","postedAt": "2026-07-30T12:00:00+00:00","firstSeen": true,"detectedAt": "2026-08-01T05:00:00.000Z"}
Edited (adds previousText):
{"type": "post","event": "edited","monitorId": "prod-alerts","channel": "telegram","postId": 28421,"postUrl": "https://t.me/telegram/28421","text": "Updated caption","previousText": "Hello from Telegram","mediaType": "photo","mediaUrls": ["https://cdn4.telegram-cdn.org/..."],"views": 1200000,"viewsRaw": "1.2M","postedAt": "2026-07-30T12:00:00+00:00","firstSeen": false,"detectedAt": "2026-08-01T05:15:00.000Z"}
Deleted (last-known content from snapshot):
{"type": "post","event": "deleted","monitorId": "prod-alerts","channel": "telegram","postId": 28410,"postUrl": "https://t.me/telegram/28410","text": "fake for delete test","mediaType": null,"mediaUrls": [],"views": null,"viewsRaw": null,"postedAt": "2026-07-29T09:00:00+00:00","firstSeen": false,"detectedAt": "2026-08-01T05:15:00.000Z"}
type: "poll" (charged channel-poll)
{"type": "poll","monitorId": "prod-alerts","channel": "telegram","status": "ok","postsVisible": 20,"windowOldestPostId": 28401,"windowNewestPostId": 28421,"newCount": 1,"editedCount": 0,"deletedCount": 0,"pendingDeletionCount": 0,"firstRun": false,"polledAt": "2026-08-01T05:15:00.000Z","detectedAt": "2026-08-01T05:15:00.000Z"}
type: "error" (uncharged; run still succeeds)
{"type": "error","monitorId": "prod-alerts","channel": "https://t.me/+inviteHash","error": "Invalid channel input \"https://t.me/+inviteHash\" — use a public handle, @handle, or t.me link (not invite links).","errorCode": "invalid_channel","detectedAt": "2026-08-01T05:00:00.000Z"}
errorCode values: invalid_channel, channel_not_found, channel_preview_disabled, fetch_failed, skipped_channel_limit, skipped_charge_limit, invalid_monitor_id, no_channels, input_overflow (more than 200 raw entries), monitor_busy (another live run holds this monitorId — see below), state_corrupt (snapshot state failed validation — polling stops, state untouched).
Concurrency guard (monitor_busy): each monitorId is protected by a server-side lock (Apify request-queue lock API — atomic and ownership-enforced, so exactly one of two overlapping runs can hold it, including takeover of a dead run's expired lock). The losing run records one uncharged monitor_busy error row and exits successfully — schedule ticks that overlap a still-running poll are skipped safely instead of double-billing the same delta. The lock is renewed with ownership verification during long runs and expires automatically (8 min) if a run dies; a run that can no longer verify ownership stops all new charges immediately. Each monitorId keeps one small named request queue (tcd-lock-…) as its lock.
Scheduling recipe
Use Apify Schedules with a fixed monitorId:
- Cron:
*/15 * * * *(every 15 minutes) - Input: same
channels+ samemonitorIdevery time - Idle cost ≈ start fee + one
channel-pollper channel per tick; you only paypost-eventwhen something actually changes
API curl recipe
curl "https://api.apify.com/v2/acts/kaz_kakyo~telegram-channel-delta/runs?token=$APIFY_TOKEN" \-H "Content-Type: application/json" \-d '{"channels": ["telegram", "durov"],"monitorId": "prod-alerts","scanDepth": 20,"emitOnFirstSeen": true}'
Then read the run's default dataset for post/poll/error rows.
MCP / agents
Call this Actor from Apify MCP or any agent that can start an Actor run and read a dataset. Keep monitorId stable across scheduled calls so the agent receives a clean delta stream (not full timelines). Error rows are uncharged and the run succeeds on bad input — safe for autonomous retries.
Honest limitations
- Deletion window — deletions are detectable only while the post id is still inside the visible
scanDepthwindow (~last N posts). Older history is truncated, not reported as deleted. - Deletion latency — a deletion is confirmed on the second consecutive poll where the post is missing (one extra poll interval of latency, by design, to avoid false positives).
- Billing exactness — billing is exactly-once in normal operation, including Apify platform migrations (each billed row settles into the snapshot immediately). In the worst case of a hard crash between a charge and the next state write, the affected channel's current delta can be re-emitted (and re-billed) once on the next run.
- Media-only edits — hash uses text + mediaType + media URL count. Replacing an image without changing type/count/text may be missed.
- Markup drift — t.me public preview HTML can change; selectors may need updates.
- Public preview only — private channels and channels with preview disabled cannot be polled (you get
channel_preview_disabled/channel_not_found). - No user-level data — by design: channel posts only, never members/commenters/profiles/phones.
Pricing
| Event | Price |
|---|---|
Actor start (apify-actor-start) | $0.005 |
Channel poll (channel-poll) | $0.001 |
Post event (post-event) | $0.001 |
Worked example: 5 channels every 15 minutes = 96 runs × ($0.005 start + 5 × $0.001 polls) ≈ $0.96/day idle, plus $0.001 per actual post event. Hourly polling of the same 5 channels ≈ $0.24/day.
FAQ
Why poll rows?
They prove a channel was checked even when nothing changed, and carry window bounds + counts for monitoring/health dashboards.
What happens on the first run?
With emitOnFirstSeen: true (default), every visible post is emitted as new (firstSeen: true) and a snapshot is saved. With false, only the poll row is emitted and the snapshot is baselined silently.
How does monitorId work?
Snapshots live in named KV store telegram-channel-delta-state under key monitor-<monitorId>. Different monitorIds never share state — use separate ids for separate jobs/customers.
Is this against Telegram ToS?
This Actor only fetches public t.me/s/ preview pages (the same HTML any browser can load). No account, no login, no private API, no member scraping.