I Built a Tool That Extracts Emails & Phone Numbers From Instagram Profiles (OSINT Breakdown)

spyboy's avatarPosted by

⚠️ Disclaimer
This article is for educational, OSINT, and security research purposes only.
The code shown collects information Instagram already exposes to authenticated users.
Do not use this for harassment, stalking, or policy violations.


Introduction: Why Instagram Is a Serious OSINT Target

Instagram hosts over 2 billion monthly active users, making it one of the richest platforms for open-source intelligence (OSINT).

Unlike many social networks, Instagram profiles often expose:

  • Business contact emails
  • Public phone numbers
  • Account status flags
  • External websites
  • High-resolution profile pictures
  • Behavioral metadata

For journalists, OSINT analysts, security researchers, fraud investigators, and bug bounty hunters, this metadata is extremely valuable.

In this guide, you’ll learn:

  • What data Instagram exposes
  • How extraction works at a technical level
  • How to safely use a real OSINT tool
  • How to get a valid Instagram session ID
  • How to run the tool properly
  • Risks, limits, and best practices

This is a complete end-to-end walkthrough.


What Information Can This Code Extract?

The tool extracts only what Instagram makes available:

Profile Metadata

  • Username
  • User ID (internal numeric ID)
  • Full name
  • Biography
  • Profile picture (HD URL)
  • External website
  • Follower & following counts
  • Post count
  • Business / verified status
  • Account flags (new, memorialized, WhatsApp linked)

Contact Information (If Public)

  • Public email
  • Public phone number (with country code)

Optional Advanced Insight (Risky)

  • Obfuscated recovery email
  • Obfuscated recovery phone

⚠️ These do not reveal full private data — only masked hints.


How This Tool Works (High Level)

  1. Uses a valid Instagram session ID
  2. Calls internal mobile API endpoints
  3. Parses structured JSON responses
  4. Normalizes the data into clean output
  5. Prints results or exports JSON

No password automation.
No HTML scraping.
No brute force.


Full Working Code (Complete)

📌 Save this file as finsta.py

import argparse
import textwrap
from dataclasses import asdict, dataclass
from json import JSONDecodeError, dumps
from typing import Any, Dict, Optional
from urllib.parse import quote_plus

import requests
from requests import Session
from requests.exceptions import RequestException

try:  # Optional enrichment dependency
    import phonenumbers
    from phonenumbers.phonenumberutil import NumberParseException, region_code_for_country_code
except ImportError:  # pragma: no cover - fallback when lib missing
    phonenumbers = None

    class NumberParseException(Exception):
        """Fallback exception when phonenumbers is unavailable."""

    def region_code_for_country_code(_: int) -> Optional[str]:
        return None

try:  # Optional enrichment dependency
    import pycountry
except ImportError:  # pragma: no cover
    pycountry = None


WEB_PROFILE_URL = "https://i.instagram.com/api/v1/users/web_profile_info/"
USER_INFO_URL = "https://i.instagram.com/api/v1/users/{user_id}/info/"
LOOKUP_URL = "https://i.instagram.com/api/v1/users/lookup/"

WEB_PROFILE_HEADERS = {
    "User-Agent": "iphone_ua",
    "x-ig-app-id": "936619743392459",
}
USER_INFO_HEADERS = {
    "User-Agent": "Instagram 64.0.0.14.96",
}
LOOKUP_HEADERS = {
    "Accept-Language": "en-US",
    "User-Agent": "Instagram 101.0.0.15.120",
    "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
    "X-IG-App-ID": "124024574287414",
    "Accept-Encoding": "gzip, deflate",
    "Host": "i.instagram.com",
    "Connection": "keep-alive",
}
DEFAULT_TIMEOUT = 15


class InstagramAPIError(RuntimeError):
    """Base error for Instagram API interactions."""


class NotFoundError(InstagramAPIError):
    """Raised when a user cannot be found."""


class RateLimitError(InstagramAPIError):
    """Raised when Instagram returns a rate-limit response."""


class InvalidInputError(InstagramAPIError):
    """Raised when the CLI receives invalid input."""


