# Instagram Stories Scraper — Instagram Highlights, No Start Fee (`steadyfetch/instagram-stories-highlights-scraper`) Actor

Instagram stories for any public account: media links, mentions, hashtags, link and poll sticker types, and when each story expires. No login. Saved highlight albums too if you ask for them, at $0.012 per album. From $8.00 per 1,000 story checks, no start fee. An empty account is never charged.

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

## Pricing

from $8.00 / 1,000 story checks

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 Stories Scraper — Instagram Highlights, No Start Fee

Type the Instagram accounts you want and get the stories they are showing **right now**, and — if you ask for them — every item inside their saved highlight albums. Media links and when each link stops working, mentions, hashtags, outbound link stickers, poll and question sticker types, and the exact moment each story expires. No login, no cookies, no proxy to configure. **There is no start fee**, and an account with nothing showing 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-stories-highlights-scraper`

- **Actor id:** `steadyfetch/instagram-stories-highlights-scraper`
- **Input:** `{ "usernames": ["nasa"], "includeHighlights": true }` — the one field you have to set is `usernames`.
- **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.
- **Price:** from **$8.00 per 1,000 story checks** on Gold and above, $16.00 per 1,000 on the Apify free plan — one charge per account, covering every story that account is showing; a highlight album is a flat $0.012. No start fee, and an account showing nothing is never charged. Full table below.
- **How often it changes:** stories expire in 24 hours, which is what makes this a scheduled job rather than a one-off — run it daily and you catch what would otherwise be gone, and the quiet days cost nothing because an account with nothing showing is never charged. Highlights barely change at all: read them once, then only when you have a reason to look again.

### What you get

One row per story or highlight item. `kind` says which, and both kinds carry the same columns, so it is one table you can sort rather than two shapes to reconcile:

| Column | What it holds |
|---|---|
| `kind` | `story` (showing now) or `highlight` (saved to the profile) |
| `username` · `userFullName` · `userIsVerified` · `userId` | whose it is |
| `highlightTitle` · `highlightId` · `highlightCreatedAt` | the album this item lives in (empty for a live story) |
| `takenAt` | when it was posted, ISO-8601 UTC |
| `expiresAt` | when this story disappears — 24 hours after `takenAt` (empty for a highlight, which does not expire) |
| `mediaType` · `isVideo` · `videoDuration` | image or video, and how long the video runs |
| `displayUrl` · `videoUrl` · `mediaUrlExpiresAt` | the media links, and exactly when each signed link stops working |
| `mentions` · `hashtags` · `links` | the @handles, tags and outbound URLs on the story, link stickers included |
| `stickerTypes` · `isSponsored` · `locationName` | poll, question, music, GIF and countdown stickers; paid-partnership flag; place |
| `shortCode` · `mediaId` | the item's own identifiers |
| `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: `mediaUrlExpiresAt` 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 — @nasa, 2026-09-11. This row charges one `stories-check`: the account's whole set of live stories, however many there are. Media links are signed and time-limited by Instagram, so they are shown here as placeholders.

```json
{
  "kind": "story",
  "mediaId": "3983283525835002608",
  "shortCode": "DdHd0MtD5Lw",
  "takenAt": "2026-09-10T18:20:20.000Z",
  "expiresAt": "2026-09-11T18:20:20.000Z",
  "mediaType": "video",
  "isVideo": true,
  "videoDuration": 15,
  "displayUrl": "https://…",
  "videoUrl": "https://…",
  "mediaUrlExpiresAt": "2026-09-12T23:08:44.000Z",
  "mentions": [],
  "hashtags": [],
  "links": [],
  "stickerTypes": [],
  "isSponsored": false,
  "locationName": null,
  "highlightId": null,
  "highlightTitle": null,
  "highlightCreatedAt": null,
  "username": "nasa",
  "userId": "528817151",
  "userFullName": "NASA",
  "userIsVerified": true,
  "source": "instagram",
  "input": "nasa",
  "sourceIndex": 0,
  "scrapedAt": "2026-09-11T13:04:04.138Z",
  "charged": true,
  "status": "delivered",
  "retryable": false,
  "statusReason": null,
  "#k": "story:nasa:3983283525835002608",
  "#ce": {"stories-check":1},
  "#ask": {"delivered":1}
}
```

