FrameFetch

Uma URL de vídeo social → metadados, transcrição (legendas ou Whisper), insights de engajamento e frames paramétricos. 6 plataformas (YouTube, Shorts, TikTok, Instagram, Pinterest, Reddit). REST + MCP. Pagamento por chamada com x402 (USDC), sem conta.

Documentação

FrameFetch logo

FrameFetch

Qualquer URL de vídeo social → respostas, transcrição, metadados, insights, frames e texto na tela (OCR).
API de dados de vídeo e servidor MCP, priorizando agentes. Pague por chamada, ou com x402 (USDC) — sem conta.

npm website status MIT


FrameFetch transforma uma URL de vídeo YouTube, YouTube Shorts, TikTok, Instagram Reels, Pinterest ou Reddit em uma única resposta JSON: uma resposta direta a uma pergunta sobre o vídeo, metadados, insights de engajamento, uma transcrição (legendas ou Whisper), um resumo para LLM (texto ou mp3 falado), JSON estruturado (capítulos/entidades/produtos/afirmações), comentários + sentimento, frames amostrados parametricamente (a cada N / 1 por segundo / um intervalo de tempo, em qualquer largura) e o texto na tela gravado nesses frames (OCR — legendas, etiquetas de preço, sinalização). Além de busca por palavras-chave quando você ainda não tem uma URL, e lote para até 10 URLs em uma única chamada. Construído com prioridade para API e MCP, voltado para agentes de IA.

Este repositório é o cliente de código aberto + documentação. O serviço em si roda em framefetch.net — você traz uma chave de API gratuita (ou paga por chamada com x402); o backend permanece hospedado.

Por quê

Um LLM não consegue assistir a um vídeo. Para raciocinar sobre ele, é preciso primeiro transformar o vídeo em texto e imagens — uma resposta, uma transcrição, metadados, alguns frames. FrameFetch retorna tudo isso a partir de uma URL, em seis plataformas, por meio de um único esquema.

Instalação

npm install framefetch

Node 18+ (usa o fetch integrado). Obtenha uma chave gratuita: framefetch.net.

Nota de versão. Este repositório está na 0.4.0. A versão mais recente atualmente no npm é a 0.3.0 — npm install framefetch ainda fornece essa versão, e ela tem apenas extract/metadata/transcript/frames/platforms/status/demo/createKey. Todo o resto documentado abaixo está ativo na API hoje e disponível neste repositório; a partir do npm 0.3.0 você pode acessar os mesmos dados por meio de extract({ fields: [...] }).

Faça uma pergunta — obtenha uma resposta, não um despejo de transcrição

Uma pergunta direta sobre um vídeo retorna uma resposta curta e fundamentada, com citações com carimbo de tempo, em vez de você mesmo analisar uma transcrição de 25.000 tokens.

import { FrameFetch } from 'framefetch';

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

const { ask } = await ff.ask(
  'https://www.youtube.com/watch?v=jNQXAC9IVRw',
  'What does the presenter say to do first?',
);

console.log(ask.answer);          // short, direct answer
console.log(ask.confidence);      // 'high' | 'medium' | 'low'
for (const q of ask.quotes) {     // verbatim, timestamped supporting quotes
  console.log(`[${q.t_sec}s] ${q.text}`);
}
console.log(ask.coverage);        // which part of the transcript was analyzed

Cobrado apenas quando uma resposta é realmente produzida. Uma pergunta repetida sobre um vídeo já extraído reutiliza a transcrição em cache, então responde rapidamente sem novo download ou nova transcrição — mas a resposta em si é sempre gerada na hora, nunca servida de cache.

Respostas baseadas em frames: quando um vídeo não tem transcrição (por exemplo, Pinterest, ou a transcrição falhou), a resposta é fundamentada em imagens de keyframes amostradas. Nesse caso, coverage.mode é "frames", quotes é [] (sem texto de transcrição para citar), e confidence é limitado a "medium".

Início rápido

import { FrameFetch } from 'framefetch';

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

