---
title: "Make a video from an image (async)"
method: POST
path: "/v1/videos"
tags: ["Video"]
---

# Make a video from an image (async)

`POST /v1/videos`

## Request

- Base URL: `https://nanostudiopro.com/api`
- URL: `https://nanostudiopro.com/api/v1/videos`
- Auth: HTTP bearer

Submits a video job from an image you own — a generation or an upload — plus a prompt. Every model the app's own video modal offers is here, with the same dials. **Async** — the response carries a `task_id`; poll `GET /v1/videos/{task_id}` until `completed`.

### Models

| `model` | length | `mode` | sound | takes |
|---|---|---|---|---|
| `kling-v3` (default) | 3-15s | std / pro / 4k | optional | `end_frame_id` — about 2 minutes |
| `kling-v3-omni` | 3-15s | std / pro / 4k | optional, never with a clip | up to 6 `reference_image_ids`, or 3 beside one `reference_video_id` (3-10s) whose acting and motion to keep — about 5 minutes |
| `kling-video-o1` | 3-10s | std / pro | none | the same references, cheaper with a clip |
| `kling-motion-control` | the clip's (3-30s) | std / pro | none | `reference_video_id` = the movement to copy; `character_orientation` picks whose face |
| `veo-3.1-lite-generate-preview` | 4, 6 or 8s | std = 720p / pro = 1080p | always | `end_frame_id` |
| `gemini-omni-1.1-flash` | 3-10s, chosen by the model (a length in the prompt is honoured) | std = 720p native / pro, 4k upscaled | always | `end_frame_id`, up to 3 `reference_image_ids`, or `reference_video_id` = a clip to **edit** by describing the change |
| `dreamina-seedance-2-5-260628` | 4-30s | lite = 480p / std = 720p / pro = 1080p | always, dialogue in sync | `end_frame_id`, OR up to 6 `reference_image_ids` and up to 10 `reference_video_ids` (clips to follow, 30 s in all). `generation_id` is optional: without it the clip follows only its references, or the prompt alone, in `aspect_ratio`. `clip_task` edits or extends `reference_video_id`. `draft: true` makes a 480p draft; `finalize_draft_id` renders it at 1080p — about 2-4 minutes |

Names work as well as ids: `kling-3.0`, `kling-3.0-omni`, `kling-o1`, `veo-3.1-lite`, `gemini-omni-flash`, `seedance-2.5`. `GET /v1/meta` returns the same table as data, and says which models are **switched off for now**: `available: false` with `unavailable_reason` ("Back soon", "Unavailable for technical reasons"). A request naming one answers `503 model_unavailable`; with no `model`, the default is used while it is on, else the first open model in this table. A model asked for something it cannot do — an end frame on O1, 4K on Veo, audio with a reference clip — answers `400 validation_error` with the reason in words.

### Cost — read before calling

The most expensive operation on the platform, priced **per second of output**: the `video_*_sec_*` rows in `GET /v1/pricing` times the clip's seconds (Motion Control: the reference clip's seconds; Veo 3.1 Lite: fixed `video_veo_lite_*` rows). The 202 response states the exact `credits_cost` and the `pricing_key` it came from. Credits are deducted **only when the video completes** — a failed video costs nothing. Video requires **purchased** credits; trial credits return `402 video_requires_purchase`.

**Quote first.** Send the same body with `dry_run: true`: every check and the planner run and the response is a `200` with `status: quote` and the same fields the 202 would carry, but no job exists and nothing is charged. A model asked for a dial it lacks answers the quote with the same `400` the real call would.

## Request body

