{
  "markdown": "<p align=\"center\">\n  <img src=\"logo.svg\" width=\"84\" height=\"84\" alt=\"FrameFetch logo\">\n</p>\n\n<h1 align=\"center\">FrameFetch</h1>\n\n<p align=\"center\">\n  <b>Any social-video URL → answers, transcript, metadata, insights, frames &amp; on-screen text (OCR).</b><br>\n  Agent-first video data API + MCP server. Pay per call, or with x402 (USDC) — no account.\n</p>\n\n<p align=\"center\">\n  <a href=\"https://www.npmjs.com/package/framefetch\"><img src=\"https://img.shields.io/npm/v/framefetch?color=ff5a36\" alt=\"npm\"></a>\n  <a href=\"https://framefetch.net\"><img src=\"https://img.shields.io/badge/website-framefetch.net-ff8562\" alt=\"website\"></a>\n  <a href=\"https://framefetch.net/status\"><img src=\"https://img.shields.io/badge/status-live-4ad8a0\" alt=\"status\"></a>\n  <img src=\"https://img.shields.io/badge/license-MIT-9a9fa6\" alt=\"MIT\">\n</p>\n\n---\n\nFrameFetch turns one **YouTube, YouTube Shorts, TikTok, Instagram Reels, Pinterest, or Reddit** video URL into a single JSON response: a **direct answer to a question about the video**, **metadata**, **engagement insights**, a **transcript** (captions or Whisper), an **LLM digest** (text or spoken mp3), **structured JSON** (chapters/entities/products/claims), **comments + sentiment**, **parametrically-sampled frames** (every Nth / 1-per-second / a time range, at any width), and the **on-screen text burned into those frames** (OCR — captions, price tags, signage). Plus keyword **search** when you don't have a URL yet, and **batch** for up to 10 URLs in one call. Built API-first and MCP-first for AI agents.\n\n> This repo is the **open-source client + docs**. The service itself runs at **[framefetch.net](https://framefetch.net)** — you bring a free API key (or pay per call with x402); the backend stays hosted.\n\n## Why\n\nAn LLM can't watch a video. To reason about one it needs the video turned into text and images first — an answer, a transcript, metadata, a few frames. FrameFetch returns all of that from a URL, across six platforms, through one schema.\n\n## Install\n\n```bash\nnpm install framefetch\n```\n\nNode 18+ (uses built-in `fetch`). Get a free key: [framefetch.net](https://framefetch.net).\n\n> **Version note.** This repo is at **0.4.0**. The newest version currently on npm is **0.3.0** — `npm install framefetch` still gives you that one, and it has only `extract`/`metadata`/`transcript`/`frames`/`platforms`/`status`/`demo`/`createKey`. Everything else documented below is live on the API today and available from this repo; from npm 0.3.0 you can reach the same data through `extract({ fields: [...] })`.\n\n## Ask a question — get an answer, not a transcript dump\n\nA direct question about a video returns a short, grounded answer with timestamped quotes, instead of you parsing a 25,000-token transcript yourself.\n\n```js\nimport { FrameFetch } from 'framefetch';\n\nconst ff = new FrameFetch({ apiKey: process.env.FRAMEFETCH_API_KEY });\n\nconst { ask } = await ff.ask(\n  'https://www.youtube.com/watch?v=jNQXAC9IVRw',\n  'What does the presenter say to do first?',\n);\n\nconsole.log(ask.answer);          // short, direct answer\nconsole.log(ask.confidence);      // 'high' | 'medium' | 'low'\nfor (const q of ask.quotes) {     // verbatim, timestamped supporting quotes\n  console.log(`[${q.t_sec}s] ${q.text}`);\n}\nconsole.log(ask.coverage);        // which part of the transcript was analyzed\n```\n\nCharged only when an answer is actually produced. A repeat question about an already-extracted video reuses the cached transcript, so it answers fast without a re-download or re-transcription — but the answer itself is always freshly generated, never cache-served.\n\n**Frames-based answers:** when a video has no transcript (e.g. Pinterest, or transcription failed), the answer is grounded in sampled keyframe images instead. Then `coverage.mode` is `\"frames\"`, `quotes` is `[]` (no transcript text to quote), and `confidence` is capped at `\"medium\"`.\n\n## Quick start\n\n```js\nimport { FrameFetch } from 'framefetch';\n\nconst ff = new FrameFetch({ apiKey: process.env.FRAMEFETCH_API_KEY });\n\nconst r = await ff.extract({\n  url: 'https://www.youtube.com/watch?v=jNQXAC9IVRw',\n  fields: ['metadata', 'transcript', 'frames', 'text_overlay'],\n  frames: { mode: 'fps', fps: 1, width: 480 },\n});\n\nconsole.log(r.metadata.title);         // \"Me at the zoo\"\nconsole.log(r.transcript.text);        // \"All right, so here we are, in front of the elephants…\"\nconsole.log(r.frames.length);          // 19 — frames is an array\nconsole.log(r.textOverlay?.[0]?.text); // on-screen text detected in the first frame, if any\n```\n\nNote the two spellings: `text_overlay` is the **request** field name, `textOverlay` is the **response** key.\n\n### Scoped helpers\n\n```js\nawait ff.metadata(url);          // title, author, duration, views, likes…\nawait ff.transcript(url);        // captions, else Whisper\nawait ff.frames(url, { mode: 'fps', fps: 1, width: 512 });\nawait ff.ask(url, 'What product is being reviewed?'); // grounded Q&A, see above\nawait ff.digest(url);            // LLM summary of the transcript\nawait ff.audioDigest(url, { voice: 'nova' }); // spoken mp3 briefing (signed URL, 24h)\nawait ff.structured(url);        // chapters/entities/products/claims/key_moments\nawait ff.comments(url, { comments_cap: 50 }); // top-level comments\nawait ff.commentSentiment(url);  // aggregated audience-mood rollup (+ the comments)\nawait ff.platforms();            // capability matrix (no key)\nawait ff.status();               // live service health (no key)\n\n// on-screen text (OCR) — requires \"frames\" alongside it, use extract() directly:\nawait ff.extract({ url, fields: ['frames', 'text_overlay'], frames: { mode: 'fps', fps: 1 } });\n```\n\nEvery helper above is a thin wrapper over `extract()`, so anything `extract()` accepts (`translate`,\n`format`, extra `fields`, …) can be passed as the last argument and is forwarded unchanged.\n\n### Search for videos, extract many at once\n\n```js\n// Find something to extract when you don't have a URL yet\nconst s = await ff.search('how to make sourdough', { limit: 5 });\nfor (const hit of s.results) {\n  console.log(hit.title, hit.url, hit.durationSec);\n}\n\n// Then extract up to 10 of them in ONE call. Shared options apply to every url.\nconst b = await ff.batch(s.results.slice(0, 3).map((r) => r.url), {\n  fields: ['metadata', 'digest'],\n});\nfor (const item of b.results) {\n  if (!item.ok) { console.error(item.url, item.error?.code); continue; }\n  console.log(item.metadata.title, '→', item.digest.gist);\n}\n```\n\nOne failing URL never fails the batch — each entry carries its own `ok` flag and, when `ok` is false,\nan `error` with `code`/`message`/`hint`. Per-URL `frames` specs are not accepted in a batch; use\n`extract()` for those.\n\n### Translate the transcript, export subtitles\n\n```js\n// translate the transcript into 1 of 25 languages (surfaced as transcript_translated)\nconst r = await ff.transcript(url, { translate: 'ja' });\nconsole.log(r.transcript_translated.text);\n\n// export subtitles directly — format is sent as a query param, response comes back as a string\nconst srt = await ff.transcript(url, { format: 'srt' });         // source-language subtitles\nconst vttJa = await ff.transcript(url, { translate: 'ja', format: 'vtt' }); // translated subtitles\n```\n\n### No signup\n\n```js\nconst ff = new FrameFetch();                              // no key\nawait ff.demo('https://youtu.be/jNQXAC9IVRw');            // instant metadata, rate-limited\nconst { key } = await ff.createKey('you@example.com');    // self-serve key + free credit\n```\n\n## Use it from an MCP agent\n\nFrameFetch ships an MCP server (Streamable HTTP) with four tools: `framefetch_extract`,\n`framefetch_platform_capabilities`, `framefetch_search` and `framefetch_account`. Add it to Claude,\nCursor, or any MCP client:\n\n```json\n{\n  \"mcpServers\": {\n    \"framefetch\": {\n      \"url\": \"https://framefetch.net/mcp\",\n      \"headers\": { \"Authorization\": \"<YOUR_FRAMEFETCH_KEY>\" }\n    }\n  }\n}\n```\n\nOr one line:\n\n```bash\nclaude mcp add --transport http framefetch https://framefetch.net/mcp \\\n  --header \"Authorization: <YOUR_FRAMEFETCH_KEY>\"\n```\n\nMCP lives at `https://framefetch.net/mcp` and speaks JSON-RPC over Streamable HTTP. REST lives under\n`/v1/*` and takes plain JSON (`{\"url\": \"…\"}`). Crossing the two is the single most common first-call\nmistake, so both directions answer clearly: a REST body POSTed to `/mcp` comes back as a JSON-RPC\nparse error, and a JSON-RPC body POSTed to `/v1/extract` comes back as `400 WRONG_ENDPOINT` naming\nthe right URL for your client.\n\n### Local stdio bridge\n\nPrefer a local stdio server (Claude Desktop, sandboxes, no inbound HTTP)? This package\nships `framefetch-mcp`, a zero-dependency stdio↔HTTP bridge that exposes the same tools\nand forwards calls to `framefetch.net`:\n\n```json\n{\n  \"mcpServers\": {\n    \"framefetch\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"framefetch-mcp\"],\n      \"env\": { \"FRAMEFETCH_API_KEY\": \"<YOUR_FRAMEFETCH_KEY>\" }\n    }\n  }\n}\n```\n\n`tools/list` works with no key; tool calls use `FRAMEFETCH_API_KEY` (or x402). Override the\nendpoint with `FRAMEFETCH_MCP_URL`.\n\n## Pay without an account (x402)\n\nAutonomous agents can pay per call in **USDC via x402** on Base — no signup, no human in the loop. Discoverable in the x402 Bazaar and at [`/.well-known/x402.json`](https://framefetch.net/.well-known/x402.json). Humans can use a free tier, prepaid credits, or a Stripe card.\n\n## Errors\n\nFailed calls throw `FrameFetchError` with `.status`, `.code`, and `.hint`:\n\n```js\nimport { FrameFetchError } from 'framefetch';\ntry {\n  await ff.transcript(url);\n} catch (e) {\n  if (e instanceof FrameFetchError && e.status === 402) {\n    // out of credit — top up at framefetch.net or via x402\n  }\n}\n```\n\n## API surface\n\n| Method | Endpoint | Auth |\n| --- | --- | --- |\n| `extract({ url, fields, frames, … })` | `POST /v1/extract` | key |\n| `ask(url, question)` | `POST /v1/extract` (`ask` param) | key |\n| `metadata(url)` | `POST /v1/metadata` | key |\n| `transcript(url, { translate, format })` | `POST /v1/transcript` | key |\n| `frames(url, spec)` | `POST /v1/frames` | key |\n| `digest(url)` | `POST /v1/extract` (`digest`) | key |\n| `audioDigest(url, { voice })` | `POST /v1/extract` (`audio_digest`) | key |\n| `structured(url)` | `POST /v1/extract` (`structured`) | key |\n| `comments(url, { comments_cap })` | `POST /v1/extract` (`comments`) | key |\n| `commentSentiment(url)` | `POST /v1/extract` (`comment_sentiment`) | key |\n| `search(query, { limit })` | `POST /v1/search` | key |\n| `batch(urls, { fields })` | `POST /v1/batch` | key |\n| `platforms()` | `GET /v1/platforms` | — |\n| `status()` | `GET /v1/status` | — |\n| `demo(url)` | `POST /v1/demo` | — |\n| `createKey(email)` | `POST /v1/keys` | — |\n\n### Full `extract()` request shape\n\n```ts\nff.extract({\n  url: string,\n  fields?: Field[],            // 'metadata' | 'insights' | 'transcript' | 'frames' | 'text_overlay'\n                               // | 'digest' | 'audio_digest' | 'structured' | 'comments'\n                               // | 'comment_sentiment' | 'delta'\n  frames?: { mode, n, fps, from, to, format, width },\n  translate?: string,          // ISO-639-1 target language (25 supported)\n  voice?: string,              // TTS voice for audio_digest\n  comments_cap?: number,       // 1-200, default 100\n  ask?: string,                // 3-500 char question — see ff.ask() above\n  publish?: boolean,           // opt in to a public per-video SEO page\n  format?: 'md' | 'markdown' | 'srt' | 'vtt', // alternate egress rendering (returns a string, not JSON)\n});\n```\n\nSee [`index.d.ts`](index.d.ts) for the complete typed response shape (`ExtractResult`, `Ask`,\n`VideoStructured`, `VideoComments`, `CommentSentiment`, `AudioDigest`, `SearchResult`,\n`BatchResult`, …).\n\nFull OpenAPI: [framefetch.net/openapi.json](https://framefetch.net/openapi.json) · Docs: [framefetch.net/docs](https://framefetch.net/docs)\n\n## Links\n\n[Website](https://framefetch.net) · [Docs](https://framefetch.net/docs) · [Pricing](https://framefetch.net/pricing) · [Status](https://framefetch.net/status) · [Guide: giving an agent video data](https://framefetch.net/ai-agent-video-data) · [Compare vs alternatives](https://framefetch.net/compare-video-data-apis)\n\n## License\n\nMIT\n",
  "bytes": 12213,
  "sha": "848908c3e823a1c3eadf244477be57e3e8ac29be591b05fddf6f25370a35efb6",
  "repo_slug": "marvinrey7879/framefetch-client",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_marvinrey7879_framefetch_f4150584/readme"
}