This is a different failure mode than a 429 or a login wall, and it's arguably more confusing because nothing looks wrong. The request returns 200 OK. The profile loads. The username, bio, and follower count are all there. And then the posts, stories, highlights, or comments array is just [] — empty, with no error message explaining why.
Developers usually spend an hour checking auth headers, retry logic, and pagination cursors before finding the actual cause: the account is private, and an empty array is the correct, intended response.
Why private accounts return a “successful” empty response
Public Instagram data APIs, including GramScraper, can only return what Instagram itself exposes to a logged-out or non-following viewer. A private account's profile metadata — username, display name, follower and following counts, whether it's verified — is still visible, because Instagram shows that much to anyone. Its actual content is not: posts, stories, highlights, reels, and tagged photos are withheld from any viewer who isn't an approved follower.
That means a well-behaved API returns a successful response for the profile call and a correctly empty response for every content call on that same account. There's no error to catch, because nothing failed — the request was answered honestly.
The field you need to check first
Before investigating pagination, headers, or retries, check the profile response for is_private. On GramScraper, every profile object includes it:
{
"username": "example_account",
"full_name": "Example Account",
"is_private": true,
"is_verified": false,
"follower_count": 4213,
"following_count": 180,
"media_count": 312
}If is_private is true, stop debugging the content endpoint. The empty array you're seeing is not a bug in your code or in the API — it's the only honest answer a public-data API can give for that account.
A private account can still show a non-zero media_count. That count reflects how many posts exist, not how many you're entitled to see. Don't use media_count > 0 as a signal that the posts endpoint should return data.
A defensive check before you call the content endpoint
async function getPublicPosts(username) {
const profileRes = await fetch(
`https://gramscraper.com/api/instagram/v1/user/by/username?username=${username}`,
{ headers: { Authorization: `Bearer ${process.env.GRAMSCRAPER_API_KEY}` } }
);
const profile = await profileRes.json();
if (profile.is_private) {
return { posts: [], reason: "private_account" };
}
const postsRes = await fetch(
`https://gramscraper.com/api/instagram/v1/user/medias/by/username?username=${username}`,
{ headers: { Authorization: `Bearer ${process.env.GRAMSCRAPER_API_KEY}` } }
);
return { posts: (await postsRes.json()).items ?? [], reason: null };
}This does two useful things: it gives your application code an explicit reason to show the user (“this account is private”) instead of a silent empty list, and on some providers it avoids spending a credit on a content call you already know will return nothing. Check your provider's documentation for whether a call against a private account's content endpoint is billed.
Other reasons a response can look emptier than expected
Private accounts are the most common cause, but not the only one. Check these before assuming a bug:
- Wrong identifier shape. Some endpoints take a username, others take a numeric user ID or a post shortcode. Passing the wrong shape to an endpoint often returns an empty result instead of a clear type error.
- Pagination cursor exhausted. A second page request with a stale or malformed cursor can return an empty page rather than an error, especially after the underlying feed has changed.
- Deleted or deactivated account. The account existed when you first queried it but was deleted or deactivated since — this typically surfaces as a 404 rather than an empty 200, so check the status code alongside the body.
- Age-restricted or geofenced content. Some posts are not shown to all viewers depending on content flags; this is rarer but worth ruling out for specific accounts that behave inconsistently.
Where to go next
If the account turns out not to be private and you're still seeing unexpected empty results or outright failures, check the login-required guide or the blocked and challenge triage guide — those cover cases where Instagram is rejecting the request itself rather than correctly withholding private content. For a working profile and posts call end to end, see getting Instagram profile data with an API.
Frequently asked questions
Why does my Instagram API call return 200 with an empty posts array?
The most common cause is a private account: the profile itself still resolves, but posts, stories, highlights, and similar content endpoints correctly return nothing because that data isn't public.
How do I tell a private account apart from a real bug?
Check the profile response for an is_private field before treating an empty content response as an error. If is_private is true, the empty result is correct behavior, not a failure to retry.
Can any Instagram API return private account data?
No. Public data APIs, including GramScraper, can only return what an account has made public. A private account's posts, stories, and followers list are not accessible without that account's own authorization, regardless of provider.
Get 100 free API credits
Test profile and content calls on real public accounts and see the is_private field for yourself.
Create a free account →