### Price

Two charged events, and both land only on something actually delivered. Nothing is charged for starting a run.

**`stories-check`** — one charge per account whose stories question was answered, covering **every** story that account is showing. Two stories or twenty, it is one charge.

| Apify plan | Per story check | Per 1,000 story checks |
|---|---|---|
| Free plan | $0.016 | $16.00 |
| Bronze | $0.012 | $12.00 |
| Silver | $0.01 | $10.00 |
| Gold and above | $0.008 | **$8.00 per 1,000** |

**`highlight`** — **$0.012 per album delivered, the same on every plan**, covering every item inside that album. A 38-item album is one charge.

Platform usage is included in both prices — there is no separate compute bill on top, and no start fee. Set `maxTotalChargeUsd` on the run and it is a hard ceiling: the run stops cleanly under it and the last row says what is left.

Because charging is per GROUP rather than per row, exactly one row of each group carries `charged: true` and the rest say they are already covered by it. Add up the `charged: true` rows and you have your invoice.

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

Every run closes its own books. An account that gave less than it was asked for leaves an uncharged row saying which of these happened, and none of them is billed:

- **`no_stories`** — the account is public, readable, and simply is not showing a story at this moment. Stories vanish 24 hours after they are posted, so this is a definitive answer about right now, not a failure. **Not charged.**
- **`no_highlights`** — the account keeps no saved albums on its profile. A definitive answer.
- **`private_account`** — Instagram shows this account's stories only to followers it has approved. Proved from the account's own privacy flag, not guessed from a failed read.
- **`account_not_found`** — Instagram has no account under that name; it is misspelled, removed or renamed.
- **`vendor_unavailable`** — the read did not go through. Temporary, says nothing about the account, and a re-run is the fix.
- **`source_refused`** — Instagram would not answer for that handle at all, and it refuses it the same way every time, so a re-run cannot change it. Check the handle in a browser; if the account opens there, say so on the Issues tab.
- **`unsupported_shape`** — Instagram answered in a shape this actor does not read yet. That is on us, not on the account, and a re-run gets the same answer — tell us on the Issues tab which account it was and support for it will be added.
- **`stopped_at_limit`** / **`vendor_budget`** — one of your own limits, the run clock, or this actor's own collection allowance stopped it first. The row names which.

The last row of every run is a receipt: accounts answered, what was asked for, stories and albums delivered, what stopped the run, and the charged-event totals. A time limit ends the reading, never the delivering — rows already in hand are always written out.

### How often this data changes

**Stories change constantly and expire in 24 hours.** An account posting daily shows something new most mornings and nothing at all on quiet days; an account that posts weekly is empty most of the time. That is what makes this a scheduled job rather than a one-off: run it once a day, or a few times a day on the accounts that matter, and you catch what would otherwise be gone. Because an account with nothing to show is never charged, a schedule over a quiet list costs you nothing on the quiet days.

**Highlights barely change at all.** An album is edited when its owner decides to edit it — often not for months. Read them once, then only when you have a reason to look again. Switch **Highlights** off on a daily schedule and leave it on for the occasional full sweep.

### Reliability

The stories and highlights come 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 an account with nothing to show.

### Related actors

- Posts published under a hashtag, or found by keyword: **Instagram Hashtag Scraper** — https://apify.com/steadyfetch/instagram-hashtag-scraper
- One account's own grid 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

### Limits and good manners

**Max accounts**, **Max highlight albums per account** and **Max run seconds** take any number you type: this actor reads at most 1,000 accounts in one run, collects at most 50 albums from any one account, and runs for at most an hour (and for at least 30 seconds). A bigger ask runs at the ceiling instead of being refused, and one uncharged row says what was asked for and what was used.

