Give it a public Instagram handle and get that account's recent feed posts back as an array of JSON objects — caption, engagement counts, timestamp, media type and thumbnail for each one. One call returns one array sized by `limit` — there is no cursor to follow — and billing is per post returned, at 1 credit each, so a default call of 20 costs 20 credits. No Graph API app review, no Business account linkage, no Page token.
| parameter | type | description |
|---|---|---|
| handle | string, required | The Instagram username, without the @. |
| limit | integer | Items per page, up to 50 synchronously. Above that the call becomes a job. |
| cursor | string | meta.next_cursor from the previous page. |
| fresh | boolean | Skip the stored copy and read live, at the same price. |
| fields | string | Comma-separated field paths to keep. |
| format | json, csv | CSV flattens each item to one row. |
1 credit per post. Failed calls are never charged, and empty results are free up to 25 a day. Stored copies are refreshed every 3 hours.
An array under `data`, newest post first, one object per post on the account's grid. Each object carries the shortcode as its id, the canonical permalink, the caption text, like and comment counts, the publish timestamp, a thumbnail URL and a small nested author block. Reels and video posts also carry a play count and a runtime.
The shape is deliberately the same object the Instagram Stats API returns for a single URL, so a list call and a detail call are interchangeable in your storage layer. Upsert on `id` and you can mix the two without writing a second mapper.
You control how much comes back with `limit`, which defaults to 20 and accepts anything from 1 to 500. Requests above 50 are executed as an asynchronous job instead of being answered inline. There is no cursor, page token or offset in the response, so how far back you reach is decided entirely by `limit` on a single call. Cost scales with what you actually receive: ask for 50 posts, pay for 50 posts. Ask for 5 and pay for 5.
curl "https://api.instadata.dev/v1/instagram/channel-posts?handle=nasa&limit=10" \ -H "Authorization: Bearer ik_live_..."
{
"data": [
{
"id": "C8xQvZ2sVAb",
"url": "https://www.instagram.com/reel/C8xQvZ2sVAb/",
"caption": "Three settings you should change today",
"posted_at": "2026-07-03T12:00:00Z",
"metrics": {
"likes": 55210,
"comments": 981
}
},
{
"id": "C8xQvZ2sVAb1",
"url": "https://www.instagram.com/reel/C8xQvZ2sVAb/",
"caption": "Three settings you should change today",
"posted_at": "2026-07-03T12:00:00Z",
"metrics": {
"likes": 55210,
"comments": 981
}
}
],
"meta": {
"count": 2,
"limit": 20,
"item": "post"
},
"request_id": "req_01JZX4M8Q2TE9W"
} | field | type | note |
|---|---|---|
| id | string | The post shortcode (the segment after /p/ or /reel/ in the URL). Stable for the life of the post — use it as your primary key. |
| url | string | Canonical instagram.com permalink, reconstructed from the shortcode when the upstream page omits it. |
| type | string|null | Media kind as Instagram labels it, e.g. Image, Video, Sidecar, Clips. Null when the page does not expose a product type. |
| caption | string|null | Caption text exactly as posted, including hashtags and line breaks. Null for posts published with no caption. |
| views | number|null | Play count for video and reel posts. Always null on still images and carousels — Instagram does not publish a view count for those. |
| likes | number|null | Public like count. Null when the account has hidden like counts on that post, which is a per-post setting. |
| comments | number|null | Number of comments. Null when comments are switched off. Fetch the text itself with the Instagram Comments API. |
| shares | null | Always null on Instagram. The field exists so post objects share one shape across endpoints; Instagram does not publish a public share count on post pages. The reel-shares endpoint reads it through a separate upstream add-on. |
| duration_sec | number|null | Video runtime in seconds. Null for images and carousels. |
| posted_at | string|null | ISO 8601 UTC publish timestamp. Sort on this rather than trusting array order if you merge results from more than one call. |
| author | object | Nested { handle, name } for the posting account. Follower and post totals are not here — those come from the Instagram Channel Stats API. |
| thumbnail | string|null | Direct URL to the display image or video cover frame. Instagram CDN links expire, so download the bytes if you need them to last. |
| hashtags | string[]|null | Hashtags associated with the post as bare strings. Null when the upstream page exposes none, which happens even on captions that visibly contain tags — parse `caption` yourself if you need them reliably. |
We read Instagram the way a logged-out visitor does. If an account is private, its grid is not public, so there is nothing to return and the call errors rather than handing back an empty array dressed up as success. Failed calls are not charged.
Check `is_private` on the Instagram Channel Stats API first if you are working through a list of handles of unknown quality. One stats call is cheaper than a failed page attempt plus a retry.
Billing is 1 credit per post returned, not per request. A `limit=50` call costs 50 credits; a `limit=5` call costs 5. Set `limit` to what you will actually store, because you pay for every item that comes back whether or not you read it.
There is no cursor, page token or offset in the response. One call returns one array and that is the end of it. To reach further back you raise `limit` and call again from the top — which re-fetches everything you already had, and bills you for it a second time. Upsert on `id` when you merge.
`limit` accepts 1 to 500, and anything above 50 runs as an asynchronous job rather than an inline response. Instagram will sometimes return fewer posts than you asked for on accounts with a short or hard-to-reach back catalogue; that is the end of what is publicly reachable, not a bug, and you are only billed for what arrived.
Several fields can be null because Instagram lets creators hide like counts per post, disable comments, and post without captions. `views` is null on every image and carousel, and `shares` is null on everything — Instagram publishes no public share figure.
Do not treat a null count as zero in an average. Filter those posts out of the denominator or your engagement rate will drift low on accounts that hide likes.
The `thumbnail` URL points at Instagram's CDN. Those links carry signed expiry parameters and stop working after a while, so storing the string and rendering it a week later will give you broken images. Fetch the bytes at ingest time and put them in your own bucket if the image needs to persist.
Responses cache in the medium class. Calling the same handle with the same limit twice in a row returns the second one from cache with `x-cache: hit`, in milliseconds, at the same credit cost. Add `fresh=true` when you specifically need to catch a post published minutes ago — a daily competitor sweep does not need it.
Send a GET to /v1/instagram/channel-posts with the handle and your API key. Instagram's Graph API only reaches accounts you own or manage, and requires a Business account, a linked Facebook Page and app review. This endpoint reads the public grid of any public account, so competitor and creator research works without any of that setup.
100 credits. Billing is 1 credit per post returned, so 100 posts is 100 credits — about $0.50 on the monthly plan at $0.005 per credit, or $0.45 on annual. That is one call with `limit=100`; because the limit is above 50 it runs as an asynchronous job rather than returning inline. Failed calls are not charged.
As far as a single call with a high `limit` reaches, which varies by account. The maximum `limit` is 500, and there is no cursor to continue past whatever that call returns. Recent posts are reliable; the deeper into a large back catalogue you ask, the more likely Instagram returns fewer items than you requested. There is no fixed cutoff we can promise, and no way to resume a deeper crawl — if a `limit=500` call comes back with 180 posts, 180 posts is what is publicly reachable for that handle.
Because Instagram only publishes a play count for video content. Still images and carousel posts have no public view figure, so `views` is null on them rather than zero. Likes can also be null when the creator has hidden the like count on that specific post. Treat null as absent data, not as an engagement of zero.
Reels can appear in the grid results and are labelled through the `type` field, but this endpoint is scoped to the main profile feed. If reels are what you are analysing, use the Instagram Channel Reels API — it targets them directly and gives cleaner coverage than filtering these results after the fact.
No. We read only what a logged-out browser can see, so a private account returns an error and no charge. There is no setting, plan or parameter that changes this. If you are running a list of handles, check the account's `is_private` flag with the Instagram Channel Stats API first and skip the private ones.
Not entirely. The `hashtags` array comes from the upstream page and is sometimes null even on captions that clearly contain tags. If hashtag analysis is central to your project, parse `caption` yourself with a simple regex and use `hashtags` only as a fallback. The caption text itself is the more dependable field.