Instagram

How to Scrape Instagram Data with an API (2026)

Pull Instagram profiles, posts, and reels as JSON via Social Fetch — plus DIY GraphQL scraping and Graph API limits.

Social FetchUpdated

This guide covers Instagram profile and post lookups via Social Fetch's /v1/instagram/** routes, with runnable examples below. It also explains what DIY browser scraping and Meta's Graph API actually involve on this platform. For the generic build-vs-buy comparison, see Social Fetch vs DIY scraping.

The shortest path is a single request:

Request
curl -sS \
  -H "x-api-key: $SOCIALFETCH_API_KEY" \
  "https://api.socialfetch.dev/v1/instagram/profiles/instagram"

You'll need an API key and curl or the TypeScript SDK. New to the API? Start with the Quickstart.

DIY browser scraping on Instagram

A plain fetch() to instagram.com gets blocked almost immediately — Instagram inspects TLS fingerprints, cookies, and request patterns. The numbers you care about sit in embedded GraphQL / shared-data JSON blobs, not the DOM.

Example
python
from playwright.async_api import async_playwright
import asyncio, random

async def scrape_instagram_profile(username: str):
    async with async_playwright() as p:
        browser = await p.chromium.launch(
            headless=False,
            proxy={
                "server": "http://residential-proxy.example:9000",
                "username": "user",
                "password": "pass",
            },
        )
        context = await browser.new_context(
            user_agent=pick_real_user_agent(),
            locale="en-US",
            timezone_id="America/New_York",
        )
        page = await context.new_page()

        await page.goto("https://www.instagram.com/", wait_until="networkidle")
        await asyncio.sleep(random.uniform(2.0, 5.0))

        await page.goto(
            f"https://www.instagram.com/{username}/",
            wait_until="networkidle",
        )
        # Profile fields usually live in embedded GraphQL / shared-data JSON,
        # not in stable DOM selectors. You still parse opaque blobs after this.
        html = await page.content()
        await browser.close()
        return html

A working DIY pipeline also needs:

  • Residential or mobile proxies. Datacenter IPs are flagged within a few hits.
  • Session and login-wall handling. Public pages often redirect into login or challenge flows mid-cron.
  • GraphQL document churn. Query hashes and doc_id values move without a changelog. A parser that worked Tuesday returns empty arrays Friday.
  • Behavioral pacing. Fixed intervals read as a bot, so you add randomized delays and cap concurrency.

Instagram quietly renames internal fields and reshapes media objects. A scraper that looked solid in staging drifts in production.

Meta Graph API limits

Meta's Instagram developer products fit apps inside Meta's ecosystem — publishing on a Business or Creator account you manage, reading insights for assets that granted access, or messaging with approved permissions:

  • Instagram Graph API exposes profiles, media, insights, and comments for accounts you own or that authorize your app. App review and Business verification sit in front of most production scopes.
  • Basic Display / consumer login paths (where still relevant) are for user-authorized personal data — not bulk competitor enrichment.
  • Marketing / ads APIs cover campaigns and ad accounts, not arbitrary public creator lookups.

The ownership wall is intentional. Check Meta for Developers before building — terms and product names change often.

Using the Social Fetch API

