FrameFetch
For agents → Try it free
Instagram

Reels API

Extract data from an Instagram Reel by URL: metadata, a Whisper transcript of the audio, frames sampled however you like, and the on-screen text burned into them. One schema, no scraping infrastructure to maintain.

Read the docs Pricing

What you get

For Instagram, FrameFetch returns metadata, transcript (Whisper), parametric frames, and on-screen text (OCR) per frame. One JSON response, billed per call — every response includes a cost block.

Quickstart

curl -X POST https://framefetch.net/v1/extract \
  -H "Authorization: Bearer <your-key>" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://www.instagram.com/reel/REEL_ID/",
    "fields": ["metadata", "transcript"]
  }'

Get a key with POST /v1/keys (free credit). Full reference in the docs. Agents can pay per call with x402 (USDC) — no account.

Same call from Node.js, using the framefetch npm client — including handling the photo-post case below gracefully:

import { FrameFetch } from 'framefetch';

const ff = new FrameFetch({ apiKey: process.env.FRAMEFETCH_API_KEY });

const r = await ff.extract({
  url: 'https://www.instagram.com/reel/REEL_ID/',
  fields: ['metadata', 'transcript', 'frames'],
  frames: { mode: 'fps', fps: 1, width: 480 },
});

console.log(r.metadata?.title, r.transcript?.source);
if (r.warnings?.length) console.warn(r.warnings); // e.g. a photo /p/ post has no video to sample frames from

npm install framefetch — a zero-dependency client. Source on GitHub.

Clearing Instagram's bot-wall

Instagram is the one platform here that fights back at the network level, not just the page level — and that's a real, documented incident, not a hypothetical. On July 18, 2026, Instagram extraction was completely broken in production: every Reel request came back a hollow HTTP 200 — a generic title of just "Instagram," with uploader, duration, and thumbnail all null — instead of an error, because Meta's bot-wall was fingerprinting the TLS handshake yt-dlp made by default. A residential proxy IP did nothing to fix it; the failure was verified live both with and without one.

The actual fix was TLS impersonation: yt-dlp's --impersonate chrome flag (backed by curl_cffi) makes the extraction request's TLS/JA3 signature match a real Chrome browser's — which is what Meta's wall was actually keying on, not the IP address behind it. It's scoped to Instagram only (TikTok and YouTube never needed it and don't get it), reads its target from an environment variable so it can be turned off without a redeploy if a platform ever regresses, and degrades gracefully: if the underlying yt-dlp binary is ever missing curl_cffi support, the request automatically retries once with the flag stripped instead of hard-failing the whole call. It's been the fix in production since — mentioned here as evidence it was found and fixed at the root cause, not as a problem that's still open.

Reels vs. a plain photo post — what's actually extractable

A Reel, or any Instagram URL that actually points at a video, gets the full treatment: metadata, insights, a Whisper transcript, frames, on-screen text. A plain photo post is a different story, and it's worth saying plainly rather than promising "works on all Instagram content": there is no video stream in a photo-only post for yt-dlp to find, so extraction falls back to the same path used for a login-walled page — reading Instagram's own public Open Graph tags for whatever's there (typically a title and a thumbnail image, nothing else). What actually comes back from a photo-only post:

FieldWhat happens on a photo-only post
metadata.title / .thumbnailUsually present — read from Instagram's public Open Graph tags
metadata.uploader / .durationSecnull — a photo post's public page doesn't carry them the way a video's does
transcriptnull — no audio track exists for Whisper to read
frames / text_overlayFail with a warnings entry (frames extraction failed; returning the result without them) instead of silently returning nothing
A private account's postUnreachable — FrameFetch never authenticates as an Instagram user, the same rule as every other platform here

None of that is a hard error — the call still returns 200 with whatever real data exists, plus an honest warnings entry for what didn't, the same graceful-degradation rule every field on every platform follows here.

Accuracy & limitations

Reel audio is transcribed by the same whisper-large-v3-turbo model (via Groq) as every other non-YouTube platform here — multilingual, strong on one clear voice talking to camera, with the same general Whisper trade-offs: it's a faster, distilled variant of the full large-v3 model, and it can mishear or briefly hallucinate a short phrase over loud music, several overlapping voices, or a stretch of near-silence — all common in Reels audio. Instagram doesn't expose a usable caption track any more reliably than TikTok does, so captionsAvailable is false in practice and Reels transcripts are Whisper-only too, with no caption ground-truth to fall back on.

