Furlen

Convierte datos públicos (Banco Mundial, FMI, FRED, Eurostat, presentaciones SEC) o tu propio CSV en un video de gráfico animado, recalculando cada cifra desde las filas de origen antes de exportar.

Documentación

Desarrolladores

Renderiza videos de datos verificados desde código.

Una sola llamada ejecuta todo el pipeline: perfil → insights → historia con IA → verificación de afirmaciones → renderizado. Los límites del plan (marca de agua, resolución, minutos de renderizado) se aplican exactamente igual que en la aplicación.

Necesitarás una cuenta gratuita para generar una clave: inicia sesión y créala en Configuración → Claves API. Las claves se muestran una sola vez: guárdala de forma segura.

POST /api/v1/renders

CSV de entrada, renderizado en cola de salida. Límite de tasa: 10 renderizados/min por espacio de trabajo.

Copiar

curl -X POST https://furlen.pro/api/v1/renders
-H "Authorization: Bearer sg_live_..."
-H "Content-Type: application/json"
-d '{ "csv": "month,revenue\n2025-01,120000\n2025-02,145000\n2025-03,210000", "audience": "investor", "outputFormat": "video_16_9", "format": "mp4", "resolution": "1080p" }'

audience: investor · executive · linkedin · youtube · internal_team · client_report · student — outputFormat: video_16_9 · video_9_16 · video_1_1 — format: mp4 · gif — resolution: 720p · 1080p · 4K (limitado por el plan).

GET /api/v1/renders/:id

Consulta cada pocos segundos; los renderizados suelen tardar entre 30 y 90 segundos. downloadUrl aparece cuando se completa.

Un renderizado avanza a través de queued → running → completed, o termina en failed / cancelled. Trata cualquier estado desconocido como "aún en proceso". Ejemplos de cuerpos:

Copiar

// en cola { "renderId": "3f9c…-uuid", "status": "queued", "progress": 0 }

// en ejecución — progress es un porcentaje, 0..100 { "renderId": "3f9c…-uuid", "status": "running", "progress": 62 }

// completado { "renderId": "3f9c…-uuid", "status": "completed", "progress": 100, "format": "mp4", "resolution": "1080p", "downloadUrl": "/api/render-jobs/3f9c…-uuid/download" }

// fallido — 'error' contiene el motivo; es seguro reintentar { "renderId": "3f9c…-uuid", "status": "failed", "progress": 0, "error": "Render worker error — safe to retry", "downloadUrl": null }

Idempotencia, límites y cabeceras

Envía un Idempotency-Key en el POST para que una solicitud reintentada nunca renderice dos veces (y nunca cobre minutos de renderizado dos veces): la misma clave devuelve el mismo trabajo.

Copiar

POST /api/v1/renders Idempotency-Key: 9f2c-your-unique-key

// Cabeceras de límite de tasa en cada respuesta: X-RateLimit-Limit: 10 X-RateLimit-Remaining: 7 X-RateLimit-Reset: 1752531600 // segundos unix Retry-After: 42 // presente en 429

Límites: cuerpo CSV/JSON de hasta 5 MB · 10 renderizados/min por espacio de trabajo · resolución limitada por el plan (720p Gratis, 1080p Creador, 4K Pro+). La entrada no válida devuelve 400 invalid_request con un message que nombra el campo problemático.

Errores

Cada error devuelve error (un mensaje legible) y code (estable — analiza este). Los mensajes pueden reformularse; los códigos no.

Copiar

401 { "error": "Invalid or missing API key.", "code": "unauthorized" } 403 { "error": "This API key does not have the 'renders:write' scope.", "code": "insufficient_scope" } 402 { "error": "This format requires a higher plan.", "code": "plan_gate" } 422 { "error": "No story-worthy insights found in this data.", "code": "no_insights" } 429 { "error": "Rate limit exceeded (10/min).", "code": "rate_limited" }

401, 403 y 402 son terminales: corrige la clave, el alcance o el plan. 429 es seguro reintentarlo una vez que haya transcurrido Retry-After. 422 significa que los datos en sí no pueden sustentar una historia; reintentar el mismo CSV fallará de forma idéntica.

Alcances de las claves API

Las claves tienen alcances. Dale a un trabajo de CI una clave que solo pueda iniciar renderizados, y no podrá descargar tus exportaciones si se filtra.

AlcancePermite
renders:writeIniciar renderizados
renders:readComprobar estado del renderizado
exports:readDescargar exportaciones terminadas
data:readObtener conjuntos de datos públicos

