Instagram API — Getting Started — FetchLayer Docs

Getting Started

Read public Instagram profiles, posts, reels, comments and trends as structured JSON. No Instagram account and no developer application — just your FetchLayer key.

1. Get your API key

Create an account and generate a FetchLayer API key from your dashboard. The same key works on every platform.

Create free account →

2. Read an account

Pass a handle with or without the @, or a profile URL. The bio links come back unwrapped to their real destinations, and the story highlights come back with them.

bash
curl -X POST https://api.fetchlayer.dev/instagram/user-profile \
  -H "Authorization: Bearer ss-your-key" \
  -H "Content-Type: application/json" \
  -d '{"username":"@nasa"}'

Story highlights come free with the profile

highlights lists the id, title and cover of every highlight on the profile, in the same call and for the same credit. Their contents are not public — only the titles and covers are.

Exact counts, and where an account is based

stats.postCount is the exact number of posts, private accounts included, alongside the follower and following counts. hasActiveStory and latestStoryAt say whether a story is up now and when the last one was posted. Add "include":["country"] to also get country, where the account is based, and dateJoined, the month it joined; it adds a second or two.

3. List what they posted

Newest first, pinned posts first, each normally with its posting date, like and comment counts, and for a video its play count and length — plus captions, hashtags, mentions and every rendition.

bash
curl -X POST https://api.fetchlayer.dev/instagram/user-posts \
  -H "Authorization: Bearer ss-your-key" \
  -H "Content-Type: application/json" \
  -d '{"username":"@nasa","limit":60}'

Twelve posts per page, and check fieldsFrom

Instagram serves 12 posts per page whatever limit says, so limit: 60 takes five pages and costs five credits, and without a limit every post on the pages fetched comes back (pages: 2 returns 24) — pagesFetched reports it. Posts come with their dates and counts and say fieldsFrom: "listing_full". A post still missing one of them is named by short code in notes, with the fields it lacks; asking again with include returns them, and /post-detail fills the gap too — so a null is never mistaken for a zero.

4. Read the reels, with play counts

Same shape as the posts listing. Every reel comes back with playCount, likeCount and commentCount, and most with their posting date, length and caption too.

bash
curl -X POST https://api.fetchlayer.dev/instagram/user-reels \
  -H "Authorization: Bearer ss-your-key" \
  -H "Content-Type: application/json" \
  -d '{"username":"@nasa","limit":12}'

Read what they have up now

The stories an account has up right now, oldest first, each frame with its posting and expiry time, every rendition, any link the account attached and any post it re-shared.

bash
curl -X POST https://api.fetchlayer.dev/instagram/user-stories \
  -H "Authorization: Bearer ss-your-key" \
  -H "Content-Type: application/json" \
  -d '{"username":"@nasa"}'

No story is an answer, not an error

An account with nothing up returns an empty stories list and a note; a private account returns stories: null and a note. Both cost 1 credit, like any other call. A call usually takes 2–5 seconds, and a 503 under load is worth retrying.

5. Open one post, and read its comments

By URL, by the short code from it, or by the post’s numeric id. A video or reel comes back with its current play count and its length in seconds; photos and carousels have no public view count. Add "include":["reposts","shares","saves"] to also get repostCount, shareCount and saveCount — reposts on any post, shares and saves on reels and videos. It adds about 1 to 2 seconds, and the same include works on /user-posts and /user-reels for a whole page at the same price — there it adds a few seconds per page, usually 2 to 8.

bash
curl -X POST https://api.fetchlayer.dev/instagram/post-detail \
  -H "Authorization: Bearer ss-your-key" \
  -H "Content-Type: application/json" \
  -d '{"post":"https://www.instagram.com/p/DcOX3hWFiey/"}'

curl -X POST https://api.fetchlayer.dev/instagram/post-comments \
  -H "Authorization: Bearer ss-your-key" \
  -H "Content-Type: application/json" \
  -d '{"post":"DcOX3hWFiey"}'

Comments page, a page at a time

Instagram serves a post’s comments newest first, 10–16 to a page. pages (default 1) returns several pages in one request and cursor continues from a nextCursor; each page costs one credit. The response can be shorter than asked and then carries a nextCursor and a note. replyCount is a number on every page; on a first page some reply counts and the photos of photo comments can be empty, the note says so, and asking again fills them. To read the replies to a comment, pass its id as commentId to /comment-replies: about 8 replies a page, oldest first, 1 credit per page. A request for several pages can take up to about 20 seconds between pages, so follow nextCursor one page at a time.

6. Discovery, without search