The one real difference from TikTok: Instagram serves genuine audio-only formats (its own DASH audio representation), so the download that feeds Whisper doesn't have to carry picture data the way a TikTok download does — Reels transcription is Whisper-only like TikTok's, but not saddled with TikTok's muxed-only bandwidth tax. See the TikTok API page for exactly what that costs on a platform that doesn't have the option.

Sample response

Shape and field names match the real schema exactly; values below are illustrative, not captured calls. First, a Reel with fields: ["metadata", "insights", "transcript"]:

{
  "platform": "instagram",
  "url": "https://www.instagram.com/reel/C1a2B3c4D5e/",
  "captionsAvailable": false,
  "metadata": {
    "title": "Reel by user",
    "uploader": "user",
    "durationSec": 42,
    "uploadDate": "2026-05-03",
    "sourceFps": 30,
    "thumbnail": "https://scontent.cdninstagram.com/....jpg"
  },
  "insights": { "views": 15900, "likes": 1120, "commentCount": 34 },
  "transcript": {
    "text": "so this is the trick nobody tells you about ...",
    "source": "whisper",
    "lang": "en",
    "segments": [ { "start": 0, "end": 2.6, "text": "so this is the trick nobody tells you about" } ]
  },
  "cost": { "totalMicros": 2000 },
  "warnings": []
}

Then a photo-only post with fields: ["metadata", "transcript", "frames"] — metadata partially resolves, transcript quietly comes back null, and frames fails loudly with a warning instead of vanishing silently:

{
  "platform": "instagram",
  "url": "https://www.instagram.com/p/C9xYzAbCdEf/",
  "captionsAvailable": false,
  "metadata": {
    "title": "Photo by user",
    "uploader": null,
    "durationSec": null,
    "uploadDate": null,
    "sourceFps": null,
    "thumbnail": "https://scontent.cdninstagram.com/....jpg"
  },
  "transcript": null,
  "cost": { "totalMicros": 2000 },
  "warnings": [ "frames extraction failed; returning the result without them" ]
}

Use it from an AI agent (MCP)

FrameFetch ships an MCP server at POST https://framefetch.net/mcp with the tools framefetch_extract, framefetch_search, framefetch_account and framefetch_platform_capabilities — point your agent at an Instagram URL directly. See the MCP setup guide for a working Claude Desktop / Cursor config.

FAQ

Which Instagram URLs work?

Public Reel and video post URLs on instagram.com.

Can I get frames at a specific resolution?

Yes — set frames.width (16–7680). Lower widths are cheaper.

Can I read captions burned into a Reel?

Yes — add text_overlay alongside frames to run OCR on each extracted frame and get the on-screen text with confidence and position.

Why might an Instagram extraction come back with nulls instead of real data?

This happened in production on July 18, 2026: Meta's bot-wall fingerprinted the TLS handshake yt-dlp made by default and returned a hollow HTTP 200 — a generic "Instagram" title with uploader, duration, and thumbnail all null, instead of an error. The fix, live since, is TLS impersonation (yt-dlp's --impersonate chrome flag via curl_cffi) so the extraction request's TLS/JA3 signature matches a real Chrome browser, which is what the bot-wall was actually keying on. It's scoped to Instagram only and degrades gracefully — if the underlying yt-dlp binary is ever missing curl_cffi support, the request retries once with the flag stripped instead of failing the whole call.

Does it work on a photo post, or only on a Reel?

Only a Reel or another Instagram URL that actually points at a video gets the full treatment — metadata, insights, a Whisper transcript, frames, on-screen text. A plain photo post has no video stream for yt-dlp to find, so extraction falls back to reading Instagram's own public Open Graph tags: metadata.title and .thumbnail are usually present, uploader and durationSec come back null, transcript comes back null (no audio track), and frames/text_overlay fail with a warnings entry instead of silently returning nothing. It's not a hard error — the call still returns 200 with whatever real data exists.

What does Instagram Reel extraction cost?

Instagram calls bill on the same rate card as every platform: metadata + insights $0.00015 per call, transcript $0.0015 per audio-minute (Whisper, since Instagram has no caption track), frames $0.00012 each, on-screen text (OCR) $0.000225 each — with a $0.002 minimum per call. A typical short Reel's metadata+transcript call lands right at that floor, as in the sample response above. Full breakdown on pricing.