# Instagram Media Downloader (`accountable_eel/ig-media-downloader`) Actor

Downloads Instagram post video/image/cover files into your run's storage and probes each one with ffprobe (duration, resolution, fps, audio, codec). Takes rows copied from ig-profile-scraper or bare post/reel URLs. Logged out, no login. A file that fails to download is never charged.

- **URL**: https://apify.com/accountable\_eel/ig-media-downloader.md
- **Developed by:** [Adrian Voss](https://apify.com/accountable_eel) (community)
- **Categories:** Social media, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $9.12 / 1,000 file storeds

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?

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 Media Downloader

Give this actor Instagram post references — rows copied straight from ig-profile-scraper's dataset,
or plain post/reel URLs — and it downloads each post's video, image, or video cover into your own
Apify storage, hashes it, and probes it with ffprobe for duration, resolution, frame rate, audio and
codec. Listing and downloading are two different jobs; this one is the download half.

### Who it's for

An agent or workflow that already listed a creator's or competitor's reels with ig-profile-scraper
now needs the actual files, not just the CDN links Instagram hands out — links that expire on their
own schedule and vanish the moment nobody fetches them. A media-ops team archiving a set of reels
for compliance or a highlight reel wants durable copies in one place, with a checksum proving what
was actually saved. A data pipeline validating scraped video metadata needs real ffprobe numbers —
duration, resolution, fps, codec — not whatever a third party claims those are. All three get the
same thing here: post references in, files landed in your run's key-value store, with the exact
bytes hashed and probed.

### Why this one

- **It re-resolves, it doesn't just trust you.** Instagram's CDN URLs (`?efg=...&oh=...&oe=...`)
  expire unpredictably. Give this actor a `video_url` you copied an hour ago and, if it's gone
  stale, it falls back to the post's own embed page — and then, only if that's not enough either,
  the full post page — for a fresh one. You never see a dead link turn into a failed row.
- **Every file is hashed while it streams.** `sha256` is computed as the download happens, never
  from a re-read after the fact, so it's a checksum of exactly what got stored, not of what a
  second pass happened to find on disk.
- **Real ffprobe numbers, not guesses.** `duration_s`, `width`, `height`, `fps` and `codec` come
  from actually running `ffprobe` against the downloaded bytes — the same tool you'd run yourself,
  just already run for you.
- **A failed download is never charged.** An expired link, a deleted post, a file over your size
  cap — each comes back as a normal row with `error` set and every probe field `null`. Only a row
  that actually landed in storage counts toward the bill.
- **The CDN fetch never touches a proxy.** Media bytes are streamed directly; only the two page
  fetches that can involve re-resolving a stale link (the embed, and — rarely — the full post page)
  go through Apify Proxy, and only when a link needs resolving at all.

### What you get

One row per file by default (turn off "Expand rows" in Advanced to get one row per post reference
instead, with every file nested under `files`).

| Field | Type | Description |
|---|---|---|
| `query` | text | The shortcode you submitted, echoed back |
| `found` | boolean | `true` if at least one file was attempted for this post |
| `status` | text | `OK` on a match; `NOT_FOUND`, `BLOCKED` or `BAD_FORMAT` on a miss |
| `message` | text | Plain-English reason for a miss — only present when `found` is `false` |
| `shortcode` | text | Instagram's own post code |
| `fileCount` | number | Files successfully stored and billed for this post |
| `kind` | text | `video`, `image`, or `cover` (a video post's still) |
| `kvs_key` | text | The key this file was stored under in the run's default key-value store |
| `kvs_url` | text (link) | Full URL to fetch the file — needs your own Apify token to read it, since the store is private |
| `bytes` | number | File size |
| `sha256` | text | SHA-256, computed while streaming |
| `duration_s` | number | Video length in seconds. `null` for a still image |
| `width` / `height` | number | Pixel dimensions |
| `fps` | number | Frame rate, from ffprobe's `r_frame_rate`. `null` for a still image |
| `has_audio` | boolean | Whether ffprobe found an audio stream. `null` for a still image |
| `codec` | text | ffprobe's codec name — the video codec, or the still image's own codec |
| `source_url` | text (link) | The URL that was actually downloaded |
| `resolved` | boolean | `true` if the URL you supplied was missing or expired and this actor found a fresh one itself |
| `downloaded_at` | date | When this file finished downloading |
| `error` | text | Why this specific file couldn't be stored. `null` on success — this is what `fileCount` excludes |
| `scrapedAt` | date | When this row was produced |

Deselect any field in the Input tab's column picker to drop it from every row — `query`, `found`,
`status`, `message` and `scrapedAt` always stay.

### Scope and limits

- **One video / one image per post, not a full carousel.** A carousel post's embed page carries a
  slide for each item, but this build stores only one representative still per carousel (its own
  cover). Per-slide downloads for carousels are a possible follow-up, not something this actor does
  today — `error`/`message` say so plainly when a carousel is asked for "Video only".
- **Logged out, same as ig-profile-scraper.** No login, no cookies, no session token, and no input
  field that could accept one.
- **`kvs_url` needs your own Apify token.** The run's default key-value store is private, matching
  every other Apify run. Fetch `kvs_url` with `Authorization: Bearer <YOUR_TOKEN>` (or `?token=`),
  the same way you'd read any other private store.

### Pricing

- **File stored**: $12 per 1,000 files
- **Megabyte downloaded**: $0.005 per MB

Plus a $0.00005 start fee per run. Each event above is billed independently, only when it actually returns data — misses (`found:false`) are never charged.

Billing is per file: one `file` event for every row that actually landed in storage. A row whose
download failed (an expired link nothing could re-resolve, a post that's gone, a file over your
size cap) is never charged — you only pay for bytes you actually got.

On top of that flat fee, a `megabyte` event is charged per megabyte (rounded up) of each
successfully stored file — a small cover image costs a fraction of a cent, a large video is
billed for its real size, and a file that fails to download is never charged for either event.

### How to use

1. **In the Apify Console.** Open the actor page and click **Start** — the `posts` field is already pre-filled with a working example. Results land in the run's dataset as soon as each item is found.
2. **Via the API.** Call it directly with a POST request — no Console needed once you have an API token:
   ```bash
   curl "https://api.apify.com/v2/acts/accountable_eel~ig-media-downloader/run-sync-get-dataset-items?token=<YOUR_TOKEN>" \
     -X POST \
     -H "Content-Type: application/json" \
     -d '{"posts":["{\"shortcode\":\"DdG4RIxIPyf\",\"video_url\":\"https://instagram.fmga4-1.fna.fbcdn.net/o1/v/t2/f2/m86/AQPDJtVww4K_686Ed9TbciE3dp0nrA4iUd5M8OJJIZKcU-RmkzTGwtpi3CsZY9Pgqj7h7kSpk9qe7k5S4JdWZ-Z2s7pzWC2DxuqU8rc.mp4\",\"display_url\":\"https://instagram.fmga4-1.fna.fbcdn.net/v/t51.82787-15/802503979_18695767555019133_6909214034622416488_n.jpg\"}","{\"shortcode\":\"DdcjXAeFyr8\",\"display_url\":\"https://instagram.fmga4-1.fna.fbcdn.net/v/t51.82787-15/817719321_18698673607019133_2085790344713099622_n.jpg\"}"]}'
   ```
3. **On a schedule.** Save this actor as an Apify **Task** with the input you want, then add a **Schedule** (hourly, daily, weekly) so it runs on its own — no server of your own required.

4) Pipe ig-profile-scraper's post rows straight in: each row's JSON — `{"shortcode":"...",
   "video_url":"...", "display_url":"..."}` — is exactly the shape `posts` accepts, so no
   reshaping is needed between the two actors.
5) Set "Which media to download" to **Image only** if you only need a video's cover (or an image
   post's picture) and not the video file itself — it skips the biggest part of the download.

