Posts
A post by id or shortcode, its comments and its likers.
/posts/{id} accepts either the numeric media pk or the shortcode.
The two are interconvertible, so there is no separate by-code or by-URL
endpoint — pull the code out of an instagram.com/p/<code>/ URL and pass it
straight in.
GET /posts/2168384974851429640 # pk
GET /posts/B4XpqViUFUI # shortcode — same postThe post
One call returns the whole object, which is more than it looks. Instagram packs into it what other providers split across extra endpoints:
| Looking for | It is already in /posts/{id} |
|---|---|
| Media file URLs | image_versions2, video_versions |
| Carousel children | carousel_media |
| Reel audio / music | clips_metadata |
| Tagged accounts (photos) | usertags |
| Collaborators | coauthor_producers |
| Like and comment counts | like_count, comment_count |
| Location | location |
So there is no download, clips-metadata, usertags or comment-count endpoint: fetching the post gives you all of it for one call.
On video posts, usertags comes back empty — Instagram does not populate
it for clips. Collaborators still surface in coauthor_producers, and caption
mentions are in the caption text. If you need a clean tagged-users list for
videos, tell us.
What comes back
Instagram's media object, roughly 240 top-level fields. Abridged:
{
"pk": "2168384974851429640",
"id": "2168384974851429640_25025320",
"code": "B4XpqViUFUI",
"taken_at": 1699999999,
"media_type": 8,
"product_type": "carousel_container",
"caption": { "text": "A caption", "created_at": 1699999999,
"user": { "pk": "25025320", "username": "instagram" } },
"like_count": 120345,
"comment_count": 892,
"play_count": null,
"user": { "pk": "25025320", "username": "instagram", "is_verified": true },
"image_versions2": { "candidates": [{ "width": 1080, "height": 1350, "url": "..." }] },
"video_versions": [],
"carousel_media": [{ "pk": "...", "media_type": 1, "image_versions2": {} }],
"usertags": { "in": [] },
"coauthor_producers": [],
"location": { "pk": "213385402", "name": "New York, New York", "lat": 40.7, "lng": -74.0 },
"clips_metadata": null
}| Field | Note |
|---|---|
media_type | 1 photo, 2 video, 8 carousel. |
product_type | feed, clips (a reel), or carousel_container. |
caption | An object, not a string. The text is caption.text, and it is null for an uncaptioned post. |
image_versions2.candidates | Sized variants, largest first. |
video_versions | Present on videos and reels; empty on photos. |
carousel_media | Child items, each with its own versions. Only on carousels. |
clips_metadata | Reel audio and music. null unless product_type is clips. |
URLs in image_versions2 and video_versions are signed and expire within
hours. Download promptly rather than storing the URL.
Comments
Pass the post's owner as user_id — the numeric user.pk on the post, or the
id of the profile you listed the post from:
GET /posts/2168384974851429640/comments?user_id=25025320This endpoint keeps Instagram's own comment-stream shape rather than the standard list envelope, because its paging token is structural to the response:
{
"comments": [],
"has_more": true,
"next_min_id": "...",
"media_pk": "2168384974851429640_25025320",
"...": "..."
}Page by passing next_min_id back as min_id, and stop when has_more is
false. Treat next_min_id as opaque — pass back exactly what you were given.
Instagram's own has_more_comments appears in this response and does not
mean what it says: it tracks the inline preview strip, so it reads false on a
post with thousands of comments. Branch on has_more above.
Replies are not included — fetch them per comment through
/comments/{id}/replies.
For a carousel, carousel_media_id and carousel_index scope comments to one
child.
Path Parameters
Query Parameters
Numeric id of the post's owner (the post's user.pk)
^\d+$Walk toward older comments
0curl -X GET "https://example.com/posts/string/comments?user_id=string"{}{}What comes back
{
"comments": [
{
"pk": "18012345678901234",
"text": "Great shot",
"created_at": 1699999999,
"comment_like_count": 12,
"child_comment_count": 2,
"is_liked_by_media_owner": false,
"user_id": "51065942432",
"user": { "pk": "51065942432", "username": "example" }
}
],
"next_min_id": "eyJzZXJ2ZXJfY3Vyc29yIjo...",
"media_pk": "2168384974851429640_25025320",
"has_more_comments": true
}child_comment_count is how many replies a comment has — fetch them with
/comments/{pk}/replies, passing the post id as media_id
and its owner as user_id.
Likers
Requires the post's owner as user_id, the same as comments.
GET /posts/2168384974851429640/likers?user_id=25025320A single page of accounts that liked the post — has_more is always false.
Instagram does not expose deep pagination here, and on high-engagement posts
it hides the list entirely.
Path Parameters
Query Parameters
Numeric id of the post's owner (the post's user.pk)
^\d+$curl -X GET "https://example.com/posts/string/likers?user_id=string"{}{}What comes back
The standard list envelope, with compact user objects identical to followers.
Errors
| Code | When |
|---|---|
not_found | Deleted, or no such pk or shortcode. |
private_account | The owner is private. |
invalid_parameter | The id is neither a valid pk nor a valid shortcode, or a composite id names a different owner than user_id. |
unprocessable_entity | user_id is missing or not numeric on comments or likers. |
upstream_error | Transient. Retry. |