Crie uma chave e envie como token Bearer
As chaves são criadas no Painel, em Chaves de API, e essa resposta é o único lugar em que a chave completa aparece: toda leitura posterior da sua lista de chaves devolve um ID, um nome e um prefixo, nunca mais o segredo. Salve a chave imediatamente em um lugar seguro. O prefixo existe para que uma linha de log possa identificar uma chave sem contê-la.
Envie a chave no cabeçalho Authorization em todas as requisições, como a palavra Bearer seguida da chave. Não existe forma via query string: a RFC 6750 lista entre suas recomendações que “Bearer tokens SHOULD NOT be passed in page URLs (for example, as query string parameters)”, porque URLs que carregam tokens acabam no histórico do navegador, em referrers e em logs de servidor.
Sem chave, ou com uma chave revogada, a resposta é 401 com authentication_required como código. Todo erro compartilha o mesmo corpo, um objeto error com message, type, param e code. Baseie a lógica no code: a message é escrita para uma pessoa, e a redação dela vai mudar.
Leia o catálogo em vez de fixá-lo no código
GET /v1/models devolve duas listas, image e video. Uma entrada de imagem lista os tamanhos que aceita, o custo em créditos de cada um e um tamanho padrão; uma entrada de vídeo traz resoluções com custo por segundo, um piso e um teto de duração e as proporções que aceitar. A lista de tamanhos pertence a um modelo, e não ao catálogo, assim como o padrão, então nenhum valor único é seguro para aplicar em todos. As páginas do catálogo de modelos e de preços leem esse mesmo endpoint. A página de cada modelo também mostra o ID a enviar, por exemplo o Seedance 2.5 para vídeo e o Qwen 3 para imagens, e as páginas da API de imagem NSFW e da API de vídeo NSFW resumem o formato das requisições.
O campo kind informa se o modelo gera imagem ou vídeo; cada entrada lista depois um modo texto e um modo referência. Você envia o mesmo id de modelo nos dois casos: anexe imagens de referência e valem os tamanhos e preços do modo referência. Um modelo desconhecido, um tamanho inválido ou uma duração fora da faixa voltam como 400, com param citando o campo, e a duração fora da faixa é recusada em vez de cortada para caber.
Inicie a execução e consulte o registro
Faça um POST para /v1/generate/image ou /v1/generate/video com um ID de modelo, um prompt e as opções que esse modelo aceita. A resposta é 202 e traz um id, o status processing e quantos créditos acabaram de ser debitados: os créditos saem no início da execução, não no fim. Depois, continue lendo GET /v1/generations/{id}; quando o status deixar de ser queued ou processing, a execução acabou. Se ela for concluída, o output_url do registro aponta para nosso endpoint de arquivos; se falhar, o registro traz error_code, um valor de um conjunto fixo, junto de error, um resumo curto pensado para seus logs.
Coloque um intervalo entre as consultas e um pouco de aleatoriedade nesse intervalo. O experimento de backoff de Marc Brooker no AWS Architecture Blog simula clientes disputando uma mesma linha e relata que, com jitter, “in the case with 100 contending clients, we've reduced our call count by more than half”. Consultar é como você fica sabendo do desfecho, não como ele acontece: as execuções são reconciliadas no servidor com ou sem alguém olhando, então uma conexão perdida custa a você o acesso ao resultado, não o resultado em si.
O ciclo inteiro em um arquivo
Salve isto como um arquivo .mjs, defina VELLRIA_API_KEY e execute em um Node recente. Nenhum ID de modelo, tamanho ou custo está fixo no código.
```js import { writeFile } from "node:fs/promises"; const BASE = "https://vellria.com"; const AUTH = { Authorization: `Bearer ${process.env.VELLRIA_API_KEY}` }; const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); async function call(path, init) { const res = await fetch(BASE + path, { ...init, headers: { ...AUTH, ...init?.headers } }); if (res.status === 429) { await sleep(Number(res.headers.get("retry-after") ?? 5) * 1000); return call(path, init); } if (!res.ok) throw new Error((await res.json().catch(() => null))?.error?.code ?? res.status); return res.json(); } const catalog = await call("/v1/models"); const model = catalog.image[0]; let rec = await call("/v1/generate/image", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ model: model.id, prompt: "a lighthouse in fog", size: model.text.default_size }), }); while (rec.status === "queued" || rec.status === "processing") { await sleep(2000 + Math.random() * 2000); rec = await call(`/v1/generations/${rec.id}`); } if (rec.status !== "completed") throw new Error(rec.error_code ?? "generation_failed"); const file = await fetch(rec.output_url, { headers: AUTH }); const ext = file.headers.get("content-type") === "image/png" ? "png" : "jpg"; await writeFile(`${rec.id}.${ext}`, Buffer.from(await file.arrayBuffer())); ```
Para um modelo que recebe imagens, faça antes um POST do arquivo para /v1/uploads com um cabeçalho Content-Type que corresponda aos bytes e passe o endereço recebido em image_urls; converter imagem em vídeo traz os formatos aceitos e a lista de recusas. Querer ou não imagens é uma propriedade do modelo, e um corpo no formato errado é recusado em vez de ajustado em silêncio, como mostra manter um personagem consistente. Corpos de vídeo levam duration e resolution em vez de size, o que escolher resolução e duração explica. Todos os IDs de modelo de vídeo, com tipo e preço por segundo, estão em uma única tabela na página da API de vídeo NSFW.
Falhas, limite de requisições e expiração
Quando uma execução falha depois de começar, os créditos voltam sozinhos ao saldo assim que o registro passa para failed. Trate failed como final e resolvido: use o error_code para saber o motivo e nunca reconcilie saldos do seu lado. O reembolso é gravado sob uma condição que só uma gravação consegue satisfazer, então uma notificação do provedor entregue de novo não tem como creditar você duas vezes nem deixar de creditar.
Ultrapassar um limite de requisições devolve 429 com o código rate_limit_exceeded e um cabeçalho Retry-After. A RFC 6585 define esse status: “the 429 status code indicates that the user has sent too many requests in a given amount of time”. A RFC 9110, na seção 10.2.3, define o cabeçalho como aquele que “indicates how long the user agent ought to wait before making a follow-up request”. Leia o valor em vez de chutar, e veja em storyboard e pré-visualização o que isso significa quando você dispara um lote. Saldo insuficiente é 402, e nenhum registro é criado. Os detalhes completos estão na referência da API.