### Input

```json
{
  "posts": [
    "{\"shortcode\":\"DdG4RIxIPyf\",\"video_url\":\"https://instagram.fmga4-1.fna.fbcdn.net/o1/v/t2/f2/m86/AQPDJtVww4K_686Ed9TbciE3dp0nrA4iUd5M8OJJIZKcU-RmkzTGwtpi3CsZY9Pgqj7h7kSpk9qe7k5S4JdWZ-Z2s7pzWC2DxuqU8rc.mp4\",\"display_url\":\"https://instagram.fmga4-1.fna.fbcdn.net/v/t51.82787-15/802503979_18695767555019133_6909214034622416488_n.jpg\"}",
    "{\"shortcode\":\"DdcjXAeFyr8\",\"display_url\":\"https://instagram.fmga4-1.fna.fbcdn.net/v/t51.82787-15/817719321_18698673607019133_2085790344713099622_n.jpg\"}"
  ]
}
```

One per line: a JSON object copied from ig-profile-scraper (e.g. {"shortcode":"DdG4RIxIPyf","video\_url":"..."}), a plain post/reel URL, or a bare shortcode. Accepted formats: {"shortcode":"DdcI4o0Prsz"}, https://www.instagram.com/reel/DdG4RIxIPyf/, DbIdlf0j6uA.

