NouveauBranchez n’importe quel agent sur UberLLM : une clé, tous les modèles, au prix catalogue ou moins.
UberLLM

Images, vidéo, musique et parole

La clé que vous utilisez pour le texte permet aussi de créer des images, des vidéos, de la musique et de la parole, et de transformer des enregistrements en texte, à partir du même solde. Choisissez un modèle dans le tableau des modèles (ou ?type=image, ?type=music, ?type=audio) ; la page de chaque modèle indique son prix, son type et une requête fonctionnelle.

Vous voulez Route L'image source va dans
Une nouvelle image à partir d'un prompt POST /v1/images/generations (aucune)
Une image modifiée POST /v1/images/edits image (ou image_url, input_images, images)
Une vidéo à partir d'un prompt POST /v1/video/generations (aucune)
Une vidéo qui commence par une image POST /v1/video/generations image_url (première image) ; end_image_url pour la dernière image
Une vidéo à partir d'images de référence POST /v1/video/generations input_images avec "role": "reference"
Une réponse textuelle à propos d'une image POST /v1/chat/completions une partie image_url dans messages[].content
Musique POST /v1/music/generations (aucune)
Parole à partir de texte POST /v1/audio/speech (aucune)
Texte à partir d'un enregistrement POST /v1/audio/transcriptions (aucune ; l'enregistrement va dans file)

Une image source est un lien https://, un URI data: (data:image/png;base64,iVBORw0…) ou une base64 simple d'un PNG, JPEG ou WebP, jusqu'à 8 par requête. Les routes d'image acceptent également un téléversement multipart (ci-dessous). Les liens doivent être des liens https:// publics ; les liens http:// sont refusés. Un lien qui redirige (par exemple https://picsum.photos/640/360) est suivi, jusqu'à 5 fois et uniquement vers d'autres liens https:// publics, et le modèle reçoit le lien final. Un lien qui ne mène pas à une image, ou qui redirige vers un endroit que nous ne pouvons pas utiliser, est refusé avec 400 invalid_image_url avant que quoi que ce soit ne soit retenu ou facturé. Les images envoyées en ligne augmentent la taille de la requête : gardez-la en dessous de 10 Mo, ou envoyez des liens.

Dans un message de chat (image_url dans messages[].content), un lien (https:// ou http://) est ouvert par UberLLM et envoyé au modèle comme une URL data:, car la plupart des modèles de vision n'acceptent que cela. Chaque image peut peser jusqu'à 20 Mo (40 Mo par requête), et les redirections sont suivies jusqu'à 5 fois, uniquement vers des adresses publiques. Les modèles Gemini reçoivent le lien tel quel. Un lien qui ne mène pas à une image est refusé avec 400 invalid_image_url, et rien n'est facturé.

Faire une image

Les images reviennent dans la réponse.

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 est ce que l'image a coûté (également dans l'en-tête X-UberLLM-Cost-USD). Dans la rare réponse où le prix n'est pas encore connu, c'est null. Vous payez ce que le fournisseur a facturé, jamais plus que max_cost_usd, ce qui a été retenu pour cela.

response_format est url par défaut, pour /v1/images/generations et /v1/images/edits. Envoyez "response_format": "b64_json" pour obtenir l'image à l'intérieur de la réponse à la place. Lorsque le modèle renvoie l'image elle-même, UberLLM la conserve comme un fichier de votre compte pendant 24 heures et répond avec son lien (https://api.uberllm.dev/v1/files/file_…/content), qui fonctionne également comme image source pour une édition ultérieure. Téléchargez une URL avec la même clé :

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

n est 1 à 10.

Modifier une image

Utilisez un modèle qui modifie (son identifiant se termine généralement par -edit) et envoyez l'image source dans 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 même image peut être fournie comme suit :

  • "image": "data:image/png;base64,iVBORw0…"
  • "image_url": "https://…"
  • "input_images": ["https://…", "https://…"] : la première est l'image à modifier, les autres sont des références. Une entrée peut aussi être {"url": "…", "role": "reference"}.
  • "images": [{"image_url": "https://…"}], le format envoyé par les SDK OpenAI

Le base64 simple fonctionne aussi, et "input_image" est lu comme "image".

Ou téléchargez le fichier, comme avec l'édition d'image d'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]

Une modification sans image source est refusée avec 400 image_required. Les masques (inpainting) ne sont pas pris en charge.