Las claves creadas antes de que existieran los alcances conservan los tres: nada se rompió cuando esto se lanzó. Elige los alcances al crear una clave en Configuración. Una llamada que carece de un alcance devuelve 403 insufficient_scope, nunca 401: la clave es válida, simplemente puede que no tenga permiso para eso.

Rotación de una clave

La rotación genera un reemplazo y deja la clave antigua funcionando durante un período de gracia (24 h por defecto, máximo 168 h), para que puedas implementar la nueva sin tiempo de inactividad. El reemplazo hereda los alcances de la original.

Copiar

Requiere tu cookie de sesión iniciada (ejecuta desde el panel, o copia la

solicitud desde DevTools mientras estás conectado) — una clave API no puede autorizar esta llamada.

curl -X POST https://furlen.pro/api/keys//rotate
-H 'cookie: '
-H 'content-type: application/json'
-d '{"graceHours": 24}'

→ { "key": "sg_live_…", "oldKeyExpiresAt": "2026-07-16T…Z" }

La rotación es una acción de cuenta conectada: una clave API no puede rotarse a sí misma, o una clave filtrada podría generar una nueva y sobrevivir a la revocación. Si una clave puede haberse filtrado, revócala en su lugar: la revocación es inmediata y no tiene período de gracia.

Especificación OpenAPI

/api/openapi.json — OpenAPI 3.1, generado a partir de las mismas constantes que la API aplica, por lo que no puede desviarse para describir algo que no hacemos.

Copiar

npx openapi-typescript https://furlen.pro/api/openapi.json -o furlen.d.ts

Node y Python

No existe un paquete SDK de Furlen: la API son dos endpoints, y una dependencia en la que tengas que confiar es un mal intercambio por las ~20 líneas siguientes. Ambos ejemplos consultan, porque Furlen no tiene webhooks (ver más abajo).

Copiar

// Node 18+ — sin dependencias. const KEY = process.env.FURLEN_API_KEY; const h = { authorization: Bearer ${KEY}, 'content-type': 'application/json' };

const start = await fetch('https://furlen.pro/api/v1/renders', { method: 'POST', headers: h, body: JSON.stringify({ csv: 'month,revenue\n2024-01,100\n2024-02,250' }), }); if (!start.ok) throw new Error(${start.status}: ${(await start.json()).code}); const { renderId } = await start.json();

// Consulta. 60/min es el límite máximo, así que ~2s es cómodo. // En 429, ESPERA a Retry-After (más jitter) — un continue simple golpearía el limitador. let attempts = 0; for (;;) { await new Promise((r) => setTimeout(r, 2000)); const res = await fetch(https://furlen.pro/api/v1/renders/${renderId}, { headers: h }); if (res.status === 429) { if (++attempts > 10) throw new Error('rate limited too long — giving up'); const wait = Number(res.headers.get('retry-after') ?? 5) * 1000 + Math.random() * 500; await new Promise((r) => setTimeout(r, wait)); continue; } attempts = 0; if (!res.ok) throw new Error(polling failed: ${res.status}); const job = await res.json(); if (job.status === 'completed') { console.log(job.downloadUrl); break; } if (job.status === 'failed') throw new Error(job.error); if (job.status === 'cancelled') throw new Error('render cancelled'); }

Copiar

Python 3.9+ — pip install requests

import os, time, requests

KEY = os.environ["FURLEN_API_KEY"] H = {"authorization": f"Bearer {KEY}"}

r = requests.post("https://furlen.pro/api/v1/renders", headers=H, json={"csv": "month,revenue\n2024-01,100\n2024-02,250"}) r.raise_for_status() render_id = r.json()["renderId"]

while True: time.sleep(2) res = requests.get(f"https://furlen.pro/api/v1/renders/{render_id}", headers=H) if res.status_code == 429: time.sleep(int(res.headers.get("Retry-After", 5))) continue res.raise_for_status() job = res.json() if job["status"] == "completed": print(job["downloadUrl"]) break if job["status"] == "failed": raise RuntimeError(job["error"]) if job["status"] == "cancelled": raise RuntimeError("render cancelled")

Descargar la exportación terminada necesita una clave con exports:read.

Lo que Furlen no tiene

