# Instagram Email Scraper & Phone Finder: Bio, Website, Leads (`moxlade/instagram-email-scraper`) Actor

Instagram profiles to emails and phones, no login: an Instagram email finder and phone number scraper. One lead per profile: the bio email and phone, link in bio, website contact pages, linked TikTok, YouTube, Threads and Telegram bios, category, exact counts, engagement, related profiles.

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

## Pricing

from $2.50 / 1,000 profiles

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

## Instagram Email Scraper & Phone Finder: Bio, Website, Leads

**Instagram profiles to emails and phones, without a login.** Give it usernames or profile links and get one row per profile: the email and phone written in the bio, the external link and every bio link, category and account type, the exact follower, following and post counts, engagement over the recent posts (up to 12), and the related accounts Instagram shows (up to 50). Then the contact finder goes further — it resolves the link in bio (Linktree, Beacons, lnk.bio and 60+ others), crawls the website's contact, about, privacy, terms and imprint pages, and reads the linked TikTok, YouTube, Threads and Telegram bios — and returns every email, phone and WhatsApp number once, each with the page it came from and an MX status. No account, no cookies, proxies handled for you. Pay per contact found.

### A business and a creator, with their contacts

Give it this:

```json
{
  "usernames": [
    "garyvee",
    "zachking"
  ],
  "findContacts": true
}
```

Back comes one row per profile (or a free row that says why there is none) — the fields below are the ones this real row carried; every row has the same 71, and a field the source did not show arrives as null, not as a zero:

```json
{
  "row_type": "profile",
  "input": "mkbhd",
  "input_index": 1,
  "source_kind": "profile",
  "status": "ok",
  "scraped_at": "2026-10-12T10:01:53Z",
  "username": "mkbhd",
  "user_id": "28943446",
  "full_name": "Marques Brownlee",
  "profile_url": "https://www.instagram.com/mkbhd/",
  "biography": "I promise I won't overdo the filters.",
  "external_url": "https://mkbhd.com/",
  "bio_links": [{"url": "https://mkbhd.com/", "title": null, "type": "external"}],
  "is_private": false,
  "is_verified": true,
  "is_business_account": true,
  "is_professional_account": true,
  "account_type": "business",
  "contact_method": "CALL",
  "has_contact_button": true,
  "follower_count": 5287960,
  "following_count": 581,
  "posts_count": 2118,
  "igtv_count": 14,
  "highlight_reel_count": 2,
  "profile_pic_url": "https://scontent-iad3-2.cdninstagram.com/v/t51.82787-19/587817368_18545874469015447_3745195562993032801_n.jpg?stp=dst-jp …",
  "profile_pic_url_hd": "https://scontent-iad3-2.cdninstagram.com/v/t51.82787-19/587817368_18545874469015447_3745195562993032801_n.jpg?stp=dst-jp …",
  "email": "contact@support.mkbhd.com",
  "emails": ["contact@support.mkbhd.com", "business@mkbhd.com"],
  "contacts": [
    {
      "type": "email",
      "value": "contact@support.mkbhd.com",
      "source": "website",
      "source_url": "https://mkbhd.com/pages/contact",
      "raw": "contact@support.mkbhd.com",
      "also_on": null,
      "mx": "ok"
    },
    "… 1 more"
  ],
  "contacts_count": 2,
  "contact_sources": ["website", "youtube"],
  "website_url": "https://mkbhd.com/",
  "website_domain": "mkbhd.com",
  "website_title": "MKBHD",
  "website_pages_read": [{"url": "https://mkbhd.com/", "type": "home", "status": "ok", "contacts": 0}, "… 4 more"],
  "website_read_from": "live",
  "website_read_at": "2026-10-12T10:01:58Z",
  "linked_accounts": {"youtube": "https://www.youtube.com/channel/UCBJycsmduvYEL83R_U4JriQ", "instagram": "https://instagram.com/MKBHD", "twitter": "https://twitter.com/MKBHD", "discord": "ht …,
  "networks_read": [{"network": "youtube", "url": "https://www.youtube.com/channel/UCBJycsmduvYEL83R_U4JriQ", "status": "ok", "read_from": "live", "contacts": 1}],
  "recent_posts_read": 12,
  "avg_likes": 206663.4,
  "avg_comments": 1908.4,
  "median_likes": 92786,
  "engagement_rate": 0.039443,
  "avg_video_views": 4133704.4,
  "last_post_at": "2026-10-02T20:50:14Z",
  "posts_per_week": 0.66,
  "related_profiles": [{"username": "tomcruise", "user_id": "6970394928", "full_name": "Tom Cruise", "is_verified": true, "is_private": false}, "… 38 more"],
  "related_count": 39,
  "record_source": "web_profile_info",
  "profile_read_from": "live",
  "profile_read_at": "2026-10-12T16:24:58Z"
}
```

