# Instagram Highlights Scraper | $2.15/1K Highlights (`publicsignallabs/instagram-highlights-scraper`) Actor

Explore Instagram Highlights to understand the products, campaigns and themes brands and creators showcase. Export collection details and optionally collect their saved Stories for content research. No Instagram login required.

- **URL**: https://apify.com/publicsignallabs/instagram-highlights-scraper.md
- **Developed by:** [Public Signal Labs](https://apify.com/publicsignallabs) (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 $2.15 / 1,000 highlight metadata

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## 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.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Instagram Highlights Scraper | $2.15/1K Highlights

**Explore the Stories brands and creators choose to keep on their profiles.** Discover Instagram Highlight collections to research products, campaigns and recurring content themes.

Export collection titles, available cover images and links, or collect the saved Stories from selected Highlights for a closer look. Start with a profile or known Highlight link; no Instagram login or technical setup required.

### Quick start

List only:

```json
{
  "profiles": ["examplebrand"],
  "maxPagesPerProfile": 1,
  "maxCollections": 20
}
```

Expand listed collections into saved Stories:

```json
{
  "profiles": ["https://www.instagram.com/examplebrand/"],
  "expandCollections": true,
  "selectedCollectionIds": ["highlight:123456789"],
  "maxCollections": 20,
  "maxItems": 100,
  "enrichProfiles": false,
  "enrichPlaces": false,
  "enrichMedia": false
}
```

Omit `selectedCollectionIds` to expand all collections actually listed within this run's bounds. An explicit selection does not trigger an extra lookup for collections that were not listed.

Direct mode:

```json
{
  "highlightIds": ["highlight:123456789", "https://www.instagram.com/stories/highlights/987654321/"],
  "maxCollections": 20,
  "maxItems": 100
}
```

Direct mode automatically selects expansion. It produces saved Story rows and collection count summaries in OUTPUT, **not metadata rows**, and performs no profile resolution or listing. Do not also set `profiles`, `expandCollections` or `selectedCollectionIds`.

### Input

Unknown fields, conflicting modes, malformed URLs and invalid limits are rejected before extraction or FREE admission. Reference equivalents are deduplicated while retaining first-input order. URLs must be canonical HTTPS Instagram URLs; arbitrary URLs are never fetched.

| Field | Default and bounds | Meaning |
|---|---|---|
| `profiles` | Up to 50 refs | Username, `@username`, canonical profile URL or numeric ID. Mutually exclusive with `highlightIds`. |
| `highlightIds` | Up to 1,000 refs | Numeric ID, `highlight:<number>` or canonical `/stories/highlights/<number>/` URL. Direct mode. |
| `maxPagesPerProfile` | 1; 1–10 per run | List page cap for each profile. Pagination is bounded, not exhaustive by default. |
| `maxCollections` | 20; 1–1,000 per run | Unique metadata limit and bounded collection expansion selection. |
| `expandCollections` | false | Expand collections observed during listing. |
| `selectedCollectionIds` | Optional; up to 1,000 | Restrict list-mode expansion to these normalized IDs. Requires `expandCollections=true`. |
| `maxItems` | 100; 1–1,000 per run | Unique saved Story delivery bound. Declared counts are not delivered item totals. |
| `enrichProfiles` | false | Look up owners where full resolution facts are not already cached. |
| `enrichPlaces` | false | Look up actual referenced Instagram place IDs in saved Story context. Requires expansion. |
| `enrichMedia` | false | Look up referenced feed-media IDs, not arbitrary sticker IDs. Requires expansion. |
| `continuationToken` | Optional | Opaque authenticated continuation from OUTPUT, valid for 15 minutes. |

Basic metadata, assets and supplied content context do not require enrichment. Enrichment does not invent missing facts, follows identity checks and caches success, missing and failure outcomes per entity/run. Username resolution facts are reused rather than looked up again. Enrichment statuses include `not_requested`, `reused`, `succeeded`, `not_available`, `charge_unconfirmed`, `failed` and `limit_reached`; base rows remain available when a lookup is unavailable.

### Two datasets and exports

1. **Default / collection metadata:** one row per unique listed collection.
2. **savedStories / saved Story items:** run-scoped second dataset, one row per unique top-level saved Story across the collections actually visited. All observed collection memberships and one-based item positions are retained. This dataset is not a global shared collection.

Use the Storage dropdown to select and export each dataset, or use the two Output links. The ordinary run-page Export button exports **only the default metadata dataset**. Direct mode can leave that dataset empty while savedStories contains results. Collection rows do not contain a grouped `stories[]` substitute.

#### Collection metadata

**Enrichment required?** Listing supplies the base collection fields; optional facts may be missing. Expansion (`expandCollections=true`) retrieves collection details and saved Stories. Expansion is **not enrichment**: `enrichProfiles` only supplies extra owner details under `enrichment.profile`, without rewriting the base owner fields.

| Field | Meaning | Enrichment required? |
|---|---|---|
| `record_kind`, `id`, `url` | `highlightMetadata`, canonical `highlight:<number>` and Highlight-domain URL. | No. |
| `owner` | Identity and observed username/private/restricted flags. Optional facts may be null. | No. |
| `title`, `cover` | Supplied title, cover image variants, media ID/crop when present; nullable. | No; a cover does not require `enrichMedia`. |
| `declared_item_count` | Count observed in the list; nullable and not proof of delivery. | No. |
| `detail_declared_item_count` | Count observed in selected detail, when checked. It may differ from the earlier list. | No — requires expansion, not enrichment. |
| `returned_item_count` | Actual detail item count; null for unexpanded/unavailable details. | No — requires expansion, not enrichment. |
| `delivered_item_count` | Distinct saved Stories confirmed delivered for this collection in this observation. | No — a positive count requires saved Story delivery through expansion. |
| `expansion_status` | `not_requested`, `completed`, `partial` or a specific unavailable/extraction outcome. | No — reports whether expansion was selected and completed. |
| `collection_memberships` | Observed profile/page/rank provenance. | No. |
| `flags`, `fetched_at` | Supplied flags and observation time. | No. |
| `enrichment.profile.status` | Selected owner lookup outcome; `not_requested` when disabled. | No — the status is present even with enrichment off. |
| `enrichment.profile.data` | Available matching owner profile details, such as biography and follower/following/post counts; null when unavailable. | Yes — select `enrichProfiles`; already-included matching details may be reused. |

#### Saved Stories

Saved Story rows require collection expansion or direct `highlightIds` mode, **not `enrichMedia`**. Their own media and supplied context are base fields. Optional lookup details are separate under `enrichment` and do not rewrite those base fields.

| Fields | Meaning | Enrichment required? |
|---|---|---|
| `record_kind`, `id`, `story_id`, `content_kind` | `savedStory`, actual Story identity and `saved_story`. | No — available through expansion/direct mode. |
| `url`, `shortcode`, `owner`, `owner_id`, `owner_username` | `url` is null: saved content has no invented active-Story or feed permalink. Optional shortcode and owner facts are retained; `owner_id` and `owner_username` are flattened owner copies. | No. |
| `collection_memberships` | Every observed `{collection_id, item_order, collection_url}` within the visited bounded selection; `collection_url` opens its Highlight viewer. | No. |
| `published_at`, `fetched_at` | Supplied publication time and observation time. | No. |
| `expires_at`, `remaining_lifetime_seconds` | Null: saved content is not presented as an expiring active Story. | No; enrichment does not supply active-Story expiry. |
| `images`, `videos`, `audio`, `width`, `height`, `duration_seconds` | Supplied asset variants, dimensions, duration and audio facts; nullable. | No; `enrichMedia` is for referenced media, not the saved Story's own assets. |
| `caption`, `hashtags`, `mentions`, `links`, `link_domains`, `location`, `locations` | Supplied context and local caption parsing; nullable if absent. | No — optional place lookup details are separate. |
| `sponsors`, `is_paid_partnership`, `stickers`, `reshared_media` | Supported supplied rich subtypes only, not exhaustive interaction coverage. | No — optional referenced-media lookup details are separate. |
| `flags` | Observed flags. | No. |
| `enrichment` | Owner lookup status plus arrays of selected place/media lookup results. The profile status is `not_requested` and place/media arrays are empty when enrichment is off. | No for the outcome containers. |
| `enrichment.profile.data` | Available matching owner profile details; null when unavailable. | Yes — select `enrichProfiles`; already-included matching details may be reused. |
| `enrichment.places[].data` | Available details of supplied Instagram places, such as address, website, phone or opening hours. Requires a usable location ID. | Yes — select `enrichPlaces`; already-included matching details may be reused. |
| `enrichment.media[].data` | Available details of referenced posts/Reels, such as captions, engagement or asset links. Requires a usable referenced media ID. | Yes — select `enrichMedia`; already-included matching details may be reused. |

Asset URLs are temporary and can expire. No binaries are downloaded, retained or archived. Nested reshared/reference objects are context, not additional Story rows.

Example saved row (abbreviated optional fields):

```json
{
  "record_kind": "savedStory",
  "id": "111222333",
  "story_id": "111222333",
  "content_kind": "saved_story",
  "media_type": "image",
  "owner": {"id": "12345", "username": "examplebrand", "is_private": false},
  "owner_id": "12345",
  "owner_username": "examplebrand",
  "url": null,
  "expires_at": null,
  "remaining_lifetime_seconds": null,
  "collection_memberships": [
    {"collection_id": "highlight:123456789", "item_order": 1, "collection_url": "https://www.instagram.com/stories/highlights/123456789/"},
    {"collection_id": "highlight:987654321", "item_order": 4, "collection_url": "https://www.instagram.com/stories/highlights/987654321/"}
  ]
}
```

### Pricing

**The headline is not an all-in price.** Collection metadata is $0.00215 each; username resolution is $0.004 and each list page is $0.008. Collecting the saved Stories from a selected Highlight costs $0.004 per collection, plus $0.00015 per unique saved Story. Optional enrichment and a $0.00005 automatic run-start fee are additional. One username, seven collections and one page costs **$0.02710**; expanding seven collections and delivering 70 unique saved Stories costs **$0.06560**, without enrichment.

USD/Starter prices; the platform's current pricing governs other plans.

| Event | Price | Charged for |
|---|---:|---|
| `highlight-metadata` — primary | $0.00215 | Unique listed metadata row accepted with named billing. |
| `profile-resolution` | $0.004 | Completed matching username resolution; numeric profile IDs avoid this work. |
| `highlight-list` | $0.008 | Valid completed list page, including verified empty and duplicate-only pages. |
| `highlight-expansion` | $0.004 | Valid selected/direct detail extraction, including valid empty detail. |
| `highlight-story-result` | $0.00015 | Unique top-level saved Story persisted in savedStories. Not declarations, covers or references. |
| `profile-enrichment` | $0.0055 | Selected successful incremental owner lookup, once per identity/run. |
| `place-enrichment` | $0.004 | Selected successful incremental place lookup, once per identity/run. |
| `media-enrichment` | $0.004 | Selected successful incremental referenced-media lookup, once per identity/run. |
| `apify-actor-start` | $0.00005 | Automatic start at up to 1 GB. |

No additional automatic default-dataset-item fee is configured. Unavailable/malformed/failed extraction is not billed as completed work. A generic missing response is `not_available`, **not a verified empty list**. Successful username resolution can still incur its own fee if the subsequent list is unavailable. A verified empty username list costs $0.01205 including start; the same list by numeric profile ID costs $0.00805. A direct valid empty collection costs $0.00405. No unperformed resolution/list/metadata fees apply in direct mode.

Base metadata, assets and supplied context have no enrichment fee. Already-included matching details, including username-resolution facts, are reused without another enrichment charge. Declared collection counts, covers and nested references are not billed as saved Story results. Resuming a partial collection detail incurs a new $0.004 fee for a valid completed expansion.

The Actor checks combined decimal affordability and event capacities before paid work and persists only an affordable prefix. Work charges precede delivery, so a limit can leave paid extraction without all possible rows. Writes and charges are not one atomic transaction: saved Story writes are serialized, followed by one explicit custom result charge for each accepted row. A write or charge whose completion is uncertain stops the run without replay. OUTPUT distinguishes confirmed delivered rows from confirmed event charges; it does not promise equality or automatic refunds. Enrichment may be charged before its row is persisted; interrupted delivery is reported, not silently treated as free work.

#### FREE demo

Five admitted runs per Actor/Apify user/UTC month. Each run permits one profile, one list page, at most five metadata rows and at most two extraction attempts. No expansion, direct mode, enrichment or continuation. A page bound above one is rejected; the ordinary collection default is clamped to five and explicit collection bounds are silently clamped to five as well. Admission uses atomic monthly slots and run fencing; missing account/quota verification fails closed. An aborted or resurrected admitted run cannot acquire a fresh allowance. Paying users bypass demo admission, not workload or spending limits. FREE eligibility is not an all-in paid invoice guarantee.

### Outcomes, counts and continuation

OUTPUT contains `metadataDatasetId` / `defaultDatasetId`, `savedStoriesDatasetId`, run-local `metadataCount`, `savedStoryCount`, per-event confirmed totals, source outcomes, collection declared/returned/delivered counts, reconciliation and continuation. AUDIT contains sanitized extraction/delivery/billing aggregates and duration, without raw responses or credentials.

Continuation is bound to normalized source refs, direct/list mode, expansion selection and enrichment flags. Keep those fields identical; page/item/collection bounds may change. It carries actual cursors, buffered unpersisted metadata, confirmed dedup progress and detail offsets. A resumed partial detail is fetched again; verified item-order fingerprints detect changed detail snapshots rather than silently skipping content. Lists are live observations, not an immutable snapshot, and later changes cannot be ruled out. Previously exported append-only rows are not retroactively revised when a later run observes another membership. Memberships and delivered counts describe **observed** work, never unvisited collections.

Normalized buffering is bounded to 8 MB, with at most 1,000 delivered saved items per run; individual detail/list responses exceeding supported safety bounds are unavailable outcomes. Continuation state is limited to 2 MB and tokens to 3 MB. If a checkpoint cannot fit, OUTPUT reports `page_limit` with no usable token rather than inventing resumability. Spending/dependency ambiguity never triggers an automatic mutation replay.

### API and CLI

With an authenticated Apify CLI session:

```sh
apify call publicsignallabs/instagram-highlights-scraper --input '{"profiles":["examplebrand"],"maxCollections":20}'
```

Or POST the quick-start JSON to the Actor's Apify run API using your normal Apify authentication. Fetch dataset items using either Output link; use OUTPUT's actual dataset IDs, not a global dataset name. Do not put Instagram credentials or cookies in input.

### Limitations and support

This is current collection/saved-content extraction, not active Story retrieval, viewer/reach/impression analytics, historical capture, private-access guarantees, OCR/transcription/AI, durable downloads or exhaustive unsupported sticker mapping. Absent optional facts are null, not fabricated. Collection counts can drift between listing and detail observation.

Use the Actor's **Issues tab** with a run ID and sanitized expected/actual details. Never share tokens, cookies, signed asset URLs or private contact information. Use extracted data lawfully and respect account holders' rights. This Actor is not affiliated with Instagram or Meta.

# Actor input Schema

## `profiles` (type: `array`):

Choose profile listing or direct highlightIds, never both. Up to 50 references.

## `highlightIds` (type: `array`):

Canonical highlight:<number>, numeric IDs or Instagram /stories/highlights/<number>/ URLs. Direct expansion has no listing or metadata fee.

## `expandCollections` (type: `boolean`):

Retrieve saved Stories from listed collections, optionally limited to selectedCollectionIds.

## `selectedCollectionIds` (type: `array`):

Optional explicit selection; requires expandCollections. Omit to expand all listed collections within bounds.

## `maxPagesPerProfile` (type: `integer`):

List page cap for each profile in this run. Pagination is bounded, not exhaustive by default; FREE demo runs require exactly one page.

## `maxCollections` (type: `integer`):

Unique collection metadata limit and bounded expansion selection per run. FREE demo runs clamp this bound to five.

## `maxItems` (type: `integer`):

Unique saved Story delivery bound per run. Declared collection counts are never used as billed item totals.

## `enrichProfiles` (type: `boolean`):

Optional successful incremental lookups have separate fees; repeated identities are cached.

## `enrichPlaces` (type: `boolean`):

Optional successful incremental lookups have separate fees; repeated identities are cached.

## `enrichMedia` (type: `boolean`):

Optional successful incremental lookups have separate fees; repeated identities are cached.

## `continuationToken` (type: `string`):

Opaque 15-minute continuation from OUTPUT. Keep source and selection fields unchanged; limits may change.

## Actor input object example

```json
{
  "profiles": [
    "instagram"
  ],
  "expandCollections": false,
  "maxPagesPerProfile": 1,
  "maxCollections": 20,
  "maxItems": 100,
  "enrichProfiles": false,
  "enrichPlaces": false,
  "enrichMedia": false
}
```

# Actor output Schema

## `collections` (type: `string`):

Unique Highlight collection metadata rows from the default dataset, ready for preview or export.

## `savedStories` (type: `string`):

Unique saved Story rows in the run-scoped savedStories dataset, with observed collection memberships and one-based item positions.

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

Run status and outcome, confirmed delivered and charged counts, source outcomes, continuation token and delivery reconciliation.

## `audit` (type: `string`):

Sanitized extraction, delivery and billing aggregates and duration, without raw responses or credentials.

# 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 = {
    "profiles": [
        "instagram"
    ]
};

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

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

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,publicsignallabs/instagram-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/QD2gy0Rv1JCRdneMJ/builds/yBibjKq2Wk8PERyee/openapi.json