Vale la pena saberlo antes de construir:

  • Sin webhooks. Los renderizados se consultan: los bucles anteriores son el patrón compatible. Si las devoluciones de llamada cambiarían tu diseño, escribe a support@furlen.pro.
  • Sin modo sandbox ni de prueba. Cada clave es sg_live_, y cada renderizado es un renderizado real contra tu cuota. Prueba en el plan gratuito: las vistas previas son ilimitadas y tienen marca de agua.
  • Sin SDK publicado. Copia los ejemplos anteriores o genera un cliente tipado a partir de la especificación OpenAPI.
  • Sin página de estado. /api/health responde { status: "ok" } a cualquiera y devuelve la sonda de configuración — servicios conectados y advertencias — solo a una solicitud que lleve una clave API. En cualquier caso, es una comprobación de accesibilidad, no un historial de disponibilidad.

¿Hay un servidor MCP para hacer gráficos?

Sí. furlen-mcp está publicado en el registro oficial del Protocolo de Contexto de Modelo como io.github.amitsha86/furlen-mcp y en npm. Proporciona a Claude, Cursor o cualquier cliente MCP tres herramientas que convierten una tabla de números en un video de gráfico animado — y recalcula cada cifra de la narración contra tus filas antes de exportar cualquier cosa. El servidor es de código abierto: github.com/amitsha86/furlen-mcp.

¿Qué puede hacer realmente un agente con él?

Tres cosas. furlen_public_data (desde v0.1.2) toma una solicitud en lenguaje natural — "PIB de India en los últimos 10 años", "compara la población de EE. UU. y China", "ingresos de Apple en 6 años" — y devuelve filas reales del Banco Mundial, FMI, FRED, Eurostat, UN Comtrade, OMS o presentaciones de empresas, con una cadena csv lista y procedencia que nombra el indicador exacto. furlen_render toma CSV — ese, o el del propio agente — y ejecuta todo el pipeline: perfil, encontrar los insights, escribir la historia, verificar cada afirmación, renderizar. Devuelve un renderId; puedes configurar la audiencia, la relación de aspecto (16:9, 9:16 o 1:1), mp4 o gif, y la resolución. Luego furlen_render_status consulta ese id para obtener una URL de descarga. Los renderizados suelen tardar entre 30 y 90 segundos, así que consulta en lugar de bloquear.

¿Puede un agente graficar datos del Banco Mundial sin una hoja de cálculo?

Sí — para eso sirve furlen_public_data, y también es un endpoint HTTP simple si prefieres no usar MCP: POST /api/v1/data con { "prompt": "…" } y el alcance data:read. Resuelve la solicitud a través de los mismos adaptadores que usa el estudio, por lo que hereda los mismos rechazos: pide algo que ninguna fuente conectada publique y obtienes un 422 con code: "subject_unsupported" nombrando el tema, no un gráfico de apariencia plausible. Límite de tasa de 6 solicitudes por minuto — más bajo que el endpoint de renderizado, porque cada llamada se expande a agencias estadísticas que nos limitan la tasa a su vez.

Copiar

curl -X POST https://furlen.pro/api/v1/data
-H "Authorization: Bearer sg_live_..."
-H "Content-Type: application/json"
-d '{"prompt": "India GDP over the last 10 years"}'

¿Qué significa "verificado" a través de MCP?

Lo mismo que significa en la aplicación, porque es la misma puerta del lado del servidor. Cada afirmación numérica que escribe la IA se recalcula a partir de las filas que pasaste, y una afirmación que no concuerda bloquea la exportación en lugar de enviarse. Esto importa más a través de un agente que a través de una interfaz: nadie está mirando la salida intermedia, así que la comprobación tiene que ser lo que rechace en lugar de que un humano lo note. Puedes verlo suceder en la página de prueba.

¿Cómo te conectas?

Crea una clave API en Configuración y luego añade el bloque siguiente a claude_desktop_config.json, .cursor/mcp.json o el equivalente de tu cliente. Es solo configuración: tu herramienta de IA inicia el conector por sí misma y no se añade nada a tu computadora.

Copiar

{ "mcpServers": { "furlen": { "command": "npx", "args": ["-y", "furlen-mcp"], "env": { "FURLEN_API_KEY": "sg_live_..." } } } }

¿Qué no puede hacer todavía?

No puede subir un archivo: los datos llegan como texto CSV en la solicitud o nombrando un conjunto de datos público, no como un adjunto de hoja de cálculo. El renderizado es asíncrono, así que no hay una llamada de "dame un gráfico ahora". Y las fuentes son las fuentes: si ningún proveedor conectado publica la cifra, la respuesta es un rechazo en lugar de una estimación. Necesita una clave API con los alcances correctos, y los límites de tu plan en resolución, marca de agua y minutos de renderizado se aplican exactamente igual que en la aplicación. Licencia MIT.