herascrape
API reference

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 post

The 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 forIt is already in /posts/{id}
Media file URLsimage_versions2, video_versions
Carousel childrencarousel_media
Reel audio / musicclips_metadata
Tagged accounts (photos)usertags
Collaboratorscoauthor_producers
Like and comment countslike_count, comment_count
Locationlocation

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.

Path Parameters

post_id*Post Id
GET
/posts/{post_id}
curl -X GET "https://example.com/posts/string"
{}
{}

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
}
FieldNote
media_type1 photo, 2 video, 8 carousel.
product_typefeed, clips (a reel), or carousel_container.
captionAn object, not a string. The text is caption.text, and it is null for an uncaptioned post.
image_versions2.candidatesSized variants, largest first.
video_versionsPresent on videos and reels; empty on photos.
carousel_mediaChild items, each with its own versions. Only on carousels.
clips_metadataReel 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=25025320

This 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

post_id*Post Id

Query Parameters

user_id*User Id

Numeric id of the post's owner (the post's user.pk)

Match^\d+$
min_id?string|null
max_id?|

Walk toward older comments

carousel_media_id?string|null
carousel_index?Carousel Index
Default0
GET
/posts/{post_id}/comments
curl -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=25025320

A 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

post_id*Post Id

Query Parameters

user_id*User Id

Numeric id of the post's owner (the post's user.pk)

Match^\d+$
GET
/posts/{post_id}/likers
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

CodeWhen
not_foundDeleted, or no such pk or shortcode.
private_accountThe owner is private.
invalid_parameterThe id is neither a valid pk nor a valid shortcode, or a composite id names a different owner than user_id.
unprocessable_entityuser_id is missing or not numeric on comments or likers.
upstream_errorTransient. Retry.

On this page