Instagram GraphQL API
Instagram's web app loads profiles, posts, and comments through an internal GraphQL endpoint. It isn't a public API: query IDs rotate, requests need session tokens, and it rate-limits hard. Here's how it works, and a stable REST API that returns the same public data.
50 free credits on signup · no card required
curl "https://api.profilequery.com/v1/profile?handle=nike" \
-H "Authorization: Bearer $INSTAGRAMAPI_KEY"What you get
Structured JSON, not raw HTML
- ▹The same public data the GraphQL endpoint serves: profiles, posts, reels, comments, likers
- ▹Stable, versioned REST paths instead of rotating doc_ids and query_hashes
- ▹No session cookies, csrftoken, or x-ig-app-id headers to forge
- ▹Flat, documented JSON instead of deeply nested edges/node wrappers
- ▹Cursor pagination that works the same on every list endpoint
- ▹Sessions, proxies, and retries handled for you
Under the hood
How Instagram's GraphQL endpoint works
Instagram's website doesn't use a public REST API. When you open a profile or scroll a feed, the browser sends POST requests to instagram.com/graphql/query (older builds used GET /graphql/query with a query_hash). Instead of sending a GraphQL query string, the client sends the ID of a persisted query plus a JSON blob of variables.
POST https://www.instagram.com/graphql/query
Content-Type: application/x-www-form-urlencoded
X-IG-App-ID: <web app id>
X-CSRFToken: <csrftoken cookie>
Cookie: csrftoken=...; sessionid=...; ...
doc_id=<numeric persisted query id>
&variables={"username":"nike", ...}The response is a deeply nested GraphQL payload: data.user.edge_owner_to_timeline_media.edges[].node and similar, with pagination in page_info.end_cursor.
Why GraphQL scrapers keep breaking
- ▹doc_id and query_hash values are build artifacts. They change when Instagram ships a new web bundle, and old IDs start returning errors.
- ▹Most queries now need a logged-in session. Anonymous requests get redirected to the login wall or rate-limited after a handful of calls.
- ▹Logged-in scraping puts the account at risk: checkpoints, challenges, and bans.
- ▹Rate limits are per IP and per session, so scale means proxy pools and account rotation.
- ▹Response shapes drift: fields move between edges, get renamed, or disappear without notice.
The same data over a stable REST API
This API sits in front of all of that. You call a versioned endpoint with an API key, and it handles sessions, proxies, query IDs, and retries, then returns flat JSON. Here's a profile's recent posts, the equivalent of the timeline-media GraphQL query:
import os, requests
r = requests.get(
"https://api.profilequery.com/v1/profile/posts",
params={"handle": "nike"},
headers={"Authorization": f"Bearer {os.environ['INSTAGRAMAPI_KEY']}"},
)
data = r.json()["data"]
for post in data["items"]:
print(post["shortcode"], post["like_count"], post["comment_count"])
# next page: pass data["next_cursor"] back as cursor=GraphQL query → REST endpoint
- ▹Profile info (user by username) → GET /v1/profile?handle=…
- ▹Timeline media (profile posts) → GET /v1/profile/posts?handle=…&cursor=…
- ▹Clips / reels tab → GET /v1/profile/reels?handle=…
- ▹Followers / following edges → GET /v1/profile/followers, /v1/profile/following
- ▹Post / shortcode media → GET /v1/post?post=…
- ▹Comments on a post → GET /v1/post/comments?post=…
- ▹Likers → GET /v1/post/likers?post=…
- ▹Hashtag top / recent media → GET /v1/hashtag/top, /v1/hashtag/recent
Example response
What comes back
{
"data": {
"id": "13460080",
"username": "nike",
"full_name": "Nike",
"biography": "Just Do It.",
"is_verified": true,
"is_private": false,
"is_business": true,
"category": null,
"followers": 291952375,
"following": 264,
"posts": 1657,
"external_url": "http://empli.fi/nike",
"profile_pic_url": "https://instagram.com/.../nike.jpg",
"public_email": null,
"public_phone_number": null
}
}Response fields
Fields returned by GET /v1/profile
| Field | Description |
|---|---|
| id | Numeric account id (stable even if the username changes). |
| username | The account's @handle. |
| full_name | Display name shown on the profile. |
| biography | The profile's bio text. |
| is_verified | Whether the account has a verified badge. |
| is_private | Whether the account is private. |
| is_business | Whether this is a business/professional account. |
| category | Business category label when set (e.g. "Sportswear"); null otherwise. |
| followers | Number of accounts following this account. |
| following | Number of accounts this account follows. |
| posts | Total number of posts on the profile. |
| external_url | The clickable link in the bio, if any. |
| profile_pic_url | URL of the account's profile picture. |
| public_email | Public contact email, if the account exposes one; null otherwise. |
| public_phone_number | Public contact phone number, if exposed; null otherwise. |
No login required
Nothing to authenticate but your API key
No OAuth flow
One static API key in an Authorization: Bearer header. No redirect, no refresh tokens, no expiring session.
No Meta app review
Never touch Meta's developer platform, Business Verification, or a connected Facebook Page. Sign up directly with Instagram API.
Public data only
Every endpoint returns only what's already visible on Instagram without logging in. Private accounts are never accessible.
Pricing
1 credit per request. Credits start at $8.00 per 1,000 and drop to $6.00 per 1,000 at volume — no subscription, no minimums. New accounts get 50 free credits.
See full pricingFAQ
Instagram GraphQL API, explained
Is the Instagram GraphQL API public?
No. It's the private endpoint Instagram's own web app uses. It's undocumented, query IDs change without notice, and most queries require a logged-in session. Meta's only official public API is the Graph API (not GraphQL), which is limited to accounts you manage.
What happened to query_hash?
Older versions of the web app sent GET requests to /graphql/query with a query_hash. Newer builds send POST requests with a numeric doc_id. Both are persisted-query IDs that rotate with each web release, which is why hard-coded values eventually stop working.
Is the Instagram Graph API the same as GraphQL?
No. The Graph API is Meta's official REST-style API for business and creator accounts you've connected through a Facebook app. GraphQL here refers to Instagram's internal web endpoint. They have different data, different auth, and different rules.
Do I need an Instagram account or session cookie?
No. You authenticate with your API key only. There's no sessionid, csrftoken, or Instagram login on your side, and no account of yours is at risk.
How much does it cost?
Profile lookups are 1 credit; posts and comments pages are 2 credits. Credits start at $8.00 per 1,000 and new accounts get 50 free.
Related
More ways to pull Instagram data
Start pulling graphql api data
Create a free key and make your first request in under a minute — 50 credits on us.