| Setting | What it does |
|---|---|
| **Which media to download** | `All` downloads the video plus its cover for a reel, or the image for an image post. `Video only` skips the cover. `Image only` downloads just the still |
| **Max file size (MB)** | Aborts a single file once it exceeds this, so one oversized video can't blow your run's budget. 1–300, default 300 |

### Sample output

| query | found | status | shortcode | fileCount | files | kind | kvs\_key | kvs\_url | bytes | sha256 | duration\_s | width | height | fps | has\_audio | codec | source\_url | resolved | downloaded\_at | error | scrapedAt |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| {"shortcode":"DdG4RIxIPyf","video\_url":"https://instagram.fmga4-1.fna.fbcdn.net/o1/v/t2/f2/m86/AQPDJtVww4K\_686Ed9TbciE3dp0nrA4iUd5M8OJJIZKcU-RmkzTGwtpi3CsZY9Pgqj7h7kSpk9qe7k5S4JdWZ-Z2s7pzWC2DxuqU8rc.mp4","display\_url":"https://instagram.fmga4-1.fna.fbcdn.net/v/t51.82787-15/802503979\_18695767555019133\_6909214034622416488\_n.jpg"} | true | OK | <shortcode> | <files stored> | \<all rows found (full list)> | <file kind> | <storage key> | <storage url> | \<size (bytes)> | <sha-256> | \<duration (s)> | <width> | <height> | <fps> | <has audio> | <codec> | <source url> | <re-resolved> | <downloaded at> | <error> | 1970-01-01T00:00:00.000Z |

A file that couldn't be stored comes back as a row with `"error"` set (not `"found": false` — the
post itself was found; the specific file wasn't) and is never charged.

### Use it from Clay, n8n, Make, or an AI agent

This actor runs synchronously over plain HTTP — call it directly from a script, a workflow tool, or an AI agent, no Apify Console needed once you have an API token.

```bash
curl "https://api.apify.com/v2/acts/accountable_eel~ig-media-downloader/run-sync-get-dataset-items?token=<YOUR_TOKEN>" \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{"posts":["{\"shortcode\":\"DdG4RIxIPyf\",\"video_url\":\"https://instagram.fmga4-1.fna.fbcdn.net/o1/v/t2/f2/m86/AQPDJtVww4K_686Ed9TbciE3dp0nrA4iUd5M8OJJIZKcU-RmkzTGwtpi3CsZY9Pgqj7h7kSpk9qe7k5S4JdWZ-Z2s7pzWC2DxuqU8rc.mp4\",\"display_url\":\"https://instagram.fmga4-1.fna.fbcdn.net/v/t51.82787-15/802503979_18695767555019133_6909214034622416488_n.jpg\"}","{\"shortcode\":\"DdcjXAeFyr8\",\"display_url\":\"https://instagram.fmga4-1.fna.fbcdn.net/v/t51.82787-15/817719321_18698673607019133_2085790344713099622_n.jpg\"}"]}'
```

