---
name: uberllm
description: Use UberLLM — one account and one API key for Claude, GPT, Gemini, DeepSeek, GLM, Qwen, Kimi and 250+ other models, plus image, video and music models, each request billed at the live market price it was served at. Use to set up Claude Code or any OpenAI- or Anthropic-compatible tool, pick a model, make an image or video, or fix an UberLLM error.
---

# UberLLM

One key for every major model. Requests are routed to the cheapest healthy seller on a live marketplace and
billed at the price shown: the seller's price plus UberLLM's 10% fee, already included. Credit is prepaid.

- OpenAI-compatible base URL: `https://api.uberllm.dev/v1`
- Anthropic-compatible base URL (Claude Code, Anthropic SDK): `https://api.uberllm.dev`
- Keys look like `ull_…`. Treat a key like a password: never print it in full, never commit it, store it in
  the user's environment or the tool's own settings file.

## 1. Get a key — the user approves in their browser

Do not ask the user for a password; UberLLM has none. Link a key with one browser approval:

1. `POST https://api.uberllm.dev/api/v1/cli/link/start` with JSON `{"client_name": "<your agent name>", "client_platform": "<os>"}`.
   The answer has `link_code`, `device_code` (secret — keep it to yourself), `verification_url`,
   `expires_in` (seconds) and `interval` (seconds).
2. Tell the user, in plain words: "Open <verification_url>, sign in (Google, or an emailed 6-digit code — it
   creates the account if they have none), check the code says <link_code>, and press Approve."
3. Every `interval` seconds: `POST https://api.uberllm.dev/api/v1/cli/link/poll` with `{"device_code": "<device_code>"}`.
   Wait in the FOREGROUND (one blocking loop, up to `expires_in`), not as a background job, so you carry on
   the moment they approve.
   - `{"status":"pending"}` → keep waiting.
   - `{"status":"approved","api_key":"ull_…","base_url":"…"}` → done; the key is shown exactly once.
   - `denied` or `expired` → stop and tell the user; start over if they want.
   - HTTP 429 `slow_down` → you polled faster than `interval`; wait and continue.

The user never creates or copies a key — this link is the whole setup. (Developers who want a key for
another tool can make one at https://uberllm.dev/dashboard/keys.)

## 2. Make sure there is credit

New accounts start at $0. Check with a 1-token call (step 4). If it answers HTTP 402 `insufficient_credits`,
ask the user to add credit at https://uberllm.dev/dashboard/credits, then try again. Usage and spend:
https://uberllm.dev/dashboard/activity.

## 3. Set up the tool

**Claude Code** — skip step 1 and run the installer; it does the browser connection itself and needs
no key (run it in the foreground and show the user the link it prints):

```sh
curl -fsSL https://uberllm.dev/install/claude-code.sh | sh
```

It writes `ANTHROPIC_BASE_URL` and `ANTHROPIC_AUTH_TOKEN` into `~/.claude/settings.json` (backing up the old file)
and verifies a live call. If you already linked a key in step 1, pass it instead:
`curl -fsSL https://uberllm.dev/install/claude-code.sh | UBERLLM_API_KEY="<key>" sh`. Then tell the user to restart
Claude Code (quit, run `claude` again). Claude Code keeps its own model names (`claude-opus-5-5`,
`claude-sonnet-5`, `haiku`…); prompt caching, thinking and tools work as with Anthropic. Undo:
`curl -fsSL https://uberllm.dev/install/claude-code.sh | sh -s -- --uninstall`.

**Any OpenAI-compatible tool or SDK** (OpenAI SDK, Cursor, Cline, Continue, OpenCode, Aider, LangChain, LiteLLM…):
base URL `https://api.uberllm.dev/v1`, API key = the `ull_` key, model = an id from step 4.

```python
from openai import OpenAI
client = OpenAI(base_url="https://api.uberllm.dev/v1", api_key=os.environ["UBERLLM_API_KEY"])
client.chat.completions.create(model="deepseek/deepseek-v4-flash", messages=[{"role": "user", "content": "hi"}])
```

**Anthropic SDK**: `Anthropic(base_url="https://api.uberllm.dev", api_key=<key>)`.

## 4. Pick a model

`GET https://api.uberllm.dev/v1/models` (header `Authorization: Bearer <key>`) lists every model that can be served right
now. Ids are `vendor/model`. Good defaults:

- Strongest for coding and agents: `anthropic/claude-opus-5.5`
- Balanced: `anthropic/claude-sonnet-5`
- Cheap and fast: `deepseek/deepseek-v4-flash`, `z-ai/glm-5.3`

Live prices per model: https://uberllm.dev/#models. A 1-token test:
`curl https://api.uberllm.dev/v1/chat/completions -H "Authorization: Bearer <key>" -H 'content-type: application/json' -d '{"model":"deepseek/deepseek-v4-flash","max_tokens":1,"messages":[{"role":"user","content":"."}]}'`

## 5. Make an image, a video or music

Same key, same balance. Image, video and music models are listed at https://uberllm.dev/?type=video#models (and
`?type=image`, `?type=music`) with their price per picture, video or track. Use the model id shown there.

