Imagens, vídeo, música e fala
A chave que você usa para texto também cria imagens, vídeos, música e fala, e transforma gravações em texto, a partir do mesmo saldo. Escolha um modelo na tabela de modelos (ou ?type=image, ?type=music, ?type=audio); a página de cada modelo mostra seu preço, seu tipo e uma requisição funcional.
| Você quer | Rota | Imagem de origem vai em |
|---|---|---|
| Uma nova imagem a partir de um prompt | POST /v1/images/generations |
(nenhum) |
| Uma imagem editada | POST /v1/images/edits |
image (ou image_url, input_images, images) |
| Um vídeo a partir de um prompt | POST /v1/video/generations |
(nenhum) |
| Um vídeo que começa a partir de uma imagem | POST /v1/video/generations |
image_url (primeiro quadro); end_image_url para o último quadro |
| Um vídeo a partir de imagens de referência | POST /v1/video/generations |
input_images com "role": "reference" |
| Uma resposta de texto sobre uma imagem | POST /v1/chat/completions |
uma parte image_url em messages[].content |
| Música | POST /v1/music/generations |
(nenhum) |
| Fala a partir de texto | POST /v1/audio/speech |
(nenhum) |
| Texto a partir de uma gravação | POST /v1/audio/transcriptions |
(nenhum; a gravação vai em file) |
Uma imagem de origem é um link https://, uma URI data: (data:image/png;base64,iVBORw0…) ou base64 simples de um PNG, JPEG ou WebP, até 8 por requisição. As rotas de imagem também aceitam um upload multipart (abaixo). Links devem ser links https:// públicos; links http:// são recusados. Um link que redireciona (por exemplo https://picsum.photos/640/360) é seguido, até 5 vezes e apenas para outros links https:// públicos, e o modelo recebe o link para onde ele aponta. Um link que não leva a uma imagem, ou redireciona para algum lugar que não podemos usar, é recusado com 400 invalid_image_url antes que algo seja retido ou cobrado. Imagens enviadas inline tornam a requisição maior: mantenha-a abaixo de 10 MB, ou envie links.
Em uma mensagem de chat (image_url em messages[].content), um link (https:// ou http://) é aberto pelo UberLLM e enviado ao modelo como uma URL data:, porque a maioria dos modelos de visão aceita apenas isso. Cada imagem pode ter até 20 MB (40 MB por requisição), e os redirecionamentos são seguidos até 5 vezes, apenas para endereços públicos. Modelos Gemini recebem o link como ele é. Um link que não leva a uma imagem é recusado com 400 invalid_image_url, e nada é cobrado.
Criar uma imagem
Imagens são retornadas na resposta.
curl https://api.uberllm.dev/v1/images/generations \
-H "Authorization: Bearer $UBERLLM_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model": "venice/z-image-turbo", "prompt": "a red apple on a white table", "n": 1, "size": "1024x1024"}'
{ "created": 1792429247, "data": [{ "url": "https://api.uberllm.dev/v1/media/artifacts/<id>/0" }],
"usage": { "cost_usd": 0.0099, "max_cost_usd": 0.0109 } }
usage.cost_usd é o custo da imagem (também no cabeçalho X-UberLLM-Cost-USD). Na rara resposta onde o preço ainda não é conhecido, é null. Você paga o que o provedor cobrou, nunca mais do que max_cost_usd, o que foi reservado para isso.
response_format é url por padrão, tanto para /v1/images/generations quanto para /v1/images/edits. Envie "response_format": "b64_json" para obter a imagem dentro da resposta, em vez disso. Quando o modelo retorna a imagem em si, o UberLLM a mantém como um arquivo da sua conta por 24 horas e responde com seu link (https://api.uberllm.dev/v1/files/file_…/content), que também funciona como a imagem de origem para uma edição posterior. Baixe uma URL com a mesma chave:
curl -L -o image.png -H "Authorization: Bearer $UBERLLM_API_KEY" \
"https://api.uberllm.dev/v1/media/artifacts/<id>/0"
n é 1 para 10.
Editar uma imagem
Use um modelo que edita (seu ID geralmente termina em -edit) e envie a imagem de origem em image:
curl https://api.uberllm.dev/v1/images/edits \
-H "Authorization: Bearer $UBERLLM_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model": "google/nano-banana-pro-edit", "prompt": "make the sky a sunset",
"image": "https://example.com/skyline.png"}'
A mesma imagem pode ser enviada como:
"image": "data:image/png;base64,iVBORw0…""image_url": "https://…""input_images": ["https://…", "https://…"]: a primeira é a imagem a ser editada, o restante são referências. Uma entrada também pode ser{"url": "…", "role": "reference"}."images": [{"image_url": "https://…"}], o formato que os SDKs da OpenAI enviam
Base64 simples também funciona, e "input_image" é lido como "image".
Ou faça o upload do arquivo, como na edição de imagem da OpenAI:
curl https://api.uberllm.dev/v1/images/edits \
-H "Authorization: Bearer $UBERLLM_API_KEY" \
-F model=google/nano-banana-pro-edit -F prompt="make the sky a sunset" \
-F [email protected]
Uma edição sem imagem de origem é recusada com 400 image_required. Máscaras (inpainting) não são suportadas.
Faça upload de uma imagem uma vez
Faça upload de uma imagem para /v1/files e use-a em quantas requisições quiser
por 24 horas, em vez de enviá-la em cada requisição:
curl https://api.uberllm.dev/v1/files \
-H "Authorization: Bearer $UBERLLM_API_KEY" \
-F [email protected]
{ "id": "file_3f9c…", "object": "file", "bytes": 482113, "content_type": "image/png",
"filename": "skyline.png", "created_at": 1792540800, "expires_at": 1792627200,
"url": "https://api.uberllm.dev/v1/files/file_3f9c…/content" }
Em seguida, coloque o id, ou o url, em qualquer lugar onde uma imagem de origem seja esperada: "image": "file_3f9c…",
"image": {"file_id": "file_3f9c…"}, "image_url": "file_3f9c…", uma entrada de
input_images, ou o frame inicial de um vídeo. Apenas sua conta pode usar seus arquivos.
- PNG, JPEG, WebP, GIF, HEIC ou AVIF, até 10 MB cada, no máximo 500 mantidos por conta por vez.
- Mantidos por 24 horas, depois excluídos.
GET /v1/files/{id}descreve um,GET /v1/files/{id}/contento baixa,DELETE /v1/files/{id}o exclui agora.
Criar um vídeo
Um vídeo leva um minuto ou mais, então é uma tarefa: inicie, acompanhe e baixe.
1. Inicie
curl https://api.uberllm.dev/v1/video/generations \
-H "Authorization: Bearer $UBERLLM_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"model": "fal-ai/vidu/q3/text-to-video/turbo", "prompt": "a red ball bouncing on green grass",
"duration_seconds": 5, "models": ["fal-ai/kling-video/v3/standard/text-to-video"]}'
models é opcional: modelos de backup são tentados em ordem somente quando o primeiro não tem um provedor gratuito no momento, para que um momento de pico não faça sua solicitação falhar (veja
Modelos de fallback). O vídeo acima leva cerca de um minuto e
custa cerca de $0.33.
Para começar a partir de uma imagem, use um modelo image-to-video e adicione image_url:
-d '{"model": "bytedance/seedance-2-0-image-to-video", "prompt": "slow push-in as the waves roll",
"image_url": "https://example.com/first-frame.png", "duration_seconds": 5}'
Um modelo que combina duas imagens também recebe end_image_url (o último quadro).
Um modelo image-to-video ou reference-to-video que não consegue criar vídeo apenas a partir de texto
é recusado com 400 image_required quando a solicitação não tem imagem,
antes de qualquer cobrança. Um modelo reference-to-video recebe as imagens em
input_images:
-d '{"model": "pixverse/pixverse-c1-reference-to-video", "prompt": "the cat walks across the room",
"input_images": [{"url": "https://example.com/cat.png", "role": "reference"}],
"aspect_ratio": "16:9", "duration_seconds": 5}'
Outros campos, quando o modelo os oferece: resolution (480p, 720p, 1080p…),
aspect_ratio (16:9, 9:16, 1:1…) e audio (true / false; um modelo
sem configuração de som não recebe esse campo e cria o clipe como sempre faz). Um
modelo reference-to-video precisa de um formato, então quando você omite aspect_ratio ele é
enviado como 16:9 (ou o primeiro formato do modelo, se ele não criar 16:9); o
schema do modelo mostra esse padrão. As opções de cada
modelo são listadas por GET /v1/models/<model id> (veja
Opções de cada modelo).
Cada modelo cria apenas certas durações de clipe, resoluções e formatos. Uma solicitação para um valor que ele não cria é recusada com uma mensagem que lista os valores que ele cria, antes de qualquer cobrança.
HTTP 202:
{
"id": "6f1c…",
"object": "media.job",
"kind": "video",
"status": "queued",
"model": "fal-ai/vidu/q3/text-to-video/turbo",
"estimated_cost_usd": 0.32725,
"estimated_cost_usdc": "327250",
"max_cost_usd": 0.359975,
"max_cost_usdc": "359975",
"poll_url": "/v1/video/generations/6f1c…",
"cancel_url": "/v1/video/generations/6f1c…"
}
model é o modelo que assumiu a tarefa (um backup de models quando o primeiro
não conseguiu). Custos que terminam em _usdc estão em milionésimos de dólar (327250 = $0.33).
estimated_cost_usd é o preço listado do que você pediu: o mesmo número
que validate e POST /v1/quote retornam como cost_usd. É o preço atual do modelo
multiplicado pelos segundos (na resolução solicitada), imagens ou outras unidades. A tarefa
é cobrada pelo que o provedor cobrou por ela, o que pode ser mais ou menos.
max_cost_usd é o que é reservado do seu saldo pela tarefa: o mesmo valor
enquanto ela roda e depois que termina, e o valor máximo que pode ser cobrado pela tarefa. Um modelo de vídeo cujo preço é por
second é cobrado por segundo do clipe; um com preço por job custa esse valor por
vídeo.
2. Acompanhe a cada 5–10 segundos:
curl -H "Authorization: Bearer $UBERLLM_API_KEY" \
https://api.uberllm.dev/v1/video/generations/<job id>
HTTP 202 enquanto a tarefa roda, HTTP 200 quando ela termina:
{
"id": "6f1c…",
"status": "succeeded",
"results": [{ "artifact_index": 0, "url": "https://api.uberllm.dev/v1/media/artifacts/6f1c…/0",
"content_type": "video/mp4", "duration_seconds": 5 }],
"usage": { "cost_usdc": "40000", "cost_usd": 0.04 }
}
O modelo informa a cobrança exata de uma tarefa um pouco depois de o arquivo ficar pronto, normalmente
em até 30 segundos. Até lá, uma tarefa succeeded mostra
"usage": { "cost_usd": null, "cost_pending": true }; consulte novamente alguns segundos
depois. Se a cobrança não chegar em até 30 minutos, a tarefa é cobrada pelo
preço listado (estimated_cost_usd) e usage.cost_note informa isso.
3. Baixe
curl -L -o video.mp4 -H "Authorization: Bearer $UBERLLM_API_KEY" \
https://api.uberllm.dev/v1/media/artifacts/<job id>/0
Peça para interromper uma tarefa que você não precisa mais: DELETE /v1/video/generations/<job id>. Pedimos ao provedor para interrompê-la; uma tarefa que ele já tinha feito é cobrada normalmente, e uma que ele interrompe termina canceled e não custa nada.
Modelos de vídeo Fal
Os modelos cujo ID começa com fal-ai/ rodam no Fal, abaixo do preço próprio do Fal: você paga o preço do Fal menos o desconto do vendedor Fal mais barato no marketplace agora (15% de desconto em 30 de outubro de 2026; 25% de desconto sempre que esse vendedor estiver vendendo). O preço é fixado pelo que você pede — segundos × a taxa para a resolução, e se o clipe tem som — então o custo do trabalho é conhecido quando ele começa e estimated_cost_usd é exatamente o que é cobrado. Um trabalho falho não custa nada. Parar um é o melhor esforço: se o Fal já fez o clipe, ele é cobrado.
curl https://api.uberllm.dev/v1/video/generations \
-H "Authorization: Bearer $UBERLLM_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model": "fal-ai/wan/v2.7/text-to-video", "prompt": "a woman twirls at a garden party at night",
"duration_seconds": 15, "resolution": "720p", "aspect_ratio": "9:16"}'
Preço próprio do Fal por segundo (o seu é este menos o desconto ao vivo; a página de cada modelo, GET /v1/models e POST /v1/quote mostram o preço exato agora):
| Modelo | Duração (s) | Preço do Fal por segundo, a partir de |
|---|---|---|
fal-ai/wan/v2.7/text-to-video, …/image-to-video |
2–15 | 720p $0.10 · 1080p $0.15 |
fal-ai/kling-video/v3/standard/text-to-video, …/image-to-video |
3–15 | $0.084 · com som $0.126 |
fal-ai/kling-video/v3/pro/text-to-video, …/image-to-video |
3–15 | $0.112 · com som $0.168 |
fal-ai/kling-video/o3/standard/text-to-video, …/image-to-video |
3–15 | $0.084 · com som $0.112 |
fal-ai/kling-video/v2.6/pro/text-to-video, …/image-to-video |
5, 10 | $0.07 · com som $0.14 |
fal-ai/veo3.1/fast, fal-ai/veo3.1/fast/image-to-video |
4, 6, 8 | 720p/1080p $0.10 · com som $0.15 · 4k $0.30 |
fal-ai/veo3.1, fal-ai/veo3.1/image-to-video |
4, 6, 8 | 720p/1080p $0.20 · com som $0.40 · 4k $0.40 |
fal-ai/veo3.1/lite |
4, 6, 8 | 720p $0.03 · com som $0.05 · 1080p $0.05 |
fal-ai/ltx-2.3/text-to-video/fast, …/image-to-video/fast |
6, 8, 10 | 1080p $0.06 · 1440p $0.12 · 2160p $0.24 |
fal-ai/ltx-2.3/text-to-video |
6, 8, 10 | 1080p $0.08 |
fal-ai/minimax/hailuo-02/standard/text-to-video, …/image-to-video |
6, 10 | 768P $0.045 · 512P (imagem) $0.017 |
fal-ai/vidu/q3/text-to-video/turbo, …/image-to-video/turbo |
1–16 | 360p/540p $0.035 · 720p/1080p $0.077 |
fal-ai/vidu/q3/text-to-video, …/image-to-video |
1–16 | 360p/540p $0.07 · 720p/1080p $0.154 |
Cada preço é próprio do Fal; o desconto é aplicado quando o trabalho começa. Se o vendedor mais barato recusar um trabalho, um mais caro o fará e o trabalho será cobrado pelo preço desse vendedor, nunca mais do que estimated_cost_usd na resposta.
Alguns modelos nos custam menos do que o desconto do vendedor indica. A cada 6 horas comparamos o que o marketplace nos cobrou por cada modelo nos últimos 7 dias com o preço do Fal para os mesmos trabalhos. Um modelo com pelo menos 20 trabalhos nesse período é então precificado com essa participação real mais 5%, quando isso for menor. A página do modelo, /v1/models e a fatura usam o mesmo preço. Se esse valor tiver mais de 3 dias, o desconto do vendedor se aplica novamente.
Os campos são nossos: duration_seconds, resolution, aspect_ratio, audio e image_url (ou input_images). A grafia própria do Fal (duration, generate_audio, start_image_url) também funciona. Kling, Veo e Kling 2.6 adicionam som a menos que você envie "audio": false.
Status do trabalho
status |
HTTP | Significado |
|---|---|---|
queued |
202 | Aceito, aguardando um modelo para iniciá-lo |
running |
202 | Sendo feito; progress vai de 0 para 1 quando o modelo o reporta |
succeeded |
200 | Concluído; baixe results[].url |
failed |
200 | Não feito. error.message diz o porquê; error.retryable diz se tentar novamente pode funcionar |
canceled |
200 | Parado antes de ser feito |
expired |
200 | Não concluído a tempo |
Estes seis são os únicos valores. failed, canceled e expired não custam nada. Um
DELETE é um pedido para parar, não uma promessa: um trabalho que o provedor termina de qualquer forma
termina succeeded e é cobrado.
Um momento de pico é retentado para você. Quando nenhum provedor está livre para fazer seu vídeo,
ou um está muito ocupado no momento, o UberLLM inicia o trabalho novamente por conta própria, até mais duas
vezes dentro de 10 minutos. Ele mantém o mesmo job id e o mesmo custo máximo. O
trabalho apenas leva um pouco mais de tempo. Você vê failed apenas quando todas as tentativas falharam, e um
trabalho falho não custa nada.
Aguardando. Envie callback_url e nós avisaremos quando o trabalho terminar (veja
Callbacks), ou consulte a poll URL a cada 5–10 segundos. A maioria dos vídeos leva
1–5 minutos; alguns trabalhos longos ou 4K levam mais tempo. Você não precisa continuar perguntando para
a conta ser liquidada: um trabalho que ninguém verifica ainda é concluído e cobrado (ou
reembolsado) pelo UberLLM, e aparece em
Painel → Atividade.
Callbacks
Adicione callback_url a um trabalho de vídeo ou música: um link https:// em um host público.
Quando o trabalho tiver terminado de vez, o UberLLM envia um POST para ele com o mesmo JSON
que GET no trabalho retorna.
{ "model": "kling/kling-o3", "prompt": "a red ball bouncing", "duration_seconds": 5,
"callback_url": "https://your-app.example/uberllm/jobs" }
- Quando. Uma vez, quando o trabalho tiver terminado e sua fatura for final:
succeededcomusage.cost_usddefinido (nuncacost_pending), oufailed,canceledouexpired(custo 0). Não é enviado novamente quando o custo chega, porque é enviado apenas depois. Comoutput_upload_urltambém, o callback chega depois que o upload é concluído, entãooutput_upload.statusjá estádeliveredoufailed. - Cabeçalhos.
Content-Type: application/json,UberLLM-Event: job.succeeded(oujob.failed,job.canceled,job.expired),UberLLM-Event-Id: <job id>:<status>eUberLLM-Signature: t=<unix seconds>,v1=<hex>. - Tentativas. Responda com qualquer 2xx dentro de 15 segundos. Qualquer outra resposta, ou nenhuma resposta, é tentada novamente após 30 segundos, 2 minutos, 10 minutos, 30 minutos e 2 horas, e então desistida. Você ainda pode consultar o trabalho a qualquer momento.
- Repetições. Uma mensagem pode chegar mais de uma vez (por exemplo, quando sua resposta é perdida). O ID do evento permanece o mesmo, então descarte um que você já tenha processado.
- Status. O trabalho mostra como foi:
"callback": { "status": "delivered", "attempts": 1, "http_status": 200, "delivered_at": 1792540990 }.statusépending,deliveredoufailed(comerrorem palavras simples). - O link deve ser
https://em um host público, sem nome de usuário ou senha nele. Ele nunca é exibido de volta e é excluído assim que o callback termina.webhook_urlé lido como o mesmo campo. Imagens e fala não o utilizam: eles retornam na própria resposta.
Verifique a assinatura
Toda mensagem é assinada com o segredo de webhook da sua conta. Encontre-o em Dashboard → API keys → Webhook signing secret; ele é criado na primeira vez que você o abre, e Replace secret o troca imediatamente.
v1 é o HMAC-SHA256 hexadecimal de <t>.<raw body>, com chave de todo o segredo
(whsec_…). Calcule-o sobre os bytes brutos que você recebeu (antes de analisar o JSON),
compare em tempo constante e recuse um t com mais de 5 minutos.
Node.js (Express):
import crypto from "node:crypto";
import express from "express";
const app = express();
const SECRET = process.env.UBERLLM_WEBHOOK_SECRET; // whsec_…
app.post("/uberllm/jobs", express.raw({ type: "application/json" }), (req, res) => {
const header = req.get("UberLLM-Signature") || "";
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=", 2)));
const expected = crypto.createHmac("sha256", SECRET)
.update(`${parts.t}.`).update(req.body).digest("hex");
const ok = parts.v1 && parts.v1.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(parts.v1), Buffer.from(expected)) &&
Math.abs(Date.now() / 1000 - Number(parts.t)) < 300;
if (!ok) return res.status(400).send("bad signature");
const job = JSON.parse(req.body);
// Drop a repeat: req.get("UberLLM-Event-Id") is the same each time.
console.log(job.id, job.status, job.usage?.cost_usd);
res.sendStatus(204);
});
Python (Flask):
import hashlib, hmac, os, time
from flask import Flask, request, abort
app = Flask(__name__)
SECRET = os.environ["UBERLLM_WEBHOOK_SECRET"].encode() # whsec_…
@app.post("/uberllm/jobs")
def uberllm_job():
raw = request.get_data() # the raw bytes, before any JSON parsing
parts = dict(p.split("=", 1) for p in request.headers.get("UberLLM-Signature", "").split(",") if "=" in p)
expected = hmac.new(SECRET, parts.get("t", "").encode() + b"." + raw, hashlib.sha256).hexdigest()
if not hmac.compare_digest(parts.get("v1", ""), expected) or abs(time.time() - int(parts.get("t", "0"))) > 300:
abort(400)
job = request.get_json()
# Drop a repeat: request.headers["UberLLM-Event-Id"] is the same each time.
print(job["id"], job["status"], job.get("usage", {}).get("cost_usd"))
return "", 204
Alertas para um webhook
Os e-mails de alerta também podem ir para um webhook: defina um link em
Dashboard → Credits → For developers: also send alerts to a webhook,
ou PUT /api/v1/billing/alerts {"alerts_webhook_url": "https://…"} (null o desativa).
As mensagens são assinadas, retentadas e identificadas da mesma forma que os callbacks. O
corpo é:
{ "id": "evt_…", "object": "event", "type": "balance.low", "created": 1792540990,
"data": { "balance_usd": 4.2, "threshold_usd": 5, "message": "Your balance is $4.20, below the $5.00 you set. Requests stop once it runs out.",
"add_credits_url": "https://uberllm.dev/dashboard/credits" } }
balance.low: o saldo caiu abaixo do valor que você definiu para o e-mail de saldo baixo. Enviado uma vez cada vez que cai abaixo; nada é enviado até que você defina um valor.hold.not_released: dinheiro reservado para um trabalho ainda estava retido mais de 15 minutos após o término do trabalho.data.jobsos lista (id,kind,model,ended_at,held_usd) edata.held_usdé o total. O dinheiro ainda é seu e nossa equipe foi informada.
Por quanto tempo os arquivos são mantidos
Baixe um resultado logo depois que ele for gerado:
- Arquivos de vídeo e música são mantidos por cerca de 3 horas após a conclusão da tarefa.
- Links de imagem para
/v1/media/artifacts/…são mantidos por cerca de 15 minutos; links de imagem para/v1/files/…por 24 horas. Peça por"response_format": "b64_json"para obter a imagem dentro da resposta e guardá-la você mesmo. - Imagens que você envia para
/v1/filessão mantidas por 24 horas.
Depois disso, o download responde 404 artifact_expired.
Envie resultados para seu próprio armazenamento
Adicione output_upload_url a uma tarefa de vídeo ou música: um link https:// que aceita um HTTP PUT, como um link de upload S3, R2 pré-assinado ou do Google Cloud Storage. Quando a tarefa é bem-sucedida, o UberLLM faz o upload do arquivo para lá com seu tipo de conteúdo. Para uma tarefa que gera vários arquivos, envie output_upload_urls, um link por arquivo em ordem (no máximo 4).
{ "model": "kling/kling-o3", "prompt": "a red ball bouncing", "duration_seconds": 5,
"output_upload_url": "https://my-bucket.s3.amazonaws.com/ball.mp4?X-Amz-Signature=…" }
O status da tarefa então tem output_upload:
"output_upload": { "status": "delivered", "attempts": 1, "delivered_at": 1792540990,
"files": [{ "artifact_index": 0, "http_status": 200, "bytes": 5242880 }] }
statusépendingaté o upload ser concluído, entãodeliveredoufailed(comerrorem palavras simples).- Um upload falho é tentado novamente após 30 segundos, 2, 10 e 30 minutos, então marcado
failed; o arquivo ainda pode ser baixado deresults[].urlenquanto é mantido. Torne o link válido por pelo menos uma hora. - O link deve ser
https://em um host público, sem nome de usuário ou senha. Ele nunca é exibido de volta e é excluído assim que o upload termina. - Imagens não aceitam isso: uma imagem retorna na própria resposta.
Música
Os mesmos três passos em /v1/music/generations. curl -OJ salva a faixa com o nome de arquivo que enviamos.
Fala
O texto entra, um arquivo de áudio retorna como resposta.
curl https://api.uberllm.dev/v1/audio/speech \
-H "Authorization: Bearer $UBERLLM_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model": "x-ai/tts-xai-v1", "input": "Hello from UberLLM."}' \
-o speech.mp3
Você também pode enviar voice, response_format (por exemplo mp3 ou wav) e speed.
Cada modelo tem suas próprias vozes (GET /v1/models/{id} as lista). Com
best-price:speech ou fastest:speech você não sabe antecipadamente qual modelo é usado,
então a voz é ajustada a ele: uma voz que o modelo possui é mantida, os nomes da OpenAI são mapeados para a
voz correspondente do modelo onde ele tiver uma (alloy → af_alloy), e qualquer outra voz, ou
nenhuma, recebe a primeira voz do modelo. Quando você nomeia um modelo, sua voz é enviada como está.
É cobrado por 1K caracteres iniciados de input. A resposta é o arquivo de áudio; seu
cabeçalho X-UberLLM-Cost-USD informa o custo, e X-Request-Id é o ID da requisição
conforme listado em Atividade. "validate": true, max_cost, metadata, models e
provider.max_price funcionam como nas imagens (veja O que você paga).
Converter uma gravação em texto
Envie a gravação como um formulário com file e model:
curl https://api.uberllm.dev/v1/audio/transcriptions \
-H "Authorization: Bearer $UBERLLM_API_KEY" \
-F model=venice/whisper-large-v3 \
-F [email protected]
{ "text": "Hello from UberLLM.", "duration": 4.2,
"usage": { "type": "duration", "seconds": 5, "cost_usd": 0.00055, "max_cost_usd": 0.00726 } }
usage.cost_usd é o custo (também no cabeçalho X-UberLLM-Cost-USD, o único
lugar para respostas text, srt e vtt). Nossos próprios campos também vão no formulário e nunca são
enviados ao modelo: -F max_cost=0.10, -F "metadata[project]=podcast",
-F validate=true.
Você também pode enviar language, prompt, temperature e response_format: json
(o padrão), ou text, srt ou vtt para receber texto simples de volta. É cobrado por minuto
de áudio. Arquivos podem ter até 40 MB. O formato (mp3, wav, ogg, flac, m4a/mp4,
webm, aac) é lido da própria gravação, não do nome do arquivo, então um upload
chamado tmp123.out funciona.
Com o OpenAI SDK
from openai import OpenAI
client = OpenAI(base_url="https://api.uberllm.dev/v1", api_key="<your UberLLM key>")
speech = client.audio.speech.create(model="x-ai/tts-xai-v1", voice="eve", input="Hello from UberLLM.")
speech.write_to_file("speech.mp3")
with open("speech.mp3", "rb") as f:
text = client.audio.transcriptions.create(model="venice/whisper-large-v3", file=f)
print(text.text)
O que você paga
- O preço do que foi feito: o que o provedor cobrou, sem taxa UberLLM. Uma resposta de imagem ou transcrição e um trabalho concluído o carregam como
usage.cost_usd(dólares americanos); toda resposta que gerou algo também o carrega no cabeçalho de respostaX-UberLLM-Cost-USD(para fala, o cabeçalho é o único lugar, já que a resposta é o arquivo de áudio). Ele aparece em Painel → Atividade. O restante da retenção é devolvido. - Nada por um trabalho que
failed, foicanceledouexpired, ou por uma solicitação de imagem, fala ou transcrição que falhe. - O vendedor mais barato que faz exatamente o que você pediu. Muitos modelos de imagem e vídeo têm mais de um vendedor; cada solicitação vai para o mais barato que pode fazê-lo com suas configurações, e para outro se aquele não puder aceitá-la. A lista de modelos e cada página de modelo mostram o preço no vendedor mais barato ativo agora.
- Veja seu saldo e o que está retido com
GET /v1/balance(abaixo).
Verifique o preço antes de gastar
Adicione "validate": true a qualquer solicitação de imagem, vídeo, música ou fala (para uma transcrição, envie validate=true como um campo de formulário com a gravação). A solicitação é verificada como se fosse real (modelo, opções, imagens de origem), nada é feito e nada é cobrado, e HTTP 200 diz o que custa ao preço listado (cost_usd, o preço mostrado na página do modelo; você paga o que o provedor cobrou, o que pode diferir) e o que é retido enquanto executa (max_cost_usd, o máximo que é cobrado):
{ "object": "quote", "model": "bytedance/seedance-2-0-image-to-video", "kind": "video",
"valid": true, "cost_usd": 0.2117, "max_cost_usd": 0.4235, "unit": "second", "units": 5, "duration_seconds": 5,
"note": "Nothing was charged. …" }
Se o vendedor do modelo precisar de uma configuração que você deixou de fora (um modelo de referência para vídeo precisa de aspect_ratio), o padrão com o qual é enviado é mostrado no esquema do modelo e na cotação como defaults_applied, por exemplo "defaults_applied": {"aspect_ratio": "16:9"}.
Uma solicitação que seria recusada é recusada da mesma forma, com o motivo.
Manter um preço por 15 minutos
POST /v1/quote aceita o mesmo corpo que a solicitação de imagem, vídeo, música ou fala e responde com o custo listado (cost_usd), o que é retido enquanto executa (max_cost_usd, o máximo que é cobrado) e um quote_id (para uma transcrição, envie model e duration_seconds; uma transcrição em si não leva quote_id, use max_cost para limitá-la):
curl https://api.uberllm.dev/v1/quote \
-H "Authorization: Bearer $UBERLLM_API_KEY" -H "Content-Type: application/json" \
-d '{"model": "kling/kling-o3", "prompt": "a red ball bouncing", "duration_seconds": 5}'
{ "object": "quote", "id": "6f1c…", "quote_id": "6f1c…", "model": "kling/kling-o3",
"kind": "video", "cost_usd": 0.225, "max_cost_usd": 0.495, "unit": "job", "units": 1,
"expires_at": 1792541700, "note": "Nothing was charged. …" }
Envie a mesma solicitação com "quote_id": "6f1c…" dentro de 15 minutos e ela é cobrada pelo que o provedor cobrou, no máximo max_cost_usd; se o que deve ser retido ao iniciar estiver acima de max_cost_usd, ela é cancelada sem custo. Nada é cobrado ou retido pela cotação em si.
- Uma cotação inicia uma solicitação. Usá-la novamente responde
409 quote_used; um trabalho que nunca atingiu um modelo devolve a cotação. - A solicitação deve pedir a mesma coisa: o mesmo modelo e as mesmas configurações (prompt, tamanho, duração do clipe, imagens de origem …). Qualquer outra coisa é recusada com
400 quote_mismatch.max_cost,metadataeoutput_upload_urlpodem diferir. - Após 15 minutos, ele responde
400 quote_expired. Modelos de chat são cobrados por token e não têm cotação: limite-os comprovider.max_price.
Modelos de fallback e um limite de preço
"models": ["model-b", "model-c"] nomeia modelos para tentar em ordem quando o primeiro não pode ser atendido agora (no_available_provider) ou está acima do seu provider.max_price. Qualquer outra recusa é respondida imediatamente. O model da resposta nomeia o modelo que a fez (uma resposta de fala é o próprio arquivo de áudio: Atividade mostra seu modelo). Uma solicitação com quote_id não leva models, e uma transcrição não leva nenhum dos dois.
"provider": {"max_price": 0.05} é o máximo que uma unidade (o unit de precificação do modelo: imagem, segundo, megapixel, minuto, 1K caracteres ou solicitação) pode custar, em dólares americanos. Um modelo acima disso é recusado com 400 max_price_exceeded, e a solicitação nunca é cobrada acima de max_price vezes suas unidades. Outras opções de provider são recusadas com 400 unsupported_routing_option, porque a escolha do vendedor para solicitações de mídia não pode ser direcionada por solicitação.
"model": "best-price:image" (ou image-edit, text-to-video, image-to-video, music, speech, transcription) escolhe o modelo ativo mais barato para o trabalho; fastest: o mais rápido medido. Veja Aliases de modelo.
Tags
"metadata": {"project": "spring-ads"} (até 16 valores de texto) é armazenado com o trabalho, retornado em seu status, mostrado em Atividade e na exportação CSV, e nunca enviado a um modelo. Em uma transcrição, envie-os como campos de formulário: -F "metadata[project]=spring-ads". Veja Tags.
Defina um teto
Envie "max_cost" (dólares americanos, por exemplo 0.50) com qualquer solicitação de imagem, vídeo, música ou fala (um campo de formulário em uma transcrição). Se puder custar mais do que isso, é recusado com 400 max_cost_too_low e nada é cobrado. Para um trabalho de vídeo ou música, o teto exato é definido quando o trabalho começa; se então exceder seu max_cost, o trabalho é cancelado sem custo. Com "validate": true, a cotação adiciona within_max_cost.
Tentar novamente sem pagar duas vezes
Envie um cabeçalho Idempotency-Key (qualquer string única de até 255 caracteres, como um UUID) ao iniciar um trabalho de vídeo ou música. Se sua conexão cair e você enviar a mesma solicitação novamente com a mesma chave dentro de 24 horas, você receberá o mesmo trabalho de volta em vez de um segundo. A mesma chave com uma solicitação diferente é recusada com 409 idempotency_key_reused. Solicitações de imagem, fala e transcrição não aceitam uma chave: uma que falhou não foi cobrada, então envie-a novamente.
Seu saldo
curl -H "Authorization: Bearer $UBERLLM_API_KEY" https://api.uberllm.dev/v1/balance
{ "balance_usd": 12.40, "held_usd": 0.35, "available_usd": 12.05 }
held_usd está reservado para solicitações ainda em andamento; ele volta (menos o custo real) quando elas terminam. available_usd é o que novas solicitações podem usar.
Opções de cada modelo
GET /v1/models?type=image (ou video, music, speech, transcription,
audio para ambos, chat, all; vários unidos por vírgulas; sem
type a lista é apenas de modelos de chat) lista os modelos que você pode chamar agora, cada um com seu type, o
endpoints para enviá-lo e seu pricing. GET /v1/models/<model id> fornece um
modelo, e para um modelo de mídia um objeto parameters: um JSON Schema do corpo da requisição
com os comprimentos de clipe, resoluções, proporções de tela e tamanhos que ele aceita, e
qual imagem de origem ele precisa.
curl -H "Authorization: Bearer $UBERLLM_API_KEY" \
https://api.uberllm.dev/v1/models/pixverse/pixverse-c1-reference-to-video
pricing tem usd (o máximo que um unit custa agora) e list_usd (o
preço próprio do criador do modelo); um modelo de chat tem input_usd, output_usd e seus
preços list_ por 1M tokens.
Erros
| HTTP | code | significado |
|---|---|---|
| 400 | wrong_endpoint |
ex.: um modelo de imagem enviado para /v1/video/generations, um modelo de chat enviado para uma rota de imagem, ou um modelo de fala enviado para /v1/audio/transcriptions; a mensagem indica o caminho correto. Nada é cobrado, e a Atividade o lista como recusado |
| 400 | invalid_request |
a requisição está errada (a mensagem diz o porquê, ex.: uma duração que o modelo não oferece ou uma imagem de origem ausente) |
| 400 | image_required |
uma edição, ou um modelo de imagem para vídeo ou de referência para vídeo, sem imagem de origem |
| 400 | max_cost_too_low |
pode custar mais do que seu max_cost; nada foi cobrado |
| 409 | idempotency_key_reused |
esse Idempotency-Key foi usado para uma requisição diferente |
| 400 | quote_mismatch / quote_expired |
a requisição não é a orçada, ou o orçamento tem mais de 15 minutos |
| 404 | quote_not_found |
nenhum orçamento assim na sua conta |
| 409 | quote_used |
esse orçamento já iniciou uma requisição |
| 400 | max_price_exceeded |
o modelo custa mais por unidade do que seu provider.max_price |
| 400 | unsupported_routing_option |
uma opção de provider diferente de max_price |
| 404 | file_not_found |
nenhum arquivo enviado assim na sua conta, ou ele expirou |
| 413 | file_too_large |
um envio com mais de 10 MB |
| 429 | too_many_files |
500 envios já estão guardados; exclua alguns ou espere que expirem |
| 503 | files_unavailable |
envios não estão disponíveis agora |
| 413 | request_too_large |
a requisição excede o limite de tamanho (40 MB); envie imagens como links https:// e divida gravações longas |
| 402 | insufficient_credits |
seu saldo não pode cobrir o custo máximo do trabalho; a mensagem diz quanto é necessário |
| 404 | model_not_found / not_found |
nenhum modelo assim, ou nenhum trabalho assim na sua conta |
| 404 | artifact_expired |
o arquivo não está mais guardado (veja acima) |
| 429 | rate_limited |
espere os segundos em Retry-After (alguns segundos se estiver ausente), então tente novamente; X-RateLimit-Remaining mostra quantas requisições restam neste minuto |
| 503 | no_available_provider |
nenhum vendedor pode fazer isso agora; UberLLM já tentou os outros. A mensagem nomeia os modelos do mesmo tipo que funcionam agora |