Social Fetch exposes Instagram under documented /v1/instagram/** routes — profiles, posts, reels, stories/highlights, search, comments, transcripts, and more. One header authenticates every call; responses share the same data + meta envelope across platforms.

Request
TypeScript
const = await client.tiktok.({
handle: "charlidamelio",
});
if (result.ok) {
const { profile } = result.value.data;
}
Response
JSON
{
"data": {
"lookupStatus": ,
"profile": {
"handle": "charlidamelio",
"displayName": "Charli D'Amelio",
},
"metrics": {
"followers": 155200000
}
},
"meta": {
"creditsCharged":
}
}

Hover underlined tokens for details.

Get a profile

The same lookup in cURL, the TypeScript SDK, Python, and more — switch tabs to match your stack:

Request
const response = await fetch(
  "https://api.socialfetch.dev/v1/instagram/profiles/instagram",
  {
    headers: {
      "x-api-key": process.env.SOCIALFETCH_API_KEY,
    },
  }
);

const body = await response.json();

console.log(response.status, body);

Most Instagram profile lookups are 1 credit when the lookup completes (including private / not_found outcomes that still return HTTP 200). Trust meta.creditsCharged on every response.

List a profile's posts

Request
const response = await fetch(
  "https://api.socialfetch.dev/v1/instagram/profiles/instagram/posts",
  {
    headers: {
      "x-api-key": process.env.SOCIALFETCH_API_KEY,
    },
  }
);

const body = await response.json();

console.log(response.status, body);

Full parameters: Get Instagram profile and Profile posts. Reels, highlights, search, and transcripts sit next to them in the API reference. Profile post and reel list routes are also 1 credit per completed page.

Paginate through everything

Post and reel list endpoints return one page plus a cursor. Pass the cursor back verbatim until it's gone:

Example
typescript
import { SocialFetchClient } from "@socialfetch/sdk";

const client = new SocialFetchClient({
  apiKey: process.env.SOCIALFETCH_API_KEY!,
});

const allPosts = [];
let cursor: string | undefined;

do {
  const result = await client.instagram.getProfilePosts({
    handle: "natgeo",
    cursor,
  });

  if (!result.ok) {
    console.error(result.error.code, result.error.requestId);
    break;
  }

  allPosts.push(...result.value.data.posts);
  cursor = result.value.data.page.nextCursor ?? undefined;
} while (cursor);

Stop when data.page.nextCursor is null. Don't loop on a total count — upstream totals are estimates. The cursor is the source of truth. Each page is a separate completed lookup for billing.

Reading the response

A successful response wraps the payload alongside billing metadata:

Response
json
{
  "data": {
    "lookupStatus": "found",
    "profile": {
      "platform": "instagram",
      "handle": "natgeo",
      "displayName": "National Geographic",
      "verified": true,
      "privateAccount": false
    },
    "metrics": {
      "followers": 280000000,
      "following": 140,
      "posts": 28000
    }
  },
  "meta": {
    "requestId": "req_01example",
    "creditsCharged": 1,
    "version": "v1"
  }
}

The one gotcha: an HTTP 200 does not guarantee lookupStatus: "found". A private or non-existent account can return 200 with a status field — handle it in application logic. Every response carries meta.requestId for support tracing. See Errors and Credits.

Post detail with downloadMedia=true is the common surcharge case: 11 credits on success (1 base + 10 for hosted media download). Leave downloadMedia off unless you need the files.

What you can build

  • Influencer vetting — pull profile metrics and recent posts before outreach, and reconcile CRM handles against live numbers.
  • Competitive content monitoring — snapshot competitor profiles, posts, and reels on a schedule without running a scraper per brand.
  • Brand-safety and topic pipelines — pair captions with post/reel transcripts when on-screen text is thin.

For the same identity pattern across Instagram, TikTok, and YouTube, see Cross-platform creator profiles.

FAQ

It depends on your jurisdiction, what data you collect, and how you use it. Collecting public, business-level metrics for B2B analytics is a common pattern, and courts (e.g. hiQ v. LinkedIn) have generally treated public-web data as fair game in several jurisdictions. You remain responsible for Instagram's terms, applicable privacy law, and your own contracts. This is a technical guide, not legal advice — talk to counsel if you're collecting at scale.

Can't I just use the Instagram Graph API?

Not for arbitrary public creator lookups. Graph covers accounts you manage or that grant your app access. For web-parity public data in a generic backend, a data API is the usual fit. See Meta Graph API limits.

How fresh is the data?

Each request fetches live from Instagram at call time — there's no cache returning an hour-old snapshot. If a number looks off, meta.requestId lets support trace that specific lookup.

What happens if a profile is private or doesn't exist?

Check data.lookupStatus. Don't treat a 200 alone as "data is present" — a private or missing account returns 200 with a status field. Details in Errors.

How are credits charged?

Credits charge when a lookup completes, including 200 outcomes like private or not_found. Most Instagram profile, post-list, reel-list, search, highlight, and transcript routes are 1 credit per completed request. Post detail with downloadMedia=true is 11 credits on success. Validation failures and upstream lookup_failed / temporary unavailability do not bill the same way — see Credits. Always reconcile against meta.creditsCharged.

How does this compare to other providers?

See the side-by-side comparisons: vs Apify, vs Bright Data, vs EnsembleData, the full compare hub, and Best Instagram data APIs in 2026.


Next steps: Quickstart · Instagram platform hub · Instagram API reference · Pricing