**n8n.** Add an HTTP Request node: Method `POST`, URL `https://api.apify.com/v2/acts/accountable_eel~ig-media-downloader/run-sync-get-dataset-items?token=<YOUR_TOKEN>`, Body Content Type `JSON`, JSON Body `{"posts":["{\"shortcode\":\"DdG4RIxIPyf\",\"video_url\":\"https://instagram.fmga4-1.fna.fbcdn.net/o1/v/t2/f2/m86/AQPDJtVww4K_686Ed9TbciE3dp0nrA4iUd5M8OJJIZKcU-RmkzTGwtpi3CsZY9Pgqj7h7kSpk9qe7k5S4JdWZ-Z2s7pzWC2DxuqU8rc.mp4\",\"display_url\":\"https://instagram.fmga4-1.fna.fbcdn.net/v/t51.82787-15/802503979_18695767555019133_6909214034622416488_n.jpg\"}","{\"shortcode\":\"DdcjXAeFyr8\",\"display_url\":\"https://instagram.fmga4-1.fna.fbcdn.net/v/t51.82787-15/817719321_18698673607019133_2085790344713099622_n.jpg\"}"]}` (swap in an expression from an earlier node for a real value).

**Clay.** Add an "HTTP API" column: Method `POST`, URL `https://api.apify.com/v2/acts/accountable_eel~ig-media-downloader/run-sync-get-dataset-items?token=<YOUR_TOKEN>`, Body `{"posts":["{{post reference}}"]}`, mapping the row's post reference into the `posts` array.

**MCP.** In Claude, Cursor, or any MCP client with the Apify MCP server, ask for "Instagram Scraper API: Video & Photo Downloader" — the agent will find and run this actor.

The `kvs_url` on every successful row needs your own Apify API token to fetch — the store is
private, the same as any other run's default key-value store. An MCP agent already holding your
token can read it directly; a script needs `?token=<YOUR_TOKEN>` appended to the URL.

The run also writes a summary to its `OUTPUT` key-value record: `{requested, returned, errors[],
caps[]}` — how many posts were submitted, how many file rows came back, and what failed and why,
without paging the whole dataset.

### Tips

- Feed this actor directly from ig-profile-scraper's dataset — the row shape lines up on purpose,
  no reshaping needed.
- Set "Which media to download" to **Video only** when you already have stills from elsewhere and
  only need the video file — it skips a whole file per post.
- A `resolved: true` row means the link you supplied was already stale and this actor found a
  fresh one on its own — nothing to fix on your end, just useful to know your source data ages.
- Read `kvs_url` promptly. It points at this run's storage, and while Apify key-value stores don't
  expire quickly, treat the run's dataset as the durable record and copy files out if you need them
  to outlive the run's own retention.

### vs. alternatives

| | What it costs | What you get | Trade-off |
|---|---|---|---|
| **This actor** (`ig-media-downloader`) | Per file actually stored; nothing for a failed download | Durable copies of Instagram post media in your own storage, hashed and ffprobed, with automatic re-resolution of expired CDN links | One video/image per post, not a full carousel; logged out, so only public posts |
| **apify/instagram-scraper** (and similar listing actors) | Per post listed | Post metadata plus direct CDN links — `video_url`, `display_url` | The links expire; nothing downloads or verifies them for you. That's this actor's job |
| Downloading the links yourself | Your time, plus retry logic | The same files | You still have to handle expired links, hashing and probing — the parts this actor exists to do once, correctly |

### Data & privacy

This actor reads and downloads media from public Instagram posts that anyone can see without
logging in. It does not log in, does not use cookies or session tokens, and does not reveal hidden
or private content.

Downloaded files may contain images or video of real people. If you process them you are the
controller of that processing and need your own lawful basis for it — this actor gives you the
files, not permission to use them. Not affiliated with Instagram or Meta.

### FAQ

