NuevoConecta cualquier agente a UberLLM: una clave, todos los modelos, a precio de lista o menos.
UberLLM

Imágenes, vídeo, música y voz

La clave que usas para texto también crea imágenes, vídeos, música y voz, y convierte grabaciones en texto, desde el mismo saldo. Elige un modelo en la tabla de modelos (o ?type=image, ?type=music, ?type=audio); la página de cada modelo muestra su precio, su tipo y una solicitud de ejemplo funcional.

Quieres Ruta La imagen de origen va en
Una nueva imagen a partir de un prompt POST /v1/images/generations (ninguno)
Una imagen editada POST /v1/images/edits image (o image_url, input_images, images)
Un vídeo a partir de un prompt POST /v1/video/generations (ninguno)
Un vídeo que empieza con una imagen POST /v1/video/generations image_url (primer fotograma); end_image_url para el último fotograma
Un vídeo a partir de imágenes de referencia POST /v1/video/generations input_images con "role": "reference"
Una respuesta de texto sobre una imagen POST /v1/chat/completions una parte image_url en messages[].content
Música POST /v1/music/generations (ninguno)
Voz a partir de texto POST /v1/audio/speech (ninguno)
Texto a partir de una grabación POST /v1/audio/transcriptions (ninguno; la grabación va en file)

Una imagen de origen es un enlace https://, una URI data: (data:image/png;base64,iVBORw0…) o base64 simple de un PNG, JPEG o WebP, hasta 8 por solicitud. Las rutas de imagen también aceptan una carga multipart (abajo). Los enlaces deben ser enlaces públicos https://; los enlaces http:// son rechazados. Un enlace que redirige (por ejemplo, https://picsum.photos/640/360) es seguido, hasta 5 veces y solo a otros enlaces públicos https://, y el modelo obtiene el enlace al que llega. Un enlace que no lleva a una imagen, o redirige a un lugar que no podemos usar, es rechazado con 400 invalid_image_url antes de que se retenga o cobre nada. Las imágenes enviadas en línea hacen la solicitud más grande: mantenla por debajo de 10 MB, o envía enlaces.

En un mensaje de chat (image_url en messages[].content), un enlace (https:// o http://) es abierto por UberLLM y enviado al modelo como una URL data:, porque la mayoría de los modelos de visión solo aceptan eso. Cada imagen puede ser de hasta 20 MB (40 MB por solicitud), y las redirecciones se siguen hasta 5 veces, solo a direcciones públicas. Los modelos Gemini obtienen el enlace tal cual. Un enlace que no lleva a una imagen es rechazado con 400 invalid_image_url, y no se cobra nada.

Crear una imagen

Las imágenes se devuelven en la respuesta.

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 es lo que costó la imagen (también en el encabezado X-UberLLM-Cost-USD). En la rara respuesta donde el precio aún no se conoce, es null. Pagará lo que el proveedor cobró, nunca más de max_cost_usd, lo que se retuvo para ello.

response_format es url por defecto, tanto para /v1/images/generations como para /v1/images/edits. Envíe "response_format": "b64_json" para obtener la imagen dentro de la respuesta en su lugar. Cuando el modelo devuelve la imagen en sí, UberLLM la guarda como un archivo de su cuenta durante 24 horas y responde con su enlace (https://api.uberllm.dev/v1/files/file_…/content), que también funciona como la imagen fuente para una edición posterior. Descargue una URL con la misma clave:

curl -L -o image.png -H "Authorization: Bearer $UBERLLM_API_KEY" \
  "https://api.uberllm.dev/v1/media/artifacts/<id>/0"

n es 1 a 10.

Editar una imagen

Usa un modelo que edite (su ID suele terminar en -edit) y envía la imagen de origen en 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"}'

La misma imagen puede enviarse como:

  • "image": "data:image/png;base64,iVBORw0…"
  • "image_url": "https://…"
  • "input_images": ["https://…", "https://…"]: la primera es la imagen a editar, el resto son referencias. Una entrada también puede ser {"url": "…", "role": "reference"}.
  • "images": [{"image_url": "https://…"}], el formato que envían los SDK de OpenAI

También funciona base64 simple, y "input_image" se lee como "image".

O sube el archivo, como con la edición de imágenes de 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]

Una edición sin imagen de origen es rechazada con 400 image_required. Las máscaras (inpainting) no son compatibles.

Sube una imagen una vez

Sube una imagen a /v1/files y úsala en tantas solicitudes como quieras durante 24 horas, en lugar de enviarla en cada solicitud:

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" }

Luego, coloca el ID, o el url, dondequiera que vaya una imagen de origen: "image": "file_3f9c…", "image": {"file_id": "file_3f9c…"}, "image_url": "file_3f9c…", una entrada de input_images, o el fotograma inicial de un video. Solo tu cuenta puede usar sus archivos.

  • PNG, JPEG, WebP, GIF, HEIC o AVIF, hasta 10 MB cada una, con un máximo de 500 guardadas por cuenta a la vez.
  • Se guardan durante 24 horas, luego se eliminan. GET /v1/files/{id} describe una, GET /v1/files/{id}/content la descarga, DELETE /v1/files/{id} la elimina ahora.

Crear un video

Un video tarda un minuto o más, así que es un trabajo: iniciarlo, verificar su estado, descargarlo.

1. Iniciarlo

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 es opcional: los modelos de respaldo se prueban en orden solo cuando el primero no tiene un proveedor gratuito disponible en este momento, para que un momento de alta demanda no haga fallar tu solicitud (ver Modelos de respaldo). El video de arriba tarda aproximadamente un minuto y cuesta alrededor de $0.33.

Para empezar desde una imagen, usa un modelo image-to-video y añade 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}'