Instagram does not make keyword search or hashtag feeds public. What it does publish is the reels it lists as popular and the terms it is promoting. /reels-feed returns popular reels from many accounts, varying between calls, each with its play count, caption, author and video; like counts, comment counts and dates come from /post-detail.

bash
curl -X POST https://api.fetchlayer.dev/instagram/reels-feed \
  -H "Authorization: Bearer ss-your-key" \
  -H "Content-Type: application/json" \
  -d '{"limit":12}'

curl -X POST https://api.fetchlayer.dev/instagram/trending-searches \
  -H "Authorization: Bearer ss-your-key" \
  -H "Content-Type: application/json" \
  -d '{"limit":48}'

Trending is not search

/trending-searches returns the terms Instagram itself is promoting, grouped into its own sections — currently seven, from “Trending now” to “Shop the trends”, named in notes — each with Instagram’s rounded count of posts under it. It is what Instagram is putting in front of everyone — not a query you control.

7. Assets that keep working

Instagram’s asset URLs are signed and expire, so every asset carries a downloadUrl pointing at GET /instagram/media, which re-fetches it on demand with Range support.

bash
curl -L "https://api.fetchlayer.dev/instagram/media?url=<asset url>" \
  -H "Authorization: Bearer ss-your-key" \
  --output post.jpg

Resolving a pasted link

When someone hands you an Instagram URL and you do not know what it points at, /resolve-url tells you — post, reel, account, hashtag, place, story or explore — plus the short code, numeric id, handle, tag or place id it encodes. It reaches Instagram not at all, so it is free.

bash
curl -X POST https://api.fetchlayer.dev/instagram/resolve-url \
  -H "Authorization: Bearer ss-your-key" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://www.instagram.com/p/DcOX3hWFiey/"}'

A link naming something Instagram does not make public — a hashtag page, a place — is still identified, with a note saying it is not available. That is the cheapest way to find out. A share link (/share/…) does not say which post it points to, so it comes back as unknown with a note to open it and pass the link it lands on.

Errors and retries

A 400 names the field to fix; unrecognised fields are rejected rather than ignored. A 404 means there is no such account or post — {"error":"no account \"fetchlayerprobe9f3\" was found","status":404} — never a 503 dressed up as a failure. An account Instagram restricts, typically an age-restricted brand, is not a 404: it answers 200 with restricted: true and less detail. A 502 or 503 is worth retrying after a wait. Read notes on successful responses too: the comment ceiling, the counts Instagram does not make public and a private account all announce themselves there rather than as an error.

Error responses

Every error, on every route, is JSON in one shape: error (what went wrong, in words), status, code, retryable and requestId, which also comes as the X-Request-Id header. Branch on code and retryable rather than on the text; on a 5xx the text is generic. Quote requestId when reporting a problem.

json
{
  "error": "Missing required parameter: url",
  "status": 400,
  "missing": ["url"],
  "code": "invalid_input",
  "retryable": false,
  "requestId": "922874e3-c0d0-40d8-8866-d198af132420"
}
codeStatusRetryableMeaning
invalid_input400noThe request needs fixing; error names the field. A missing required parameter also lists it in missing.
unauthorized401noThe API key is missing or not valid.
insufficient_credits402noNothing left to spend: no credits, a used-up free trial, or a monthly allowance and credit balance both spent. error says which. Waiting does not help; topping up does.
forbidden403noThis key or account is not allowed this request.
not_found404noWhat the request names does not exist or is not public.
expired410noWhat the request refers to is no longer available; ask for it afresh.
quota_exceeded429noA request-rate limit was reached; error says which. Slow down before sending more.
cancelled499noThe caller closed or cancelled the request before it finished. The work was stopped and nothing was charged.
time_limit503yesYour timeoutMs ran out before there was anything to return; ask again, or allow more time.
temporarily_unavailable503yesNot available right now; ask again shortly.
posts_unavailable403noTikTok user videos: TikTok is not showing this account’s posts publicly right now. Asking again at once returns the same answer; nothing is charged.
store_unreachable503yesStore routes (Shopify, WooCommerce): the store itself can’t be reached right now — it may be offline. Ask again later. Where a store route doesn’t say so yet, an offline store is temporarily_unavailable.
upstream_error502yesThe source answered in a way that could not be used; ask again later.
internal_error500yesSomething failed on our side; ask again later, and quote requestId if it persists.
invalid_cursor400noThe cursor is not one this route returned for this account; start again without it.
tool_error—noMCP only: a tool call failed in a way no other code describes; error says what.

More codes may be added; treat one you do not recognise by its retryable flag. Over MCP, a tool error carries the same fields in structuredContent, and as a JSON text block beside the message.

Next steps

Product overviewInstagram API: what it returns, pricing and use cases