@dataclass
class UserProfile:
    username: str
    user_id: str
    full_name: str
    is_verified: bool
    is_business: bool
    is_private: bool
    follower_count: int
    following_count: int
    media_count: int
    total_igtv_videos: int
    biography: str
    external_url: Optional[str]
    is_whatsapp_linked: bool
    is_memorialized: bool
    is_new_to_instagram: bool
    public_email: Optional[str]
    public_phone_country_code: Optional[str]
    public_phone_number: Optional[str]
    hd_profile_pic_url: Optional[str]

    @classmethod
    def from_payload(cls, payload: Dict[str, Any], user_id: str) -> "UserProfile":
        return cls(
            username=payload.get("username", ""),
            user_id=user_id,
            full_name=payload.get("full_name") or "-",
            is_verified=payload.get("is_verified", False),
            is_business=payload.get("is_business", False),
            is_private=payload.get("is_private", False),
            follower_count=payload.get("follower_count", 0),
            following_count=payload.get("following_count", 0),
            media_count=payload.get("media_count", 0),
            total_igtv_videos=payload.get("total_igtv_videos", 0),
            biography=payload.get("biography") or "",
            external_url=payload.get("external_url"),
            is_whatsapp_linked=payload.get("is_whatsapp_linked", False),
            is_memorialized=payload.get("is_memorialized", False),
            is_new_to_instagram=payload.get("is_new_to_instagram", False),
            public_email=payload.get("public_email"),
            public_phone_country_code=payload.get("public_phone_country_code"),
            public_phone_number=payload.get("public_phone_number"),
            hd_profile_pic_url=(payload.get("hd_profile_pic_url_info") or {}).get("url"),
        )


@dataclass
class LookupInsight:
    message: Optional[str]
    obfuscated_email: Optional[str]
    obfuscated_phone: Optional[str]

    @classmethod
    def from_payload(cls, payload: Dict[str, Any]) -> "LookupInsight":
        return cls(
            message=payload.get("message"),
            obfuscated_email=payload.get("obfuscated_email"),
            obfuscated_phone=payload.get("obfuscated_phone"),
        )

    def has_data(self) -> bool:
        return any([self.message, self.obfuscated_email, self.obfuscated_phone])


class InstagramClient:
    def __init__(self, session_id: str, timeout: int = DEFAULT_TIMEOUT):
        if not session_id:
            raise InvalidInputError("A session ID is required.")
        self.session: Session = Session()
        self.session.cookies.set("sessionid", session_id)
        self.timeout = timeout

    def get_profile(self, search: str, search_type: str) -> UserProfile:
        if search_type == "username":
            user_id = self._fetch_user_id(search)
        else:
            user_id = self._normalize_user_id(search)

        return self._fetch_profile_by_id(user_id)

    def advanced_lookup(self, username: str) -> LookupInsight:
        signed_body = "signed_body=SIGNATURE." + quote_plus(
            dumps({"q": username, "skip_recovery": "1"}, separators=(",", ":"))
        )
        payload = self._request(
            "POST",
            LOOKUP_URL,
            headers=LOOKUP_HEADERS,
            data=signed_body,
        )
        return LookupInsight.from_payload(payload)

    def _fetch_user_id(self, username: str) -> str:
        if not username:
            raise InvalidInputError("Username cannot be empty.")
        payload = self._request(
            "GET",
            f"{WEB_PROFILE_URL}?username={username}",
            headers=WEB_PROFILE_HEADERS,
        )
        try:
            return payload["data"]["user"]["id"]
        except KeyError as exc:
            raise InstagramAPIError("Unexpected response: missing user id.") from exc

    def _fetch_profile_by_id(self, user_id: str) -> UserProfile:
        payload = self._request(
            "GET",
            USER_INFO_URL.format(user_id=user_id),
            headers=USER_INFO_HEADERS,
        )
        user_data = payload.get("user")
        if not user_data:
            raise InstagramAPIError("Instagram did not return user data.")
        return UserProfile.from_payload(user_data, user_id)

    def _normalize_user_id(self, user_id: str) -> str:
        try:
            return str(int(user_id))
        except (TypeError, ValueError) as exc:
            raise InvalidInputError("User ID must be numeric.") from exc

    def _request(self, method: str, url: str, *, headers: Dict[str, str], data: Optional[str] = None) -> Dict[str, Any]:
        try:
            response = self.session.request(
                method,
                url,
                headers=headers,
                data=data,
                timeout=self.timeout,
            )
        except RequestException as exc:
            raise InstagramAPIError("Unable to reach Instagram. Check your connection.") from exc

        if response.status_code == 404:
            raise NotFoundError("User not found.")
        if response.status_code == 429:
            raise RateLimitError("Instagram rate limit reached. Try again later.")
        if response.status_code >= 400:
            raise InstagramAPIError(f"Instagram returned HTTP {response.status_code}.")

        if not response.content:
            return {}

        try:
            return response.json()
        except JSONDecodeError as exc:
            raise InstagramAPIError("Instagram responded with invalid JSON.") from exc