Un modelo que combina dos imágenes también toma end_image_url (el último fotograma). Un modelo image-to-video o reference-to-video que no puede crear video solo a partir de texto es rechazado con 400 image_required cuando la solicitud no tiene imagen, antes de que se cobre nada. Un modelo reference-to-video toma las imágenes en 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}'

Otros campos, donde el modelo los ofrece: resolution (480p, 720p, 1080p…), aspect_ratio (16:9, 9:16, 1:1…) y audio (true / false; un modelo sin configuración de sonido no lo recibe y crea el clip como siempre lo hace). Un modelo reference-to-video necesita una forma, así que cuando omites aspect_ratio se envía como 16:9 (o la primera forma del modelo si no crea 16:9); el esquema del modelo muestra ese valor predeterminado. Las opciones de cada modelo se listan por GET /v1/models/<model id> (ver Opciones de cada modelo).

Cada modelo produce solo ciertas duraciones de clip, resoluciones y formas. Una solicitud de uno que no produce es rechazada con un mensaje que lista los que sí produce, antes de que se cobre nada.

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 es el modelo que tomó el trabajo (un respaldo de models cuando el primero no pudo). Los costos que terminan en _usdc son en millonésimas de dólar (327250 = $0.33). estimated_cost_usd es el precio de lista de lo que solicitaste: el mismo número validate y POST /v1/quote responden como cost_usd. Es el precio actual del modelo multiplicado por los segundos (a la resolución solicitada), imágenes u otras unidades. El trabajo se factura por lo que el proveedor cobró por él, lo cual puede ser más o menos. max_cost_usd es lo que se retiene de tu saldo para el trabajo: la misma cifra mientras se ejecuta y después de que termina, y lo máximo que se factura por el trabajo. Un modelo de video cuyo precio es por second se factura por segundo del clip; uno con precio por job cuesta eso por video.

2. Verificar su estado cada 5–10 segundos:

curl -H "Authorization: Bearer $UBERLLM_API_KEY" \
  https://api.uberllm.dev/v1/video/generations/<job id>

HTTP 202 mientras el trabajo se ejecuta, HTTP 200 una vez que 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 }
}

El modelo informa el cargo exacto de un trabajo un poco después de que el archivo está listo, normalmente en 30 segundos. Hasta entonces, un trabajo succeeded muestra "usage": { "cost_usd": null, "cost_pending": true }; pregunta de nuevo unos segundos después. Si el cargo no ha llegado en 30 minutos, el trabajo se factura a su precio de lista (estimated_cost_usd) y usage.cost_note lo indica.

3. Descargarlo

curl -L -o video.mp4 -H "Authorization: Bearer $UBERLLM_API_KEY" \
  https://api.uberllm.dev/v1/media/artifacts/<job id>/0

