# Instagram Post Data Scraper — Instagram Reel Data by URL (`steadyfetch/instagram-post-details-scraper`) Actor

Instagram post data and reel data from one link: play count, saves, reshares, likes, comments, three video renditions, the audio track, co-authors, tagged accounts, location, carousel slides and the caption. No login. From $3.00 per 1,000 posts, no start fee.

- **URL**: https://apify.com/steadyfetch/instagram-post-details-scraper.md
- **Developed by:** [Steadyfetch Team](https://apify.com/steadyfetch) (community)
- **Categories:** Social media, Lead generation, Automation
- **Stats:** 1 total users, 0 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.00 / 1,000 posts

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Instagram Post Data Scraper — Instagram Reel Data by URL

Paste the links of the Instagram posts and reels you want and get one row each, with everything the post exposes: play count, saves, reshares, likes, comments, video length and codec, the three video renditions, the audio track, co-authors, tagged accounts, location, carousel slides, owner and the caption. No login, no cookies, no proxy to configure. **Nothing is charged for starting a run**, and a post we could not deliver is never charged.

**Using an AI agent?** Pin this actor in Apify's MCP server with one link: `https://mcp.apify.com?tools=steadyfetch/instagram-post-details-scraper`

- **Actor id:** `steadyfetch/instagram-post-details-scraper`
- **Input:** `{ "postUrls": ["https://www.instagram.com/reel/DdG2ps5y5-l/"] }` — the one field you have to set. A bare code (`"DdG2ps5y5-l"`) or a numeric media id works too.
- **Cap the bill:** set `maxTotalChargeUsd` on the run (a run option, not Actor input), e.g. `0.50` — the run stops when it reaches it, and stops reading, not just billing.

### What you get

One row per post, with everything the post exposes at one price — there is no second charge for "details":

| Column | What it holds |
|---|---|
| `shortCode` · `url` · `type` · `productType` | the post's own code, its permalink, image / video / carousel, and whether it is a reel (`clips`) |
| `caption` · `hashtags` · `mentions` | the caption, and the tags and @handles pulled out of it |
| `takenAt` | when it was posted, ISO-8601 UTC |
| `likeCount` · `commentCount` · `playCount` · `saveCount` · `reshareCount` | engagement, including the two counts a feed listing does not carry |
| `countsHiddenByOwner` | `true` when the owner has hidden their like and view counts — so a small `likeCount` is never mistaken for the truth |
| `videoDuration` · `videoCodec` · `hasAudio` · `videoRenditions` | the video's length, codec, and every rendition Instagram serves, each with its own width, height and URL |
| `audioTitle` · `audioArtist` · `audioIsOriginal` · `audioIsExplicit` · `audioDuration` | the track the reel is set to, whether it is a licensed song or the creator's own audio |
| `coauthors` · `taggedUsers` | the accounts sharing the post, and the accounts tagged in it |
| `locationName` · `locationId` · `latitude` · `longitude` | the place it was tagged in |
| `carouselCount` · `carouselMedia` | how many slides, and one entry per slide with its own media links |
| `ownerUsername` · `ownerFullName` · `ownerId` · `ownerIsVerified` · `ownerIsPrivate` | who posted it |
| `displayUrl` · `videoUrl` · `videoUrlExpiresAt` | the media links, and when the signed link stops working |
| `charged` · `status` · `statusReason` | whether this row was billed, and why, so your invoice reconciles from the dataset itself |

Media links are signed and time-limited by Instagram: `videoUrlExpiresAt` tells you exactly how long each one stays fetchable, so download what you need in the same session. The requesting network address is stripped out of every link before it reaches your dataset.

### What a row looks like

One delivered row from a real run — a reel posted by `reuters`, read 2026-09-12. One post or reel is one `post-details` charge, whatever is on it: the play count, the audio track, the three video renditions and the carousel slides all ride the same row. Media links are signed and time-limited by Instagram, so they are shown here as placeholders.

```json
{
  "postId": "3983326735612528547",
  "shortCode": "DdHno-8gEej",
  "url": "https://www.instagram.com/p/DdHno-8gEej/",
  "type": "video",
  "productType": "clips",
  "caption": "As New York City marks 25 years since the September 11, 2001 attacks, a former principal of an elementary school on Manhattan’s Upper East Side, Dan Feigelson, reflected on how educators helped kids cope in the days and years that followed, including a student who lost a parent.\n \n 🔗 Read more at our link in bio",
  "hashtags": [],
  "mentions": [],
  "takenAt": "2026-09-10T20:15:08.000Z",
  "takenAtTimestamp": 1789071308,
  "likeCount": 1575,
  "commentCount": 36,
  "playCount": 61680,
  "videoDuration": 70.421,
  "displayUrl": "https://…",
  "videoUrl": "https://…",
  "videoUrlExpiresAt": "2026-09-14T01:07:51.000Z",
  "carouselCount": null,
  "dimensionsWidth": 1080,
  "dimensionsHeight": 1920,
  "ownerId": null,
  "ownerUsername": "reuters",
  "ownerFullName": "Reuters",
  "ownerIsVerified": true,
  "taggedUsers": [],
  "coauthors": [],
  "locationName": null,
  "locationId": null,
  "isPinned": false,
  "isSponsored": false,
  "postSource": "post-details",
  "profileHandle": null,
  "profileFullName": null,
  "profileFollowers": null,
  "profileIsVerified": null,
  "profilePostCount": null,
  "audioTitle": "Original audio",
  "audioArtist": "reuters",
  "audioIsOriginal": true,
  "audioIsExplicit": false,
  "audioId": "28153942414290931",
  "audioDuration": 70.403,
  "viewCountInstagram": 61680,
  "saveCount": null,
  "reshareCount": 40,
  "countsHiddenByOwner": false,
  "hasAudio": true,
  "videoCodec": "av01.0.05M.08.0.111.01.01.01.0",
  "videoRenditions": [
    {"url":"https://…","width":720,"height":1280,"type":101},
    {"url":"https://…","width":720,"height":1280,"type":102},
    {"url":"https://…","width":720,"height":1280,"type":103}
  ],
  "carouselMedia": [],
  "latitude": null,
  "longitude": null,
  "ownerIsPrivate": false,
  "captionIsEdited": false,
  "commentsDisabled": false,
  "source": "instagram",
  "isVideo": true,
  "input": "https://www.instagram.com/p/DdHno-8gEej/",
  "inputKind": "code",
  "sourceIndex": 0,
  "scrapedAt": "2026-09-12T14:47:58.490Z",
  "charged": true,
  "status": "delivered",
  "retryable": false,
  "statusReason": null,
  "#k": "3983326735612528547",
  "#ce": {"post-details":1},
  "#ask": {"delivered":1}
}
```

### What to paste

Every shape Instagram serves for a single post is read, and each is turned into the post's own code before anything is fetched:

- `https://www.instagram.com/p/CODE/`
- `https://www.instagram.com/reel/CODE/` and `/reels/CODE/`
- `https://www.instagram.com/tv/CODE/`
- `https://www.instagram.com/username/reel/CODE/`
- the bare code, `DdHaXadN9KF`, or the numeric media id

Tracking parameters on the end of a link are ignored. A whole block of links pasted into one box is opened out into one entry per link. A **share** link (`/share/…`) hides the code behind a redirect — open it in a browser and copy the address it lands on.

### Price

One charged event, `post-details`, and it lands only on a post that was actually delivered.

| Apify plan | Per post | Per 1,000 posts |
|---|---|---|
| Free plan | $0.006 | $6.00 |
| Bronze | $0.0045 | $4.50 |
| Silver | $0.0036 | $3.60 |
| Gold and above | $0.003 | **$3.00 per 1,000** |

Platform usage is included in that price — there is no separate compute bill on top, and **no start fee**: a run that reads one post pays for one post. Set `maxTotalChargeUsd` on the run and it is a hard ceiling on reading as well as on billing: the run stops cleanly under it and names every link it did not reach.

### Honest rows: what "not charged" actually means

Every run closes its own books. A link that did not produce a post leaves an uncharged row saying which of eight things happened, and none of them is billed:

- **`not_found`** — Instagram has nothing at that link: the post was deleted, made private, or the link is wrong. A definitive answer, not a failure.
- **`private_account`** — the owner is private or the post is restricted, so nothing is served without a follow.
- **`user_input`** — the value is not a link to one post, and the row says exactly what to paste instead (a profile link and a hashtag link are each answered by name).
- **`stopped_at_limit`** — one of your own limits, or the run clock, stopped it first. The row names which, and the link it did not read.
- **`vendor_unavailable`** — the read did not go through. Temporary, says nothing about the post, and a re-run is the fix.
- **`source_refused`** — the code or media id is not one Instagram issues, so the read was refused outright. The same every time, so a re-run cannot fix it — check the value against the post.
- **`unsupported_shape`** — Instagram answered in a shape this actor does not read yet. That is on us, it will answer the same way on a re-run, and the Issues tab is how you get it supported.
- **`vendor_budget`** — this actor reached its own monthly collection allowance and stopped rather than reading more.

A link that names a post already read in the same run ships as `duplicate`: the post is delivered once, charged once, and the repeat is named rather than dropped in silence, so a 50-link list still reconciles against the rows you get. That holds whichever way you name it — a post's link and its numeric media id are the same post, and naming it both ways still buys it once. The last row of every run is a receipt: delivered, asked for, what stopped it, and the charged-event totals.

### How often this data changes

Constantly, and that is the reason this actor has no memory of what it gave you. Play counts, likes, comments, saves and reshares on a live post move by the minute; on a reel that is spreading they move by the second. Handing back a row this actor delivered yesterday would be handing back a stale number, so every run re-reads every link you give it and charges for what it re-read. That is deliberate: this actor exists for the second run.

What that means in practice — a post's numbers are usually worth re-reading after an hour while it is young, daily for its first week, and weekly after that; the caption, media, audio and tagged accounts almost never change after the first hour. If you are watching a set of posts, schedule the run at the interval you actually need and set `maxTotalChargeUsd` so a schedule can never surprise you.

### Reliability

The data comes from a licensed data feed rather than from scraping Instagram's own web pages, which is why there is nothing to log into and nothing to configure. When that feed cannot answer, the run says so on an uncharged row and finishes successfully — a failed read is never billed and never dressed up as a deleted post.

### Related actors

- Every post published under a hashtag: **Instagram Hashtag Scraper** — https://apify.com/steadyfetch/instagram-hashtag-scraper
- One account's own posts: **Instagram Profile Posts Scraper** — https://apify.com/steadyfetch/instagram-profile-posts
- The spoken words inside a reel: **Instagram Reel Transcript Scraper** — https://apify.com/steadyfetch/instagram-reel-transcript-scraper
- The comments under a post: **Instagram Comments Scraper** — https://apify.com/steadyfetch/instagram-comments-scraper

### Limits and good manners

**Your settings are never refused for being too big.** This actor delivers up to 5,000 posts per run (`resultsLimit`) and runs for up to 1 hour (`maxRunSeconds`, minimum 30 seconds). Ask for more than that — or for a shorter clock — and the run still starts: it continues at the nearest limit and leaves one uncharged row saying what you asked for and what bound it. Nothing is refused before it starts.

Only public posts are returned. Instagram serves a fixed set of fields for a post and this actor returns all of them — it does not add anything Instagram does not publish, so a column can be null simply because that post has no such value (a photo has no play count, a post with no music has no audio title). The list of accounts who liked a post is deliberately not offered: Instagram caps that list at a sample of about a hundred whatever the real number is, and a sample sold under the name "likers" would be a false promise. This actor may stop working if Instagram changes how the data is served; if it does, failed items are never charged.

### Support

Something off, or a column you need that is not here? Open an issue on the Issues tab — we usually reply within a couple of hours.

If it earned its keep, a rating helps other buyers find it, and saving the actor keeps it one click away.

# Actor input Schema

## `postUrls` (type: `array`):

Links to single Instagram posts or reels, one per line — https://www.instagram.com/p/CODE/, https://www.instagram.com/reel/CODE/, https://www.instagram.com/tv/CODE/ and https://www.instagram.com/username/reel/CODE/ all work, as does the bare code ("DdHaXadN9KF") or the numeric media id. A link that does not produce a post is never charged. A profile link or a hashtag link is refused with a row naming the actor that does take it. Leave it empty and the run returns built-in sample rows instead of reading anything, so you can see the output shape at no result fee.

## `resultsLimit` (type: `integer`):

Hard cap on the posts this run delivers in total. Your list is normally the limit — this is here for the case where you paste more links than you meant to pay for. Links beyond the cap leave an uncharged row each, naming the link and the limit that stopped it. This actor delivers at most 5,000 posts in one run: ask for more and the run continues at 5,000, with one uncharged row saying so, rather than being refused.

## `maxRunSeconds` (type: `integer`):

The run stops cleanly before this many seconds and reports what is left, instead of being killed by a timeout. A time limit ends the reading, never the delivering: posts already in hand are always written out. The clock runs between 30 seconds and 1 hour: set it outside that and the run continues at the nearest one, with one uncharged row saying so, rather than being refused.

## Actor input object example

```json
{
  "postUrls": [],
  "resultsLimit": 1000,
  "maxRunSeconds": 600
}
```

# Actor output Schema

## `posts` (type: `string`):

One row per post or reel you named: caption, hashtags, mentions, likes, comments, play count, saves, reshares, video length and codec, the three video renditions, the audio track, co-authors, tagged accounts, location, carousel slides, owner and the permalink. Every row carries `charged` and `status`, so the invoice reconciles from the dataset itself — only rows with charged = true were billed, at one price for the whole row with no second charge for details.

## `misses` (type: `string`):

One uncharged row for every link that did not produce a post, saying which of eight things happened: `not_found` (Instagram has nothing at that link), `private_account` (the owner is private or the post restricted), `user_input` (the value is not a link to one post, and the row says what to paste instead), `stopped_at_limit` (one of your own limits or the run clock stopped it first), `vendor_unavailable` (the read failed and a re-run is the fix), `source_refused` (the code or media id is not one Instagram issues, so the read was refused outright and a re-run cannot change it), `unsupported_shape` (Instagram answered in a shape this actor does not read yet, which a re-run cannot change either), `vendor_budget` (this actor reached its own monthly collection allowance). A link that names a post already in the run ships as `duplicate`. None of these is charged.

## `summary` (type: `string`):

Posts delivered, links asked for, links that named the same post twice, what stopped the run, and the charged-event totals.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "postUrls": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("steadyfetch/instagram-post-details-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "postUrls": [] }

# Run the Actor and wait for it to finish
run = client.actor("steadyfetch/instagram-post-details-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "postUrls": []
}' |
apify call steadyfetch/instagram-post-details-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,steadyfetch/instagram-post-details-scraper"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/gG0nqF1dcsrcswjQy/builds/Uf3w32562WDWuS3dd/openapi.json