Téléchargez une image une seule fois

Téléchargez une image vers /v1/files et utilisez-la dans autant de requêtes que vous le souhaitez pendant 24 heures, au lieu de l'envoyer à chaque requête :

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

Ensuite, placez l'ID, ou le url, partout où une image source est attendue : "image": "file_3f9c…", "image": {"file_id": "file_3f9c…"}, "image_url": "file_3f9c…", une entrée de input_images, ou l'image de début d'une vidéo. Seul votre compte peut utiliser ses fichiers.

  • PNG, JPEG, WebP, GIF, HEIC ou AVIF, jusqu'à 10 Mo chacun, au maximum 500 conservés par compte à la fois.
  • Conservés pendant 24 heures, puis supprimés. GET /v1/files/{id} en décrit un, GET /v1/files/{id}/content le télécharge, DELETE /v1/files/{id} le supprime maintenant.

Créer une vidéo

Une vidéo prend une minute ou plus, c'est donc un travail : la démarrer, la vérifier, la télécharger.

1. La démarrer

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 est facultatif : les modèles de secours sont essayés dans l'ordre uniquement lorsque le premier n'a pas de fournisseur disponible actuellement, afin qu'un moment d'affluence ne fasse pas échouer votre requête (voir Modèles de secours). La vidéo ci-dessus prend environ une minute et coûte environ $0.33.

Pour partir d'une image, utilisez un modèle image-vers-vidéo et ajoutez 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 modèle qui mélange deux images prend également end_image_url (la dernière image). Un modèle image-vers-vidéo ou référence-vers-vidéo qui ne peut pas créer de vidéo à partir de texte seul est refusé avec 400 image_required lorsque la requête n'a pas d'image, avant que quoi que ce soit ne soit facturé. Un modèle référence-vers-vidéo prend les images dans 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}'

Autres champs, lorsque le modèle les propose : resolution (480p, 720p, 1080p…), aspect_ratio (16:9, 9:16, 1:1…) et audio (true / false ; un modèle sans réglage de son ne le reçoit pas et crée le clip comme il le fait toujours). Un modèle référence-vers-vidéo a besoin d'une forme, donc lorsque vous omettez aspect_ratio, il est envoyé comme 16:9 (ou la première forme du modèle s'il ne fait pas 16:9) ; le schéma du modèle montre cette valeur par défaut. Les choix de chaque modèle sont listés par GET /v1/models/<model id> (voir Options de chaque modèle).

