List an account’s posts
The posts a public Instagram account has published, newest first, each with its date, like, comment and play counts, caption and media. Pass a username or profile URL, or the account’s numeric userId. Paged: 12 posts per page, 1 credit per page fetched.
/user-posts Accounts https://api.fetchlayer.dev/instagram/user-posts Headers
Authorization string required Bearer token — your FetchLayer API key.
Parameters
username string optional A username with or without the @, or a profile URL. Give this or userId.
userId string optional The account’s numeric id (profile.id), as a string of digits. Give this or username. An id names the account even after it changes its username; an id that belongs to no account is a 404. Name the account with exactly one of username and userId; both or neither is a 400.
include string[] optional Optional extra counts: any of "reposts", "shares" and "saves". "reposts" fills repostCount on every kind of post; "shares" and "saves" fill shareCount and saveCount on reels and videos (photos and carousels have none). It fills them on every post of the page and adds a few seconds per page, usually 2 to 8. A count you asked for is never silently empty: when it is not available for some posts, the note names them — "The repost, share and save counts asked for are not available for these posts in this response, so they are empty: <short codes>. Asking again, or post-detail with include, returns them." The price is unchanged: 1 credit per page fetched. Without include the three are null and the note says so. "native" adds each post’s native record, the account’s own record (account) and the page’s paging fields (nativePageInfo), on every page, about a second more per 24 posts, at the same price. "taggedUsers" (with "native") adds the tagged accounts to reels that have them, in taggedUsers and native.usertags: about 1 to 2 seconds more on a page with tagged reels. A reel whose tags could not be read in time names "taggedUsers" in its missingFields. Any other value is a 400; "country" belongs to user-profile.
limit integer optional Maximum items returned, up to 240. Use -1 for the maximum. Without it, every item on the pages fetched comes back, 12 a page (pages: 2 alone returns 24). A limit takes as many pages as it needs: a limit of 60 takes five pages and costs five credits. (default: every item on the pages fetched, 12 a page)
pages integer optional Pages of results to return in one request, up to 20. Use -1 for the maximum. Instagram serves 12 items per page and each page fetched is 1 credit, so a call costs the pages it fetches — set by pages, or by how many pages a limit needs — not the number of items returned. The response reports them as pagesFetched. A page takes about 2–4 seconds without include. (default: 1)
cursor string optional Continue from a previous response’s nextCursor. Send it back to the same route, with the same account, that produced it; a cursor from another route or another account, or a malformed one, is a 400.
format string optional json or markdown, lowercase; any other value is a 400. Over MCP, markdown returns the result rendered as text; the REST API accepts it and returns JSON. (default: json)
timeoutMs integer optional Time limit for the whole request, in milliseconds (1000–120000); a value outside that range is a 400. When it runs out, a paged request returns what it has with nextCursor, billed for the pages it returned; a request with nothing to return yet fails with a 503 you can retry, marked with the response header X-Timeout-Source: caller. Left out, the request runs as long as it needs.
Response Fields
user-posts
List an account’s posts
transport string The data source: always the platform id, "instagram".
notes string[] Caveats you can act on — a ceiling Instagram imposes, a counter it withholds, a list that stopped early. Several real limits are reported only here, so read them. An empty result always comes with a note explaining it, never as an unexplained empty array.
pagesFetched number Pages of results fetched, which is what the request costs: one credit per page. resolve-url reports 0 and is not billed.
restricted boolean True for an account Instagram restricts — typically an age-restricted brand such as alcohol or cannabis — and false for every other. Such an account answers 200, not 404, with less detail. It returns its basic profile and up to 12 recent posts with like and comment counts and dates; it cannot be paged further, and play counts are not available for it.
minimumAge integer | null The minimum age Instagram states for a restricted account, or null when it states none (and on every other account).
profile object | null For a restricted account, its basic profile in the user-profile shape. Null for every other account.
username string The account the posts belong to.
posts object[] The posts, newest first, pinned posts first on page one. Each normally carries its date, like and comment counts, and for a video its play count, length and video links; with include every post does. A post still missing one of them is named in the note by short code, with the fields it lacks; asking again with include returns them, and so does post-detail. Empty for a private account, with the note "This account is private, so its posts and reels are not public."
posts[].id string Instagram’s exact media id, as a string: it is a 64-bit number, larger than a JSON number can carry exactly, so keep it as a string. post-detail and post-comments accept it as post.
posts[].shortcode string The code in the post URL. post-detail and post-comments both accept it.
posts[].url string Canonical link to the post or reel.
posts[].kind string image, video or carousel.
posts[].productType string | null What Instagram calls the surface — "feed" for a grid post, "clips" for a reel — or null.
posts[].caption string | null The author’s own written post body, or null. This is a caption, not a transcript: Instagram publishes no transcript of spoken audio.
posts[].createdAt string | null ISO 8601 timestamp, or null: always null on reels-feed, and null on a listing record whose fieldsFrom is "listing".
posts[].likeCount integer | null Likes, or null: always null on reels-feed, null on a listing record whose fieldsFrom is "listing", and absent when countsHidden is true.
posts[].commentCount integer | null Comments, or null in the same cases as likeCount.
posts[].playCount integer | null Instagram plays on a video or reel: the view count Instagram shows on it, the same figure on listings and on post-detail (plays on Facebook are not included). Null for photos and carousels, whose views Instagram does not make public, and for posts from a restricted account. On post-detail it can occasionally be null for a video; the note says so, and a retry usually returns it.
posts[].videoDuration number | null Length of a video or reel in seconds, with decimals. Null for a photo or carousel (each carousel frame carries its own carousel[].videoDuration), on reels-feed, and on a listing record whose fieldsFrom is "listing".
posts[].shareCount integer | null How many times the post was shared, as Instagram counts it under a reel. Filled when include lists "shares" (post-detail, user-posts, user-reels); reels and videos only, null on photos and carousels. Also null — not 0 — when the account hides it, and the note says so.
posts[].repostCount integer | null How many times the post was reposted to other accounts’ profiles. Filled when include lists "reposts" (post-detail, user-posts, user-reels); every kind of post.
posts[].saveCount integer | null How many times the post was saved. Filled when include lists "saves" (post-detail, user-posts, user-reels); reels and videos only, null on photos and carousels.
posts[].countsHidden boolean | null True when the account has turned like and view counts off. The counts are then absent rather than zero.
posts[].width integer | null Pixel width, or null where the record does not carry it.
posts[].height integer | null Pixel height, or null where the record does not carry it.
posts[].altText string | null Instagram’s own auto-generated image description ("Photo by NASA on August 19, 2026. May be an image of text."), or null. Machine-written alt text, not a caption and not a transcript.
posts[].thumbnailUrl string | null Preview frame, or null.
posts[].assets object[] Ordered and de-duplicated renditions, each with kind, url, width, height and downloadUrl.
posts[].assets[].downloadUrl string | null Pass it to GET /media to stream the bytes. Instagram’s own links are signed and expire; this one keeps working.
posts[].carousel object[] One entry per frame of a carousel, each with its own renditions and, for a video frame, its own videoDuration. Empty for a single image or video.
posts[].author object id, username, fullName, profilePicUrl, verified, privateAccount and profileUrl. Fields Instagram does not publish on a listing are null.
posts[].coauthors object[] Accounts credited as co-authors of a collab post.
posts[].taggedUsers object[] Accounts tagged in the image.
posts[].location object | null The place the post was tagged at, or null.
posts[].hashtags string[] Without the #.
posts[].mentions string[] Usernames without the @.
posts[].topics string[] Instagram’s own subject labels for the post ("jwst", "star birth"), where it publishes them.
posts[].hasAudio boolean | null Whether the video carries audio, or null.
posts[].fieldsFrom string Which shape this record has, so a null count is never mistaken for a zero. "detail" is a full post, from post-detail. "listing_full" is a listing record with its posting date and counts, which is what user-posts and user-reels return. "listing" is a record without them: every reel on reels-feed, and on user-posts or user-reels only a post that is named in the note by short code, with the fields it lacks. Asking again with include returns them, and so does post-detail.
posts[].audio object | null The sound on a video or reel, where Instagram publishes it with the post; null otherwise. Ids are exact strings.
posts[].audio.type string | null Instagram’s label for the sound, e.g. original_sounds or licensed_music.
posts[].audio.originalSoundTitle string | null The title of an original sound, e.g. Original audio.
posts[].audio.artistUsername string | null The account an original sound is credited to.
posts[].audio.durationMs integer | null Length of the sound in milliseconds.
posts[].audio.audioAssetId string | null The sound’s own id.
posts[].audio.canonicalId string | null The id Instagram groups uses of the same sound under.
posts[].audio.songTitle string | null Licensed music: the song’s title. Null for an original sound, or a song Instagram no longer makes available.
posts[].audio.songArtist string | null Licensed music: the artist as Instagram shows it.
posts[].audio.isExplicit boolean | null Licensed music: whether Instagram marks the song explicit. Null when there are no song details.
posts[].audio.audioClusterId string | null Licensed music: the id Instagram groups every use of the song under.
posts[].fullId string | null Instagram’s full id for the post, <id>_<author id>. Null when the author’s id is not known.
posts[].videoDownloadUrl string | null The first video’s downloadUrl, for GET /media, valid as long as its own link. Null for a photo, or when this response has no video link for the post.
posts[].thumbnailDownloadUrl string | null thumbnailUrl as a downloadUrl for GET /media, valid as long as thumbnailUrl is.
posts[].brandedContent object The paid-partnership label, and how far the accounts a post names are known in this response. Each status is found (present), not-published (Instagram shows none) or not-looked-up (not known in this response — never a statement that there is none). Sponsor accounts are not returned.
posts[].brandedContent.isPaidPartnership boolean | null Instagram’s paid-partnership label. Null when not known in this response.
posts[].brandedContent.paidPartnershipStatus string found, not-published or not-looked-up, for isPaidPartnership.
posts[].brandedContent.coauthorsStatus string found, not-published or not-looked-up, for coauthors.
posts[].brandedContent.taggedUsersStatus string found, not-published or not-looked-up, for taggedUsers.
posts[].brandedContent.captionTagsStatus string found, not-published or not-looked-up, for hashtags and mentions, which come from the caption and carry no account ids.
posts[].missingFields string[] The fields this post still lacks in this response, empty when it has them all: any of createdAt, caption, likeCount, commentCount, playCount, videoDuration, width, height and videoUrl (no video link in assets), and on user-posts and user-reels taggedUsers when include asked for it and the tags could not be read in time. A field the post really has none of — no caption, a count the account turned off — is not listed. More values may be added.
posts[].native object | null Only with include ["native"]: the post’s full record as Instagram publishes it, field for field under Instagram’s own field names, with every id an exact string. A post that came as a short record is completed too. Null when not available this time (nativeUnavailableReason says why); absent without the include.
posts[].nativeUnavailableReason object | null Only with include ["native"]: null when native is present, otherwise {code, retryable} saying why it is null: time-limit (timeoutMs left too little time; allow more), temporarily-unavailable (asking again usually returns it), or capacity (full records are temporarily unavailable for this request; retry shortly). All are retryable, and the rest of the response is unaffected.
account object Only with include ["native"], on every page: the account the posts belong to. Absent without the include.
account.id string | null The account’s numeric id, as an exact string.
account.username string The account’s username.
account.native object | null The account’s full profile record under Instagram’s own field names, as user-profile returns it in profile.native. Null when not available this time (the note says so, and nativeUnavailableReason says why).
account.nativeUnavailableReason object | null Null when native is present, otherwise {code, retryable}: time-limit (timeoutMs left too little time; allow more), temporarily-unavailable (asking again usually returns it), or capacity (full records are temporarily unavailable for this request; retry shortly).
nativePageInfo object | null Only with include ["native"], on every page: the paging fields Instagram sent with the page this answer ends on, under Instagram’s own field names (whether more follow, how many came), for reference only — continue with nextCursor. Null when they could not be read, with nativePageInfoUnavailableReason saying why; absent without the include.
nativePageInfoUnavailableReason object | null Only with include ["native"]: null when nativePageInfo is present, otherwise {code, retryable} saying why nativePageInfo is null. temporarily-unavailable: the page’s own paging fields could not be read for this response. On a private account, or an account with no posts, Instagram publishes no such fields, so nativePageInfo stays null when you ask again; on other accounts it is transient, and asking for the same cursor again usually returns it. time-limit: the time limit you sent (timeoutMs) ran out before they were read; allow more time. capacity: they could not be read for this request right now; retry shortly. Restricted (age-limited) accounts never have them.
status string What the answer amounts to. ok: what was asked for (a continuation past the last page is ok with no posts). partial: something is missing; partialReasons says what. empty: the account has nothing in this list. private: the account is private. restricted: Instagram restricts the account. More values may be added; treat one you do not recognise as partial.
partialReasons string[] Why the answer is partial; empty otherwise. time_limit: timeoutMs ran out; continue with nextCursor. stopped_early: a later page was not returned; continue with nextCursor. fields_missing: some posts lack a field, named in each post’s missingFields. counts_unavailable: some counts asked for with include are empty. More values may be added.
complete boolean True when every post returned has every field you can rely on; otherwise each post’s missingFields names what is missing.
stopReason string Why the list stopped where it did. exhausted: nothing after it (hasMore false). page_limit: the limit or pages asked for were reached. time_limit: timeoutMs ran out. upstream_stopped: a later page was not returned. Continue with nextCursor for every value but exhausted. More values may be added.
postCount number How many items are in this response.
hasMore boolean Whether more results exist beyond what was returned.
nextCursor string | null Opaque value to pass back as cursor, to the same route (and the same account on an account listing), to continue. null when there is nothing further.
Request
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":12}'Response
{
"transport": "instagram",
"notes": [
"repostCount, shareCount and saveCount are empty unless \"reposts\", \"shares\" or \"saves\" is listed in include.",
"More results are available. Continue with nextCursor."
],
"pagesFetched": 1,
"posts": [
{
"id": "3967213292204992434",
"shortcode": "DcOX3hWFiey",
"url": "https://www.instagram.com/p/DcOX3hWFiey/",
"kind": "image",
"productType": "feed",
"caption": "With your powers combined…\n\nThis colorful picture of the cosmos is the product of teamwork between our @NASAHubble, @NASAWebb, and @NASAChandraXray telescopes. …",
"createdAt": "2026-08-19T14:11:47.000Z",
"likeCount": 573281,
"commentCount": 4334,
"playCount": null,
"videoDuration": null,
"shareCount": null,
"repostCount": null,
"saveCount": null,
"countsHidden": false,
"width": 1280,
"height": 1129,
"altText": null,
"thumbnailUrl": "https://scontent-muc2-1.cdninstagram.com/v/t51.82787-15/...",
"assets": [
{
"kind": "image",
"url": "https://scontent-muc2-1.cdninstagram.com/v/t51.82787-15/...",
"width": 1080,
"height": 953,
"downloadUrl": "https://api.fetchlayer.dev/instagram/media?url=..."
}
],
"carousel": [],
"author": {
"id": "528817151",
"username": "nasa",
"fullName": "NASA",
"profilePicUrl": "https://scontent-muc2-1.cdninstagram.com/v/t51.2885-19/...",
"verified": true,
"privateAccount": false,
"profileUrl": "https://www.instagram.com/nasa/"
},
"coauthors": [],
"taggedUsers": [],
"location": null,
"hashtags": [
"nasa",
"universe",
"nebula"
],
"mentions": [
"nasahubble",
"nasawebb",
"nasachandraxray"
],
"topics": [],
"hasAudio": null,
"fieldsFrom": "listing_full"
}
],
"postCount": 12,
"hasMore": true,
"nextCursor": "528817151:4006570159456294956_582986390",
"restricted": false,
"minimumAge": null,
"profile": null,
"username": "nasa"
}