Solicita detener un trabajo que ya no necesitas: DELETE /v1/video/generations/<job id>. Le pedimos al proveedor que lo detenga; un trabajo que ya había realizado se factura como de costumbre, y uno que detiene termina canceled y no cuesta nada.

Modelos de vídeo de Fal

Los modelos cuyo ID empieza por fal-ai/ funcionan en Fal, por debajo del precio propio de Fal: pagas el precio de Fal menos el descuento del vendedor de Fal más barato en el marketplace ahora mismo (15% de descuento en 30 de octubre de 2026; 25% de descuento siempre que ese vendedor esté vendiendo). El precio se fija por lo que pides — segundos × la tarifa para la resolución, y si el clip tiene sonido — así que el coste del trabajo se conoce cuando empieza y estimated_cost_usd es exactamente lo que se factura. Un trabajo fallido no cuesta nada. Detener uno es un esfuerzo de buena fe: si Fal ya ha creado el clip, se factura.

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"}'

Precio propio de Fal por segundo (el tuyo es este menos el descuento en vivo; la página de cada modelo, GET /v1/models y POST /v1/quote muestran el precio exacto ahora):

Modelo Duración (s) Precio de Fal por segundo, desde
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 · con sonido $0.126
fal-ai/kling-video/v3/pro/text-to-video, …/image-to-video 3–15 $0.112 · con sonido $0.168
fal-ai/kling-video/o3/standard/text-to-video, …/image-to-video 3–15 $0.084 · con sonido $0.112
fal-ai/kling-video/v2.6/pro/text-to-video, …/image-to-video 5, 10 $0.07 · con sonido $0.14
fal-ai/veo3.1/fast, fal-ai/veo3.1/fast/image-to-video 4, 6, 8 720p/1080p $0.10 · con sonido $0.15 · 4k $0.30
fal-ai/veo3.1, fal-ai/veo3.1/image-to-video 4, 6, 8 720p/1080p $0.20 · con sonido $0.40 · 4k $0.40
fal-ai/veo3.1/lite 4, 6, 8 720p $0.03 · con sonido $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 (imagen) $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 precio es propio de Fal; el descuento se aplica cuando el trabajo empieza. Si el vendedor más barato rechaza un trabajo, uno más caro lo realiza y el trabajo se factura al precio de ese vendedor, nunca más de estimated_cost_usd en la respuesta.

Algunos modelos nos cuestan menos de lo que indica el descuento del vendedor. Cada 6 horas comparamos lo que el marketplace nos cobró por cada modelo durante los últimos 7 días con el precio de Fal para los mismos trabajos. Un modelo con al menos 20 trabajos en ese tiempo se valora entonces a esa cuota real más 5%, cuando esta es inferior. La página del modelo, /v1/models y la factura utilizan el mismo precio. Si esa cifra tiene más de 3 días, el descuento del vendedor se aplica de nuevo.

Los campos son nuestros: duration_seconds, resolution, aspect_ratio, audio y image_url (o input_images). La propia ortografía de Fal (duration, generate_audio, start_image_url) también funciona. Kling, Veo y Kling 2.6 añaden sonido a menos que envíes "audio": false.

Estado del trabajo

status HTTP Significado
queued 202 Aceptado, esperando que un modelo lo inicie
running 202 En proceso; progress pasa de 0 a 1 cuando el modelo lo reporta
succeeded 200 Hecho; descarga results[].url
failed 200 No realizado. error.message indica el motivo; error.retryable indica si se puede intentar de nuevo
canceled 200 Detenido antes de ser realizado
expired 200 No finalizado a tiempo

Estos seis son los únicos valores. failed, canceled y expired no tienen costo. Un DELETE es una solicitud para detener, no una promesa: un trabajo que el proveedor finaliza de todos modos termina succeeded y se factura.

Un momento de alta demanda se reintenta por ti. Cuando ningún proveedor está libre para crear tu video, o uno está demasiado ocupado en este momento, UberLLM inicia el trabajo de nuevo por sí mismo, hasta dos veces más en 10 minutos. Mantiene el mismo job id y el mismo costo máximo posible. El trabajo solo tarda un poco más. Verás failed solo cuando todos los intentos hayan fallado, y un trabajo fallido no tiene costo.

