# Telegram Lead Extractor — Emails & Contacts (`apt_marble/telegram-lead-extractor`) Actor

Turn public Telegram channels into a contact list. Every email address, phone number, website and handle a channel has published comes back as its own row, tied to the post it came from and ranked by confidence. Deduplicated per channel. No Telegram account or login needed.

- **URL**: https://apify.com/apt\_marble/telegram-lead-extractor.md
- **Developed by:** [Hamza](https://apify.com/apt_marble) (community)
- **Categories:** Lead generation, Social media
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $4.00 / 1,000 lead rows

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/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 Lead Extractor — Emails & Contacts

Paste a list of public Telegram channels and get back a contact list. This actor
reads what each channel has published about itself and inside its posts, and
returns every email address, phone number, website and linked handle it finds —
one row per contact, deduplicated per channel, each one carrying the post it came
from and the wording it sat in, so you can check any row in a second. No Telegram
account, no phone number, no login, nothing to install.

> **Use this lawfully.** Contact details are personal data. This actor only ever
> reads what a channel has chosen to publish in public, and it touches nothing
> private. You are responsible for having a lawful basis to store and use what
> you collect, for honouring marketing and opt-out rules in your market, and for
> deleting anything you should not keep. If you are not sure, do not run it.

### What you can do with it

- **Build a media buying list.** Most channels that sell advertising say so in
  their own description, with the address or handle to write to. A run across a
  few hundred channels turns that into a spreadsheet.
- **Find the human behind a channel.** Press addresses, feedback bots, editor
  handles and support lines are all published in the open by channels that want
  to be reached.
- **Enrich a list you already have.** Feed in channels you are tracking and get
  their website, their team handles and the audience size next to each one.
- **Qualify before you reach out.** Every row says how many followers the channel
  has and whether the detail came from the channel's own description or from a
  single post, so you can work the strongest ones first.
- **Audit your own footprint.** Run it over your channels to see exactly which
  contact details you have left visible to the public.
- **Keep it clean.** Rows are deduplicated per channel and counted, so a detail
  published once and repeated twenty times arrives as one row that says twenty.

### What you get

One row per contact detail per channel. Abridged examples, with the field names
the actor really emits:

```json
[
  {
    "channel": "varlamov",
    "channelTitle": "Илья Варламов",
    "channelId": -1005684212,
    "subscriberCount": 238954,
    "subscriberCountIsExact": true,
    "contactType": "website",
    "contactValue": "links.varlamov.ru",
    "domain": "links.varlamov.ru",
    "isVerifiedContact": false,
    "source": "channel_description",
    "confidence": "high",
    "hasContactIntent": false,
    "sourceContext": "Соцсети и YouTube: links.varlamov.ru/p/6a1270",
    "messageId": null,
    "postDate": null,
    "occurrences": 1,
    "scrapedAt": "2026-08-05T09:12:44.108Z"
  },
  {
    "channel": "citydealsdaily",
    "channelTitle": "City Deals Daily",
    "subscriberCount": 41207,
    "subscriberCountIsExact": true,
    "contactType": "email",
    "contactValue": "hello@example.com",
    "domain": "example.com",
    "isVerifiedContact": true,
    "source": "post",
    "confidence": "medium",
    "hasContactIntent": true,
    "sourceContext": "…For advertising enquiries write to hello@example.com before Friday…",
    "messageId": 4471,
    "postDate": "2026-08-03T11:42:00.000Z",
    "occurrences": 3,
    "scrapedAt": "2026-08-05T09:12:44.108Z"
  }
]
```

Each row also carries `contactValueRaw` (the detail exactly as it was published),
`contactUrl`, `channelUrl`, `postUrl`, `channelType`, `confidenceScore` and
`detectionSignal`; the examples above are trimmed to stay readable.

### Input reference

| Setting | Type | Default | What it does |
| --- | --- | --- | --- |
| Channels | list of links | — | The public Telegram channels to scan. A channel link or an @username, one per line. Private invite links and folder invites are skipped and listed in the run log. Up to 1,000 per run. |
| Contact details to collect | multi-select | all four | Which kinds to collect: email addresses, phone numbers, websites, Telegram handles. |
| Posts to scan per channel | integer | `50` | How many recent posts to read from each channel, on top of its description. `0` scans descriptions only — the fastest and cheapest way to find what a channel advertises about itself. Maximum 1,000. |
| Browse from | select | `United States` | Which country the run browses from. A few channels are only viewable in some countries. |
| Parallel channels | integer | `16` | How many channels to work on at the same time. Maximum 32. |

### Output fields

| Field | Type | Meaning |
| --- | --- | --- |
| `contactType` | string | `email`, `phone`, `website` or `handle`. |
| `contactValue` | string | The detail, tidied up: an address in lower case, a number reduced to digits, a website reduced to its domain, a handle with its `@`. |
| `contactValueRaw` | string | The detail exactly as the channel published it, punctuation and spacing included. |
| `contactUrl` | string | A ready-made link for a website or a handle. `null` for addresses and numbers. |
| `domain` | string | The domain of a website or an email address. |
| `isVerifiedContact` | boolean | `true` when the row is an email address or a phone number — a contact you can act on directly. |
| `detectionSignal` | string | For a phone number, what made it a phone number: an international prefix, a bracketed area code, a trunk prefix, or an explicit phone word next to it. |
| `source` | string | `channel_description` when the channel published it about itself, `post` when it appeared inside a post. |
| `confidence` | string | `high`, `medium` or `low`. Highest for a channel's own description; middle for a post that also carries contact wording; lowest for a bare mention inside a post. |
| `confidenceScore` | number | The same judgement as a number between 0 and 1, for sorting. |
| `hasContactIntent` | boolean | Whether wording such as "for enquiries", "advertising" or "get in touch" sits next to the detail. |
| `sourceContext` | string | The sentence the detail was taken from, quoted, so every row is checkable. `null` when the detail was published as a link behind other wording rather than written out in the text — there is then no sentence containing it, and `postUrl` is the row's audit trail instead. |
| `occurrences` | number | How many times this exact detail was seen for this channel. |
| `messageId` | number | The number of the post the detail came from. `null` for a description. |
| `postUrl` | string | Link to that post. `null` for a description. |
| `postDate` | string | When that post was published (ISO-8601). |
| `channel` | string | The channel the detail belongs to. |
| `channelUrl` | string | Link to the channel. |
| `channelTitle` | string | The channel's display name. |
| `channelId` | number | Telegram's own numeric id for the channel — stable even when a channel answers on more than one name. Telegram publishes it alongside posts, so it is present when posts were scanned and `null` in a description-only run and for groups, bots and personal profiles. |
| `channelType` | string | `channel`, `group`, `bot` or `user`. |
| `subscriberCount` | number | How large the audience is, or `null` when Telegram does not publish it. |
| `subscriberCountIsExact` | boolean | `false` when Telegram published that figure in rounded form. |
| `scrapedAt` | string | When the row was collected. |

### Pricing

Pay per result — you pay for what you actually get, and nothing for a channel
that turns out to have published no contact details at all.

| What is charged | Price per 1,000 |
| --- | --- |
| Lead row — one row per contact detail collected | **$4.00** |
| Contact detail found — only when the row is an email address or a phone number | **$10.00** |

Worked example: 100 channels with 50 posts scanned in each, producing 900 contact
rows, of which 120 are email addresses or phone numbers.

- 900 rows × $4.00 / 1,000 = **$3.60**
- 120 reachable contacts × $10.00 / 1,000 = **$1.20**
- **Total: $4.80**

Websites and handles are charged at the row price only. Channels that publish
nothing, and channels that are not publicly viewable, produce no rows and cost
nothing.

### Limits & what this actor cannot do

- **It finds what was published, and nothing else.** There is no lookup of
  private details, no guessing at addresses and no enrichment from anywhere
  outside Telegram. A channel that keeps its contact details to itself returns no
  rows, and that is the honest answer.
- Only **public** Telegram channels and groups are covered. Private channels,
  invite-only groups and one-to-one chats are not publicly viewable and are
  reported as unavailable rather than returned as empty rows.
- Groups, bots and personal profiles have no public post history, so only their
  description is scanned. They are reported as such, not as failures.
- Telegram publishes a limited amount of public history, so very large targets
  return what Telegram makes available rather than an unlimited archive.
- A phone number is only reported when it is written like one — with an
  international prefix, a bracketed area code, a full-length national number or
  an explicit phone word beside it. A bare run of digits is deliberately ignored,
  because dates, prices, post numbers and audience figures all look like phone
  numbers otherwise. That means the occasional oddly formatted number is missed;
  the trade is fewer wrong rows.
- A detail found inside a post is not necessarily the channel's own — it may
  belong to someone the channel wrote about. That is exactly what `confidence`,
  `source` and `sourceContext` are for.
- One channel can answer on more than one name. If you list both names the run
  scans both, but each contact is written and charged **once**, and the log says
  which name it was first collected under. In a description-only run the numeric
  id that matches the two names is not published, so there a repeated channel
  can come back under both names.
- Audience figures are a snapshot at the moment of collection and keep changing
  afterwards. Telegram publishes very large ones in rounded form, so the biggest
  numbers are approximate and `subscriberCountIsExact` says so.
- Some channels are restricted in some countries, so the same channel can return
  a different result depending on the country the run browses from.
- Speed depends on the size of the job and on Telegram's own response times; no
  fixed rate is promised.
- Telegram's terms govern automated access. You are responsible for using the
  data lawfully and in line with the source site's terms, and for handling
  personal data in line with applicable privacy law.

### FAQ

**Do I need a Telegram account?**
No. Nothing is connected to any account, and no account is created.

**Does it need my phone number or login?**
No. There is nothing to sign in to, and none of your own details are used.

**Can I schedule it?**
Yes. Use Apify's scheduler to run it as often as you like — weekly, monthly, or
whatever suits — and each run appends to your dataset.

**Is the data complete?**
It is complete for what the channels themselves have published publicly, which
is the only thing any tool can honestly promise here. Plenty of channels publish
no contact details at all; those return nothing rather than a guess. Private
channels, and details a channel has never published, are out of reach.

**Why do some rows say "low" confidence?**
Because the detail turned up inside a post with nothing around it suggesting
"contact us". It may still be the right lead — it may also be an address quoted
in a news story. Sort by `confidence` and start at the top.

**Is this legal?**
The actor reads only public pages, and it collects only details their owners
chose to publish. What you then do with those details is governed by the privacy
and marketing law that applies to you and to the people behind the channels.
Have a lawful basis, honour opt-outs, and do not use it for unsolicited bulk
messaging where that is prohibited.

**How is this different from scraping a whole channel?**
A channel scraper gives you posts. This one gives you a contact list: it reads
the same public material, throws away everything that is not a way to reach
someone, deduplicates what is left and ranks it.

# Actor input Schema

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

The public Telegram channels to scan. Paste a channel link or an @username — one per line. Private invite links and anything that is not a public channel are skipped and listed in the run log.

## `contactTypes` (type: `array`):

Which kinds of contact detail you want. Leave everything selected to collect the lot. Email addresses and phone numbers are the directly reachable ones; websites and handles are useful for enrichment.

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

How many recent posts to read from each channel, on top of the channel's own description. Set this to 0 to scan descriptions only, which is the fastest and cheapest way to find the contacts a channel advertises about itself.

## `country` (type: `string`):

Which country the run should browse from. Useful because a few channels are only viewable in some countries.

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

How many channels to work on at the same time. Higher finishes a long list sooner; the default is a polite balance.

## Actor input object example

```json
{
  "channels": [
    "@lentachold",
    "@meduzalive",
    "@varlamov"
  ],
  "contactTypes": [
    "email",
    "phone",
    "website",
    "handle"
  ],
  "maxPostsPerChannel": 50,
  "country": "us",
  "maxConcurrency": 6
}
```

# Actor output Schema

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

No description

# 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": [
        "@lentachold",
        "@meduzalive",
        "@varlamov"
    ],
    "contactTypes": [
        "email",
        "phone",
        "website",
        "handle"
    ],
    "maxPostsPerChannel": 50,
    "country": "us",
    "maxConcurrency": 6
};

// Run the Actor and wait for it to finish
const run = await client.actor("apt_marble/telegram-lead-extractor").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": [
        "@lentachold",
        "@meduzalive",
        "@varlamov",
    ],
    "contactTypes": [
        "email",
        "phone",
        "website",
        "handle",
    ],
    "maxPostsPerChannel": 50,
    "country": "us",
    "maxConcurrency": 6,
}

# Run the Actor and wait for it to finish
run = client.actor("apt_marble/telegram-lead-extractor").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": [
    "@lentachold",
    "@meduzalive",
    "@varlamov"
  ],
  "contactTypes": [
    "email",
    "phone",
    "website",
    "handle"
  ],
  "maxPostsPerChannel": 50,
  "country": "us",
  "maxConcurrency": 6
}' |
apify call apt_marble/telegram-lead-extractor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,apt_marble/telegram-lead-extractor"
        }
    }
}

```

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/GbuCgh5mRErw6wjxX/builds/bwvbkKfZUBtQq9HOp/openapi.json