Chaque modèle ne produit que certaines durées de clip, résolutions et formes. Une requête pour une qu'il ne produit pas est refusée avec un message listant celles qu'il produit, avant que quoi que ce soit ne soit facturé.

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 est le modèle qui a pris le travail (un modèle de secours de models lorsque le premier n'a pas pu). Les coûts se terminant par _usdc sont en millionièmes de dollar (327250 = $0.33). estimated_cost_usd est le prix affiché de ce que vous avez demandé : le même nombre validate et POST /v1/quote répondent comme cost_usd. C'est le prix actuel du modèle multiplié par les secondes (à la résolution demandée), les images ou d'autres unités. Le travail est facturé ce que le fournisseur a facturé pour cela, ce qui peut être plus ou moins. max_cost_usd est ce qui est retenu de votre solde pour le travail : le même chiffre pendant qu'il s'exécute et après qu'il se termine, et le maximum que le travail est facturé. Un modèle vidéo dont le prix est par second est facturé par seconde du clip ; un modèle dont le prix est par job coûte cela par vidéo.

2. La vérifier toutes les 5–10 secondes :

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

HTTP 202 pendant que le travail s'exécute, HTTP 200 une fois qu'il se termine :

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

Le modèle signale le coût exact d'un travail un peu après que le fichier soit prêt, généralement dans les 30 secondes. Jusque-là, un travail succeeded affiche "usage": { "cost_usd": null, "cost_pending": true } ; demandez à nouveau quelques secondes plus tard. Si le coût n'est pas arrivé dans les 30 minutes, le travail est facturé à son prix affiché (estimated_cost_usd) et usage.cost_note l'indique.

3. La télécharger

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

Demandez d'arrêter un travail dont vous n'avez plus besoin : DELETE /v1/video/generations/<job id>. Nous demandons au fournisseur de l'arrêter ; un travail qu'il avait déjà effectué est facturé comme d'habitude, et un qu'il arrête se termine canceled et ne coûte rien.

Modèles vidéo Fal

Les modèles dont l'ID commence par fal-ai/ fonctionnent sur Fal, en dessous du prix de Fal : vous payez le prix de Fal moins la réduction du vendeur Fal le moins cher sur la marketplace en ce moment (15% de réduction sur 30 octobre 2026 ; 25% de réduction chaque fois que ce vendeur vend). Le prix est fixé par ce que vous demandez — secondes × le taux pour la résolution, et si le clip a du son — donc le coût du travail est connu au moment où il commence et estimated_cost_usd est exactement ce qui est facturé. Un travail échoué ne coûte rien. L'arrêter est un effort optimal : si Fal a déjà créé le clip, il est facturé.

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

Prix de Fal par seconde (le vôtre est celui-ci moins la réduction en direct ; la page de chaque modèle, GET /v1/models et POST /v1/quote, affiche le prix exact maintenant) :

Modèle Durées (s) Prix de Fal par seconde, à 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 · avec son $0.126
fal-ai/kling-video/v3/pro/text-to-video, …/image-to-video 3–15 $0.112 · avec son $0.168
fal-ai/kling-video/o3/standard/text-to-video, …/image-to-video 3–15 $0.084 · avec son $0.112
fal-ai/kling-video/v2.6/pro/text-to-video, …/image-to-video 5, 10 $0.07 · avec son $0.14
fal-ai/veo3.1/fast, fal-ai/veo3.1/fast/image-to-video 4, 6, 8 720p/1080p $0.10 · avec son $0.15 · 4k $0.30
fal-ai/veo3.1, fal-ai/veo3.1/image-to-video 4, 6, 8 720p/1080p $0.20 · avec son $0.40 · 4k $0.40
fal-ai/veo3.1/lite 4, 6, 8 720p $0.03 · avec son $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 (image) $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

Chaque prix est celui de Fal ; la réduction est appliquée au début du travail. Si le vendeur le moins cher refuse un travail, un plus cher le réalise et le travail est facturé au prix de ce vendeur, jamais plus de estimated_cost_usd sur la réponse.

Certains modèles nous coûtent moins cher que ce qu'indique la réduction du vendeur. Toutes les 6 heures, nous comparons ce que la marketplace nous a facturé pour chaque modèle au cours des 7 derniers jours avec le prix de Fal pour les mêmes travaux. Un modèle avec au moins 20 travaux pendant cette période est alors tarifé à cette part réelle plus 5%, lorsque c'est inférieur. La page du modèle, /v1/models et la facture utilisent toutes le même prix. Si ce chiffre a plus de 3 jours, la réduction du vendeur s'applique à nouveau.

Les champs sont les nôtres : duration_seconds, resolution, aspect_ratio, audio et image_url (ou input_images). L'orthographe de Fal (duration, generate_audio, start_image_url) fonctionne aussi. Kling, Veo et Kling 2.6 ajoutent du son sauf si vous envoyez "audio": false.

État du travail

status HTTP Signification
queued 202 Accepté, en attente qu'un modèle le démarre
running 202 En cours de création ; progress passe de 0 à 1 lorsque le modèle le signale
succeeded 200 Terminé ; téléchargez results[].url
failed 200 Non créé. error.message indique pourquoi ; error.retryable indique si une nouvelle tentative peut fonctionner
canceled 200 Arrêté avant sa création
expired 200 Non terminé à temps

Ces six sont les seules valeurs. failed, canceled et expired ne coûtent rien. Une DELETE est une demande d'arrêt, pas une promesse : un travail que le fournisseur termine quand même se termine succeeded et est facturé.

En cas de forte affluence, nous relançons pour vous. Quand aucun fournisseur n'est libre pour créer votre vidéo, ou qu'un fournisseur est trop occupé en ce moment, UberLLM relance le travail tout seul, jusqu'à deux fois de plus dans les 10 minutes. Il conserve le même job id et le même coût maximal. Le travail prend juste un peu plus de temps. Vous ne voyez failed que lorsque toutes les tentatives ont échoué, et un travail échoué ne coûte rien.

Attente. Envoyez callback_url et nous vous dirons quand le travail se termine (voir Callbacks), ou interrogez la poll URL toutes les 5–10 secondes. La plupart des vidéos prennent 1–5 minutes ; certains travaux longs ou 4K prennent plus de temps. Vous n'avez pas besoin de continuer à demander le règlement de la facture : un travail que personne ne vérifie est quand même terminé et facturé (ou remboursé) par UberLLM, et apparaît dans Dashboard → Activity.

Callbacks

Ajoutez callback_url à une tâche vidéo ou musicale : un lien https:// sur un hôte public. Quand la tâche est définitivement terminée, UberLLM lui envoie un POST avec le même JSON que GET renvoie pour la tâche.

{ "model": "kling/kling-o3", "prompt": "a red ball bouncing", "duration_seconds": 5,
  "callback_url": "https://your-app.example/uberllm/jobs" }
  • Quand. Une seule fois, quand la tâche est terminée et que sa facture est finale : succeeded avec usage.cost_usd défini (jamais cost_pending), ou failed, canceled ou expired (coût 0). Il n'est pas renvoyé quand le coût arrive, car il n'est envoyé qu'après. Avec output_upload_url aussi, le callback arrive après que l'upload est terminé, donc output_upload.status est déjà delivered ou failed.
  • Headers. Content-Type: application/json, UberLLM-Event: job.succeeded (ou job.failed, job.canceled, job.expired), UberLLM-Event-Id: <job id>:<status> et UberLLM-Signature: t=<unix seconds>,v1=<hex>.
  • Retries. Répondez avec n'importe quel 2xx dans les 15 secondes. Toute autre réponse, ou l'absence de réponse, est réessayée après 30 secondes, 2 minutes, 10 minutes, 30 minutes et 2 heures, puis abandonnée. Vous pouvez toujours interroger la tâche à tout moment.
  • Repeats. Un message peut arriver plus d'une fois (par exemple quand votre réponse est perdue). L'identifiant d'événement reste le même, donc ignorez ceux que vous avez déjà traités.
  • Status. La tâche indique comment elle s'est déroulée : "callback": { "status": "delivered", "attempts": 1, "http_status": 200, "delivered_at": 1792540990 }. status est pending, delivered ou failed (avec error en toutes lettres).
  • Le lien doit être https:// sur un hôte public, sans nom d'utilisateur ni mot de passe. Il n'est jamais affiché à nouveau et est supprimé une fois le callback terminé. webhook_url est lu comme le même champ. Les images et la parole ne le prennent pas : elles reviennent dans la réponse elle-même.

Vérifier la signature

Chaque message est signé avec le secret de webhook de votre compte. Trouvez-le dans Tableau de bord → Clés API → Secret de signature de webhook ; il est créé la première fois que vous l'ouvrez, et Remplacer le secret le change immédiatement.

v1 est le HMAC-SHA256 hexadécimal de <t>.<raw body>, chiffré avec le secret complet (whsec_…). Calculez-le sur les octets bruts que vous avez reçus (avant d'analyser le JSON), comparez en temps constant, et refusez un t de plus de 5 minutes.

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

Alertes vers un webhook

Les e-mails d'alerte peuvent aussi être envoyés à un webhook : définissez un lien sous Tableau de bord → Crédits → Pour les développeurs : envoyer aussi les alertes à un webhook, ou PUT /api/v1/billing/alerts {"alerts_webhook_url": "https://…"} (null le désactive). Les messages sont signés, réessayés et identifiés de la même manière que les callbacks. Le corps est :

{ "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 : le solde est tombé en dessous du montant que vous avez défini pour l'e-mail de solde faible. Envoyé une fois chaque fois qu'il tombe en dessous ; rien n'est envoyé tant que vous n'avez pas défini un montant.
  • hold.not_released : l'argent mis de côté pour une tâche était toujours retenu plus de 15 minutes après la fin de la tâche. data.jobs les liste (id, kind, model, ended_at, held_usd) et data.held_usd est le total. L'argent est toujours le vôtre et notre équipe a été informée.

Combien de temps les fichiers sont conservés

Téléchargez un résultat dès qu'il est créé :

  • Les fichiers vidéo et musicaux sont conservés pendant environ 3 heures après la fin de la tâche.
  • Les liens d'image vers /v1/media/artifacts/… sont conservés pendant environ 15 minutes ; les liens d'image vers /v1/files/… pendant 24 heures. Demandez "response_format": "b64_json" pour obtenir l'image directement dans la réponse et la conserver vous-même.
  • Les images que vous téléchargez sur /v1/files sont conservées pendant 24 heures.

Après cela, le téléchargement répond 404 artifact_expired.

Envoyer les résultats vers votre propre stockage

Ajoutez output_upload_url à une tâche vidéo ou musicale : un lien https:// qui accepte un HTTP PUT, tel qu'un lien de téléchargement S3, R2 pré-signé ou Google Cloud Storage. Lorsque la tâche réussit, UberLLM télécharge le fichier à cet endroit avec son content type. Pour une tâche qui génère plusieurs fichiers, envoyez output_upload_urls, un lien par fichier dans l'ordre (au maximum 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=…" }

Le statut de la tâche contient alors output_upload :

"output_upload": { "status": "delivered", "attempts": 1, "delivered_at": 1792540990,
  "files": [{ "artifact_index": 0, "http_status": 200, "bytes": 5242880 }] }
  • status est pending jusqu'à ce que le téléchargement soit terminé, puis delivered ou failed (avec error en clair).
  • Un téléchargement échoué est réessayé après 30 secondes, 2, 10 et 30 minutes, puis marqué failed ; le fichier peut toujours être téléchargé depuis results[].url tant qu'il est conservé. Rendez le lien valide pendant au moins une heure.
  • Le lien doit être https:// sur un hôte public, sans nom d'utilisateur ni mot de passe. Il n'est jamais affiché en retour et il est supprimé une fois le téléchargement terminé.
  • Les images ne le prennent pas en charge : une image revient dans la réponse elle-même.

Musique

Les mêmes trois étapes à /v1/music/generations. curl -OJ enregistre la piste sous le nom de fichier que nous envoyons.

Parole

Du texte entre, un fichier audio revient en réponse.

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

Vous pouvez aussi envoyer voice, response_format (par exemple mp3 ou wav) et speed. Chaque modèle a ses propres voix (GET /v1/models/{id} les liste). Avec best-price:speech ou fastest:speech, vous ne savez pas à l'avance quel modèle est utilisé, donc la voix est adaptée à celui-ci : une voix que le modèle possède est conservée, les noms d'OpenAI sont mappés à la voix correspondante du modèle s'il en a une (alloy → af_alloy), et toute autre voix, ou aucune, obtient la première voix du modèle. Lorsque vous nommez un modèle, votre voix est envoyée telle quelle. C'est facturé par tranche de 1K caractères de input commencée. La réponse est le fichier audio ; son en-tête X-UberLLM-Cost-USD indique ce qu'il a coûté, et X-Request-Id est l'ID de la requête telle qu'elle est listée dans l'Activité. "validate": true, max_cost, metadata, models et provider.max_price fonctionnent comme sur les images (voir Ce que vous payez).

Transformer un enregistrement en texte

Envoyez l'enregistrement sous forme de formulaire avec file et 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 } }

Voici le coût : usage.cost_usd (également dans l'en-tête X-UberLLM-Cost-USD, le seul endroit pour les réponses text, srt et vtt). Nos propres champs sont également inclus dans le formulaire et ne sont jamais envoyés au modèle : -F max_cost=0.10, -F "metadata[project]=podcast", -F validate=true.

Vous pouvez également envoyer language, prompt, temperature et response_format : json (par défaut), ou text, srt ou vtt pour obtenir du texte brut en retour. C'est facturé à la minute d'audio. Les fichiers peuvent peser jusqu'à 40 Mo. Le format (mp3, wav, ogg, flac, m4a/mp4, webm, aac) est lu à partir de l'enregistrement lui-même, et non de son nom de fichier, donc un téléchargement nommé tmp123.out fonctionne.

Avec le 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)

Ce que vous payez

  • Le prix de ce qui a été produit : ce que le fournisseur a facturé, sans frais UberLLM. Une réponse d'image ou de transcription et un travail terminé le portent comme usage.cost_usd (dollars US) ; chaque réponse qui a produit quelque chose le porte également dans l'en-tête de réponse X-UberLLM-Cost-USD (pour la parole, l'en-tête est le seul endroit, puisque la réponse est le fichier audio). Il apparaît dans Tableau de bord → Activité. Le reste de la retenue est remboursé.
  • Rien pour un travail qui failed, a été canceled ou expired, ou pour une demande d'image, de parole ou de transcription qui échoue.
  • Le vendeur le moins cher qui produit exactement ce que vous avez demandé. De nombreux modèles d'images et de vidéos ont plus d'un vendeur ; chaque demande est envoyée au moins cher qui peut la produire avec vos paramètres, et à un autre si celui-ci ne peut pas la prendre. La liste des modèles et chaque page de modèle affichent le prix chez le vendeur le moins cher actuellement en ligne.
  • Voir votre solde et ce qui est retenu avec GET /v1/balance (ci-dessous).

Vérifier le prix avant de dépenser

Ajoutez "validate": true à toute demande d'image, de vidéo, de musique ou de parole (pour une transcription, envoyez validate=true comme champ de formulaire avec l'enregistrement). La demande est vérifiée comme si elle était réelle (modèle, options, images source), rien n'est produit et rien n'est facturé, et HTTP 200 indique ce qu'elle coûte au prix affiché (cost_usd, le prix indiqué sur la page du modèle ; vous payez ce que le fournisseur a facturé, ce qui peut différer) et ce qui est retenu pendant son exécution (max_cost_usd, le montant maximum facturé) :

{ "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 le vendeur du modèle a besoin d'un paramètre que vous avez omis (un modèle de référence à la vidéo a besoin de aspect_ratio), la valeur par défaut avec laquelle il est envoyé est affichée sur le schéma du modèle et dans le devis comme defaults_applied, par exemple "defaults_applied": {"aspect_ratio": "16:9"}.

Une demande qui serait refusée est refusée de la même manière, avec la raison.

Retenir un prix pendant 15 minutes

POST /v1/quote prend le même body que la demande d'image, de vidéo, de musique ou de parole et répond avec le coût affiché (cost_usd), ce qui est retenu pendant son exécution (max_cost_usd, le montant maximum facturé) et un quote_id (pour une transcription, envoyez model et duration_seconds ; une transcription elle-même ne prend pas de quote_id, utilisez max_cost pour la plafonner) :

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

Envoyez la même demande avec "quote_id": "6f1c…" dans les 15 minutes et elle sera facturée ce que le fournisseur a facturé, au maximum max_cost_usd ; si ce qui doit être retenu au démarrage est supérieur à max_cost_usd, elle est annulée sans frais. Rien n'est facturé ou retenu pour le devis lui-même.

  • Un devis lance une demande. L'utiliser à nouveau répond 409 quote_used ; un travail qui n'a jamais atteint un modèle rend le devis.
  • La demande doit demander la même chose : même modèle et mêmes paramètres (prompt, size, clip length, source pictures …). Tout autre élément est refusé avec 400 quote_mismatch. max_cost, metadata et output_upload_url peuvent différer.
  • Après 15 minutes, il répond 400 quote_expired. Les modèles de chat sont facturés par token et n'ont pas de devis : plafonnez-les avec provider.max_price.

Modèles de secours et plafond de prix

"models": ["model-b", "model-c"] nomme les modèles à essayer dans l'ordre lorsque le premier ne peut pas être servi immédiatement (no_available_provider) ou est au-dessus de votre provider.max_price. Tout autre refus est répondu immédiatement. Le model de la réponse nomme le modèle qui l'a produite (une réponse vocale est le fichier audio lui-même : l'Activité montre son modèle). Une demande avec quote_id ne prend pas de models, et une transcription ne prend ni l'un ni l'autre.

"provider": {"max_price": 0.05} est le coût maximum d'une unité (le unit de tarification du modèle : image, seconde, mégapixel, minute, 1K caractères ou demande), en dollars US. Un modèle au-dessus de ce montant est refusé avec 400 max_price_exceeded, et la demande n'est jamais facturée au-delà de max_price fois ses unités. D'autres options provider sont refusées avec 400 unsupported_routing_option, car le choix du vendeur pour les demandes de médias ne peut pas être dirigé par demande.

"model": "best-price:image" (ou image-edit, text-to-video, image-to-video, music, speech, transcription) choisit le modèle en ligne le moins cher pour le travail ; fastest: le plus rapide mesuré. Voir Alias de modèles.

Tags

"metadata": {"project": "spring-ads"} (jusqu'à 16 valeurs textuelles) est stocké avec le travail, renvoyé sur son statut, affiché dans l'Activité et dans l'export CSV, et n'est jamais envoyé à un modèle. Sur une transcription, envoyez-les comme champs de formulaire : -F "metadata[project]=spring-ads". Voir Tags.

Définir un plafond

Envoyez "max_cost" (dollars US, par exemple 0.50) avec toute demande d'image, de vidéo, de musique ou de parole (un champ de formulaire sur une transcription). Si cela peut coûter plus que cela, c'est refusé avec 400 max_cost_too_low et rien n'est facturé. Pour un travail vidéo ou musical, le plafond exact est défini au démarrage du travail ; si le coût dépasse ensuite votre max_cost, le travail est annulé sans frais. Avec "validate": true, le devis ajoute within_max_cost.

Réessayer sans payer deux fois

Envoyez un en-tête Idempotency-Key (toute chaîne unique jusqu'à 255 caractères, comme un UUID) lorsque vous démarrez un travail vidéo ou musical. Si votre connexion est interrompue et que vous envoyez la même demande à nouveau avec la même clé dans les 24 heures, vous récupérez le même travail au lieu d'un second. La même clé avec une demande différente est refusée avec 409 idempotency_key_reused. Les demandes d'image, de parole et de transcription ne prennent pas de clé : une demande qui a échoué n'a pas été facturée, donc envoyez-la à nouveau.

Votre solde

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 mis de côté pour les requêtes en cours ; il revient (moins le coût réel) une fois qu'elles sont terminées. available_usd est ce que les nouvelles requêtes peuvent utiliser.

Options de chaque modèle

GET /v1/models?type=image (ou video, music, speech, transcription, audio pour les deux, chat, all ; plusieurs séparés par des virgules ; sans type la liste est limitée aux modèles de chat) liste les modèles que vous pouvez appeler dès maintenant, chacun avec son type, l'endpoints où l'envoyer et son pricing. GET /v1/models/<model id> fournit un modèle, et pour un modèle média, un objet parameters : un schéma JSON du corps de la requête avec les durées de clip, résolutions, rapports d'aspect et tailles qu'il accepte, et l'image source dont il a besoin.

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

pricing inclut usd (le coût maximum d'un unit actuellement) et list_usd (le prix du fournisseur du modèle) ; un modèle de chat a input_usd, output_usd et leurs prix list_ par 1M tokens.

Erreurs

HTTP code signification
400 wrong_endpoint par ex. un modèle d'image envoyé à /v1/video/generations, un modèle de chat envoyé à une route d'image, ou un modèle de parole envoyé à /v1/audio/transcriptions ; le message indique le bon chemin. Rien n'est facturé, et l'Activité le liste comme refusé
400 invalid_request la requête est erronée (le message explique pourquoi, par ex. une durée que le modèle n'offre pas ou une image source manquante)
400 image_required une édition, ou un modèle image-vers-vidéo ou référence-vers-vidéo, sans image source
400 max_cost_too_low cela peut coûter plus que votre max_cost ; rien n'a été facturé
409 idempotency_key_reused ce Idempotency-Key a été utilisé pour une requête différente
400 quote_mismatch / quote_expired la requête n'est pas celle citée, ou le devis a plus de 15 minutes
404 quote_not_found aucun devis de ce type sur votre compte
409 quote_used ce devis a déjà démarré une requête
400 max_price_exceeded le modèle coûte plus par unité que votre provider.max_price
400 unsupported_routing_option une option provider autre que max_price
404 file_not_found aucun fichier téléversé de ce type sur votre compte, ou il a expiré
413 file_too_large un téléversement de plus de 10 Mo
429 too_many_files 500 téléversements sont déjà conservés ; supprimez-en quelques-uns ou attendez qu'ils expirent
503 files_unavailable les téléversements ne sont pas disponibles pour le moment
413 request_too_large la requête dépasse la limite de taille (40 Mo) ; envoyez les images sous forme de liens https:// et divisez les longs enregistrements
402 insufficient_credits votre solde ne peut pas couvrir le coût maximum du travail ; le message indique le montant nécessaire
404 model_not_found / not_found aucun modèle de ce type, ou aucun travail de ce type sur votre compte
404 artifact_expired le fichier n'est plus conservé (voir ci-dessus)
429 rate_limited attendez les secondes indiquées dans Retry-After (quelques secondes s'il est absent), puis réessayez ; X-RateLimit-Remaining indique le nombre de requêtes restantes cette minute
503 no_available_provider aucun vendeur ne peut le faire pour le moment ; UberLLM a déjà essayé les autres. Le message nomme les modèles du même type qui fonctionnent actuellement