def bool_label(value: bool) -> str:
    return "Yes" if value else "No"


def format_phone(country_code: Optional[str], number: Optional[str]) -> Optional[str]:
    if not country_code or not number:
        return None
    raw = f"+{country_code} {number}"
    try:
        parsed = phonenumbers.parse(raw)
        iso = region_code_for_country_code(parsed.country_code)
        country = pycountry.countries.get(alpha_2=iso) if iso else None
        country_name = country.name if country else "Unknown country"
        return f"{raw} ({country_name})"
    except (NumberParseException, AttributeError, KeyError):
        return raw


def render_rows(rows: Dict[str, Optional[str]]) -> str:
    filtered = {k: v for k, v in rows.items() if v not in (None, "", [])}
    if not filtered:
        return ""
    width = max(len(key) for key in filtered)
    return "\n".join(f"{key:<{width}} : {value}" for key, value in filtered.items())


def format_biography(text: str) -> str:
    if not text:
        return "  -"
    return textwrap.indent(text.strip(), "  ")


def print_profile(profile: UserProfile, lookup: Optional[LookupInsight]) -> None:
    rows = {
        "Username": profile.username,
        "User ID": profile.user_id,
        "Full Name": profile.full_name,
        "Verified": bool_label(profile.is_verified),
        "Business Account": bool_label(profile.is_business),
        "Private Account": bool_label(profile.is_private),
        "Followers": f"{profile.follower_count:,}",
        "Following": f"{profile.following_count:,}",
        "Posts": f"{profile.media_count:,}",
        "IGTV Videos": f"{profile.total_igtv_videos:,}",
        "External URL": profile.external_url,
        "WhatsApp Linked": bool_label(profile.is_whatsapp_linked),
        "Memorial Account": bool_label(profile.is_memorialized),
        "New to Instagram": bool_label(profile.is_new_to_instagram),
        "Public Email": profile.public_email,
        "Public Phone": format_phone(profile.public_phone_country_code, profile.public_phone_number),
        "Profile Picture": profile.hd_profile_pic_url,
    }

    if lookup and lookup.has_data():
        rows["Lookup Message"] = lookup.message
        rows["Obfuscated Email"] = lookup.obfuscated_email
        rows["Obfuscated Phone"] = lookup.obfuscated_phone

    print("=" * 60)
    print("INSTAGRAM PROFILE INSIGHTS")
    print("=" * 60)
    print(render_rows(rows))
    print("\nBiography:")
    print(format_biography(profile.biography))
    print("=" * 60)


def parse_args() -> argparse.Namespace:
    parser = argparse.ArgumentParser(
        description="Lightweight Instagram OSINT helper. Provide a session id and a username or numeric id."
    )
    parser.add_argument("-s", "--sessionid", required=True, help="Instagram session ID cookie value.")
    group = parser.add_mutually_exclusive_group(required=True)
    group.add_argument("-u", "--username", help="Instagram username to inspect.")
    group.add_argument("-i", "--id", help="Numeric Instagram user ID to inspect.")
    parser.add_argument(
        "--skip-lookup",
        action="store_true",
        help="Skip the secondary lookup request (avoids extra API traffic).",
    )
    parser.add_argument(
        "--json",
        action="store_true",
        help="Print the collected data as formatted JSON instead of human-readable text.",
    )
    return parser.parse_args()


def main() -> None:
    args = parse_args()
    client = InstagramClient(args.sessionid)
    search_type = "id" if args.id else "username"
    search_value = args.id or args.username

    try:
        profile = client.get_profile(search_value, search_type)
        lookup = None if args.skip_lookup else client.advanced_lookup(profile.username)
    except InstagramAPIError as exc:
        raise SystemExit(str(exc)) from exc

    if args.json:
        payload = {
            "profile": asdict(profile),
            "lookup": asdict(lookup) if lookup else None,
        }
        print(dumps(payload, indent=2))
        return

    print_profile(profile, lookup)