The same actor, asked differently:

- **Just the bio and the profile, no website crawl** — `{"usernames": ["nasa", "redbull"], "findContacts": false}`
- **Expand a seed into its related creators, keep only those with a contact** — `{"usernames": ["garyvee"], "relatedProfilesPerSeed": 20, "onlyWithContact": true, "maxItems": 20}`
- **Watch a list's follower counts from run to run** — `{"usernames": ["garyvee", "zachking"], "stateStoreName": "my-watchlist"}`

### What a row carries

**Three places a contact hides, all read.** The bio text and the recent captions (the email or phone written there, in E.164); the link in bio resolved and its website crawled — the home page plus the contact, about, privacy, terms and imprint pages, following the site's own links first; and the linked TikTok, YouTube, Threads and Telegram bios. Every email, phone and WhatsApp number comes back once, with the exact page it was read from and an MX check on each email.

**The whole public profile, exact.** The pk and username, the exact follower, following and post counts (not the rounded `11K` the page shows), the bio with its mentions and hashtags, the external link and every bio link, the business or creator category, the account type, the verified and private flags, the story-highlight and IGTV counts, how many of the recent posts are reels, and engagement over the recent posts (up to 12; the last 6 on a row whose `record_source` is `graphql`) — average and median likes, average comments, average video views, an engagement rate and a posting rate.

**Honest about the contact button.** Instagram shows a business account's contact-button email and phone to logged-in viewers only. This Actor does not log in, so it does not invent those values: it tells you the account has a contact button and which kind (`TEXT`, `CALL` or `EMAIL`), and finds the real address in the bio, the website and the linked networks instead. What it cannot reach logged out — that button's value, the follower and liker lists, keyword, hashtag and location search, and `About this account` — it never ships as an empty column that reads like a miss.

**Discovery that stays public.** Ask for the related profiles Instagram shows next to each account and get them as full rows of their own (up to 50 a seed), each with `seed_username` so you can see where it came from. It is the one discovery Instagram serves a visitor, and it turns a short seed list into a wide lead list.

**Filters before you pay.** Keep only profiles above or below a follower count, of a given account type, or that have a contact at all — a profile a filter leaves out is a free row with its identity and the reason, never charged. `maxItems` caps the charged rows; a missing, private-empty or unreadable handle is a free row that says which.

**Built for lists and schedules.** Any number of usernames in one run, the same profile given several ways merged into one row. Turn on `onlyNew` with a saved task and each run delivers only the profiles no earlier run did; keep only `stateStoreName` and every run shows how each account's followers moved since the last read. Every row says whether its website contacts were read live or from our cache, and when. Ready-made: [monitor related accounts for new leads](https://apify.com/moxlade/instagram-email-scraper/examples/monitor-instagram-related-accounts-for-new-leads).

**Measured, not promised.** Every figure on this page comes from our own runs. One profile's record is about 50 KB on the wire; the contact finder reads a profile's link in bio, website pages and linked networks in about 1 MB.

### All 71 fields of a row

Every row carries all of them. A field the source did not show arrives as null, never as a zero or an empty string, and the note says when that happens.