Esperando. Envía callback_url y te diremos cuándo termina el trabajo (ver Callbacks), o consulta la poll URL cada 5–10 segundos. La mayoría de los videos tardan 1–5 minutos; algunos trabajos largos o 4K tardan más. No tienes que seguir preguntando por la factura: un trabajo que nadie revisa es igualmente finalizado y facturado (o reembolsado) por UberLLM, y aparece en Dashboard → Activity.

Callbacks

Añade callback_url a un trabajo de vídeo o música: un enlace https:// en un host público. Cuando el trabajo haya terminado definitivamente, UberLLM le envía un POST con el mismo JSON que GET devuelve sobre el trabajo.

{ "model": "kling/kling-o3", "prompt": "a red ball bouncing", "duration_seconds": 5,
  "callback_url": "https://your-app.example/uberllm/jobs" }
  • Cuándo. Una vez, cuando el trabajo haya terminado y su factura sea final: succeeded con usage.cost_usd establecido (nunca cost_pending), o failed, canceled o expired (coste 0). No se envía de nuevo cuando llega el coste, porque solo se envía después. Con output_upload_url también, el callback llega después de que la subida esté hecha, así que output_upload.status ya está delivered o failed.
  • Cabeceras. Content-Type: application/json, UberLLM-Event: job.succeeded (o job.failed, job.canceled, job.expired), UberLLM-Event-Id: <job id>:<status> y UberLLM-Signature: t=<unix seconds>,v1=<hex>.
  • Reintentos. Responde con cualquier 2xx en 15 segundos. Cualquier otra respuesta, o ninguna respuesta, se intenta de nuevo después de 30 segundos, 2 minutos, 10 minutos, 30 minutos y 2 horas, y luego se abandona. Aun así, puedes consultar el estado del trabajo en cualquier momento.
  • Repeticiones. Un mensaje puede llegar más de una vez (por ejemplo, cuando tu respuesta se pierde). El ID del evento permanece igual, así que descarta los que ya hayas procesado.
  • Estado. El trabajo muestra cómo fue: "callback": { "status": "delivered", "attempts": 1, "http_status": 200, "delivered_at": 1792540990 }. status es pending, delivered o failed (con error en palabras sencillas).
  • El enlace debe ser https:// en un host público, sin nombre de usuario ni contraseña. Nunca se muestra de vuelta y se elimina una vez que el callback finaliza. webhook_url se lee como el mismo campo. Las imágenes y el habla no lo toman: vuelven en la propia respuesta.

Comprueba la firma

Cada mensaje está firmado con el secreto de webhook de tu cuenta. Encuéntralo en Dashboard → API keys → Webhook signing secret; se crea la primera vez que lo abres, y Reemplazar secreto lo cambia al instante.

v1 es el HMAC-SHA256 hexadecimal de <t>.<raw body>, cifrado con el secreto completo (whsec_…). Calcúlalo sobre los bytes en bruto que recibiste (antes de analizar el JSON), compara en tiempo constante y rechaza un t de más de 5 minutos de antigüedad.

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 a un webhook

Los correos de alerta también pueden ir a un webhook: establece un enlace en Dashboard → Credits → For developers: also send alerts to a webhook, o PUT /api/v1/billing/alerts {"alerts_webhook_url": "https://…"} (null lo desactiva). Los mensajes se firman, reintentan e identifican de la misma manera que los callbacks. El cuerpo es:

{ "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: el saldo cayó por debajo de la cantidad que estableciste para el correo de saldo bajo. Se envía una vez cada vez que cae por debajo; no se envía nada hasta que establezcas una cantidad.
  • hold.not_released: el dinero reservado para un trabajo seguía retenido más de 15 minutos después de que el trabajo terminara. data.jobs los enumera (id, kind, model, ended_at, held_usd) y data.held_usd es el total. El dinero sigue siendo tuyo y nuestro equipo ha sido informado.

Cuánto tiempo se guardan los archivos

Descarga un resultado poco después de que se genere:

  • Los archivos de vídeo y música se guardan durante aproximadamente 3 horas después de que finalice el trabajo.
  • Los enlaces de imagen a /v1/media/artifacts/… se guardan durante aproximadamente 15 minutos; los enlaces de imagen a /v1/files/… durante 24 horas. Pide "response_format": "b64_json" para obtener la imagen dentro de la respuesta y guardarla tú mismo.
  • Las imágenes que subes a /v1/files se guardan durante 24 horas.

Después de eso, la descarga responde 404 artifact_expired.

Envía los resultados a tu propio almacenamiento

Añade output_upload_url a un trabajo de vídeo o música: un enlace https:// que acepte un HTTP PUT, como un enlace de subida presignado de S3, R2 o de Google Cloud Storage. Cuando el trabajo finaliza con éxito, UberLLM sube el archivo allí con su tipo de contenido. Para un trabajo que genera varios archivos, envía output_upload_urls, un enlace por archivo en orden (como 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=…" }

El estado del trabajo entonces tiene output_upload:

"output_upload": { "status": "delivered", "attempts": 1, "delivered_at": 1792540990,
  "files": [{ "artifact_index": 0, "http_status": 200, "bytes": 5242880 }] }
  • status está pending hasta que la subida se completa, luego delivered o failed (con error en palabras sencillas).
  • Un intento de subida fallido se vuelve a intentar después de 30 segundos, 2, 10 y 30 minutos, luego se marca failed; el archivo aún se puede descargar desde results[].url mientras se guarda. Haz que el enlace sea válido durante al menos una hora.
  • El enlace debe ser https:// en un host público, sin nombre de usuario ni contraseña. Nunca se muestra de vuelta, y se elimina una vez que finaliza la subida.
  • Las imágenes no lo aceptan: una imagen vuelve en la propia respuesta.

Música

Los mismos tres pasos en /v1/music/generations. curl -OJ guarda la pista con el nombre de archivo que enviamos.

Voz

Se introduce texto y se obtiene un archivo de audio como respuesta.

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

También puedes enviar voice, response_format (por ejemplo mp3 o wav) y speed. Cada modelo tiene sus propias voces (GET /v1/models/{id} las enumera). Con best-price:speech o fastest:speech no sabes de antemano qué modelo se utiliza, por lo que la voz se adapta a él: se mantiene una voz que el modelo tenga, los nombres de OpenAI se asignan a la voz coincidente del modelo donde la tenga (alloy → af_alloy), y cualquier otra voz, o ninguna, obtiene la primera voz del modelo. Cuando nombras un modelo, tu voz se envía tal cual. Se factura por cada 1K caracteres iniciados de input. La respuesta es el archivo de audio; su encabezado X-UberLLM-Cost-USD indica el coste, y X-Request-Id es el ID de la solicitud tal como aparece en Actividad. "validate": true, max_cost, metadata, models y provider.max_price funcionan como en las imágenes (consulta Lo que pagas).

Convertir una grabación en texto

Envía la grabación como un formulario con file y 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 es lo que cuesta (también en el encabezado X-UberLLM-Cost-USD, el único lugar para las respuestas text, srt y vtt). Nuestros propios campos también van en el formulario y nunca se envían al modelo: -F max_cost=0.10, -F "metadata[project]=podcast", -F validate=true.

También puedes enviar language, prompt, temperature y response_format: json (el predeterminado), o text, srt o vtt para obtener texto plano de vuelta. Se factura por minuto de audio. Los archivos pueden ser de hasta 40 MB. El formato (mp3, wav, ogg, flac, m4a/mp4, webm, aac) se lee de la propia grabación, no de su nombre de archivo, por lo que una subida llamada tmp123.out funciona.

Con el SDK de OpenAI

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)

Lo que pagas

  • El precio de lo que se hizo: lo que cobró el proveedor, sin comisión de UberLLM. Una respuesta de imagen o transcripción y un trabajo terminado lo llevan como usage.cost_usd (dólares estadounidenses); cada respuesta que generó algo también lo lleva en el encabezado de respuesta X-UberLLM-Cost-USD (para voz, el encabezado es el único lugar, ya que la respuesta es el archivo de audio). Se muestra en Dashboard → Activity. El resto de la retención se devuelve.
  • Nada por un trabajo que failed, fue canceled o expired, o por una solicitud de imagen, voz o transcripción que falle.
  • El vendedor más barato que hace exactamente lo que pediste. Muchos modelos de imagen y video tienen más de un vendedor; cada solicitud va al más barato que puede hacerlo con tu configuración, y a otro si ese no puede aceptarla. La lista de modelos y cada página de modelo muestran el precio del vendedor más barato disponible ahora.
  • Consulta tu saldo y lo que está retenido con GET /v1/balance (abajo).

