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
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.
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 framefetchainda fornece essa versão, e ela tem apenasextract/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 deextract({ 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étodo | Endpoint | Autenticação |
|---|---|---|
extract({ url, fields, frames, … }) | POST /v1/extract | chave |
ask(url, question) | POST /v1/extract (parâmetro ask) | chave |
metadata(url) | POST /v1/metadata | chave |
transcript(url, { translate, format }) | POST /v1/transcript | chave |
frames(url, spec) | POST /v1/frames | chave |
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/search | chave |
batch(urls, { fields }) | POST /v1/batch | chave |
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