const r = await ff.extract({
  url: 'https://www.youtube.com/watch?v=jNQXAC9IVRw',
  fields: ['metadata', 'transcript', 'frames', 'text_overlay'],
  frames: { mode: 'fps', fps: 1, width: 480 },
});

console.log(r.metadata.title);         // "Me at the zoo"
console.log(r.transcript.text);        // "All right, so here we are, in front of the elephants…"
console.log(r.frames.length);          // 19 — frames is an array
console.log(r.textOverlay?.[0]?.text); // on-screen text detected in the first frame, if any

Observe as duas grafias: text_overlay é o nome do campo de solicitação, textOverlay é a chave de resposta.

Helpers específicos

await ff.metadata(url);          // title, author, duration, views, likes…
await ff.transcript(url);        // captions, else Whisper
await ff.frames(url, { mode: 'fps', fps: 1, width: 512 });
await ff.ask(url, 'What product is being reviewed?'); // grounded Q&A, see above
await ff.digest(url);            // LLM summary of the transcript
await ff.audioDigest(url, { voice: 'nova' }); // spoken mp3 briefing (signed URL, 24h)
await ff.structured(url);        // chapters/entities/products/claims/key_moments
await ff.comments(url, { comments_cap: 50 }); // top-level comments
await ff.commentSentiment(url);  // aggregated audience-mood rollup (+ the comments)
await ff.platforms();            // capability matrix (no key)
await ff.status();               // live service health (no key)

// on-screen text (OCR) — requires "frames" alongside it, use extract() directly:
await ff.extract({ url, fields: ['frames', 'text_overlay'], frames: { mode: 'fps', fps: 1 } });

Cada helper acima é um wrapper fino sobre extract(), então qualquer coisa que extract() aceite (translate, format, fields extra, …) pode ser passada como último argumento e é encaminhada sem alterações.

Busque vídeos, extraia vários de uma vez

// Find something to extract when you don't have a URL yet
const s = await ff.search('how to make sourdough', { limit: 5 });
for (const hit of s.results) {
  console.log(hit.title, hit.url, hit.durationSec);
}

// Then extract up to 10 of them in ONE call. Shared options apply to every url.
const b = await ff.batch(s.results.slice(0, 3).map((r) => r.url), {
  fields: ['metadata', 'digest'],
});
for (const item of b.results) {
  if (!item.ok) { console.error(item.url, item.error?.code); continue; }
  console.log(item.metadata.title, '→', item.digest.gist);
}

Uma URL com falha nunca falha o lote — cada entrada carrega seu próprio sinalizador ok e, quando ok é false, um error com code/message/hint. Especificações de frames por URL não são aceitas em um lote; use extract() para essas.

Traduza a transcrição, exporte legendas

// translate the transcript into 1 of 25 languages (surfaced as transcript_translated)
const r = await ff.transcript(url, { translate: 'ja' });
console.log(r.transcript_translated.text);

// export subtitles directly — format is sent as a query param, response comes back as a string
const srt = await ff.transcript(url, { format: 'srt' });         // source-language subtitles
const vttJa = await ff.transcript(url, { translate: 'ja', format: 'vtt' }); // translated subtitles

Sem cadastro

const ff = new FrameFetch();                              // no key
await ff.demo('https://youtu.be/jNQXAC9IVRw');            // instant metadata, rate-limited
const { key } = await ff.createKey('you@example.com');    // self-serve key + free credit

Use a partir de um agente MCP

FrameFetch inclui um servidor MCP (Streamable HTTP) com quatro ferramentas: framefetch_extract, framefetch_platform_capabilities, framefetch_search e framefetch_account. Adicione-o ao Claude, Cursor ou qualquer cliente MCP:

{
  "mcpServers": {
    "framefetch": {
      "url": "https://framefetch.net/mcp",
      "headers": { "Authorization": "<YOUR_FRAMEFETCH_KEY>" }
    }
  }
}

Ou em uma linha:

claude mcp add --transport http framefetch https://framefetch.net/mcp \
  --header "Authorization: <YOUR_FRAMEFETCH_KEY>"