if __name__ == "__main__":
    main()

🔴 Important
Do not modify headers or endpoints unless you understand Instagram fingerprinting.


System Requirements

Required

  • Python 3.9+
  • Internet connection
  • Instagram account (for session ID)

Python Dependencies

pip install requests

Optional (recommended):

pip install phonenumbers pycountry


How to Get Your Instagram Session ID (Step-by-Step)

This is critical. Without a valid session ID, the tool will not work.

Method: Browser (Recommended)

  1. Open Chrome / Firefox
  2. Log in to Instagram normally
    👉 https://www.instagram.com
  3. Right-click → Inspect
  4. Open Application tab (Chrome) or Storage (Firefox)
  5. Go to:Cookies → https://www.instagram.com
  6. Find the cookie named:sessionid
  7. Copy its value (very long string)

Example:

73xxxxxxx%3AJxxxxxxD%3xxxxx4%3xxxxxxxpao0M-gxxxxxxxxjZ7t-TxxxI

⚠️ Keep this private. Anyone with this can access your account.


Running the Tool (Correct Usage)

Inspect by Username

python finsta.py -s YOUR_SESSION_ID -u natgeo

Inspect by Numeric User ID

python finsta.py -s YOUR_SESSION_ID -i 1234567890

Skip Risky Lookup Endpoint (Recommended)

python finsta.py -s YOUR_SESSION_ID -u natgeo --skip-lookup

Output as JSON (Automation / OSINT Pipelines)

python finsta.py -s YOUR_SESSION_ID -u natgeo --json


Example Output (Human-Readable)

python finsta.py -s 73xxxxxxx%3AJxxxxxxD%gxxxxxxxxjZ7t-TxxxI -u natgeo
============================================================
INSTAGRAM PROFILE INSIGHTS
============================================================
Username            : natgeo
User ID             : 787132
Verified            : Yes
Business Account    : Yes
Private Account     : No
Followers           : 287,000,000
Following           : 130
Posts               : 29,000
Public Email        : editor@natgeo.com
Profile Picture     : https://instagram.fxyz1-1.fna.fbcdn.net/...
Phone                 : +** ***** ***82
============================================================

Biography:
  Experience the world through the eyes of National Geographic.
============================================================


Real-World Use Cases

1️⃣ OSINT & Threat Intelligence

  • Identify fake or impersonation accounts
  • Cross-reference social profiles
  • Attribute campaigns

2️⃣ Journalism & Verification

  • Validate public claims
  • Verify business accounts
  • Track digital footprints

3️⃣ Bug Bounty & Security Research

  • Identify exposed contact information
  • Understand metadata leakage
  • Document privacy risks responsibly

Rate Limits, Safety & Account Risks

Instagram actively monitors API abuse.

What Can Happen?

  • HTTP 429 (rate limit)
  • Session invalidation
  • Temporary action blocks
  • Verification challenges
  • Account bans (rare, but possible)

Safe Usage Guidelines

✔ Use burner accounts
✔ Limit to 30–40 lookups/day
✔ Add random delays
✔ Stop immediately on errors
✔ Prefer --skip-lookup
❌ Never use main personal account
❌ Never brute-force usernames


Ethical & Legal Boundaries

OSINT ≠ hacking.

You should:

  • Collect only exposed data
  • Respect platform policies
  • Follow local laws
  • Avoid harassment or misuse

If data is not public, do not chase it.


Final Thoughts: Power With Responsibility

Instagram OSINT tools are powerful because users overshare, not because the platform is broken.

This code demonstrates:

  • How metadata exposure works
  • Why authentication matters
  • How OSINT automation is built
  • Why ethics and restraint are essential

Good OSINT is invisible.
Bad OSINT gets accounts burned.


Call to Action

If you’re interested in:

  • Ethical OSINT research
  • Privacy & metadata exposure
  • Security automation
  • Recon tooling breakdowns

👉 Follow this blog, share responsibly, and build tools that respect both law and logic.

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.