**Image** — one call, the picture comes back in the answer:

```sh
curl https://api.uberllm.dev/v1/images/generations -H "Authorization: Bearer $UBERLLM_API_KEY" -H 'content-type: application/json' \
  -d '{"model": "<image model id>", "prompt": "a red apple on a white table", "n": 1, "size": "1024x1024"}'
```

The answer is `{"data": [{"url": "https://api.uberllm.dev/v1/media/artifacts/<id>/0"}]}` (or `b64_json` if you send
`"response_format": "b64_json"`). Download the url with the same `Authorization` header.
`/v1/images/edits` takes the same body plus the source picture(s), for models that edit.

**Video** (and **music**: the same steps with `/v1/music/generations`) — start a job, check on it, download:

1. `POST https://api.uberllm.dev/v1/video/generations` `{"model": "<video model id>", "prompt": "...", "duration_seconds": 5}`
   → HTTP 202 `{"id": "<job id>", "status": "queued", "max_cost_usdc": "...", "poll_url": "/v1/video/generations/<job id>"}`.
   `max_cost_usdc` (millionths of a dollar) is the most it can cost; that much is held from the balance.
2. `GET https://api.uberllm.dev/v1/video/generations/<job id>` every 5–10 seconds. HTTP 202 while `queued` / `running`
   (with `progress` 0–1); HTTP 200 once it ends: `succeeded`, `failed`, `canceled` or `expired`.
   A video usually takes 1–5 minutes.
3. On `succeeded`: download `results[0].url` (`https://api.uberllm.dev/v1/media/artifacts/<job id>/0`) with the key.
   Download it soon; a file that is no longer kept answers 404 `artifact_expired`.

You pay the real cost when it is done (`usage.cost_usd` on the finished job; also in
https://uberllm.dev/dashboard/activity), and the rest of the hold comes back. `failed`, `canceled` and `expired`
cost nothing. Stop a job: `DELETE https://api.uberllm.dev/v1/video/generations/<job id>`. A model sent to the wrong path
answers 400 `wrong_endpoint` and names the right one; a 402 says how much credit the job needs.

## 6. Errors and what to do

| HTTP | code | meaning | do |
|---|---|---|---|
| 401 | `invalid_api_key` / `missing_api_key` | key wrong or revoked | link a new key (step 1) |
| 402 | `insufficient_credits` | balance is empty | user adds credit at https://uberllm.dev/dashboard/credits |
| 400 | `invalid_request` | the request itself is wrong (message says why) | fix the request; do not retry as-is |
| 404 | `model_not_found` | that model id is not available | pick one from `/v1/models` |
| 429 | `rate_limited` | too many requests | wait a few seconds, then retry |
| 503 | `no_available_provider` | no seller serves that model right now; UberLLM already retried | retry in a minute or pick another model |
| 503 | `service_unavailable` | temporary problem on our side | retry in a few minutes |

Streaming (`"stream": true`) is supported on both APIs. Once a stream has started, a failure is not retried
automatically — retry the request yourself.

## 7. Sell capacity (list the user's provider key)

If the user wants to *sell* spare capacity on a provider key (Together, OpenRouter, DeepSeek, Venice, their own
OpenAI-compatible server…), use the same `ull_…` key from step 1 against `https://api.uberllm.dev/v1/seller`. Only list
capacity the user may resell — never a key for OpenAI, Anthropic or Google. Ask the user to confirm the models and
the discount before listing; nothing goes live until you call `offers/bulk`.

1. Once: `POST /v1/seller/register` `{"display_name": "<name buyers see>"}`.
2. `GET /v1/seller/providers` gives each provider's `base_url`.
3. `POST /v1/seller/discover` `{"base_url": "...", "api_key": "<their provider key>"}` lists the models that key can
   serve, matched to ours (`model_id`; absent = not supported). It uses no provider quota.
4. `POST /v1/seller/offers/bulk` `{"endpoint_url": "...", "api_key": "...", "save_config": true, "offers": [{"model_id":
   "...", "upstream_model": "<server_model>", "match_best": true}]}`. Price is a discount off the list price:
   `"discount_pct": 40` or `"match_best": true` (the deepest live discount). Per-token prices are refused. Each
   model gets one small test request; one that fails comes back with `error` and is not listed. At most 50 per call.
5. Watch it: `GET /v1/seller/offers` (price, health, rank), `GET /v1/seller/health-log`,
   `GET /v1/seller/earnings?range=30d`, `GET /v1/seller/payouts` (owed / on the way / paid; payouts are sent by
   hand in US dollars once $5 is owed or after 72 hours).
6. Change it: `PATCH /v1/seller/offers/{id}` `{"discount_pct": 45}` or `{"status": "paused"}`;
   `POST /v1/seller/offers/{id}/reset-health` after a fix; `DELETE /v1/seller/offers/{id}` removes it and erases the
   stored key.

A `409 duplicate_offer_for_credential` means that key already serves that model at that address — update the
existing offer instead. Never print the user's provider key.
