Make a video or image

The key you use for text also makes images, videos and music. Pick a model at uberllm.dev/models?type=video (or ?type=image, ?type=music); each one shows its price per video, picture or track.

Make an image

Images come back in the answer.

curl https://api.uberllm.dev/v1/images/generations \
  -H "Authorization: Bearer $UBERLLM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model": "venice/sd35", "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" }] }

Send "response_format": "b64_json" to get the picture inline instead. Download a url with the same key:

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

n is 1 to 10. Models that edit pictures take the same JSON at /v1/images/edits.

Make a video

A video takes a minute or more, so it is a job: start it, check on it, download it.

1. Start it

curl https://api.uberllm.dev/v1/video/generations \
  -H "Authorization: Bearer $UBERLLM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model": "venice/seedance-2-fast-t2v", "prompt": "a red ball bouncing on green grass", "duration_seconds": 5}'

HTTP 202:

{
  "id": "6f1c…",
  "object": "media.job",
  "kind": "video",
  "status": "queued",
  "estimated_cost_usdc": "40000",
  "max_cost_usdc": "…",
  "poll_url": "/v1/video/generations/6f1c…",
  "cancel_url": "/v1/video/generations/6f1c…"
}

Costs are in millionths of a dollar (40000 = $0.04). max_cost_usdc is the most the job can cost; that much is held from your balance while it runs.

2. Check on it every 5–10 seconds:

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

HTTP 202 while status is queued or running (with progress from 0 to 1). HTTP 200 once it ends, with status succeeded, failed, canceled or expired:

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

3. Download it

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

Download it soon after it is made. A file that is no longer kept answers 404 artifact_expired.

Stop a job you no longer need: DELETE /v1/video/generations/<job id>.

Get told when it is done — no polling

Instead of checking on the job, pass a webhook_url when you start it and we POST the finished job to that URL once — the moment it ends, whether it succeeded or failed.

curl https://api.uberllm.dev/v1/video/generations \
  -H "Authorization: Bearer $UBERLLM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model": "venice/seedance-2-fast-t2v", "prompt": "a red ball bouncing",
       "duration_seconds": 5,
       "webhook_url": "https://your-app.example.com/uberllm/hook",
       "webhook_secret": "a-random-string-you-choose"}'

The start response echoes "callback_url" so you can confirm it is armed. Your URL must be https:// and public (private, loopback and link-local addresses are rejected). You can still poll the job as well; the callback is in addition, not instead.

When the job ends we POST this JSON to your URL:

{
  "event": "media.succeeded",
  "sent_at": 1730000000000,
  "job": {
    "id": "6f1c…", "status": "succeeded",
    "results": [{ "artifact_index": 0, "url": "https://api.uberllm.dev/v1/media/artifacts/6f1c…/0" }],
    "usage": { "cost_usdc": "40000", "cost_usd": "0.04" }
  }
}

job is exactly what the poll endpoint returns, so the same code handles both. event is media.succeeded, media.failed, media.canceled or media.expired.

Headers on the POST:

Header Meaning
X-UberLLM-Event the event name, same as in the body
X-UberLLM-Delivery the job id
X-UberLLM-Timestamp when we sent it (ms since epoch)
X-UberLLM-Signature sha256=<hex> — present only when you set webhook_secret

Verify the signature (when you set a secret): compute HMAC-SHA256 of the raw request body with your secret and compare, in constant time, to the hex in X-UberLLM-Signature.

import { createHmac, timingSafeEqual } from "node:crypto";
function verify(rawBody, header, secret) {
  const want = "sha256=" + createHmac("sha256", secret).update(rawBody).digest("hex");
  const a = Buffer.from(header || ""), b = Buffer.from(want);
  return a.length === b.length && timingSafeEqual(a, b);
}

Answer 2xx to acknowledge. Any other status (or a timeout) is retried with backoff for up to a few hours, so make your handler idempotent on the job id. A job started without a webhook_url is never called back — poll it as above.

Music

The same three steps at /v1/music/generations. curl -OJ saves the track under the file name we send.

What you pay

  • The price shown for what was made, with UberLLM's 10% fee already included. It shows as usage.cost_usd on the finished job and in Dashboard → Activity. The rest of the hold comes back.
  • Nothing for a job that failed, was canceled or expired.
  • You don't have to keep checking for the bill to settle: a job nobody checks on is still finished and billed (or refunded) by UberLLM.

Errors

HTTP code meaning
400 wrong_endpoint e.g. an image model sent to /v1/video/generations; the message names the right path
400 invalid_request the request is wrong (the message says why, e.g. a duration the model doesn't offer)
402 insufficient_credits your balance can't cover the job's maximum cost; the message says how much it needs
404 model_not_found / not_found no such model, or no such job on your account
429 rate_limited wait a few seconds and retry
503 no_available_provider no seller makes that right now; UberLLM already retried