Furlen
Converte dados públicos (Banco Mundial, FMI, FRED, Eurostat, arquivos da SEC) ou seu próprio CSV em um vídeo de gráfico animado, recalculando cada número a partir das linhas de origem antes da exportação.
Documentação
Desenvolvedores
Gere vídeos de dados verificados a partir de código.
Uma única chamada executa todo o pipeline: perfil → insights → narrativa de IA → verificação de afirmações → renderização. Os limites do plano (marca d'água, resolução, minutos de renderização) se aplicam exatamente como no aplicativo.
Você precisará de uma conta gratuita para gerar uma chave: faça login e crie uma em Configurações → Chaves de API. As chaves são exibidas apenas uma vez — guarde a sua com segurança.
POST /api/v1/renders
CSV entra, renderização em fila sai. Limite de taxa: 10 renderizações/min por espaço de trabalho.
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 pelo plano).
GET /api/v1/renders/:id
Consulte a cada poucos segundos; renderizações normalmente levam 30–90s. downloadUrl aparece quando concluído.
Uma renderização passa por queued → running → completed ou termina em failed / cancelled. Trate qualquer status desconhecido como "ainda em processamento". Exemplos de corpos:
Copiar
// na fila { "renderId": "3f9c…-uuid", "status": "queued", "progress": 0 }
// em execução — progress é uma porcentagem, 0..100 { "renderId": "3f9c…-uuid", "status": "running", "progress": 62 }
// concluído { "renderId": "3f9c…-uuid", "status": "completed", "progress": 100, "format": "mp4", "resolution": "1080p", "downloadUrl": "/api/render-jobs/3f9c…-uuid/download" }
// falhou — 'error' traz o motivo; seguro tentar novamente { "renderId": "3f9c…-uuid", "status": "failed", "progress": 0, "error": "Render worker error — safe to retry", "downloadUrl": null }
Idempotência, limites e cabeçalhos
Envie um Idempotency-Key no POST para que uma solicitação repetida nunca seja renderizada duas vezes (e nunca cobre minutos de renderização em dobro) — a mesma chave retorna o mesmo trabalho.
Copiar
POST /api/v1/renders Idempotency-Key: 9f2c-sua-chave-unica
// Cabeçalhos de limite de taxa em cada resposta: X-RateLimit-Limit: 10 X-RateLimit-Remaining: 7 X-RateLimit-Reset: 1752531600 // segundos unix Retry-After: 42 // presente em 429
Limites: corpo CSV/JSON até 5 MB · 10 renderizações/min por espaço de trabalho · resolução limitada pelo plano (720p Free, 1080p Creator, 4K Pro+). Entrada inválida retorna 400 invalid_request com um message nomeando o campo problemático.
Erros
Todo erro retorna error (uma mensagem legível) e code (estável — use este). As mensagens podem ser reformuladas; os códigos não.
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 e 402 são terminais — corrija a chave, o escopo ou o plano. 429 é seguro tentar novamente após Retry-After ter decorrido. 422 significa que os dados em si não conseguem sustentar uma história; tentar novamente o mesmo CSV falhará de forma idêntica.
Escopos de chave de API
As chaves têm escopos. Dê a um trabalho de CI uma chave que só pode iniciar renderizações, e ela não poderá baixar suas exportações se vazar.
| Escopo | Permite |
|---|---|
| renders:write | Iniciar renderizações |
| renders:read | Verificar status de renderização |
| exports:read | Baixar exportações concluídas |
| data:read | Buscar conjuntos de dados públicos |
Chaves criadas antes dos escopos existirem mantêm todos os três — nada quebrou quando isso foi lançado. Escolha os escopos ao criar uma chave em Configurações. Uma chamada sem escopo retorna 403 insufficient_scope, nunca 401: a chave é válida, apenas pode não ter permissão para aquilo.
Rotação de chave
A rotação gera uma substituição e mantém a chave antiga funcionando por um período de carência (padrão de 24h, máximo de 168h), para que você possa implantar a nova sem tempo de inatividade. A substituição herda os escopos da original.
Copiar
Requer seu cookie de sessão autenticado (execute no painel ou copie a
solicitação do DevTools enquanto estiver autenticado) — uma chave de API não pode autorizar esta chamada.
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" }
A rotação é uma ação de conta autenticada — uma chave de API não pode rotacionar a si mesma, ou uma chave vazada poderia gerar uma nova e sobreviver à revogação. Se uma chave pode ter vazado, revogue-a em vez disso: a revogação é imediata e não tem período de carência.
Especificação OpenAPI
/api/openapi.json — OpenAPI 3.1, gerada a partir das mesmas constantes que a API impõe, portanto não pode se desviar para descrever algo que não fazemos.
Copiar
npx openapi-typescript https://furlen.pro/api/openapi.json -o furlen.d.ts
Node e Python
Não existe pacote SDK Furlen — a API tem dois endpoints, e uma dependência que você precisa confiar é um mau negócio em troca das ~20 linhas abaixo. Ambos os exemplos fazem polling, porque Furlen não tem webhooks (veja abaixo).
Copiar
// Node 18+ — sem dependências.
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();
// Poll. O teto é 60/min, então ~2s é confortável.
// Em 429, AGUARDE o Retry-After (com jitter) — um continue puro atingiria o 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")
Baixar a exportação concluída requer uma chave com exports:read.
O que Furlen não tem
Vale saber antes de construir:
- Sem webhooks. As renderizações são consultadas — os loops acima são o padrão suportado. Se callbacks mudariam seu design, envie um e-mail para support@furlen.pro.
- Sem sandbox ou modo de teste. Toda chave é
sg_live_, e toda renderização é real contra sua cota. Teste no plano gratuito — as prévias são ilimitadas e com marca d'água. - Sem SDK publicado. Copie os exemplos acima ou gere um cliente tipado a partir da especificação OpenAPI.
- Sem página de status. /api/health responde
{ status: "ok" }para qualquer pessoa e retorna a sonda de configuração — serviços conectados e avisos — apenas para uma solicitação que contenha uma chave de API. De qualquer forma, é uma verificação de acessibilidade, não um histórico de uptime.
Existe um servidor MCP para criar gráficos?
Sim. furlen-mcp é publicado no registro oficial do Model Context Protocol como io.github.amitsha86/furlen-mcp e no npm. Ele dá ao Claude, Cursor ou qualquer cliente MCP três ferramentas que transformam uma tabela de números em um vídeo de gráfico animado — e ele recalcula cada número na narração em relação às suas linhas antes de exportar qualquer coisa. O servidor é de código aberto: github.com/amitsha86/furlen-mcp.
O que um agente pode realmente fazer com ele?
Três coisas. furlen_public_data (desde v0.1.2) recebe um pedido em linguagem natural — "PIB da Índia nos últimos 10 anos", "compare a população dos EUA e da China", "receita da Apple em 6 anos" — e retorna linhas reais do Banco Mundial, FMI, FRED, Eurostat, UN Comtrade, OMS ou registros de empresas, com uma string csv pronta e proveniência nomeando o indicador exato. furlen_render recebe CSV — aquele ou o do próprio agente — e executa todo o pipeline: perfil, encontrar os insights, escrever a história, verificar cada afirmação, renderizar. Retorna um renderId; você pode definir o público, a proporção de aspecto (16:9, 9:16 ou 1:1), mp4 ou gif e a resolução. Então furlen_render_status consulta esse id para obter uma URL de download. As renderizações geralmente levam 30–90 segundos, então consulte em vez de bloquear.
Um agente pode criar gráficos com dados do Banco Mundial sem uma planilha?
Sim — é para isso que serve furlen_public_data, e também é um endpoint HTTP simples se você preferir não usar MCP: POST /api/v1/data com { "prompt": "…" } e o escopo data:read. Ele resolve a solicitação pelos mesmos adaptadores que o estúdio usa, então herda as mesmas recusas: peça algo que nenhuma fonte conectada publica e você recebe um 422 com code: "subject_unsupported" nomeando o assunto, não um gráfico que parece plausível. Limitado a 6 solicitações por minuto — menor que o endpoint de renderização, porque cada chamada se espalha para agências estatísticas que nos limitam por sua 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"}'
O que "verificado" significa via MCP?
O mesmo que significa no aplicativo, porque é o mesmo mecanismo do lado do servidor. Cada afirmação numérica que a IA escreve é recalculada a partir das linhas que você enviou, e uma afirmação que não se reconcilia bloqueia a exportação em vez de ser enviada. Isso importa mais por meio de um agente do que por uma interface: ninguém está observando a saída intermediária, então a verificação tem que ser a coisa que recusa, em vez de um humano perceber. Você pode ver isso acontecer na página de prova.
Como conectar?
Crie uma chave de API em Configurações e adicione o bloco abaixo a claude_desktop_config.json, .cursor/mcp.json ou ao equivalente do seu cliente. É apenas configuração — sua ferramenta de IA inicia o conector sozinha, e nada é adicionado ao seu computador.
Copiar
{ "mcpServers": { "furlen": { "command": "npx", "args": ["-y", "furlen-mcp"], "env": { "FURLEN_API_KEY": "sg_live_..." } } } }
O que ainda não pode fazer?
Não pode enviar um arquivo — os dados chegam como texto CSV na solicitação ou nomeando um conjunto de dados público, não como anexo de planilha. A renderização é assíncrona, então não existe uma chamada de "me devolva um gráfico agora". E as fontes são as fontes: se nenhum provedor conectado publica o número, a resposta é uma recusa em vez de uma estimativa. Precisa de uma chave de API com os escopos corretos, e os limites do seu plano de resolução, marca d'água e minutos de renderização se aplicam exatamente como no aplicativo. Licença MIT.