FrameFetch
Una URL de video social de entrada → metadatos, transcripción (subtítulos o Whisper), información de interacción y fotogramas paramétricos de salida. 6 plataformas (YouTube, Shorts, TikTok, Instagram, Pinterest, Reddit). REST + MCP. Pago por llamada con x402 (USDC), sin cuenta.
Documentación
FrameFetch
Cualquier URL de video social → respuestas, transcripción, metadatos, información, fotogramas y texto en pantalla (OCR).
API de datos de video y servidor MCP, pensados para agentes. Paga por llamada, o con x402 (USDC) — sin cuenta.
FrameFetch convierte una URL de video de YouTube, YouTube Shorts, TikTok, Instagram Reels, Pinterest o Reddit en una única respuesta JSON: una respuesta directa a una pregunta sobre el video, metadatos, información de interacción, una transcripción (subtítulos o Whisper), un resumen para LLM (texto o mp3 hablado), JSON estructurado (capítulos/entidades/productos/afirmaciones), comentarios + sentimiento, fotogramas muestreados paramétricamente (cada N / 1 por segundo / un rango de tiempo, a cualquier ancho), y el texto en pantalla incrustado en esos fotogramas (OCR — subtítulos, etiquetas de precio, señalización). Además, búsqueda por palabras clave cuando aún no tienes una URL, y procesamiento por lotes para hasta 10 URLs en una sola llamada. Construido API-first y MCP-first para agentes de IA.
Este repositorio es el cliente de código abierto + documentación. El servicio en sí se ejecuta en framefetch.net — tú aportas una clave API gratuita (o pagas por llamada con x402); el backend permanece alojado.
Por qué
Un LLM no puede ver un video. Para razonar sobre uno, necesita que el video se convierta primero en texto e imágenes — una respuesta, una transcripción, metadatos, algunos fotogramas. FrameFetch devuelve todo eso desde una URL, en seis plataformas, a través de un solo esquema.
Instalación
npm install framefetch
Node 18+ (usa el fetch integrado). Obtén una clave gratuita: framefetch.net.
Nota de versión. Este repositorio está en 0.4.0. La versión más reciente actualmente en npm es 0.3.0 —
npm install framefetchtodavía te da esa, y solo tieneextract/metadata/transcript/frames/platforms/status/demo/createKey. Todo lo demás documentado a continuación está activo en la API hoy y disponible desde este repositorio; desde npm 0.3.0 puedes acceder a los mismos datos a través deextract({ fields: [...] }).
Haz una pregunta — obtén una respuesta, no un volcado de transcripción
Una pregunta directa sobre un video devuelve una respuesta breve y fundamentada con citas con marca de tiempo, en lugar de que tú mismo proceses una transcripción 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
Se cobra solo cuando realmente se produce una respuesta. Una pregunta repetida sobre un video ya extraído reutiliza la transcripción en caché, por lo que responde rápido sin una nueva descarga o transcripción — pero la respuesta en sí siempre se genera fresca, nunca se sirve desde caché.
Respuestas basadas en fotogramas: cuando un video no tiene transcripción (por ejemplo, Pinterest, o la transcripción falló), la respuesta se fundamenta en imágenes de fotogramas clave muestreados. Entonces coverage.mode es "frames", quotes es [] (no hay texto de transcripción para citar), y confidence está limitado a "medium".
Inicio 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
Nota las dos grafías: text_overlay es el nombre del campo de solicitud, textOverlay es la clave de respuesta.
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 anterior es un envoltorio delgado sobre extract(), por lo que cualquier cosa que extract() acepte (translate,
format, fields adicional, …) se puede pasar como último argumento y se reenvía sin cambios.
Busca videos, extrae muchos a la 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);
}
Una URL que falle nunca hace fallar el lote — cada entrada lleva su propio indicador ok y, cuando ok es false,
un error con code/message/hint. Las especificaciones de frames por URL no se aceptan en un lote; usa
extract() para esas.
Traduce la transcripción, exporta subtítulos
// 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
Sin registro
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
Úsalo desde un agente MCP
FrameFetch incluye un servidor MCP (Streamable HTTP) con cuatro herramientas: framefetch_extract,
framefetch_platform_capabilities, framefetch_search y framefetch_account. Añádelo a Claude,
Cursor, o cualquier cliente MCP:
{
"mcpServers": {
"framefetch": {
"url": "https://framefetch.net/mcp",
"headers": { "Authorization": "<YOUR_FRAMEFETCH_KEY>" }
}
}
}
O en una línea:
claude mcp add --transport http framefetch https://framefetch.net/mcp \
--header "Authorization: <YOUR_FRAMEFETCH_KEY>"
MCP vive en https://framefetch.net/mcp y habla JSON-RPC sobre Streamable HTTP. REST vive bajo
/v1/* y acepta JSON plano ({"url": "…"}). Cruzar los dos es el error más común en la primera llamada,
por lo que ambas direcciones responden claramente: un cuerpo REST enviado a /mcp regresa como un error de análisis JSON-RPC,
y un cuerpo JSON-RPC enviado a /v1/extract regresa como 400 WRONG_ENDPOINT indicando
la URL correcta para tu cliente.
Puente stdio local
¿Prefieres un servidor stdio local (Claude Desktop, sandboxes, sin HTTP entrante)? Este paquete
incluye framefetch-mcp, un puente stdio↔HTTP sin dependencias que expone las mismas herramientas
y reenvía llamadas a framefetch.net:
{
"mcpServers": {
"framefetch": {
"command": "npx",
"args": ["-y", "framefetch-mcp"],
"env": { "FRAMEFETCH_API_KEY": "<YOUR_FRAMEFETCH_KEY>" }
}
}
}
tools/list funciona sin clave; las llamadas a herramientas usan FRAMEFETCH_API_KEY (o x402). Anula el
endpoint con FRAMEFETCH_MCP_URL.
Paga sin cuenta (x402)
Los agentes autónomos pueden pagar por llamada en USDC vía x402 en Base — sin registro, sin humano en el bucle. Descubrible en el Bazaar de x402 y en /.well-known/x402.json. Los humanos pueden usar un nivel gratuito, créditos prepagados o una tarjeta Stripe.
Errores
Las llamadas fallidas lanzan FrameFetchError con .status, .code y .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
}
}
Superficie de la API
| Método | Endpoint | Autenticación |
|---|---|---|
extract({ url, fields, frames, … }) | POST /v1/extract | clave |
ask(url, question) | POST /v1/extract (parámetro ask) | clave |
metadata(url) | POST /v1/metadata | clave |
transcript(url, { translate, format }) | POST /v1/transcript | clave |
frames(url, spec) | POST /v1/frames | clave |
digest(url) | POST /v1/extract (digest) | clave |
audioDigest(url, { voice }) | POST /v1/extract (audio_digest) | clave |
structured(url) | POST /v1/extract (structured) | clave |
comments(url, { comments_cap }) | POST /v1/extract (comments) | clave |
commentSentiment(url) | POST /v1/extract (comment_sentiment) | clave |
search(query, { limit }) | POST /v1/search | clave |
batch(urls, { fields }) | POST /v1/batch | clave |
platforms() | GET /v1/platforms | — |
status() | GET /v1/status | — |
demo(url) | POST /v1/demo | — |
createKey(email) | POST /v1/keys | — |
Forma completa de la solicitud 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)
});
Consulta index.d.ts para la forma completa de la respuesta tipada (ExtractResult, Ask,
VideoStructured, VideoComments, CommentSentiment, AudioDigest, SearchResult,
BatchResult, …).
OpenAPI completo: framefetch.net/openapi.json · Documentación: framefetch.net/docs
Enlaces
Sitio web · Documentación · Precios · Estado · Guía: dar datos de video a un agente · Comparación con alternativas
Licencia
MIT