# Video generation API

Everything an app needs to call SuperbAPI's video models. One endpoint pair,
one key, every model — the gateway hides the fact that the upstreams speak
three different dialects and bill two different ways.

Base URL: `https://www.superbapi.com/v1`
Auth: `Authorization: Bearer sk-sbapi-…` (the same key as chat)

## Submit

```
POST /v1/videos
Content-Type: application/json

{
  "model": "doubao-seedance-2-0-mini",
  "prompt": "a paper boat drifting down a calm stream, morning light",
  "duration": 10,          // seconds. `seconds` is accepted too
  "ratio": "9:16",         // optional
  "image_url": "https://…" // optional — image-to-video
}
```

Returns `{ "task_id": "task_…" }`. **The charge happens here**, on a successful
submit only — a rejected submit costs nothing.

## Poll

```
GET /v1/videos/{task_id}
```

Free, uncharged. Poll about every 15 s.

```jsonc
// working
{ "status": "processing", "progress": 40 }
// done
{ "status": "completed", "video_url": "https://…/clip.mp4", "usage": {…} }
// failed — ALWAYS read error.message, it is often actionable
{ "status": "failed", "error": { "message": "…copyright restrictions" } }
```

`video_url` links **expire** — download immediately, don't store the URL.

## Reference images (image-to-video)

Pass `image_url` as **either** an `https://` URL **or** a base64 `data:image/...`
URL. The gateway handles the rest — each model family takes its start frame a
different way, and it converts for you.

You do not need an image host: a data URL works everywhere.

Requirements, all enforced up front:

| | |
|---|---|
| Format | JPEG, PNG or WebP |
| Size | at least **300 x 300** px, at most 10 MB |
| URL | must be reachable from the public internet |

A reference that cannot be used is a **400 that says why** — never a silent
text-only render you still pay for. If you send an image and get a task id,
the image reached the model.

## Billing: two classes

| | Models | How it is charged |
|---|---|---|
| **Per clip** (按次计费) | Kling · Veo · PixVerse · Grok Video · Vidu · **Seedance 1.5** | Flat price per generation, whatever the length |
| **By length** (按量计费) | **Seedance 2.0** | Priced per second — a 10 s clip costs 2× a 5 s clip |

Seedance 2.0 per 5 s @ 720p: mini **$5.01** · fast **$8.06** · full **$11.11**.
So a 10 s mini clip is **$10.02**, not $5.01 — surface this in your UI.

Seedance 2.0 currently accepts **720p only, 4–12 s** (3 s and below are
rejected by the model itself). Anything outside that is rejected up front with
a clear message rather than billed on a guess.

Extra options — `ratio`, `seed`, and any other scalar field — are passed
through to the model untouched.

Every model's exact price is on <https://www.superbapi.com/models>, and the
submit response carries `x-superbapi-cost-usd`.

## Model ids

Both spellings work and bill identically:

- bare — `doubao-seedance-2-0-mini` (what `GET /v1/models` returns)
- catalog — `bytedance/doubao-seedance-2-0-mini` (what the Models page shows)

**Build your picker from `GET /v1/models`.** It only ever lists models the
gateway can actually serve, so a model that disappears upstream also
disappears from your UI instead of turning into a runtime error.

## Two upstream keys, zero configuration

Video models are split across two upstream accounts — one billed per call, one
billed by token. The gateway resolves this itself: a key that cannot bill a
given model rejects it, and the request falls through to the one that can.
Callers never pick a key or a route.

## Failure modes worth handling

| What you see | What it means |
|---|---|
| `404` at submit | The model isn't enabled. Check `GET /v1/models` |
| `400` at submit | Bad shape — e.g. Seedance 2.0 above 12 s or not 720p. The message says which |
| `402` at submit | Balance below the clip price. Top up |
| `429` / `502` at submit | Upstream busy or down. Retry later — nothing was charged |
| `status: "failed"` at poll | The render itself failed. **Read `error.message`** — content-moderation rejections need a different prompt, not a retry |

Video submits can take up to ~30 s to return a task id (the upstream starts
rendering first). Give the submit call a generous client timeout, and **do not
retry a submit that timed out** — it may already be rendering, and a retry
pays for a second clip.