- object
  - `generation_id` string: Source image / first frame: a generation or upload id you own. Required except on Seedance, which can follow references or the prompt alone.
  - `project_id` string, required
  - `prompt` string, required: The motion/action to depict — or, with a Gemini Omni `reference_video_id`, the change to make.
  - `model` 'kling-v3' | 'kling-v3-omni' | 'kling-video-o1' | 'kling-motion-control' | 'veo-3.1-lite-generate-preview' | 'gemini-omni-1.1-flash' | 'dreamina-seedance-2-5-260628': By id or by name (see the table). `kling-v2-6` is retired but still accepted.
  - `duration` integer: Whole seconds, within the model's range or menu (Veo snaps to 4/6/8; Gemini Omni treats it as a hint). Default 5, Veo 8. Ignored by Motion Control.
  - `mode` 'lite' | 'std' | 'pro' | '4k': Render size. `480p` (Seedance only), `720p`, `1080p` and `4K` are accepted as aliases.
  - `audio` boolean: Optional on Kling 3.0 / Omni (more credits; refused together with a reference clip). Veo, Gemini Omni and Seedance always carry sound; O1 and Motion Control never do.
  - `end_frame_id` string: An image the clip must land on. Kling 3.0, Veo, Gemini Omni, Seedance (not together with references).
  - `reference_video_id` string: A finished video generation you own: the acting to keep (Kling Omni / O1), the movement to copy (Motion Control), or the clip to edit (Gemini Omni).
  - `reference_image_ids` string[]: Extra stills the clip should stay faithful to, beyond the first frame. Kling Omni / O1 up to 6, or 3 once a reference clip takes a place in the same list. Gemini Omni up to 3. Seedance up to 6, never beside an end frame.
  - `character_orientation` 'image' | 'video': Motion Control only: the face as in the still, or as in the clip. Default `video`.
  - `entity_ids` string[]: Saved characters, props or places (ids from `GET /v1/entities`) whose baked reference sheet to attach. Writing `@slug` in the prompt does the same — a look is `@parent/look` or its own slug — and the prompt is rewritten so the model knows which picture is whom. Sheets go after your own references, within the model's reference limit; the response's `entities` says which went in and which were dropped. Unknown `@words` are left as typed. To leave something out, write `-@slug` (that prop comes off every items sheet the request sends, and the words are removed from the prompt) or pass `-<id>` here; `@slug/alone` or `<id>/alone` sends the entity without its items sheet. The response's `entities.excluded` lists what was left out. Sheets count against `reference_image_ids`' limit; Kling 3.0, Veo and Motion Control take none, so there a mention becomes the plain name.
  - `reference_video_ids` string[]: Seedance References: finished clips to follow for movement, camera or pacing (@Video1, @Video2… in the prompt), each 2-30 s and 30 s in all. Not edited. Billed as clip input: the per-second row x 0.6 x (clip seconds + output seconds).
  - `reference_audio_ids` string[]: Seedance References: sounds to follow (@Audio1, @Audio2… in the prompt), ids from `POST /v1/videos/reference-audio`. MP3 or WAV, 2-30 s each, 30 s in all.
  - `reference_audio_urls` string[]: The same as public links, fetched and stored on this call; they come after `reference_audio_ids` in @Audio order. At most 10 sounds in all.
  - `aspect_ratio` '16:9' | '9:16' | '1:1' | '4:3' | '3:4' | '21:9': Seedance without a first frame: the shape to render. With a first frame the shape follows it.
  - `clip_task` 'edit' | 'extend': Seedance: edit `reference_video_id` (4-30 s; keeps its length and shape) or extend it (2-30 s; `duration` is the new part). Billed as clip input, like `reference_video_ids`.
  - `extend_direction` 'forward' | 'backward': With `clip_task: extend`: the new part follows the clip, or leads into it.
  - `name` string: The clip's title in the library. Without it the clip is untitled and shows its prompt.
  - `keep_sound` boolean: On an edit (Gemini Omni with `reference_video_id`, or Seedance `clip_task: edit`): the clip's own sound is put back under the edited picture, since the model redraws voices and can repeat a line. false keeps the model's sound.
  - `draft` boolean: Seedance: a 480p draft at the 480p price. When you like it, send `finalize_draft_id` to render the same shot at 1080p.
  - `finalize_draft_id` string: A finished Seedance draft's video id. Sent alone (with `project_id`), it renders the same shot at 1080p within 7 days of the draft, billed at the 1080p row for the draft's length. `dry_run` quotes it.
  - `dry_run` boolean: Quote only. Every check and the planner run and the exact `credits_cost` comes back as a `200` with `status: quote`, but nothing is submitted or charged. Then send the same body without it.

## Response `200`

Quote (`dry_run: true`): the plan and its exact price. Nothing was submitted or charged.

## Other responses

- `202`: Job submitted; poll the task.
- `400`: A missing field, an unknown model, or a dial this model does not have (the message says which)
- `401`: Missing or invalid token
- `402`: Trial credits (video requires purchase) or insufficient credits
- `404`: Generation or project not found
- `429`: Rate limit exceeded
- `503`: The model is switched off for now (GET /v1/meta says why); or `feature_unavailable` when pricing is not configured

---

[API](https://skmtc.dev/nanostudiopro/apis/nano-studio-pro-api.md) · [All operations](https://skmtc.dev/nanostudiopro/apis/nano-studio-pro-api/llms.txt) · [OpenAPI document](https://skmtc.dev/nanostudiopro/apis/nano-studio-pro-api/revisions/7f7d4a72b17c?raw)
