Técnica
Como chamar a API da Vellria pelo seu código
Quatro chamadas levam você do zero a um arquivo pronto: criar uma chave, iniciar uma geração, consultar o registro e baixar o resultado. Este guia percorre esse ciclo de ponta a ponta, com o erro que cada etapa devolve e o que um cliente deve fazer diante dele.
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.
O campo kind informa o formato de corpo que o modelo quer: texto para imagem, imagem para imagem, texto para vídeo, imagem para vídeo. Use-o em vez de deduzir a mesma coisa pela presença de um objeto de imagem de referência, o que está certo hoje e vai ficar errado sem aviso para o que for adicionado depois. 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.find((m) => m.kind === "text-to-image"); 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.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 }); await writeFile(`${rec.id}.png`, 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; o histórico mantém as duas entradas, a falha e o reembolso. 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.
Dúvidas frequentes
Preciso ficar consultando até a geração terminar?
Não. Consultar é como você descobre, não como acontece. As execuções são reconciliadas do nosso lado com ou sem um cliente conectado, então um trabalho iniciado por um processo que depois morreu ainda chega a completed ou failed; recupere-o pelo id mais tarde.
Posso chamar a API a partir de código no navegador?
Não com sua chave. Uma chave gasta créditos e não pode ter o escopo reduzido, então qualquer coisa enviada a um navegador é uma chave que você publicou. Coloque as chamadas atrás do seu próprio servidor, mantenha a chave no ambiente dele e deixe seu front-end conversar com ele.
Posso fixar no código IDs de modelo, tamanhos e custos?
Pode, e eles vão ficar desatualizados. O endpoint do catálogo é a fonte que as páginas de modelo exibem e que o servidor usa para cobrar, então um valor copiado deixa de bater no dia em que o catálogo muda. Busque-o na inicialização e mantenha em cache enquanto o processo durar.