Verifica el precio antes de gastar

Añade "validate": true a cualquier solicitud de imagen, video, música o voz (para una transcripción, envía validate=true como un campo de formulario con la grabación). La solicitud se verifica como si fuera real (modelo, opciones, imágenes de origen), no se genera nada y no se cobra nada, y HTTP 200 indica lo que cuesta al precio listado (cost_usd, el precio que se muestra en la página del modelo; pagas lo que cobró el proveedor, que puede diferir) y lo que se retiene mientras se ejecuta (max_cost_usd, el máximo que se factura):

{ "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. …" }

Si el vendedor del modelo necesita una configuración que omitiste (un modelo de referencia a video necesita aspect_ratio), el valor predeterminado con el que se envía se muestra en el esquema del modelo y en la cotización como defaults_applied, p. ej. "defaults_applied": {"aspect_ratio": "16:9"}.

Una solicitud que sería rechazada se rechaza de la misma manera, con el motivo.

Retener un precio por 15 minutos

POST /v1/quote toma el mismo cuerpo que la solicitud de imagen, video, música o voz y responde con el costo listado (cost_usd), lo que se retiene mientras se ejecuta (max_cost_usd, el máximo que se factura) y un quote_id (para una transcripción, envía model y duration_seconds; una transcripción en sí misma no toma quote_id, usa max_cost para limitarla):

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. …" }

Envía la misma solicitud con "quote_id": "6f1c…" dentro de 15 minutos y se factura lo que cobró el proveedor, como máximo max_cost_usd; si lo que debe retenerse al iniciar supera max_cost_usd, se cancela sin cargo. No se cobra ni se retiene nada por la cotización en sí.

  • Una cotización inicia una solicitud. Usarla de nuevo responde 409 quote_used; un trabajo que nunca llegó a un modelo devuelve la cotización.
  • La solicitud debe pedir lo mismo: el mismo modelo y la misma configuración (prompt, tamaño, duración del clip, imágenes de origen…). Cualquier otra cosa se rechaza con 400 quote_mismatch. max_cost, metadata y output_upload_url pueden diferir.
  • Después de 15 minutos responde 400 quote_expired. Los modelos de chat se facturan por token y no tienen cotización: limítalos con provider.max_price.

Modelos de respaldo y un tope de precio

"models": ["model-b", "model-c"] nombra modelos para intentar en orden cuando el primero no puede ser atendido en este momento (no_available_provider) o está por encima de tu provider.max_price. Cualquier otro rechazo se responde de inmediato. El model de la respuesta nombra el modelo que lo hizo (una respuesta de voz es el archivo de audio en sí: Activity muestra su modelo). Una solicitud con quote_id no toma models, y una transcripción tampoco.

"provider": {"max_price": 0.05} es lo máximo que una unidad (la unit de precios del modelo: imagen, segundo, megapixel, minuto, 1K caracteres o solicitud) puede costar, en dólares estadounidenses. Un modelo por encima de este se rechaza con 400 max_price_exceeded, y la solicitud nunca se factura por encima de max_price veces sus unidades. Otras opciones provider se rechazan con 400 unsupported_routing_option, porque la elección del vendedor para solicitudes de medios no puede dirigirse por solicitud.

"model": "best-price:image" (o image-edit, text-to-video, image-to-video, music, speech, transcription) elige el modelo en vivo más barato para el trabajo; fastest: el más rápido medido. Ver Alias de modelos.

Etiquetas

"metadata": {"project": "spring-ads"} (hasta 16 valores de texto) se almacena con el trabajo, se devuelve en su estado, se muestra en Activity y en la exportación CSV, y nunca se envía a un modelo. En una transcripción, envíalos como campos de formulario: -F "metadata[project]=spring-ads". Ver Etiquetas.

Establecer un límite