Only public accounts can be read; a private account gets an honest uncharged row. Instagram itself decides what a logged-out viewer may see, so what comes back is what the account is showing publicly. This actor may stop working if Instagram changes how the data is served; if it does, items that cannot be delivered 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

## `usernames` (type: `array`):

Instagram account names, one per line — "nasa", "@nasa", https://www.instagram.com/nasa/ or https://www.instagram.com/stories/nasa/ all work. An account showing nothing, a private account and a handle that does not exist are never charged. A hashtag or a reel 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.

## `includeStories` (type: `boolean`):

ON (default): read the stories the account is showing right now. One charge per account whose stories question was answered, covering every story it is showing. An account showing nothing is answered honestly and is not charged.

## `includeHighlights` (type: `boolean`):

OFF (default): also read the saved highlight albums on the account's profile and return every item inside them. Charged per album delivered, at one flat price whatever your Apify plan is, covering every item in that album. An account with no albums is answered honestly and is not charged.

## `maxProfiles` (type: `integer`):

Hard cap on how many accounts this run reads, counted from the top of your list. Accounts past it are never read and never charged, and the run summary says how many were left. This is the ceiling on what one run can cost you: an account is charged only if it had something to give, so the bill is at most this many charges and usually fewer. This actor reads at most 1,000 accounts in one run: ask for more and the run continues at 1,000, with one uncharged row saying so.

## `maxHighlightsPerProfile` (type: `integer`):

Hard cap on how many of one account's highlight albums are collected, newest tray order first. Only used when "Highlights" is on. Each album delivered is one charge, so this is also the most one account can cost you. This actor collects at most 50 albums from one account: ask for more and the run continues at 50, with one uncharged row saying so.

## `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: rows already in hand are always written out. One run lasts at most 3,600 seconds and needs at least 30: ask outside that and the run continues at the nearest of the two, with one uncharged row saying so.

## Actor input object example

```json
{
  "usernames": [],
  "includeStories": true,
  "includeHighlights": false,
  "maxProfiles": 10,
  "maxHighlightsPerProfile": 10,
  "maxRunSeconds": 600
}
```

# Actor output Schema

## `items` (type: `string`):

One row per story or highlight item: `kind` says which. Each row carries the media links and their expiry, the take time, the mentions, hashtags, outbound links and sticker types, and — for a highlight item — the album's title and id. Every row carries `charged` and `status`, so the invoice reconciles from the dataset itself. Charging is PER GROUP, not per row: one `stories-check` covers every active story of one account and one `highlight` covers every item of one album, so exactly one row of each group says charged = true and the rest say why they are already covered.

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

One uncharged row for every account that returned less than it was asked for, saying which of eight things happened: `no_stories` (the account is public and is showing nothing right now), `no_highlights` (it keeps no saved albums), `private_account` (only approved followers may read it), `account_not_found` (Instagram has no such handle), `vendor_unavailable` (the read failed and a re-run is the fix), `source_refused` (Instagram would not answer for that handle at all, the same way every time — a re-run cannot change it), `unsupported_shape` (Instagram answered in a shape this actor does not read yet — on us, and a re-run gets the same answer), `stopped_at_limit` or `vendor_budget` (a limit on this run stopped it first). A limit you typed above this actor's own ceiling adds one `input_note` row instead of refusing the run, saying what was asked and what was used. None of these is charged.

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

Accounts answered, what was asked for, active stories delivered, highlight albums and items delivered, 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 = {
    "usernames": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("steadyfetch/instagram-stories-highlights-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 = { "usernames": [] }

# Run the Actor and wait for it to finish
run = client.actor("steadyfetch/instagram-stories-highlights-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 '{
  "usernames": []
}' |
apify call steadyfetch/instagram-stories-highlights-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,steadyfetch/instagram-stories-highlights-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/lp71hjpxayFKVCpZu/builds/2f9UOj5JSIgufAOvL/openapi.json
