Reels are where Instagram engagement now lives, and they are also where measurement is easiest to get wrong. “Views”, “plays”, “impressions” and “reach” are four different numbers, and the one you actually receive depends on which API you call. Before you build a dashboard on top of a number, it is worth knowing which number it is.
This guide fetches reels for public accounts through the GramScraper API (an Instagram-specific wrapper), shows the real response fields, prices each call in credits, and then compares it honestly against Meta’s official Instagram API, which is a different tool with a different purpose.
Reels are served through the “clips” endpoints
Instagram’s own API surface calls short-form video clips. That naming carries into the endpoints, so if you go looking for a path literally called /reels you will miss the ones that work. The documented reels routes are:
| Endpoint | What it returns |
|---|---|
/v2/user/clips | A page of a user’s reels. Takes user_id and an optional page_id cursor. |
/gql/user/clips | Reels via GraphQL. Takes user_id, an optional max_id cursor, flat, and sort_by_views. |
/v1/user/clips/chunk | The chunked variant, paged with end_cursor. Also includes trial publications. |
/v1/media/insight | Insight data for a single media object by media_id. |
/gql/media/clips_metadata | Audio and music metadata for a reel, by media_id. |
Base URL: https://gramscraper.com/api/instagram, authorised with Authorization: Bearer gs_your_key. A free account starts you with 100 credits.
Step 1: turn a username into a user ID
The clips endpoints take a numeric user_id, not a username. Resolve it once and cache it — this is a genuine cost saver, because every reels page for that account then needs no lookup:
curl -s -H "Authorization: Bearer gs_your_key" \
"https://gramscraper.com/api/instagram/v1/user/by/username?username=natgeo"The response contains pk (for example 787132). That number is the user_id for every clips call. This lookup costs 1 credit.
Step 2: fetch the reels
curl
curl -s -H "Authorization: Bearer gs_your_key" \
"https://gramscraper.com/api/instagram/v2/user/clips?user_id=787132"Node.js 18+
const BASE = "https://gramscraper.com/api/instagram";
const KEY = process.env.GRAMSCRAPER_API_KEY;
async function getReels(userId, pageId = null) {
const url = new URL(`${BASE}/v2/user/clips`);
url.searchParams.set("user_id", userId);
if (pageId) url.searchParams.set("page_id", pageId);
const res = await fetch(url, { headers: { Authorization: `Bearer ${KEY}` } });
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
}
const page = await getReels("787132");
for (const reel of page.response.items) {
console.log(
reel.code,
"plays:", reel.play_count ?? 0,
"likes:", reel.like_count ?? 0,
"comments:", reel.comment_count ?? 0
);
}Python 3
import os, requests
BASE = "https://gramscraper.com/api/instagram"
HEADERS = {"Authorization": f"Bearer {os.environ['GRAMSCRAPER_API_KEY']}"}
def get_reels(user_id, page_id=None):
params = {"user_id": user_id}
if page_id:
params["page_id"] = page_id
r = requests.get(f"{BASE}/v2/user/clips", headers=HEADERS, params=params)
r.raise_for_status()
return r.json()
page = get_reels("787132")
for reel in page["response"]["items"]:
print(reel.get("code"), reel.get("play_count"), reel.get("like_count"))What reels actually return
Clips pages use the same envelope as other list endpoints: items under response.items, cursor under next_page_id. A reel item looks like this:
{
"response": {
"items": [
{
"pk": "3162273595027945978",
"id": "3162273595027945978_787132",
"code": "DYNOQDIgNmw",
"media_type": 2,
"product_type": "clips",
"taken_at": "2026-08-14T17:02:03+00:00",
"caption_text": "Behind the scenes of the shoot 🎬",
"like_count": 184322,
"comment_count": 1204,
"play_count": 4820331,
"view_count": 0,
"video_duration": 18.4,
"video_url": "https://scontent.cdninstagram.com/...",
"thumbnail_url": "https://scontent.cdninstagram.com/...",
"share_count_disabled": false,
"comments_disabled": false,
"is_paid_partnership": false,
"user": {
"pk": "787132",
"username": "natgeo",
"full_name": "National Geographic",
"is_verified": true
}
}
],
"paging_info": { "more_available": true },
"status": "ok"
},
"next_page_id": "QVFCcnhIWE82OUswLXBtNzlpZ0ZaUnhWZEhr"
}| Field | What it is |
|---|---|
pk / id | The reel’s media ID. Use it for /v1/media/insight and /gql/media/clips_metadata. |
code | The shortcode in the reel URL: instagram.com/reel/<code>/. |
play_count | The play count for the reel. This is the field to read for reels views. Can be null. |
like_count | Likes on the reel. |
comment_count | Comment count as a number. To read the comments themselves, see the comments API guide. |
view_count | A view counter on the media object. Frequently 0 for reels — prefer play_count. |
taken_at | ISO-8601 publish timestamp in UTC. |
caption_text | The caption as plain text. |
video_duration | Length in seconds. |
video_url / thumbnail_url | CDN URLs. These are signed and expire, so download promptly if you need the asset. |
product_type | clips for reels, feed for an ordinary post. |
On “shares”. Do not expect a share counter here. The reel object exposes share_count_disabled — a boolean flag telling you whether sharing is disabled — not the number of shares. Meta’s official API does expose a shares metric, but only for media your own account owns, as we cover below. Any reel API advertising public share counts for arbitrary accounts should be checked carefully before you trust it.
Paging through reels
Pagination is cursor-based and identical in spirit to comments. Terminate on a null cursor, not on a short page.
async function allReels(userId, maxPages = 10) {
const out = [];
let cursor = null, pages = 0;
do {
const page = await getReels(userId, cursor);
out.push(...page.response.items);
cursor = page.next_page_id || null;
pages += 1;
} while (cursor && pages < maxPages);
return out;
}The /v1/user/clips/chunk variant pages differently — it returns a two-element array of [items, end_cursor] and you feed end_cursor straight back. If you prefer GraphQL, /gql/user/clips accepts max_id and, usefully, a sort_by_views flag so you can retrieve the highest-performing reels first instead of walking chronologically and sorting client-side.
Credit cost breakdown
| Call | Credits |
|---|---|
/v1/user/by/username (resolve the user ID) | 1, once per account |
/v2/user/clips (one page of reels) | 1 |
/gql/user/clips | 1 |
/v1/user/clips/chunk | 1 |
/v1/media/insight (per-post insight) | 1 |
/gql/media/clips_metadata (audio metadata) | 1 |
So a practical job — resolve one account and pull one page of reels — is 2 credits. If you reuse the cached user_id for further pages, each additional page is 1 credit. The response _meta block reports credits_consumed and credits_remaining, so the authoritative number is always in the payload rather than your own table.
At published rates ($9 for 10,000 credits), 10,000 page-fetches works out at roughly $0.90 per 1,000 calls. Purchased credits do not expire, which suits reels monitoring that runs in bursts.
How this compares to Meta’s official Reels API
Meta’s Instagram API is not a worse version of this — it is a different tool, and for some jobs it is the correct one.
| GramScraper (Instagram-specific API) | Meta Instagram API (/insights) | |
|---|---|---|
| Whose media | Any public account | Media your own app user owns |
| Auth | One API key | An Instagram professional account, a Meta app, and approved permissions such as instagram_business_basic and instagram_business_manage_insights |
| Metrics | play_count, like_count, comment_count, caption, duration, audio metadata | Rich insight metrics including reach, saved, shares, reposts, ig_reels_avg_watch_time, and total_interactions |
| Freshness | Returned as the platform serves it | Meta documents that metric data can be delayed up to 48 hours |
| Missing data | Fields are nullable | Meta returns an empty dataset rather than 0 when a metric is unavailable |
| Best for | Competitor and creator research at scale, without owning the accounts | Analytics for accounts you own, where you need watch-time and reach |
The honest summary: if you own the account and have the permissions, Meta’s insights endpoint gives you metrics nothing else can — average watch time, reach, saves, and share counts. Those are first-party numbers. Our API cannot produce them for arbitrary third-party accounts, and no third-party API can produce Meta’s first-party insight metrics at all.
What this API is good at is the other half of the problem: reading reels for accounts you do not own — competitors, prospective influencers, or a market you are researching — without an app review, a linked professional account, or an owned-media constraint. If your need is “the top reels of these 200 creators, sorted by plays”, the official API simply is not the tool for the job.
Also worth noting: Meta documents that insights metrics such as comments, likes, and views count organic interaction only, that album media has no insights at all, and that story metrics expire after 24 hours. Those are real constraints of the official path, not a criticism invented here.
Honest limitations of the reels endpoints
- Public accounts only. Private accounts return nothing. Check
is_privateon the profile first rather than spending credits to discover it. - No share counts. As covered above, the reel object carries a sharing-disabled flag, not a share total.
- Play counts can be null. Encoding them as
0silently turns “unknown” into “no views”. Use a null-aware default. - No watch time or reach. Average watch time and reach are first-party metrics. They are not available for third-party accounts from any provider.
- CDN URLs expire. If you need the video file, download and store it, because the signed URL will stop working.
- Page sizes are not fixed. Drive loops from the cursor, and cap the number of pages so a runaway loop cannot drain a balance.
- Ordering and freshness are Instagram’s. A page may not be strictly chronological. Where order matters, sort by
taken_at.
Get 100 free API credits
Resolve a creator, pull a page of reels, and check the fields against your own requirement — before choosing a pack. Starter is $9 for 10,000 non-expiring credits.
Create a free account →