Blog / Endpoint guides

How to Get Instagram Profile Data via API

By the InScrape API team · Published 2026-08-13 · 3 min read

A profile card connected to a stack of message tiles

Profile data is the first call almost everyone makes, and the one most likely to be built badly — usually by fetching more often than necessary and paying for it every time.

Here is the working version.

The request

One GET, one header:

curl "https://api.socialscrape.dev/v1/instagram/profile?handle=natgeo" \
  -H "x-api-key: $INSCRAPE_KEY"
const res = await fetch(
  "https://api.socialscrape.dev/v1/instagram/profile?handle=natgeo",
  { headers: { "x-api-key": process.env.INSCRAPE_KEY } }
);
const { data, credits_remaining, requested_at } = await res.json();
import os, requests

res = requests.get(
    "https://api.socialscrape.dev/v1/instagram/profile",
    params={"handle": "natgeo"},
    headers={"x-api-key": os.environ["INSCRAPE_KEY"]},
)
profile = res.json()["data"]

What comes back

{
  "success": true,
  "credits_charged": 1,
  "credits_remaining": 99,
  "processing_time_ms": 1842,
  "requested_at": "2026-09-13T14:32:18Z",
  "query": { "username": "natgeo" },
  "data": {
    "id": "787132",
    "handle": "natgeo",
    "full_name": "National Geographic",
    "is_verified": true,
    "is_private": false,
    "is_business": true,
    "category": "Media/News Company",
    "follower_count": 279000000,
    "following_count": 158,
    "media_count": 30412,
    "biography": "Experience the world through the eyes of National Geographic photographers.",
    "external_url": "https://on.natgeo.com/instagram",
    "profile_pic_url": "https://cdn.example.com/p/natgeo.jpg"
  }
}

Note the envelope, not just data. credits_charged, credits_remaining, processing_time_ms, requested_at and query tell you what the call cost, when it ran and what triggered it.

Five things to get right

1. Normalise the handle before you call

@NatGeo, natgeo and natgeo are the same account. If you are deduping on your side, do it before the call — otherwise you will store three rows for one creator.

const normalise = (h) => h.trim().toLowerCase().replace(/^@/, "");

2. Handle private and missing accounts as lookup states

Confirmed private accounts return 200 with available profile details. Missing accounts return 200 with data.profile=null. That is not a failure of your integration, it is a fact about the account, and it is worth storing.

const body = await res.json();
if (!res.ok) throw new Error(body.error?.message || "Lookup failed");
if (body.data?.profile === null) {
  await markMissing(handle);
  return null;
}

Both completed outcomes are charged at the profile endpoint rate. Retrying a private or missing account unnecessarily consumes credits.

3. Pick a refresh interval deliberately

Follower counts move slowly. Most products do not need to refresh the same profile every few minutes.

Two consequences worth designing around:

  • Scheduled jobs can refresh profiles daily unless a user is actively waiting for a new number
  • If you display the number later, store requested_at with the row so the age is explicit

That distinction is the whole cost model. Background jobs should run on a sane schedule; user-triggered refreshes should be deliberate.

4. Mirror the profile picture if you display it

profile_pic_url points at a CDN with expiring links. If you render it directly in your product, images will break at unpredictable intervals. Fetch once, store in your own bucket, and serve from there.

5. Batch with concurrency, not with a batch endpoint

There is no batch profile endpoint, and adding one would mostly hide the cost. Run the calls concurrently instead — there is no published rate limit, so the constraint is your own connection pool:

const profiles = await Promise.all(
  handles.map((h) =>
    fetch(`https://api.socialscrape.dev/v1/instagram/profile?handle=${h}`, {
      headers: { "x-api-key": key },
    }).then((r) => r.json())
  )
);

For a few hundred handles, chunk it so you are not opening five hundred sockets at once. For a few thousand, put it behind a queue.

Building a follower time series

The profile endpoint is a snapshot. Growth rate — which is the number that actually predicts anything — only exists as a difference between snapshots, so you have to start collecting before you need the answer.

// once a day, per tracked handle
const { data } = await getProfile(handle);
await db.insert("profile_snapshots", {
  handle,
  follower_count: data.follower_count,
  media_count: data.media_count,
  captured_at: new Date(),
});

One credit per handle per day. Five hundred tracked accounts is 15,000 credits a month, and you own the history permanently — including after you stop paying anyone for it.

What this endpoint does not give you

  • The follower list. That is the followers endpoint, it costs 2 credits per page, and it exists because paginating a list is genuinely more work than reading a page.
  • Reach or impressions. Private metrics, owner-only, through the official API.
  • Email addresses, unless the account publishes a business contact.

Next

Try it with 100 free credits.

No credit card, credits never expire, and failed requests are not charged.