**Why is `resolved` sometimes true even though I supplied a URL?**
The URL you supplied had expired or otherwise failed, and this actor found a fresh one itself —
from the post's embed page, or as a last resort its full page. You still get the file; you just
didn't need to have supplied a working link in the first place.

**Does it download full carousels?**
Not per-slide, no — only one representative still per carousel post today. See "Scope and limits"
above.

**Why is `duration_s` null on some rows?**
Those rows are stills (an image post, or a video's cover) — duration, fps and has\_audio are only
meaningful for the video file itself and are deliberately left `null` rather than a misleading `0`.

**What happens if a file is bigger than my size cap?**
The download is aborted once it exceeds "Max file size (MB)", and the row comes back with `error`
set and no charge — the same as any other failed download.

**Do I need a proxy?**
Not for the CDN download itself — it never uses one. The embed/post-page fetches (only needed when
a supplied link has to be re-resolved) go through the standard proxy field if you set one; the
2026-09-20 build probe needed none at all.

### Related actors

- [Instagram Profile Scraper: Posts and Reels](https://apify.com/accountable_eel/ig-profile-scraper) —
  lists a profile's posts and reels with direct CDN links; this actor downloads what it lists.

# Actor input Schema

## `posts` (type: `array`):

One per line: a JSON object copied from ig-profile-scraper (e.g. {"shortcode":"DdG4RIxIPyf","video\_url":"..."}), a plain post/reel URL, or a bare shortcode. Accepted formats: {"shortcode":"DdcI4o0Prsz"}, https://www.instagram.com/reel/DdG4RIxIPyf/, DbIdlf0j6uA. You're only charged for the ones we actually find — a miss costs nothing.

## `testRun` (type: `boolean`):

Turn this on to test your input on a small sample before running the full list. Turn it off to process everything.

## `onlyFound` (type: `boolean`):

Only keep rows where something was actually found. Misses are always free, whether or not you show them here.

## `includeKeywords` (type: `array`):

Optional. Only keep results that mention at least one of these words (e.g. a job title, a city, a product name). Leave empty to keep everything.

## `excludeKeywords` (type: `array`):

Optional. Drop any result that mentions one of these words. Leave empty to skip nothing.

## `maxResults` (type: `integer`):

Optional. Stop the run once this many results have been found — useful for a quick, cheap sample. Leave blank for no limit.

## `columns` (type: `array`):

Choose which pieces of information to include in each result row. All are included by default.

## `expandRows` (type: `boolean`):

When on, each file found gets its own row instead of being grouped under its post reference. You're still only charged once per post reference, no matter how many rows it produces.

## `kinds` (type: `string`):

"All" (default) downloads the video plus a cover still for a reel, or the image for an image post. "Video only" skips the cover. "Image only" downloads just the still (the cover, for a reel) and skips the video file.

## `maxMb` (type: `integer`):

Aborts a single file's download once it exceeds this size, so one oversized video can't blow your run's memory or storage budget. 1-300.

## `maxConcurrency` (type: `integer`):

Parallel requests. Keep conservative — this target has no browser fallback, so getting blocked costs more than slow-and-steady.

## `proxyConfiguration` (type: `object`):

Apify Proxy config. Residential recommended for anti-bot-sensitive targets.

## Actor input object example

```json
{
  "posts": [
    "{\"shortcode\":\"DdG4RIxIPyf\",\"video_url\":\"https://instagram.fmga4-1.fna.fbcdn.net/o1/v/t2/f2/m86/AQPDJtVww4K_686Ed9TbciE3dp0nrA4iUd5M8OJJIZKcU-RmkzTGwtpi3CsZY9Pgqj7h7kSpk9qe7k5S4JdWZ-Z2s7pzWC2DxuqU8rc.mp4\",\"display_url\":\"https://instagram.fmga4-1.fna.fbcdn.net/v/t51.82787-15/802503979_18695767555019133_6909214034622416488_n.jpg\"}",
    "{\"shortcode\":\"DdcjXAeFyr8\",\"display_url\":\"https://instagram.fmga4-1.fna.fbcdn.net/v/t51.82787-15/817719321_18698673607019133_2085790344713099622_n.jpg\"}"
  ],
  "testRun": false,
  "onlyFound": false,
  "includeKeywords": [],
  "excludeKeywords": [],
  "columns": [
    "shortcode",
    "fileCount",
    "files",
    "kind",
    "kvs_key",
    "kvs_url",
    "bytes",
    "sha256",
    "duration_s",
    "width",
    "height",
    "fps",
    "has_audio",
    "codec",
    "source_url",
    "resolved",
    "downloaded_at",
    "error"
  ],
  "expandRows": true,
  "kinds": "all",
  "maxMb": 300,
  "maxConcurrency": 5,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `results` (type: `string`):

No description

# 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 = {
    "posts": [
        "{\"shortcode\":\"DdG4RIxIPyf\",\"video_url\":\"https://instagram.fmga4-1.fna.fbcdn.net/o1/v/t2/f2/m86/AQPDJtVww4K_686Ed9TbciE3dp0nrA4iUd5M8OJJIZKcU-RmkzTGwtpi3CsZY9Pgqj7h7kSpk9qe7k5S4JdWZ-Z2s7pzWC2DxuqU8rc.mp4\",\"display_url\":\"https://instagram.fmga4-1.fna.fbcdn.net/v/t51.82787-15/802503979_18695767555019133_6909214034622416488_n.jpg\"}",
        "{\"shortcode\":\"DdcjXAeFyr8\",\"display_url\":\"https://instagram.fmga4-1.fna.fbcdn.net/v/t51.82787-15/817719321_18698673607019133_2085790344713099622_n.jpg\"}"
    ],
    "includeKeywords": [],
    "excludeKeywords": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("accountable_eel/ig-media-downloader").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 = {
    "posts": [
        "{\"shortcode\":\"DdG4RIxIPyf\",\"video_url\":\"https://instagram.fmga4-1.fna.fbcdn.net/o1/v/t2/f2/m86/AQPDJtVww4K_686Ed9TbciE3dp0nrA4iUd5M8OJJIZKcU-RmkzTGwtpi3CsZY9Pgqj7h7kSpk9qe7k5S4JdWZ-Z2s7pzWC2DxuqU8rc.mp4\",\"display_url\":\"https://instagram.fmga4-1.fna.fbcdn.net/v/t51.82787-15/802503979_18695767555019133_6909214034622416488_n.jpg\"}",
        "{\"shortcode\":\"DdcjXAeFyr8\",\"display_url\":\"https://instagram.fmga4-1.fna.fbcdn.net/v/t51.82787-15/817719321_18698673607019133_2085790344713099622_n.jpg\"}",
    ],
    "includeKeywords": [],
    "excludeKeywords": [],
}

# Run the Actor and wait for it to finish
run = client.actor("accountable_eel/ig-media-downloader").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 '{
  "posts": [
    "{\\"shortcode\\":\\"DdG4RIxIPyf\\",\\"video_url\\":\\"https://instagram.fmga4-1.fna.fbcdn.net/o1/v/t2/f2/m86/AQPDJtVww4K_686Ed9TbciE3dp0nrA4iUd5M8OJJIZKcU-RmkzTGwtpi3CsZY9Pgqj7h7kSpk9qe7k5S4JdWZ-Z2s7pzWC2DxuqU8rc.mp4\\",\\"display_url\\":\\"https://instagram.fmga4-1.fna.fbcdn.net/v/t51.82787-15/802503979_18695767555019133_6909214034622416488_n.jpg\\"}",
    "{\\"shortcode\\":\\"DdcjXAeFyr8\\",\\"display_url\\":\\"https://instagram.fmga4-1.fna.fbcdn.net/v/t51.82787-15/817719321_18698673607019133_2085790344713099622_n.jpg\\"}"
  ],
  "includeKeywords": [],
  "excludeKeywords": []
}' |
apify call accountable_eel/ig-media-downloader --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,accountable_eel/ig-media-downloader"
        }
    }
}
```

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/PKWBbfcbr7pTrnsh5/builds/rMwbfgR6EYhZUVgOF/openapi.json