O MCP fica em https://framefetch.net/mcp e fala JSON-RPC sobre Streamable HTTP. O REST fica sob /v1/* e aceita JSON simples ({"url": "…"}). Cruzar os dois é o erro mais comum na primeira chamada, então ambas as direções respondem claramente: um corpo REST enviado via POST para /mcp retorna um erro de análise JSON-RPC, e um corpo JSON-RPC enviado via POST para /v1/extract retorna 400 WRONG_ENDPOINT indicando a URL correta para o seu cliente.

Ponte stdio local

Prefere um servidor stdio local (Claude Desktop, sandboxes, sem HTTP de entrada)? Este pacote inclui framefetch-mcp, uma ponte stdio↔HTTP sem dependências que expõe as mesmas ferramentas e encaminha chamadas para framefetch.net:

{
  "mcpServers": {
    "framefetch": {
      "command": "npx",
      "args": ["-y", "framefetch-mcp"],
      "env": { "FRAMEFETCH_API_KEY": "<YOUR_FRAMEFETCH_KEY>" }
    }
  }
}

tools/list funciona sem chave; chamadas de ferramentas usam FRAMEFETCH_API_KEY (ou x402). Substitua o endpoint com FRAMEFETCH_MCP_URL.

Pague sem conta (x402)

Agentes autônomos podem pagar por chamada em USDC via x402 na Base — sem cadastro, sem humano no processo. Descobrível no Bazaar x402 e em /.well-known/x402.json. Humanos podem usar um nível gratuito, créditos pré-pagos ou cartão Stripe.

Erros

Chamadas com falha lançam FrameFetchError com .status, .code e .hint:

import { FrameFetchError } from 'framefetch';
try {
  await ff.transcript(url);
} catch (e) {
  if (e instanceof FrameFetchError && e.status === 402) {
    // out of credit — top up at framefetch.net or via x402
  }
}

Superfície da API

MétodoEndpointAutenticação
extract({ url, fields, frames, … })POST /v1/extractchave
ask(url, question)POST /v1/extract (parâmetro ask)chave
metadata(url)POST /v1/metadatachave
transcript(url, { translate, format })POST /v1/transcriptchave
frames(url, spec)POST /v1/frameschave
digest(url)POST /v1/extract (digest)chave
audioDigest(url, { voice })POST /v1/extract (audio_digest)chave
structured(url)POST /v1/extract (structured)chave
comments(url, { comments_cap })POST /v1/extract (comments)chave
commentSentiment(url)POST /v1/extract (comment_sentiment)chave
search(query, { limit })POST /v1/searchchave
batch(urls, { fields })POST /v1/batchchave
platforms()GET /v1/platforms—
status()GET /v1/status—
demo(url)POST /v1/demo—
createKey(email)POST /v1/keys—

Formato completo da solicitação extract()

ff.extract({
  url: string,
  fields?: Field[],            // 'metadata' | 'insights' | 'transcript' | 'frames' | 'text_overlay'
                               // | 'digest' | 'audio_digest' | 'structured' | 'comments'
                               // | 'comment_sentiment' | 'delta'
  frames?: { mode, n, fps, from, to, format, width },
  translate?: string,          // ISO-639-1 target language (25 supported)
  voice?: string,              // TTS voice for audio_digest
  comments_cap?: number,       // 1-200, default 100
  ask?: string,                // 3-500 char question — see ff.ask() above
  publish?: boolean,           // opt in to a public per-video SEO page
  format?: 'md' | 'markdown' | 'srt' | 'vtt', // alternate egress rendering (returns a string, not JSON)
});

Veja index.d.ts para o formato completo e tipado da resposta (ExtractResult, Ask, VideoStructured, VideoComments, CommentSentiment, AudioDigest, SearchResult, BatchResult, …).

OpenAPI completo: framefetch.net/openapi.json · Documentação: framefetch.net/docs

Links

Site · Documentação · Preços · Status · Guia: dando dados de vídeo a um agente · Compare com alternativas

Licença

MIT