| field | type | what it holds |
|---|---|---|
| `row_type` | string | Always `profile` (one row type). |
| `input` | string | The value you gave that this row answers (the first form, when the same profile was given several ways). |
| `input_index` | integer | The 1-based position of the input in your list. |
| `inputs_merged` | array | Other forms of the same profile you gave (a handle and a link), merged into this row. |
| `source_kind` | string | `profile` (a profile you named) or `related` (a related profile read with relatedProfilesPerSeed). |
| `seed_username` | string | On a related-profile row: the profile it was found next to. |
| `status` | string | `ok` = a delivered profile; `not_found`, `invalid`, `failed`, `filtered`, `duplicate` = a free row that says why there is no profile. |
| `status_detail` | string | Why a row is not `ok`, in words. |
| `scraped_at` | string | When this row was read (UTC). |
| `username` | string | The Instagram username, lower-cased. |
| `user_id` | string | Instagram's numeric id of the account, as a string (the `pk`). |
| `full_name` | string | The display name. |
| `profile_url` | string | The profile's link on instagram.com. |
| `biography` | string | The bio text, as written. |
| `bio_mentions` | array | The `@accounts` named in the bio. |
| `bio_hashtags` | array | The `#tags` in the bio. |
| `external_url` | string | The link in the bio (the website or link-in-bio URL Instagram shows). |
| `bio_links` | array | Every bio link: `url`, `title`, `type`. Business and creator accounts can show several. |
| `is_private` | boolean | The account is private (its posts are not served; identity, counts and bio still are). |
| `is_verified` | boolean | The account has Instagram's verified badge. |
| `is_business_account` | boolean | The account is a business account. |
| `is_professional_account` | boolean | The account is a professional account (business or creator). |
| `account_type` | string | `business`, `creator` or `personal`, derived from the account's own flags; `professional` when the record does not say business or creator (a handle the record route answers a schema error for). |
| `category` | string | The business or creator category Instagram shows (`Fitness Trainer`, `Entrepreneur`); null when none. |
| `contact_method` | string | The contact button the account chose: `TEXT`, `CALL` or `EMAIL`; null when there is none. Instagram shows the button's value to logged-in viewers only, so this is the flag, not the address. |
| `has_contact_button` | boolean | The account has a contact button (the value is login-only on Instagram's side; the finder gets the address from the bio and the website instead). |
| `follower_count` | integer | Followers, exact. |
| `following_count` | integer | Following, exact. |
| `posts_count` | integer | Posts, exact (not the rounded `11K` the page shows). |
| `igtv_count` | integer | IGTV videos on the account (Instagram's long-video tab). Not a reels count: the logged-out record carries no reels total. |
| `recent_reels_count` | integer | How many of the recent posts read are reels; null when a video's reel flag is unknown (a row whose `record_source` is `graphql`). |
| `highlight_reel_count` | integer | Story highlights on the profile. |
| `pronouns` | array | The pronouns the account set, when any. |
| `profile_pic_url` | string | Profile picture URL (signed, it expires). |
| `profile_pic_url_hd` | string | High-resolution profile picture URL (signed, it expires). |
| `email` | string | The first email found, for a flat CRM column; `emails` has them all. |
| `phone` | string | The first phone found, in E.164; `phones` has them all. |
| `whatsapp` | string | The first WhatsApp number found, in E.164; `whatsapp_numbers` has them all. |
| `emails` | array | Every email found, each once, across the bio, the captions, the links, the website and the linked networks. |
| `phones` | array | Every phone found, each once, in E.164. |
| `whatsapp_numbers` | array | Every WhatsApp number found, each once, in E.164. |
| `contacts` | array | One object per distinct contact: `type` (email / phone / whatsapp), `value`, `source` (bio / caption / bio_link / link_in_bio / website / tiktok / youtube / threads / telegram), `source_url` (the evidence), `raw` (as written), `also_on` (other places it was seen), and `mx` for an email. |
| `contacts_count` | integer | How many distinct contacts were found (the `contact_found` charge equals this). |
| `contact_sources` | array | The kinds of source the contacts came from. |
| `website_url` | string | The website the link in bio resolved to (the crawl's starting page). |
| `website_domain` | string | The website's registrable domain. |
| `website_title` | string | The website's title or og:site_name. |
| `website_pages_read` | array | Each website page read: `url`, `type` (home / contact / about / privacy / terms / imprint), `status`, `contacts` found on it. |
| `website_read_from` | string | `live` (read this run) or `cache` (kept from an earlier read; see `website_read_at`). |
| `website_read_at` | string | When the website was read (UTC); older than the run when it came from the cache. |
| `link_in_bio_pages` | array | Each link-in-bio page resolved (Linktree and the like): `url`, `host`, `status`, `links`, `contacts`. |
| `linked_accounts` | object | The other-network accounts found (a URL per network: instagram, facebook, twitter, linkedin, tiktok, youtube, threads, telegram and more). |
| `networks_read` | array | Each linked network read for contacts: `network`, `url`, `status`, `read_from`, `contacts`. |
| `finder_note` | string | What the contact finder did or could not do, in words. |
| `recent_posts_read` | integer | How many recent posts the engagement figures are over (up to 12; pinned posts left out). |
| `avg_likes` | number | Average likes over the recent posts. |
| `avg_comments` | number | Average comments over the recent posts. |
| `median_likes` | integer | Median likes over the recent posts. |
| `engagement_rate` | number | (average likes + average comments) / followers, over the recent posts. |
| `avg_video_views` | number | Average video views over the recent video posts. |
| `last_post_at` | string | When the most recent of the read posts was published (UTC). |
| `posts_per_week` | number | Posting rate over the span of the recent posts read (pinned posts left out). |
| `recent_posts` | array | With includeRecentPosts: the recent posts, up to 12 (6 when `record_source` is `graphql`): shortcode, url, display_url (the image; signed, it expires), taken_at, likes, comments, video_views, is_video, is_reel (false for a photo; null for a video the record does not label), is_pinned, caption. |
| `related_profiles` | array | The related profiles Instagram shows for this account (up to 50; fewer or none for some accounts): username, user_id, full_name, is_verified, is_private. |
| `related_count` | integer | How many related profiles the record carried. |
| `followers_previous` | integer | With the monitoring memory on: the follower count the last delivered run saw. |
| `followers_change` | integer | With the memory on: the change in followers since the last delivered run. |
| `previous_read_at` | string | With the memory on: when this profile was last delivered (UTC). |
| `record_source` | string | `web_profile_info` (the full record) or `graphql` (the page's own record, for a handle Instagram answers a schema error for: identity, counts, bio and links, filled with the profile embed's posts count and last 6 posts and Instagram's related suggestions; no category or contact method). |
| `profile_read_from` | string | `live` (Instagram read this run) or `cache` (the profile and its contacts kept from a live read in the last 30 days; see `profile_read_at`). |
| `profile_read_at` | string | When the profile was read live from Instagram; on a `cache` row, the time of that earlier read. |
| `raw` | object | With includeRaw: Instagram's own profile object, without the posts' media payloads. |
| `run_tag` | string | The runTag you set, copied onto every row. |

### Ways to run it

**Read profiles.** Put usernames, @handles or profile links in `usernames`, one per line. Each gives one row with the bio, the links, the counts, the engagement and the bio's own email and phone. The same profile given several ways is one row; a value that is not a profile is a free row that says where it belongs. Ready-made: [exact follower counts and engagement rate for a list](https://apify.com/moxlade/instagram-email-scraper/examples/instagram-follower-counts-and-engagement-rate-for-a-list), [recent posts with likes, comments and image links](https://apify.com/moxlade/instagram-email-scraper/examples/instagram-recent-posts-with-likes-comments-and-image-links).

**Find contacts beyond the bio.** `findContacts` (on by default) resolves the link in bio, crawls the website and reads the linked networks. Choose the website pages with `websitePages` and bound them with `maxWebsitePages`; bound the aggregator's outbound links with `maxLinkInBioLinks`; turn the network bios off with `readLinkedNetworks`. On a creator's or a personal account a bio link is crawled as its website only when the domain carries the account's name: a link to a brand, a shop or a coaching platform is named in `finder_note`, not crawled, so their contacts are never sold as the profile's. Every contact carries its `source` and `source_url`. Ready-made: [email and phone of a business profile](https://apify.com/moxlade/instagram-email-scraper/examples/instagram-email-and-phone-of-a-business-profile), [creators' emails from the bio, link in bio and website](https://apify.com/moxlade/instagram-email-scraper/examples/find-instagram-creators-emails-in-bio-link-in-bio-and-website).

**Expand related profiles.** `relatedProfilesPerSeed` reads that many of the related accounts Instagram shows for each profile, as full rows of their own. It is the one discovery Instagram serves a visitor; the follower and liker lists and keyword, hashtag and location search are login-only and are not offered. Ready-made: [related accounts of a brand as lead rows](https://apify.com/moxlade/instagram-email-scraper/examples/instagram-related-accounts-of-a-brand-as-lead-rows).

**Filter and cap before the charge.** `minFollowers`, `maxFollowers`, `accountTypes` and `onlyWithContact` keep only the profiles you want; a filtered profile is a free row. `maxItems` caps the charged rows. `onlyNew` with `stateStoreName` makes a scheduled run deliver only what is new. Ready-made: [business accounts with 50K+ followers and an email](https://apify.com/moxlade/instagram-email-scraper/examples/instagram-business-accounts-with-an-email-and-50k-followers), [monitor related accounts for new leads](https://apify.com/moxlade/instagram-email-scraper/examples/monitor-instagram-related-accounts-for-new-leads).

### Everything you can ask for

#### What to read

A list of Instagram profiles. Every form is accepted; a value that is not a profile comes back as a free row that says why.

| input | accepts | default | what it changes |
|---|---|---|---|
| `usernames` | `array` | `[]` | Instagram profiles, one per line, in any form: `garyvee`, `@garyvee`, `https://www.instagram.com/garyvee/`, or `instagram.com/garyvee?hl=en`. Each gives one row with the bio, the emails and phones written in it, the external link and every bio link, category and account type, exact counts, engagement over the recent posts (up to 12), and the contact finder's results. The same profile given several ways is one row. A numeric user id or a post or reel link is read too: the id's account, or the post's author (`https://www.instagram.com/p/<code>/`). A value that is not an Instagram profile is a free row that says so. |

#### Related profiles

| input | accepts | default | what it changes |
|---|---|---|---|
| `relatedProfilesPerSeed` | `integer` | `0` | Also read this many of the related profiles Instagram shows for each profile you give (0-50; Instagram shows up to 50, fewer or none for some accounts). Each related profile is a full row of its own, with `source_kind` `related` and `seed_username` set, charged like any profile. 0 reads none; the related accounts are listed on the seed's row either way. |

#### Contact finder

Beyond the bio and the captions: the link in bio resolved, the website crawled, the linked other-network bios read.

| input | accepts | default | what it changes |
|---|---|---|---|
| `findContacts` | `boolean` | `true` | Resolve the link in bio (Linktree, Beacons, lnk.bio and 60+ others), read the website's pages and the linked TikTok, YouTube, Threads and Telegram bios for more emails, phones and WhatsApp numbers. On a creator's or a personal account a bio link is read as its website only when the domain carries the account's name, so a brand's or a platform's contacts are never sold as the profile's. Turn it off to read only the Instagram record (the bio and the captions). |
| `websitePages` | `array` | `[]` | Which of the website's pages the finder reads, beyond the home page. Empty reads all five types. The finder follows the site's own links first and the standard paths second. |
| `maxWebsitePages` | `integer` | `8` | The most pages the finder reads on one profile's website (the home page counts). Most sites yield their contacts in two or three. |
| `maxLinkInBioLinks` | `integer` | `3` | When the bio points at a link aggregator (Linktree and the like), how many of its outbound links the finder checks. One whose domain carries the profile's name (its username or full name) is crawled as its website; a shop, a brand or an affiliate link is named in the row's note, never crawled, so a brand's contacts are never sold as the profile's. The aggregator page itself is always read. |
| `readLinkedNetworks` | `boolean` | `true` | When a profile links a TikTok, YouTube, Threads or Telegram account (in the bio, the bio links or the website), read that account's public bio for contacts too, logged out. A network whose page is a login wall is reported, not promised. |
| `refreshWebsites` | `boolean` | `false` | A profile read in the last 30 days is served from our side with its contacts, and website and network results are kept for repeat runs; every row says where its profile and its website contacts came from (`profile_read_from`, `website_read_from`) and when (`profile_read_at`, `website_read_at`). Turn this on to read every profile, website and network live this run. |

#### Row options

| input | accepts | default | what it changes |
|---|---|---|---|
| `includeRecentPosts` | `boolean` | `false` | Add the recent posts to each row, up to 12 (6 on a row whose `record_source` is `graphql`): shortcode, URL, image link, time, likes, comments, video views, pinned flag, caption. The engagement averages are on every row regardless. |
| `includeRaw` | `boolean` | `false` | Add Instagram's own profile object to each row (`raw`), without the posts' image and video payloads. |

#### Filters (applied before the charge)

A profile a filter leaves out is a free row with its identity and the reason, never charged.

| input | accepts | default | what it changes |
|---|---|---|---|
| `minFollowers` | `integer` | `0` | Deliver only profiles with at least this many followers. 0 = no minimum. |
| `maxFollowers` | `integer` | `0` | Deliver only profiles with at most this many followers. 0 = no maximum. |
| `accountTypes` | `array` | `[]` | Deliver only these account types. Empty keeps all. `business` = a business account, `creator` = a professional non-business account, `personal` = neither. A professional account whose record does not say which (`account_type` `professional`) matches both `business` and `creator`. |
| `onlyWithContact` | `boolean` | `false` | Deliver only profiles for which an email, phone or WhatsApp number was found; a profile with none is a free row. |

#### Monitoring and limits

| input | accepts | default | what it changes |
|---|---|---|---|
| `onlyNew` | `boolean` | `false` | With a saved task and `stateStoreName`, deliver only profiles no earlier run of this memory delivered; a profile skipped this way is a free row. With `relatedProfilesPerSeed`, a skipped seed still gives its new related profiles. To track follower change instead, leave this off and keep `stateStoreName`: every profile is delivered with how its follower count moved since the last read. |
| `stateStoreName` | `string` | `""` | A name for the memory that `onlyNew` and the follower-change columns use, so the same task finds the same memory. Empty derives one from the usernames. |
| `maxItems` | `integer` | `0` | Stop after this many charged profile rows (free rows do not count). 0 = no limit. |
| `runTag` | `string` | `""` | A label copied onto every row of this run, for your own grouping. Optional. |
| `proxyUrl` | `string` | — | Optional. Your own proxy as `scheme://user:pass@host:port`, used in front of the Actor's own exits. Leave empty to use the maintained exits (recommended). |

### If an input can't be used

The run still ends Succeeded and charges nothing. Its status message begins `INPUT REJECTED` and says what to change. The dataset holds a single row, `{"error": true, "code": …, "message": …}`, with no results, and the key-value record `ERROR` repeats it, so an agent should check `error` on the first row. A search that finds nothing is not an error, only an empty dataset. A run marked Failed is a fault on our side, not your input; it costs nothing and reaches us.

### Before you run it

**Does it return the email behind Instagram's contact button?** Not that exact value — Instagram shows a business account's contact-button email and phone to logged-in viewers only, and this Actor does not log in. It tells you the account has a contact button and which kind, and it finds the real email and phone in the bio, the recent captions, the link in bio, the website's pages and the linked TikTok, YouTube, Threads and Telegram bios. On an account whose only contact is behind the button and nowhere public, you get the profile and the button flag, and the row is honest that no address was found.

**Where do the contacts come from, and how do I know?** Every contact carries a `source` and a `source_url`: `bio`, `caption`, `bio_link`, `link_in_bio` (the resolved aggregator page), `website` (with the exact page), or the name of a linked network (`tiktok`, `youtube`, `threads`, `telegram`). The same contact found in two places is listed once, with the other places in `also_on`. Emails carry an `mx` status (`ok`, `none` or `unknown`).

**Does it need a login, a cookie or a proxy?** No. Instagram is read logged out, through maintained exits; the websites and linked networks are read logged out too. There is nothing for you to supply. You may put your own proxy in `proxyUrl` if you prefer; it is used in front of the Actor's own exits.

**What does a run cost, and what is free?** You pay per profile delivered and per distinct contact found. A profile that a filter leaves out, a handle that does not exist, a private account with nothing readable, or a value that is not a profile is a free row that says why, and a run that fails before its first row charges nothing. Before the first request the log says the most the run can cost at your plan's prices.

**Can it find profiles by keyword, hashtag or location, or list a target's followers?** No. Instagram serves those lists to logged-in viewers only, so this Actor does not offer them — it would have to log in, which it does not do. The one discovery it has is the related profiles Instagram shows a visitor, through `relatedProfilesPerSeed`. Give it the handles you already have, or expand them through related profiles.

**Is this legal, and what about personal data?** Everything is read from what Instagram and the linked sites serve a logged-out visitor: no account, no cookie, nothing behind a login. A business or creator profile's public contact details are what the account itself published to be contacted by. Every row is read at run time, so an account or a page that has been removed cannot be returned by a later run. What you may do with the data depends on your purpose and your jurisdiction, and that part is yours, not ours.

### What it costs

**Pay for the profiles you read and the contacts you find.** No start fee, no platform usage billed to you. Prices fall with your Apify plan.

| per 1,000 | Free | Starter | Scale | Business |
|---|---|---|---|---|
| Profile (`profile`) | $5.00 | $4.25 | $3.50 | $2.50 |
| Contact found (`contact_found`) | $4.995 | $4.24575 | $3.4965 | $2.4975 |

A profile row is **$0.005** on the Free plan ($0.0025 on Business), whether a contact is found or not. Each distinct email, phone or WhatsApp number found for it is **$0.004995** more ($0.0024975 on Business), counted once across the bio, the captions, the link in bio, the website and the linked networks. A profile with no contact costs the row only; turn on `onlyWithContact` and it is a free row instead. With `findContacts` off only the contacts written in the bio and the captions are found and charged. Free: a handle that does not exist, a value that is no Instagram profile, a profile a filter leaves out, a duplicate, a profile the exits could not read, and a run that failed before its first row. A run that stops early (a timeout, an abort) is charged for the rows it delivered. `maxItems` counts the charged profile rows; before the first request the log says the most the profile rows can cost at your plan's prices and the price of each contact. Enterprise plans have their own rates; the Actor's Pricing tab shows the price for your plan.

| event | what one event is | per 1,000 |
|---|---|---|
| `profile` | One profile row: identity, exact counts, bio, links, category, account type, the contact-button flag, engagement over 12 posts and the related profiles. Charged only once the row is in your dataset. | $5.00 |
| `contact_found` | One distinct email, phone or WhatsApp number found for a profile, across the bio, the captions, the link in bio, the website and the linked networks. One per distinct contact; a profile with none is not charged for one. | $4.995 |

### Who asks this

**Lead generation and outbound teams** — turn a list of Instagram handles into a contact list: the email and phone from the bio and the website, in E.164 and MX-checked, one row per profile, flat columns for your CRM.

**Influencer and creator marketing** — vet and reach creators: exact followers and engagement, the account type and category, the business contact route, and the email behind the link in bio — plus the related creators Instagram suggests.

**Agencies and researchers** — a wide, deduplicated contact set from a seed list: related profiles expanded, the website and the linked networks read, every contact with its evidence.

**Developers and agents** — one call, typed JSON rows, one row per input in order, a free labelled row for every miss, and a run summary to check a pipeline against.

### Questions

support@moxlade.com

*Every figure on this page was measured on our own runs or read from our own tables, on the date given next to it.*

# Actor input Schema

## `usernames` (type: `array`):

Instagram profiles, one per line, in any form: `garyvee`, `@garyvee`, `https://www.instagram.com/garyvee/`, or `instagram.com/garyvee?hl=en`. Each gives one row with the bio, the emails and phones written in it, the external link and every bio link, category and account type, exact counts, engagement over the recent posts (up to 12), and the contact finder's results. The same profile given several ways is one row. A numeric user id or a post or reel link is read too: the id's account, or the post's author (`https://www.instagram.com/p/<code>/`). A value that is not an Instagram profile is a free row that says so.

## `relatedProfilesPerSeed` (type: `integer`):

Also read this many of the related profiles Instagram shows for each profile you give (0-50; Instagram shows up to 50, fewer or none for some accounts). Each related profile is a full row of its own, with `source_kind` `related` and `seed_username` set, charged like any profile. 0 reads none; the related accounts are listed on the seed's row either way.

## `findContacts` (type: `boolean`):

Resolve the link in bio (Linktree, Beacons, lnk.bio and 60+ others), read the website's pages and the linked TikTok, YouTube, Threads and Telegram bios for more emails, phones and WhatsApp numbers. On a creator's or a personal account a bio link is read as its website only when the domain carries the account's name, so a brand's or a platform's contacts are never sold as the profile's. Turn it off to read only the Instagram record (the bio and the captions).

## `websitePages` (type: `array`):

Which of the website's pages the finder reads, beyond the home page. Empty reads all five types. The finder follows the site's own links first and the standard paths second.

## `maxWebsitePages` (type: `integer`):

The most pages the finder reads on one profile's website (the home page counts). Most sites yield their contacts in two or three.

## `maxLinkInBioLinks` (type: `integer`):

When the bio points at a link aggregator (Linktree and the like), how many of its outbound links the finder checks. One whose domain carries the profile's name (its username or full name) is crawled as its website; a shop, a brand or an affiliate link is named in the row's note, never crawled, so a brand's contacts are never sold as the profile's. The aggregator page itself is always read.

## `readLinkedNetworks` (type: `boolean`):

When a profile links a TikTok, YouTube, Threads or Telegram account (in the bio, the bio links or the website), read that account's public bio for contacts too, logged out. A network whose page is a login wall is reported, not promised.

## `refreshWebsites` (type: `boolean`):

A profile read in the last 30 days is served from our side with its contacts, and website and network results are kept for repeat runs; every row says where its profile and its website contacts came from (`profile_read_from`, `website_read_from`: `live` or `cache`) and when (`profile_read_at`, `website_read_at`). Turn this on to read every profile, website and network live this run.

## `includeRecentPosts` (type: `boolean`):

Add the recent posts to each row, up to 12 (6 on a row whose `record_source` is `graphql`): shortcode, URL, image link, time, likes, comments, video views, pinned flag, caption. The engagement averages are on every row regardless.

## `includeRaw` (type: `boolean`):

Add Instagram's own profile object to each row (`raw`), without the posts' image and video payloads.

## `minFollowers` (type: `integer`):

Deliver only profiles with at least this many followers. 0 = no minimum.

## `maxFollowers` (type: `integer`):

Deliver only profiles with at most this many followers. 0 = no maximum.

## `accountTypes` (type: `array`):

Deliver only these account types. Empty keeps all. `business` = a business account, `creator` = a professional non-business account, `personal` = neither. A professional account whose record does not say which (`account_type` `professional`) matches both `business` and `creator`.

## `onlyWithContact` (type: `boolean`):

Deliver only profiles for which an email, phone or WhatsApp number was found; a profile with none is a free row.

## `onlyNew` (type: `boolean`):

With a saved task and `stateStoreName`, deliver only profiles no earlier run of this memory delivered; a profile skipped this way is a free row. With `relatedProfilesPerSeed`, a skipped seed still gives its new related profiles. To track follower change instead, leave this off and keep `stateStoreName`: every profile is delivered with how its follower count moved since the last read.

## `stateStoreName` (type: `string`):

A name for the memory that `onlyNew` and the follower-change columns use, so the same task finds the same memory. Empty derives one from the usernames.

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

Stop after this many charged profile rows (free rows do not count). 0 = no limit.

## `runTag` (type: `string`):

A label copied onto every row of this run, for your own grouping. Optional.

## `proxyUrl` (type: `string`):

Optional. Your own proxy as `scheme://user:pass@host:port`, used in front of the Actor's own exits. Leave empty to use the maintained exits (recommended).

## Actor input object example

```json
{
  "usernames": [
    "garyvee",
    "https://www.instagram.com/zachking/"
  ],
  "relatedProfilesPerSeed": 0,
  "findContacts": true,
  "websitePages": [],
  "maxWebsitePages": 8,
  "maxLinkInBioLinks": 3,
  "readLinkedNetworks": true,
  "refreshWebsites": false,
  "includeRecentPosts": false,
  "includeRaw": false,
  "minFollowers": 0,
  "maxFollowers": 0,
  "accountTypes": [],
  "onlyWithContact": false,
  "onlyNew": false,
  "stateStoreName": "",
  "maxItems": 0,
  "runTag": ""
}
```

# Actor output Schema

## `results` (type: `string`):

This run's rows, read from its default dataset. One row per profile (or a free row that says why there is none):

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

How the run went: profiles and contacts charged, free rows by status, the finder's counters (websites and network bios read, cache hits), what Instagram does not serve logged out, and the source counters (requests, exits, walls, bytes).

# 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 = {
    "usernames": [
        "garyvee",
        "https://www.instagram.com/zachking/"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("moxlade/instagram-email-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 = { "usernames": [
        "garyvee",
        "https://www.instagram.com/zachking/",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("moxlade/instagram-email-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 '{
  "usernames": [
    "garyvee",
    "https://www.instagram.com/zachking/"
  ]
}' |
apify call moxlade/instagram-email-scraper --silent --output-dataset

```

## MCP server setup

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