# Video Generation API

Create video generation tasks with POST /api/v1/videos, understand the response, credits, and errors.

## Create a video task

`POST /api/v1/videos` validates the request, charges credits, and submits the job to the provider before it responds. The video then renders asynchronously: poll [`GET /api/v1/tasks/{taskId}`](https://viewmax.studio/docs/api/tasks) every 5–10 seconds until `success`, `failed`, or `canceled`. There is no server-side timeout, so set your own deadline.

```http
POST https://viewmax.studio/api/v1/videos
Authorization: Bearer sk-your-api-key
Content-Type: application/json
Idempotency-Key: kite-001
```

`Idempotency-Key` is optional but recommended; see [Idempotency](#idempotency).

## Request body

| Field        | Required | Type     | Description                                                                                                                                |
| ------------ | -------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| `model`      | Yes      | string   | A model `id` from `GET /api/v1/models`. Models taken offline (such as `sora-2`) are not listed and return 400                              |
| `prompt`     | Yes      | string   | Non-empty; trimmed. At most the model's `max_prompt_length` characters when the catalog lists one (currently only `minimax-h3`: 7000)      |
| `mode`       | No       | string   | `text-to-video`, `image-to-video`, `video-to-video`, `frames-to-video`, or `reference-to-video`; must be a key of the model's `modes`      |
| `image_urls` | No       | string[] | `http://` or `https://` image URLs the provider can fetch without authentication. Counts per model and mode: [Input limits](#input-limits) |
| `video_urls` | No       | string[] | One `http(s)` source video. Required for `video-to-video`; optional for `reference-to-video` on some models                                |
| `options`    | No       | object   | Generation controls from the model's catalog entry                                                                                         |

If `mode` is omitted, it is `image-to-video` when `image_urls` is non-empty, otherwise `video-to-video` when `video_urls` is non-empty, otherwise `text-to-video`. Always send `mode` for `frames-to-video` and `reference-to-video`.

### `options`

| Field                           | Type              | Description                                                                                                                                                                                     |
| ------------------------------- | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `duration`                      | string or integer | One of `modes.<mode>.durations`; `"8s"`, `"8"`, and `8` are equivalent. Omit it when the list is empty (Veo 3.1 models)                                                                         |
| `resolution`                    | string            | One of `modes.<mode>.resolutions`, such as `480p`, `720p`, `1080p`, `4k`, `768P`, or `2K`. Matching ignores case; send the catalog spelling. Omit it when the list is empty (Kling 2.6, FLUX 3) |
| `aspect_ratio`                  | string            | One of `modes.<mode>.aspect_ratios`, exact case (`Auto`, not `auto`)                                                                                                                            |
| `audio`                         | boolean           | `true` only when `modes.<mode>.audio_toggle` is `true`; `false` is always accepted                                                                                                              |
| `source_video_duration_seconds` | number            | Length of the `video_urls` source in seconds. Required with `video_urls` when the mode lists `credits_per_second` (Seedance 2.x, Seedance 2.5, MiniMax H3)                                      |

Omitted `duration`, `resolution`, and `aspect_ratio` are filled with `modes.<mode>.defaults` from `GET /api/v1/models` before pricing, and the filled values are sent to the provider. For example, `viewmax-c1` defaults to `5s`, `768P`, and `16:9`. Some models allow fewer durations at a given resolution (`runway` allows only `5s` at `1080p`). Other `options` keys are forwarded to the provider without validation.

### Input limits

ViewMax rejects these counts with 400 before charging. `—` means the model does not offer the mode.

| Model                   | text-to-video | image-to-video | frames-to-video | reference-to-video       | video-to-video         |
| ----------------------- | ------------- | -------------- | --------------- | ------------------------ | ---------------------- |
| `viewmax-c1`            | yes           | 1 image        | 2 images        | 1–9 images               | —                      |
| `viewmax-turbo`         | yes           | 1 image        | 2 images        | —                        | —                      |
| `seedance-2-5`          | yes           | 1 image        | 2 images        | 1–3 images               | 1 video ¹              |
| `seedance-2-0`          | yes           | 1–2 images     | 2 images        | 1–2 images + 0–1 video ¹ | —                      |
| `seedance-2-0-fast`     | yes           | 1–2 images     | 2 images        | 1–2 images + 0–1 video ¹ | —                      |
| `seedance-2-mini`       | yes           | 1–2 images     | 2 images        | 1–2 images + 0–1 video ¹ | —                      |
| `seedance-1-5-pro`      | yes           | 1 image        | —               | —                        | —                      |
| `minimax-h3`            | yes           | 1 image        | 2 images        | 1–4 images + 0–1 video ¹ | 1 video + 0–2 images ¹ |
| `veo-3-1`               | yes           | 1 image        | 2 images        | 1–3 images               | —                      |
| `veo-3-1-fast`          | yes           | 1 image        | 2 images        | 1–3 images               | —                      |
| `veo-3-1-lite`          | yes           | 1 image        | 2 images        | 1–3 images               | —                      |
| `gemini-omni`           | yes           | 1 image        | —               | —                        | 1 video + 0–2 images ² |
| `gemini-omni-video`     | yes           | 1 image        | —               | —                        | 1 video ³              |
| `gemini-omni-cheap`     | yes           | 1–2 images     | —               | —                        | —                      |
| `flux-3`                | yes           | 1 image        | 2–3 images      | —                        | 1 video                |
| `kling-2-6`             | yes           | 1 image        | —               | —                        | —                      |
| `grok-imagine`          | yes           | 1 image        | —               | —                        | —                      |
| `grok-imagine-1-5`      | yes           | 1 image        | —               | —                        | —                      |
| `runway`                | yes           | 1 image        | —               | —                        | —                      |
| `p-video`               | yes           | 1–2 images     | —               | —                        | —                      |
| `seedence-1-0-pro`      | yes           | 1 image        | —               | —                        | —                      |
| `seedence-1-0-pro-fast` | —             | 1 image        | —               | —                        | —                      |
| `seedence-1-0-lite`     | yes           | 1 image        | —               | —                        | —                      |

¹ With a video, also send `options.source_video_duration_seconds`.

² `gemini-omni` accepts source videos of 10 seconds or less; a longer source fails at the provider (the task fails and is refunded).

³ `gemini-omni-video` accepts sources up to 30 seconds and uses the first 10 seconds.

## Example

```bash
curl -X POST https://viewmax.studio/api/v1/videos \
  -H "Authorization: Bearer $VIEWMAX_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: kite-001" \
  -d '{
    "model": "viewmax-c1",
    "mode": "text-to-video",
    "prompt": "A paper kite crossing a bright summer sky",
    "options": { "duration": "5s", "resolution": "768P", "aspect_ratio": "16:9" }
  }'
```

This costs 10 credits (2 credits per second × 5 seconds), or `0` on Ultra while the ViewMax C1 allowance lasts.

## Successful response

```json
{
  "code": 0,
  "message": "ok",
  "data": {
    "id": "3f1c9a2e-5b7d-4c1e-9a0f-2d6e8b4c7a10",
    "userId": "usr_123",
    "mediaType": "video",
    "model": "viewmax-c1/text-to-video",
    "prompt": "A paper kite crossing a bright summer sky",
    "options": "{\"duration\":5,\"resolution\":\"768P\",\"aspect_ratio\":\"16:9\",\"mode\":\"text-to-video\"}",
    "status": "pending",
    "createdAt": "2026-09-29T08:00:00.000Z",
    "updatedAt": "2026-09-29T08:00:01.000Z",
    "deletedAt": null,
    "taskId": "job_abc",
    "taskInfo": null,
    "taskResult": null,
    "taskUrls": [],
    "costCredits": 10,
    "scene": "text-to-video",
    "visibility": "user",
    "creditId": "cr_123",
    "waivedCredits": 0,
    "fairUseKey": null
  }
}
```

### Task fields

The same task object is returned by every create endpoint and by `GET /api/v1/tasks/{taskId}`.

| Field                    | Description                                                                                                                                                                                   |
| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`                     | Task ID; poll `GET /api/v1/tasks/{taskId}` with it                                                                                                                                            |
| `status`                 | `pending` or `processing`: keep polling. `success`: read `taskUrls`. `failed` or `canceled`: stop; the credits were refunded                                                                  |
| `taskUrls`               | Result URLs, always an array; empty until `success`                                                                                                                                           |
| `costCredits`            | Credits charged; `0` when a plan allowance covered the task                                                                                                                                   |
| `waivedCredits`          | Normal price waived by a plan allowance, otherwise `0`                                                                                                                                        |
| `fairUseKey`             | Plan allowance slot used by the task, or `null`                                                                                                                                               |
| `mediaType`, `scene`     | `video` and the mode                                                                                                                                                                          |
| `model`                  | Model identifier used for the job; can differ from the `model` you sent                                                                                                                       |
| `options`                | JSON string of the options as priced and submitted, including filled defaults and `mode`                                                                                                      |
| `taskInfo`, `taskResult` | JSON strings. While running or after success they hold job diagnostics with no stable shape; do not depend on them. On `failed` or `canceled` both are `{"errorCode","error","errorMessage"}` |

Also returned: `userId`, `taskId` (upstream job ID), `creditId`, `visibility`, `createdAt`, `updatedAt`, and `deletedAt`. `errorCode` is one of `insufficient_credits`, `auth_required`, `rate_limited`, `request_cancelled`, `provider_unavailable`, `invalid_input`, `content_rejected`, `busy`, `duplicate_request`, or `generic`; `errorMessage` carries the provider's reason only for `invalid_input`.

## Idempotency

Send an `Idempotency-Key` header (1–200 visible ASCII characters, no spaces) with every create call. Repeating a key returns the task the first request created, with its original `costCredits`, and charges nothing. The body is not compared, and keys are shared by all create endpoints of the account.

- After a timeout or dropped connection, retry with the **same** key.
- For a deliberate new attempt, for example after a `failed` task or a 400 with `error.task_id`, use a **new** key; the old key keeps returning the old task.
- Without the header, every POST creates and charges a new task.
- An invalid key returns 400 `Idempotency-Key must be 1-200 visible ASCII characters without spaces`.

## Credits and refunds

- Credits are deducted when the task is created. If the balance is lower than the cost, the call returns 402 and creates no task.
- A task that ends `failed` or `canceled` is refunded automatically, and any plan allowance it used is returned.
- On Ultra, ViewMax C1 draws on a daily allowance of 300 seconds of output per UTC day, shared with the ViewMax app and MCP. While it lasts, `costCredits` is `0`, `waivedCredits` holds the normal price, and `fairUseKey` is set. A request longer than what remains is charged in full. Other video allowances in the web app do not apply to the API.

## Estimate the cost

There is no cost-preview endpoint. Compute the cost from `modes.<mode>` in `GET /api/v1/models`, using the values you send or `defaults`:

| Catalog field                                        | Cost                                                                                                                                |
| ---------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| `credits[resolution][duration]`                      | Fixed price for that pair                                                                                                           |
| `credits[resolution].per_generation`                 | Fixed price per video                                                                                                               |
| `credits[duration]["with-audio" \| "without-audio"]` | Kling 2.6, selected by `audio`                                                                                                      |
| `credits_per_second[resolution]`                     | `ceil(rate × duration seconds)`                                                                                                     |
| `credits_per_second_with_video_input[resolution]`    | Used when `video_urls` is sent. Seedance 2.0, 2.0 Fast, and 2 Mini bill `duration + min(source_video_duration_seconds, 15)` seconds |
| `pricing_note: "Audio enabled costs 2x credits."`    | Doubles the price when `audio` is `true`                                                                                            |

Examples: `viewmax-c1`, `5s`, `768P` → 10; `seedance-2-0` reference-to-video, `8s`, `720p`, 5-second source video → `ceil(28 × 13)` = 364; `kling-2-6`, `5s`, `audio: true` → 110.

## Errors

Checks run in this order and stop at the first failure: request shape, prohibited-use wording, model, mode, prompt length, inputs, options, balance. Nothing is charged until all of them pass. All errors use the [error envelope](https://viewmax.studio/docs/api/errors).

| HTTP | `error.type`           | Example `message`                                                                                                         | What to do                                                                                                                                                                |
| ---- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 400  | `invalid_request`      | `unsupported video model: sora-3`                                                                                         | Use an `id` from `GET /api/v1/models`                                                                                                                                     |
| 400  | `invalid_request`      | `video model temporarily unavailable: sora-2. The provider is not serving this model right now — pick a different model.` | Pick another model                                                                                                                                                        |
| 400  | `invalid_request`      | `This capability is unavailable.`                                                                                         | The model or mode is disabled (for example Motion Control); pick another                                                                                                  |
| 400  | `invalid_request`      | `viewmax-c1 does not support mode video-to-video`                                                                         | Use a key of the model's `modes`                                                                                                                                          |
| 400  | `invalid_request`      | `minimax-h3 prompt must be 7000 characters or fewer, got 12459`                                                           | Shorten the prompt                                                                                                                                                        |
| 400  | `invalid_request`      | `viewmax-c1 frames-to-video requires image_urls (at least 2)`                                                             | Add the inputs in [Input limits](#input-limits)                                                                                                                           |
| 400  | `invalid_request`      | `veo-3-1-fast reference-to-video supports at most 3 image_urls, got 4`                                                    | Send fewer URLs                                                                                                                                                           |
| 400  | `invalid_request`      | `veo-3-1 image-to-video does not accept video_urls; supported inputs: image_urls`                                         | Remove `video_urls` or change `mode`                                                                                                                                      |
| 400  | `invalid_request`      | `image_urls must contain valid http(s) URLs`                                                                              | Send `http://` or `https://` URLs                                                                                                                                         |
| 400  | `invalid_request`      | `seedance-2-0 reference-to-video requires options.source_video_duration_seconds when video_urls is provided`              | Send the source length in seconds                                                                                                                                         |
| 400  | `invalid_request`      | `runway text-to-video supports duration 5s at 1080p, got 10s`                                                             | Send a value listed in the catalog                                                                                                                                        |
| 400  | `invalid_request`      | `viewmax-c1 text-to-video supports audio false, got true`                                                                 | Send `audio: true` only when `audio_toggle` is `true`                                                                                                                     |
| 400  | `invalid_request`      | `the generation request uses unsupported settings`, with `error.task_id`                                                  | The provider rejected the settings; the task failed and was refunded. Read its `taskInfo.errorMessage`, adjust, and retry with a new `Idempotency-Key`                    |
| 400  | `content_rejected`     | `input content was rejected by safety filters`                                                                            | Prohibited-use wording (deepfake, face swap, impersonation, matched even inside negative instructions) or provider moderation. Change the content; do not retry unchanged |
| 402  | `insufficient_credits` | `insufficient credits`                                                                                                    | No task was created. Add credits; do not retry until then                                                                                                                 |
| 500  | `internal_error`       | `the generation service is temporarily unavailable`                                                                       | With `error.task_id` the task failed and was refunded: retry once. If the hint says the outcome is unknown, poll that task instead of retrying                            |
| 503  | `internal_error`       | —                                                                                                                         | The content-safety check is temporarily unavailable; retry after a short delay                                                                                            |

## Legacy `/api/ai/*` endpoints

The legacy `POST /api/ai/generate` and `POST /api/ai/query` endpoints remain available for existing integrations. They retain their legacy request shapes and response behavior. New integrations should use the v1 resource endpoints documented here: `POST /api/v1/videos` and `GET /api/v1/tasks/{taskId}`.
