Instagram Profile Email Scraper
Pricing
from $1.00 / 1,000 profile results
Instagram Profile Email Scraper
Extract public email addresses and rich Instagram profile data, including followers, bio, links, verification status, and business metadata. Ideal for lead generation, CRM enrichment, influencer discovery, and automation.
Pricing
from $1.00 / 1,000 profile results
Rating
5.0
(1)
Developer
LanceAPI
Maintained by CommunityActor stats
0
Bookmarked
24
Total users
5
Monthly active users
5 hours ago
Last modified
Categories
Features
Share
Instagram Profile Email Scraper API
Instagram Profile Email Scraper turns Instagram profile URLs into structured public profile data plus any email address that profile publishes — and, when the profile publishes none, reads the website that profile links to and picks up the address there. One dataset item per profile, a clear status for every profile, and two-part pricing: a low base price per returned result, plus an email-found charge only when an address is actually delivered.
No Instagram login, cookies, browser session, proxy or external data-source credentials are required from you.
Key features
- Batch input: one profile URL or up to 100 per run.
- Public email detection: addresses published in the profile's public contact fields, biography and bio links.
- Second pass over the linked website: when the profile itself publishes no address, the Actor reads the site it links to — contact page, about page, and the privacy or terms page small shops use instead — and keeps the address it finds there. Client-side sites are handled by reading their JavaScript bundle.
- Structured enrichment: username, user ID, display name, category, followers, following, account flags, links and avatar URLs when available.
- One item per profile, always: every valid unique input produces exactly one dataset item, including failures — so a batch never silently loses a row.
- Explicit statuses: eight documented statuses and a stable
errorCode, so a downstream job can branch without parsing prose. - Ready-made views: All profiles, Emails found, No email found, Failed profiles.
- Run summary: counts written to the run's key-value store as
RUN_SUMMARY.
Quick input example
{"profileUrls": ["https://www.instagram.com/example.creator/"]}
Short output example
{"username": "example.creator","profileUrl": "https://www.instagram.com/example.creator/","displayName": "Example Creator","emails": ["hello@examplecreator.com"],"followersCount": 24500,"followingCount": 312,"category": "Digital creator","status": "EMAIL_FOUND","errorCode": null}
Try it in 3 steps
- Paste one or more Instagram profile URLs.
- Click Start.
- Open the Dataset to inspect or export the results.
Start with one profile to confirm the output format, or process batches of up to 100 profile URLs per run. When you are ready, scale with Apify Tasks, schedules, API calls or integrations.
Table of contents
- What does this Actor do?
- Input
- API usage
- Output
- Complete output field reference
- Status values
- Where are email addresses found?
- Dataset views
- Run summary
- Pricing
- Public data and responsible use
- FAQ
- Limitations
- Support
What does this Actor do?
This Actor is an Instagram profile email scraper for profile-level contact discovery. It accepts Instagram profile URLs (including share links with query strings), resolves each one to a canonical username, retrieves that profile's public data, extracts the addresses the profile publishes, and stores one result item per profile.
It does not search for profiles by keyword, scrape follower lists, search hashtags, read posts or Reels for addresses, follow links that are not published by the profile itself, access private content, or solve CAPTCHA challenges.
Duplicates are collapsed: submitting the same profile twice in one run processes it once and returns one item, so you are never billed twice for the same profile in the same run.
Input
| Field | Type | Required | Description |
|---|---|---|---|
profileUrls | array of strings | Yes | One or more Instagram profile URLs. Query strings are accepted and normalized. Minimum 1, maximum 100. |
scrapeWebsiteContacts | boolean | No | Default true. When a profile publishes no address of its own, visit the website or link-in-bio page listed on that profile and read the addresses it publishes. Adds no extra charge; adds a few seconds for those profiles only. |
maxWebsitePages | integer | No | Default 3, range 1–5. How many pages of a linked site may be read, starting from the homepage. |
websiteTimeoutSeconds | integer | No | Default 15, range 5–30. Total time allowed for one website, including page discovery. A slow site is cut off rather than allowed to hold up the run. |
websiteCrossDomainEmails | boolean | No | Default false. Keep an address found on the site whose domain differs from the site's own (an agency, a parent brand, a press contact) in the website evidence block, but do not add it to emails. Set true to accept those addresses too. |
Accepted forms — all of these resolve to the same profile:
https://www.instagram.com/example.creator/instagram.com/example.creatorhttps://instagram.com/Example.Creator/?igsh=EXAMPLE@example.creatorexample.creator
Rejected forms — a post, Reel, story or any sub-path is not a profile:
https://www.instagram.com/p/CxYz123/https://www.instagram.com/example.creator/reels/
Invalid values never abort the run. They are skipped, counted in the run summary, and listed there so you can correct them. If every submitted value is invalid, the run still succeeds and returns a single INVALID_INPUT item explaining why — the item is the answer, so the run does not have to fail to deliver it.
API usage
The Actor slug is lance_api/instagram-profile-email-scraper.
cURL
curl -X POST "https://api.apify.com/v2/acts/lance_api~instagram-profile-email-scraper/run-sync-get-dataset-items?token=YOUR_TOKEN" \-H "Content-Type: application/json" \-d '{"profileUrls":["https://www.instagram.com/example.creator/"]}'
JavaScript
import { ApifyClient } from 'apify-client';const client = new ApifyClient({ token: 'YOUR_TOKEN' });const run = await client.actor('lance_api/instagram-profile-email-scraper').call({profileUrls: ['https://www.instagram.com/example.creator/'],});const { items } = await client.dataset(run.defaultDatasetId).listItems();console.log(items);
Python
from apify_client import ApifyClientclient = ApifyClient("YOUR_TOKEN")run = client.actor("lance_api/instagram-profile-email-scraper").call(run_input={"profileUrls": ["https://www.instagram.com/example.creator/"]})items = client.dataset(run["defaultDatasetId"]).list_items().itemsprint(items)
Output
One dataset item per valid unique input profile. The order of the fields below is the order they appear in the Dataset.
Full output example
{"actorVersion": "1.0.0","inputUrl": "https://www.instagram.com/example.creator/?igsh=EXAMPLE","profileUrl": "https://www.instagram.com/example.creator/","username": "example.creator","userId": "52410962","eimuId": null,"displayName": "Example Creator","category": "Digital creator","emails": ["hello@examplecreator.com", "partners@examplecreator.com"],"externalUrls": ["https://examplecreator.com"],"bioLinks": [{"title": "Work with us","url": "https://examplecreator.com/contact","linkType": "external"}],"threadsUrl": null,"profilePictureUrl": "https://example.com/profile.jpg","profilePictureUrlHd": "https://example.com/profile-hd.jpg","followersCount": 24500,"followingCount": 312,"isVerified": false,"isPrivate": false,"isBusinessAccount": true,"isProfessionalAccount": true,"businessPhoneNumber": null,"businessContactMethod": "EMAIL","highlightReelCount": 4,"website": {"url": "https://examplecreator.com","finalUrl": "https://examplecreator.com/","status": "ok","reason": null,"httpStatus": 200,"jsSuspected": false,"pagesCrawled": 3,"elapsedMs": 1840,"candidatesTried": 1,"emails": [{"email": "hello@examplecreator.com","method": "mailto","confidence": 0.95,"crossDomain": false,"sourceUrl": "https://examplecreator.com/contact"}],"phones": [],"addresses": []},"status": "EMAIL_FOUND","scrapedAt": "2026-11-05T08:00:00.000Z","error": null,"errorCode": null}
Complete output field reference
Input and identity
| Field | Type | Description |
|---|---|---|
actorVersion | string | Code version that produced the item. |
inputUrl | string | The value you submitted, unchanged. |
profileUrl | string or null | Canonical profile URL. |
username | string or null | Normalized lowercase username. |
userId | string or null | Instagram numeric user ID when available. |
eimuId | string or null | Additional profile identifier when the data source returns one. |
Profile information
| Field | Type | Description |
|---|---|---|
displayName | string or null | Display name or full name. |
category | string or null | Public profile category. |
isVerified | boolean or null | Verified badge. |
isPrivate | boolean or null | Private account flag. |
isBusinessAccount | boolean or null | Business account flag. |
isProfessionalAccount | boolean or null | Professional or creator account flag. |
Contact information
| Field | Type | Description |
|---|---|---|
emails | array of strings | Deduplicated, lowercased, validated public addresses. Empty when none were found. |
businessPhoneNumber | string or null | Public business phone number when the profile publishes one. |
businessContactMethod | string or null | Public contact method, for example EMAIL. |
Audience information
| Field | Type | Description |
|---|---|---|
followersCount | integer or null | Follower count. |
followingCount | integer or null | Following count. |
highlightReelCount | integer or null | Number of highlight reels. |
Profile links and media
| Field | Type | Description |
|---|---|---|
externalUrls | array of strings | Public external URLs returned with the profile. |
bioLinks | array of objects | { title, url, linkType } for each structured bio link. |
threadsUrl | string or null | Threads URL when one is published. |
profilePictureUrl | string or null | Profile picture URL. |
profilePictureUrlHd | string or null | High-resolution profile picture URL. |
Website second pass
| Field | Type | Description |
|---|---|---|
website | object or null | What the linked site published, when it was visited. null when the profile carried no crawlable link, when the pass is switched off, or when the profile already published an address of its own. |
website.url | string or null | Link taken from the profile that was crawled. |
website.finalUrl | string or null | Where the request landed after redirects. |
website.status | string | ok (an address on the site's own domain was found), no_contact (the site was read and publishes nothing usable), error (the page could not be read), skipped (nothing to crawl). |
website.reason | string or null | Why the pass ended the way it did — for example js_rendered_page or parked. |
website.httpStatus | integer or null | HTTP status of the homepage. |
website.jsSuspected | boolean | true when the page body is rendered by JavaScript; its copy then lives in a bundle, which is read separately. |
website.pagesCrawled | integer | Pages that were actually read successfully. |
website.elapsedMs | integer | Time spent on the site. |
website.candidatesTried | integer | How many links from the profile were tried before giving up (1 or 2). |
website.emails | array of objects | Every address the site published, with email, method, confidence, crossDomain and sourceUrl. |
website.phones | array of objects | Phone numbers the site published, with phone, e164, method, confidence and sourceUrl. |
website.addresses | array of objects | Postal addresses the site published, with address, method, confidence and sourceUrl. |
Processing status
| Field | Type | Description |
|---|---|---|
status | string | One of the eight statuses below. |
errorCode | string or null | Stable machine-readable reason. null when status is EMAIL_FOUND or NO_EMAIL_FOUND. |
error | string or null | Human-readable sentence for a failed item. Never contains internal detail. |
scrapedAt | string | ISO 8601 timestamp of when the item was produced. |
Status values
| Status | Meaning | Base result charge? | email-found charge? |
|---|---|---|---|
EMAIL_FOUND | At least one valid public address was delivered. | Yes | Yes |
NO_EMAIL_FOUND | The profile was read, but publishes no address. | Yes | No |
PROFILE_NOT_FOUND | The account does not exist. | Yes | No |
PRIVATE_OR_LOGIN_REQUIRED | The profile is private or behind a login wall. | Yes | No |
BLOCKED_OR_RATE_LIMITED | The data source refused or throttled the request. | Yes | No |
FETCH_FAILED | The profile could not be read right now; retry later. | Yes | No |
INVALID_INPUT | The submitted value is not an Instagram profile URL. | Yes | No |
FREE_USAGE_LIMIT_REACHED | The free-plan allowance for this period is used up. | Yes | No |
On the free plan, profiles are capped at 50 per day, 500 per calendar month and 1000 in total
(UTC), and a single free run accepts at most 20 profiles while only one free run per account
may be in progress at a time. Once any cap is reached, further profiles in that run return
FREE_USAGE_LIMIT_REACHED with a message quoting the exact used/limit numbers of the cap that
applies. Only profiles that were actually looked up count toward the allowance — duplicate inputs,
retries and blocked profiles do not. Paid plans have no such cap. The daily and monthly allowances
reset automatically; nothing needs to be done on your side.
A batch over the 20-profile free ceiling, or a second free run started while another is still
running, processes nothing and returns one FREE_USAGE_LIMIT_REACHED item naming the limit that
was hit. Both cases leave the run itself successful: the refusal is the answer, not a crash, and a
caller reads it from the item's status/errorCode rather than from the run's exit code. Split the
batch, or wait for the running one to finish, and start again.
errorCode narrows the failure without changing the status:
errorCode | Typical status |
|---|---|
PROFILE_NOT_FOUND | PROFILE_NOT_FOUND |
PROFILE_IS_PRIVATE | PRIVATE_OR_LOGIN_REQUIRED |
UPSTREAM_RATE_LIMITED | BLOCKED_OR_RATE_LIMITED |
UPSTREAM_QUOTA_EXHAUSTED | BLOCKED_OR_RATE_LIMITED |
UPSTREAM_AUTH_FAILED | FETCH_FAILED |
UPSTREAM_TIMEOUT | FETCH_FAILED |
UPSTREAM_ERROR | FETCH_FAILED |
MALFORMED_RESPONSE | FETCH_FAILED |
PROFILE_METADATA_UNAVAILABLE | FETCH_FAILED |
FREE_USAGE_LIMIT_REACHED | FREE_USAGE_LIMIT_REACHED |
FREE_BATCH_LIMIT_EXCEEDED | FREE_USAGE_LIMIT_REACHED |
FREE_ACTIVE_RUN_LIMIT | FREE_USAGE_LIMIT_REACHED |
FREE_USAGE_SERVICE_UNAVAILABLE | FETCH_FAILED |
INVALID_PROFILE_URL | INVALID_INPUT |
BATCH_LIMIT_EXCEEDED | INVALID_INPUT |
DATASET_WRITE_FAILED | FETCH_FAILED |
INTERNAL_ERROR | FETCH_FAILED |
New errorCode values may be added over time. Branch on status for stable behaviour; treat errorCode as extra detail.
Where are email addresses found?
Only in fields that belong to the requested profile and are publicly visible, plus the website that same profile publishes:
- Public contact fields on the profile (for example a published business or contact email).
- The biography text.
- The bio link URL, when the link itself is a
mailto:or contains an address. - The title of a bio link, when the title is an address.
- The profile's own website, and only when steps 1–4 produced nothing. The Actor reads the homepage and the contact, about or imprint pages it links to, decodes
mailto:links,[at]/[dot]obfuscation, JSON-LD and microdata, and — for client-side sites whose HTML is an empty shell — the site's own JavaScript bundle. If those still yield nothing, it takes one more look at the refund, privacy or terms page, because small shops often publish their only address there.
Addresses from the website are added to the same emails list and are marked in website.emails with the page they came from (sourceUrl) and the method that proved them. An address on a different domain than the site — an agency, a payment processor, a regulator named in a privacy notice — stays in the evidence block but is not added to emails, unless you set websiteCrossDomainEmails: true.
Every address is normalized to lowercase, de-duplicated and format-checked. Obvious non-addresses are dropped: template placeholders such as your@email.com, documentation and example domains, and strings that only look like addresses.
Addresses are never mined from unrelated parts of the profile data. A profile that publishes nothing on Instagram and links to a site that publishes nothing either yields NO_EMAIL_FOUND — that outcome is a normal, correctly billed result rather than an error.
Dataset views
The dataset ships with four views, so you do not have to build filters:
| View | What it shows |
|---|---|
| All profiles | Every item, with username, profileUrl, emails, status, displayName, followersCount, category first. |
| Emails found | Only EMAIL_FOUND items, with the addresses and the core profile fields. |
| No email found | Profiles that were read successfully but publish nothing. |
| Failed profiles | Everything with an errorCode, including the public error sentence. |
Run summary
Each run writes a RUN_SUMMARY record to its key-value store, also linked from the run's Output tab. It contains:
status:SUCCEEDED,PARTIALorFAILED;input: how many values were submitted, how many were unique, how many were duplicates, how many were invalid (with the invalid values listed), how many were processed, and how many were left unprocessed;output: dataset rows,EMAIL_FOUND,NO_EMAIL_FOUND,PROFILE_NOT_FOUND,PRIVATE_OR_LOGIN_REQUIRED,BLOCKED_OR_RATE_LIMITED,FETCH_FAILED,FREE_USAGE_LIMIT_REACHED, total addresses, addresses that came from a linked website, how many profiles had their website visited, total failures;errorTaxonomy: a count pererrorCode;notes: present only when something needs explaining — for example, profiles skipped because the run reached the maximum charge set for it.input.notProcessedcounts those, and they are never added tooutput.failed;settings: the per-run profile cap and the concurrency used.
It is safe to log or forward: it contains business counts only.
Pricing
Two events, unchanged from previous versions of this Actor.
| Event | When it is charged | Price |
|---|---|---|
| Base result | Once per dataset item, charged automatically by Apify | $0.001 per item |
email-found | Once per profile whose item delivers at least one address | $0.006 (FREE) / $0.004 (BRONZE) / $0.0035 (SILVER) / $0.003 (GOLD, PLATINUM, DIAMOND) |
What this means in practice:
- Several addresses in one profile = one
email-foundcharge. Two addresses are not two charges. - No address = no
email-foundcharge. You still pay the base result price for the item. - One profile per run = at most one
email-foundcharge, even if the profile is submitted twice. - The website pass adds no extra charge. An address it finds is charged as the same single
email-found, and if it finds nothing you pay nothing for the attempt. - Retries and any internal recovery cost you nothing extra. Only the two events above are ever billed.
- A failed item can never carry an
email-foundcharge, because the charge is raised only after the item has been written to the dataset.
Public data and responsible use
This Actor reads publicly visible profile data. It does not log in, does not bypass privacy settings and does not return anything a logged-out visitor cannot see. You remain responsible for how you use the data: comply with Instagram's terms, applicable privacy law (for example GDPR or CCPA) and anti-spam rules in your jurisdiction. Use the addresses for legitimate business communication and honour opt-out requests.
FAQ
What input do I need?
One or more Instagram profile URLs. Post links, Reel links and other sub-paths are rejected as INVALID_INPUT.
Can I process many profiles at once? Yes, up to 100 unique profiles per run. Duplicates are collapsed and processed once.
Where do you find email addresses? In the profile's own public contact fields, biography and bio links — and, when those publish nothing, on the website that same profile links to. See Where are email addresses found?.
Are the addresses verified? They are validated for format, normalized and de-duplicated, and they are published by the profile itself. They are not SMTP-verified, so an address can still be stale.
Does it work with private profiles?
No. A private profile returns PRIVATE_OR_LOGIN_REQUIRED with no data.
Do I need Instagram cookies or a login? No.
What happens when no email is found?
You get one item with status: "NO_EMAIL_FOUND", the profile fields that were available, and only the base result charge.
How is a paid result counted?
One base result charge per dataset item plus one email-found charge per item that contains at least one address.
Can I use it through an API? Yes — REST, the JavaScript client, the Python client, Tasks, schedules and webhooks all work.
Can I export the results? Yes: JSON, CSV, Excel or XML from the Dataset, or through the API.
Limitations
- Profile-level only: no keyword search, follower lists, hashtags, posts or Reels.
- The website pass reads only the links the profile itself publishes, and only when the profile published no address. It follows no third-party links found on that site.
- A site that hides everything behind a login, a bot challenge (for example a Cloudflare interstitial) or client-side rendering it never exposes in its bundle cannot be read; the
website.statusfield says which of those happened. - One Instagram profile per input value; a share link that points at a post is not a profile.
- Address discovery is limited to what the profile and its own site publish; many creators publish nothing.
- Availability depends on public profile data being reachable. A blocked or throttled request returns
BLOCKED_OR_RATE_LIMITED, and the same profile may succeed on a later run. - Very large batches can take a while; the run summary reports progress and outcomes when it finishes.
Support
Open an issue from the Actor's Issues tab with the run ID and the affected profile URL. Please do not paste tokens or private data into an issue.