Envía "max_cost" (dólares estadounidenses, p. ej. 0.50) con cualquier solicitud de imagen, video, música o voz (un campo de formulario en una transcripción). Si puede costar más que eso, se rechaza con 400 max_cost_too_low y no se cobra nada. Para un trabajo de video o música, el límite exacto se establece cuando comienza el trabajo; si luego resulta por encima de tu max_cost, el trabajo se cancela sin cargo. Con "validate": true, la cotización añade within_max_cost.

Reintentar sin pagar dos veces

Envía un encabezado Idempotency-Key (cualquier cadena única de hasta 255 caracteres, como un UUID) cuando inicies un trabajo de video o música. Si tu conexión se cae y envías la misma solicitud de nuevo con la misma clave dentro de 24 horas, obtienes el mismo trabajo de vuelta en lugar de uno segundo. La misma clave con una solicitud diferente se rechaza con 409 idempotency_key_reused. Las solicitudes de imagen, voz y transcripción no toman una clave: una que falló no fue cobrada, así que envíala de nuevo.

Tu 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 solicitudes aún en curso; vuelve (menos el costo real) cuando finalizan. available_usd es lo que pueden usar las nuevas solicitudes.

Opciones de cada modelo

GET /v1/models?type=image (o video, music, speech, transcription, audio para ambos, chat, all; varios unidos con comas; sin type la lista es solo de modelos de chat) enumera los modelos que puedes llamar ahora mismo, cada uno con su type, el endpoints al que enviarlo y su pricing. GET /v1/models/<model id> proporciona un modelo, y para un modelo de medios un objeto parameters: un JSON Schema del cuerpo de la solicitud con las duraciones de los clips, resoluciones, relaciones de aspecto y tamaños que acepta, y qué imagen de origen necesita.

curl -H "Authorization: Bearer $UBERLLM_API_KEY" \
  https://api.uberllm.dev/v1/models/pixverse/pixverse-c1-reference-to-video

pricing tiene usd (lo máximo que cuesta un unit ahora) y list_usd (el precio propio del creador del modelo); un modelo de chat tiene input_usd, output_usd y sus precios list_ por 1M tokens.

Errores

HTTP código significado
400 wrong_endpoint p. ej. un modelo de imagen enviado a /v1/video/generations, un modelo de chat enviado a una ruta de imagen, o un modelo de voz enviado a /v1/audio/transcriptions; el mensaje indica la ruta correcta. No se cobra nada, y Actividad lo lista como rechazado
400 invalid_request la solicitud es incorrecta (el mensaje indica el motivo, p. ej. una duración que el modelo no ofrece o una imagen de origen faltante)
400 image_required una edición, o un modelo de imagen a video o de referencia a video, sin imagen de origen
400 max_cost_too_low puede costar más que tu max_cost; no se cobró nada
409 idempotency_key_reused ese Idempotency-Key se usó para una solicitud diferente
400 quote_mismatch / quote_expired la solicitud no es la cotizada, o la cotización tiene más de 15 minutos
404 quote_not_found no existe tal cotización en tu cuenta
409 quote_used esa cotización ya inició una solicitud
400 max_price_exceeded el modelo cuesta más por unidad que tu provider.max_price
400 unsupported_routing_option una opción de provider diferente a max_price
404 file_not_found no existe tal archivo subido en tu cuenta, o ha expirado
413 file_too_large una subida de más de 10 MB
429 too_many_files ya se mantienen 500 subidas; elimina algunas o espera a que expiren
503 files_unavailable las subidas no están disponibles en este momento
413 request_too_large la solicitud supera el límite de tamaño (40 MB); envía las imágenes como enlaces https:// y divide las grabaciones largas
402 insufficient_credits tu saldo no puede cubrir el costo máximo del trabajo; el mensaje indica cuánto se necesita
404 model_not_found / not_found no existe tal modelo, o no existe tal trabajo en tu cuenta
404 artifact_expired el archivo ya no se mantiene (ver arriba)
429 rate_limited espera los segundos en Retry-After (unos pocos segundos si está ausente), luego reintenta; X-RateLimit-Remaining muestra cuántas solicitudes quedan este minuto
503 no_available_provider ningún vendedor puede hacerlo en este momento; UberLLM ya probó los demás. El mensaje nombra los modelos del mismo tipo que funcionan ahora