> ## Documentation Index
> Fetch the complete documentation index at: https://docs.nunchux.ai/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> Authenticate every request with your API key in the `X-API-Key` header or `Authorization: Bearer`. Keys start with `sk-nunchux-`.
> Image generation is synchronous: the image is in the response.
> Video generation is asynchronous: submit the job, poll its task until it reaches a terminal status, then download the output promptly, because output URLs expire.

# Kling

> Kling V3 and Kling V3 Omni video generation with your Nunchux key: text-to-video, image-to-video, omni video and motion control.

# Kling

Kling is Kuaishou's video generation model family. Every Kling endpoint is asynchronous: submit a job, poll it until it reaches a terminal status, then download the output. The routes mirror Kling's own API, so the request bodies, response shapes and error codes follow Kling's documentation. You call them with your Nunchux API key and need no Kling account.

## Models

| Model | Model ID | Tasks | Modes/resolution | Duration |
| - | - | - | - | - |
| Kling V3 | `kling-v3` | Text-to-video, image-to-video, motion control | `std`, `pro`, `4k` (motion control: `std`, `pro`) | 3 to 15 s (motion control: follows the reference video) |
| Kling V3 Omni | `kling-v3-omni` | Omni video | `std`, `pro`, `4k` | 3 to 15 s (storyboard: up to 30 s) |

`std` is the standard tier. It is the fastest and the most economical. `pro` gives sharper detail and stronger motion. `4k` gives 4K-resolution output. Higher tiers take longer.

Start with `kling-v3`. It supports the `std`, `pro` and `4k` modes, and it is the only model with optional native audio. Use `kling-v3-omni` on the [omni video](#omni-video) endpoint for multi-reference or storyboard work. For cost-sensitive jobs, use `std`. It is the cheapest per second.

## Endpoints

| Capability | Route |
| - | - |
| [Text-to-video](#text-to-video) | `POST /v1/klingai/videos/text2video` |
| [Image-to-video](#image-to-video) | `POST /v1/klingai/videos/image2video` |
| [Omni video](#omni-video) | `POST /v1/klingai/videos/omni-video` |
| [Motion control](#motion-control) | `POST /v1/klingai/videos/motion-control` |
| [Poll a task](#poll-a-task) | `GET /v1/klingai/videos/{capability}/{task_id}` |

The base URL is `https://api.nunchux.ai`. Each submit returns a `task_id` immediately. Poll the same capability that you submitted to, with the `task_id` appended to the route.

## Common fields

Send your API key in the `X-API-Key` header (see [Authentication](/authentication)). Send `Content-Type: application/json` on every submit. Send a `User-Agent` header that identifies your application, such as `YourApp/1.0`. The request fields below appear on more than one endpoint.

<ParamField body="model_name" type="string" required>
  The model to generate with. See [Models](#models). Defaults to `kling-v3` on text-to-video, image-to-video and motion control. Defaults to `kling-v3-omni` on omni video.

  Options: `kling-v3` on text-to-video, image-to-video and motion control. `kling-v3-omni` on omni video.
</ParamField>

<ParamField body="mode" type="string" default="std">
  Quality and speed tier. Higher tiers take longer. Motion control accepts `std` and `pro` only.

  Options: `std`, `pro`, `4k`
</ParamField>

<ParamField body="prompt" type="string" required>
  Description of the video to generate. Max 2500 characters. On image-to-video, describe the motion or the transformation. On omni video, refer to an input with a placeholder such as `<<<image_1>>>`. Not sent on motion control. Ignored in [storyboard mode](#storyboard-mode).
</ParamField>

<ParamField body="negative_prompt" type="string">
  What to exclude from the video. Max 2500 characters. Text-to-video and image-to-video only.
</ParamField>

<ParamField body="duration" type="string" default="5">
  Output length in seconds, sent as a string. Billed per second. Common values are `"5"` and `"10"`. Not sent on motion control, where the output length follows the reference video. Ignored in [storyboard mode](#storyboard-mode).

  Range: 3-15.
</ParamField>

<ParamField body="sound" type="string" default="off">
  Native audio generation. Only on `kling-v3`. Text-to-video and image-to-video only. In `4k` mode, audio is always included and this flag is ignored.

  Options: `on`, `off`
</ParamField>

<ParamField body="callback_url" type="string">
  If set, Kling POSTs status updates to this URL, and you do not need to poll. Text-to-video and image-to-video only.
</ParamField>

## Text-to-video

`POST https://api.nunchux.ai/v1/klingai/videos/text2video`

Generate a video from a text prompt. Send `model_name` and `prompt`. Every other field is optional and falls back to its default. Use `kling-v3`.

<ParamField body="aspect_ratio" type="string" default="16:9">
  Output aspect ratio.

  Options: `16:9`, `9:16`, `1:1`
</ParamField>

To plan a sequence of shots instead of one prompt, see [Storyboard mode](#storyboard-mode).

<CodeGroup>
  ```bash cURL theme={"system"}
  curl https://api.nunchux.ai/v1/klingai/videos/text2video \
    -H "X-API-Key: $NUNCHUX_API_KEY" \
    -H "Content-Type: application/json" \
    -H "User-Agent: YourApp/1.0" \
    -d '{
      "model_name": "kling-v3",
      "prompt": "a paper crane unfolding into a real bird, cinematic slow motion",
      "mode": "std",
      "duration": "5",
      "aspect_ratio": "16:9"
    }'
  ```

  ```python Python theme={"system"}
  import os, requests

  resp = requests.post(
      "https://api.nunchux.ai/v1/klingai/videos/text2video",
      headers={"X-API-Key": os.environ["NUNCHUX_API_KEY"], "User-Agent": "YourApp/1.0"},
      json={
          "model_name": "kling-v3",
          "prompt": "a paper crane unfolding into a real bird, cinematic slow motion",
          "mode": "std",
          "duration": "5",
          "aspect_ratio": "16:9",
      },
  )
  task_id = resp.json()["data"]["task_id"]
  ```

  ```javascript JavaScript theme={"system"}
  const submit = await fetch("https://api.nunchux.ai/v1/klingai/videos/text2video", {
    method: "POST",
    headers: {
      "X-API-Key": process.env.NUNCHUX_API_KEY,
      "Content-Type": "application/json",
      "User-Agent": "YourApp/1.0",
    },
    body: JSON.stringify({
      model_name: "kling-v3",
      prompt: "a paper crane unfolding into a real bird, cinematic slow motion",
      mode: "std",
      duration: "5",
      aspect_ratio: "16:9",
    }),
  }).then((r) => r.json());
  const taskId = submit.data.task_id;
  ```
</CodeGroup>

## Image-to-video

`POST https://api.nunchux.ai/v1/klingai/videos/image2video`

Animate a starting image into a video clip. Send `model_name`, `image` and a `prompt` that describes the motion. Use `kling-v3`.

<ParamField body="image" type="string" required>
  The starting frame. Pass a public URL or raw base64, with no `data:image/...;base64,` prefix. Kling rejects the prefixed form with code 1201.
</ParamField>

<CodeGroup>
  ```bash cURL theme={"system"}
  IMG_B64=$(base64 < input.jpg | tr -d '\n')

  curl https://api.nunchux.ai/v1/klingai/videos/image2video \
    -H "X-API-Key: $NUNCHUX_API_KEY" \
    -H "Content-Type: application/json" \
    -H "User-Agent: YourApp/1.0" \
    -d "{
      \"model_name\": \"kling-v3\",
      \"prompt\": \"camera pushes in slowly, subtle ambient motion\",
      \"mode\": \"pro\",
      \"duration\": \"5\",
      \"image\": \"$IMG_B64\"
    }"
  ```

  ```python Python theme={"system"}
  import base64, os, requests

  with open("input.jpg", "rb") as f:
      img_b64 = base64.b64encode(f.read()).decode()

  resp = requests.post(
      "https://api.nunchux.ai/v1/klingai/videos/image2video",
      headers={"X-API-Key": os.environ["NUNCHUX_API_KEY"], "User-Agent": "YourApp/1.0"},
      json={
          "model_name": "kling-v3",
          "prompt": "camera pushes in slowly, subtle ambient motion",
          "mode": "pro",
          "duration": "5",
          "image": img_b64,
      },
  )
  task_id = resp.json()["data"]["task_id"]
  ```

  ```javascript JavaScript theme={"system"}
  import { readFile } from "node:fs/promises";

  const imgB64 = (await readFile("input.jpg")).toString("base64");

  const submit = await fetch("https://api.nunchux.ai/v1/klingai/videos/image2video", {
    method: "POST",
    headers: {
      "X-API-Key": process.env.NUNCHUX_API_KEY,
      "Content-Type": "application/json",
      "User-Agent": "YourApp/1.0",
    },
    body: JSON.stringify({
      model_name: "kling-v3",
      prompt: "camera pushes in slowly, subtle ambient motion",
      mode: "pro",
      duration: "5",
      image: imgB64,
    }),
  }).then((r) => r.json());
  const taskId = submit.data.task_id;
  ```
</CodeGroup>

## Omni video

`POST https://api.nunchux.ai/v1/klingai/videos/omni-video`

Compose one clip from reference images, videos and elements. Use `kling-v3-omni`. At least one of `image_list`, `video_list` or `element_list` must be non-empty. You can send up to 7 reference inputs across the three lists. Refer to an input from the prompt with a 1-indexed placeholder in submission order: `<<<image_1>>>`, `<<<video_1>>>`, `<<<element_1>>>`.

<ParamField body="image_list" type="array">
  Reference images.

  <Expandable title="Item properties">
    <ParamField body="image_url" type="string" required>
      Public URL or raw base64 of the reference image, with no `data:image/...;base64,` prefix.
    </ParamField>

    <ParamField body="type" type="string">
      Optional sequence anchor. An entry with no type acts as an additional reference.

      Options: `first_frame`, `end_frame`
    </ParamField>
  </Expandable>
</ParamField>

<ParamField body="video_list" type="array">
  Reference videos to continue or to composite from.

  <Expandable title="Item properties">
    <ParamField body="video_url" type="string" required>
      Public URL of the reference video.
    </ParamField>
  </Expandable>
</ParamField>

<ParamField body="element_list" type="array">
  Reference elements, such as segmented objects or characters, to compose into the video.
</ParamField>

To plan a sequence of shots, see [Storyboard mode](#storyboard-mode).

<CodeGroup>
  ```bash cURL theme={"system"}
  # Placeholder URL: point it at your own publicly reachable file.
  curl https://api.nunchux.ai/v1/klingai/videos/omni-video \
    -H "X-API-Key: $NUNCHUX_API_KEY" \
    -H "Content-Type: application/json" \
    -H "User-Agent: YourApp/1.0" \
    -d '{
      "model_name": "kling-v3-omni",
      "prompt": "<<<image_1>>> walks through a neon-lit market, cinematic",
      "mode": "std",
      "duration": "5",
      "image_list": [{ "image_url": "https://example.com/example.jpg" }]
    }'
  ```

  ```python Python theme={"system"}
  # Placeholder URL: point it at your own publicly reachable file.
  import os, requests

  resp = requests.post(
      "https://api.nunchux.ai/v1/klingai/videos/omni-video",
      headers={"X-API-Key": os.environ["NUNCHUX_API_KEY"], "User-Agent": "YourApp/1.0"},
      json={
          "model_name": "kling-v3-omni",
          "prompt": "<<<image_1>>> walks through a neon-lit market, cinematic",
          "mode": "std",
          "duration": "5",
          "image_list": [{"image_url": "https://example.com/example.jpg"}],
      },
  )
  task_id = resp.json()["data"]["task_id"]
  ```

  ```javascript JavaScript theme={"system"}
  // Placeholder URL: point it at your own publicly reachable file.
  const submit = await fetch("https://api.nunchux.ai/v1/klingai/videos/omni-video", {
    method: "POST",
    headers: {
      "X-API-Key": process.env.NUNCHUX_API_KEY,
      "Content-Type": "application/json",
      "User-Agent": "YourApp/1.0",
    },
    body: JSON.stringify({
      model_name: "kling-v3-omni",
      prompt: "<<<image_1>>> walks through a neon-lit market, cinematic",
      mode: "std",
      duration: "5",
      image_list: [{ image_url: "https://example.com/example.jpg" }],
    }),
  }).then((r) => r.json());
  const taskId = submit.data.task_id;
  ```
</CodeGroup>

## Motion control

`POST https://api.nunchux.ai/v1/klingai/videos/motion-control`

Drive a still character with the motion of a reference video. Kling transfers the action from the driver clip and preserves the character's identity. Use `kling-v3`. Do not send `prompt` or `duration`. The output length follows the reference video.

<ParamField body="image_url" type="string" required>
  Public URL or raw base64 of the still character to animate, with no `data:image/...;base64,` prefix.
</ParamField>

<ParamField body="video_url" type="string" required>
  Public URL of the driver clip whose motion is transferred onto the character.
</ParamField>

<ParamField body="character_orientation" type="string" required>
  How the character reference is provided. `image` expects a still image in `image_url` (cap: 10 s). `video` expects a video in `image_url` (cap: 30 s).

  Options: `image`, `video`
</ParamField>

<ParamField body="input_video_seconds" type="integer" required>
  Length of the driver clip in seconds. This value drives per-second billing, so declare it accurately.

  Range: 1-10.
</ParamField>

<Note>
  Billing

  Kling bills per second of generated output. If you under-declare `input_video_seconds`, a follow-up charge is applied after the job completes. If you over-declare, the difference is not refunded automatically.
</Note>

<CodeGroup>
  ```bash cURL theme={"system"}
  # Placeholder URLs: point them at your own publicly reachable files.
  curl https://api.nunchux.ai/v1/klingai/videos/motion-control \
    -H "X-API-Key: $NUNCHUX_API_KEY" \
    -H "Content-Type: application/json" \
    -H "User-Agent: YourApp/1.0" \
    -d '{
      "model_name": "kling-v3",
      "mode": "std",
      "image_url": "https://example.com/example.jpg",
      "video_url": "https://example.com/example.mp4",
      "character_orientation": "image",
      "input_video_seconds": 5
    }'
  ```

  ```python Python theme={"system"}
  # Placeholder URLs: point them at your own publicly reachable files.
  import os, requests

  resp = requests.post(
      "https://api.nunchux.ai/v1/klingai/videos/motion-control",
      headers={"X-API-Key": os.environ["NUNCHUX_API_KEY"], "User-Agent": "YourApp/1.0"},
      json={
          "model_name": "kling-v3",
          "mode": "std",
          "image_url": "https://example.com/example.jpg",
          "video_url": "https://example.com/example.mp4",
          "character_orientation": "image",
          "input_video_seconds": 5,
      },
  )
  task_id = resp.json()["data"]["task_id"]
  ```

  ```javascript JavaScript theme={"system"}
  // Placeholder URLs: point them at your own publicly reachable files.
  const submit = await fetch("https://api.nunchux.ai/v1/klingai/videos/motion-control", {
    method: "POST",
    headers: {
      "X-API-Key": process.env.NUNCHUX_API_KEY,
      "Content-Type": "application/json",
      "User-Agent": "YourApp/1.0",
    },
    body: JSON.stringify({
      model_name: "kling-v3",
      mode: "std",
      image_url: "https://example.com/example.jpg",
      video_url: "https://example.com/example.mp4",
      character_orientation: "image",
      input_video_seconds: 5,
    }),
  }).then((r) => r.json());
  const taskId = submit.data.task_id;
  ```
</CodeGroup>

## Storyboard mode

Text-to-video and omni video accept a multi-shot storyboard. Set `multi_shot` to `true` and describe each shot in `multi_prompt`. The top-level `prompt` and `duration` are ignored. The total length is the sum of the per-shot durations. All constraints are validated before billing, so a malformed request returns a 400 with no charge.

<ParamField body="multi_shot" type="boolean" default={false}>
  Enable multi-shot storyboard mode.
</ParamField>

<ParamField body="shot_type" type="string">
  How shots are planned. Required when `multi_shot` is `true`.

  Options: `customize`, `intelligence`
</ParamField>

<ParamField body="multi_prompt" type="array">
  Per-shot instructions. Text-to-video accepts up to 6 shots and 15 seconds in total. Omni video caps the total at 30 seconds.

  <Expandable title="Item properties">
    <ParamField body="index" type="integer" required>
      Sequential from 1, with no gaps or duplicates.
    </ParamField>

    <ParamField body="prompt" type="string" required>
      Non-empty description for this shot.
    </ParamField>

    <ParamField body="duration" type="string" required>
      Integer seconds for this shot, sent as a string. Range 1-15 on text-to-video and 1-10 on omni video.
    </ParamField>
  </Expandable>
</ParamField>

```bash theme={"system"}
curl https://api.nunchux.ai/v1/klingai/videos/text2video \
  -H "X-API-Key: $NUNCHUX_API_KEY" \
  -H "Content-Type: application/json" \
  -H "User-Agent: YourApp/1.0" \
  -d '{
    "model_name": "kling-v3",
    "mode": "std",
    "multi_shot": true,
    "shot_type": "customize",
    "multi_prompt": [
      { "index": 1, "prompt": "wide establishing shot at dawn, fog over a mountain lake", "duration": "5" },
      { "index": 2, "prompt": "close-up on a ripple spreading across the water surface", "duration": "5" }
    ]
  }'
```

## Poll a task

`GET https://api.nunchux.ai/v1/klingai/videos/{capability}/{task_id}`

`{capability}` is one of `text2video`, `image2video`, `omni-video` and `motion-control`. Poll the same capability that you submitted to. A `task_id` polled at another capability's route returns 404.

Poll every 5 to 10 seconds at first, then back off to 15 to 30 seconds. Polls do not use credits and do not count toward your concurrency limit. `task_status` moves from `submitted` to `processing`, then to `succeed` or `failed`. A task handle expires after 7 days. Output URLs expire within hours, so download the output as soon as the task succeeds.

<ResponseField name="code" type="integer" required>
  0 on success. A non-zero value indicates a Kling error.
</ResponseField>

<ResponseField name="message" type="string" required>
  Human-readable status, such as `SUCCEED`.
</ResponseField>

<ResponseField name="data" type="object" required>
  The task payload.

  <Expandable title="Item properties">
    <ResponseField name="task_id" type="string" required>
      The task handle. Poll the same capability that you submitted to with it.
    </ResponseField>

    <ResponseField name="task_status" type="string" required>
      `submitted`, `processing`, `succeed` or `failed`.
    </ResponseField>

    <ResponseField name="task_status_msg" type="string">
      Explains why a job failed. Show it to your users.
    </ResponseField>

    <ResponseField name="task_result.videos" type="array">
      Present once `task_status` is `succeed`.

      <Expandable title="Item properties">
        <ResponseField name="url" type="string">
          URL of the output. It expires within hours, so download promptly.
        </ResponseField>

        <ResponseField name="duration" type="string">
          Length of the generated clip in seconds.
        </ResponseField>
      </Expandable>
    </ResponseField>
  </Expandable>
</ResponseField>

The submit response has the same shape, with `task_status` set to `submitted`. A poll response when the task has succeeded:

```json theme={"system"}
{
  "code": 0,
  "message": "SUCCEED",
  "data": {
    "task_id": "eyJhbGciOiJIUzI1NiIs...",
    "task_status": "succeed",
    "task_result": {
      "videos": [
        { "url": "https://v16-kling-fdl.klingai.com/...",
          "duration": "5.0" }
      ]
    }
  }
}
```

## Example

Submit a text-to-video job, poll it until it reaches a terminal status, then download the clip.

<CodeGroup>
  ```bash cURL theme={"system"}
  # 1. Submit
  SUBMIT=$(curl -sS --fail-with-body https://api.nunchux.ai/v1/klingai/videos/text2video \
    -H "X-API-Key: $NUNCHUX_API_KEY" \
    -H "Content-Type: application/json" \
    -H "User-Agent: YourApp/1.0" \
    -d '{
      "model_name": "kling-v3",
      "prompt": "A cinematic drone shot sweeping over snow-capped mountains at sunrise",
      "mode": "std",
      "duration": "5",
      "aspect_ratio": "16:9"
    }') || { echo "submit failed: $SUBMIT" >&2; exit 1; }
  TASK=$(jq -er '.data.task_id' <<<"$SUBMIT") || { echo "no task id in: $SUBMIT" >&2; exit 1; }

  # 2. Poll until the task reaches a terminal status
  DELAY=5
  TERMINAL=0
  for _ in $(seq 1 90); do
    RESP=$(curl -sS https://api.nunchux.ai/v1/klingai/videos/text2video/$TASK \
      -H "X-API-Key: $NUNCHUX_API_KEY" \
      -H "User-Agent: YourApp/1.0")
    STATUS=$(jq -r '.data.task_status' <<<"$RESP")
    case "$STATUS" in succeed|failed) TERMINAL=1; break ;; esac
    sleep "$DELAY"; DELAY=$(( DELAY * 2 > 30 ? 30 : DELAY * 2 ))
  done
  [ "$TERMINAL" = 1 ] || { echo "timed out (last status: $STATUS)" >&2; exit 1; }

  # 3. Check for failure
  [ "$STATUS" = "succeed" ] || { echo "Kling: status $STATUS, not succeed: $(jq -c '.data.task_status_msg // empty' <<<"$RESP")" >&2; exit 1; }

  # 4. Download
  curl -sSL -o kling.mp4 "$(jq -r '.data.task_result.videos[0].url' <<<"$RESP")"
  ```

  ```python Python theme={"system"}
  import os, time, requests

  BASE = "https://api.nunchux.ai"
  HEADERS = {
      "X-API-Key": os.environ["NUNCHUX_API_KEY"],
      "Content-Type": "application/json",
      "User-Agent": "YourApp/1.0",
  }

  # 1. Submit
  submit = requests.post(
      f"{BASE}/v1/klingai/videos/text2video",
      headers=HEADERS,
      json={
          "model_name": "kling-v3",
          "prompt": "A cinematic drone shot sweeping over snow-capped mountains at sunrise",
          "mode": "std",
          "duration": "5",
          "aspect_ratio": "16:9",
      },
  )
  if not submit.ok:
      raise RuntimeError(f"submit failed: HTTP {submit.status_code} {submit.text}")
  task_id = submit.json()["data"]["task_id"]

  # 2. Poll until the task reaches a terminal status
  TERMINAL = ("succeed", "failed")
  delay = 5
  for _ in range(90):
      task = requests.get(f"{BASE}/v1/klingai/videos/text2video/{task_id}", headers=HEADERS).json()
      if task["data"]["task_status"] in TERMINAL:
          break
      time.sleep(delay)
      delay = min(delay * 2, 30)
  else:
      raise TimeoutError("Kling task did not finish")

  # 3. Check for failure
  if task["data"]["task_status"] != "succeed":
      raise RuntimeError(f"Kling: status {task['data']['task_status']}, not succeed: {task['data'].get('task_status_msg')}")

  # 4. Download
  with requests.get(task["data"]["task_result"]["videos"][0]["url"], stream=True, timeout=300) as r:
      r.raise_for_status()
      with open("kling.mp4", "wb") as f:
          for chunk in r.iter_content(1 << 14):
              f.write(chunk)
  ```

  ```javascript JavaScript theme={"system"}
  import { writeFile } from "node:fs/promises";

  const BASE = "https://api.nunchux.ai";
  const HEADERS = {
    "X-API-Key": process.env.NUNCHUX_API_KEY,
    "Content-Type": "application/json",
    "User-Agent": "YourApp/1.0",
  };
  const sleep = (ms) => new Promise((r) => setTimeout(r, ms));

  // 1. Submit
  const submitRes = await fetch(`${BASE}/v1/klingai/videos/text2video`, {
    method: "POST",
    headers: HEADERS,
    body: JSON.stringify({
      model_name: "kling-v3",
      prompt: "A cinematic drone shot sweeping over snow-capped mountains at sunrise",
      mode: "std",
      duration: "5",
      aspect_ratio: "16:9",
    }),
  });
  if (!submitRes.ok) throw new Error(`submit failed: HTTP ${submitRes.status} ${await submitRes.text()}`);
  const taskId = (await submitRes.json()).data.task_id;

  // 2. Poll until the task reaches a terminal status
  const TERMINAL = ["succeed", "failed"];
  let task;
  let delay = 5_000;
  for (let i = 0; i < 90; i++) {
    task = await fetch(`${BASE}/v1/klingai/videos/text2video/${taskId}`, { headers: HEADERS }).then((r) =>
      r.json()
    );
    if (TERMINAL.includes(task?.data?.task_status)) break;
    await sleep(delay);
    delay = Math.min(delay * 2, 30_000);
  }
  if (!TERMINAL.includes(task?.data?.task_status)) throw new Error("Kling task did not finish");

  // 3. Check for failure
  if (task.data.task_status !== "succeed") {
    throw new Error(`Kling: status ${task.data.task_status}, not succeed: ${JSON.stringify(task.data.task_status_msg)}`);
  }

  // 4. Download
  const res = await fetch(task.data.task_result.videos[0].url);
  if (!res.ok) throw new Error(`download failed: HTTP ${res.status}`);
  await writeFile("kling.mp4", Buffer.from(await res.arrayBuffer()));
  ```
</CodeGroup>

## Tips

* Poll; do not busy-wait. Poll every 5 to 10 seconds at first, then back off to 15 to 30 seconds.
* Expect text-to-video and image-to-video to finish in 60 to 120 seconds. A `kling-v3` `std` 5-second clip takes about 45 seconds. Motion control and omni video run 2 to 6 minutes, and omni video takes longer with more shots.
* Set a client timeout of at least 5 minutes for `std` text-to-video and image-to-video. Use at least 10 minutes for `pro`, `4k`, motion control and omni video.
* Download outputs as soon as the task succeeds. Output URLs expire within hours.
* Keep prompts concrete and visual. Describe what the camera sees, not abstract concepts.
* Use `negative_prompt` to rule out unwanted elements such as blur, text overlays and watermarks.
* Set `sound: "on"` on `kling-v3` to add ambient audio with no post-processing. In `4k` mode, audio is always included.
* Send base64 images as raw bytes with no `data:image/...;base64,` prefix. Public URLs must be reachable by Kling's servers: no auth walls and no localhost.
* Use a clear, well-composed starting frame for image-to-video. Kling extends what it sees, so a blurry or cluttered input produces inconsistent motion.
* Use a clean, well-lit character image for motion control. Identity preservation is only as good as the source.
* Pass a `callback_url` on text-to-video or image-to-video to receive status updates without polling.

## Errors and limits

A 200 means that Kling accepted the job and billing started, not that the job succeeded. Do not resubmit after a 200; a second submit creates and bills a second job. Retry only on a network or timeout error that arrives before the submit response.

| Response | Meaning | What to do |
| - | - | - |
| `400` | Invalid request: a bad model name, an unsupported mode or duration, or a missing field. | Read the message and fix the request. A 400 is rejected before billing, so it is never charged. |
| `401` | Authentication failed, or the task handle expired. | Check your API key and the `task_id`. Task handles expire after 7 days. |
| `200` with `code` `1201` | Kling could not read an asset. Usually an `image_url` or `video_url` that is not publicly reachable, or an image sent with a `data:` prefix. | The submit was billed. Fix the asset (public URL or raw base64) and submit again. Open a support ticket for a refund. |
| `200` with `task_status` `failed` | The job failed after Kling accepted it. | No action is needed. Credits are refunded automatically. Show `task_status_msg` to your users. |
| Other `4xx` or `5xx` | Forwarded from Kling: content moderation, bad parameters or another upstream reason. The response body explains the cause. | Retry only when the cause is transient. |

Retry a `5xx` or a transient `429` with backoff. Never retry another `4xx`; the request is invalid as sent. For the general error contract, see [Error codes](/errors). For the concurrency caps and request limits, see [Rate limits](/rate-limits). Every job counts toward your plan's simultaneous-jobs cap; polls do not. Credits are deducted at submit and refunded when a job fails; see [Credits & pricing](/credits-pricing) and the [Partner models overview](/partner-models/overview).

Kling-specific limits:

* `prompt` and `negative_prompt`: up to 2500 characters.
* `duration`: 3 to 15 seconds. In storyboard mode, text-to-video accepts up to 6 shots and 15 seconds in total, and omni video accepts up to 30 seconds in total.
* Omni video: up to 7 reference inputs across `image_list`, `video_list` and `element_list`.
* Motion control: `input_video_seconds` from 1 to 10, and `mode` is `std` or `pro`.
* Voice control is not supported on `kling-v3`. Sending `voice_control` or `voice_list` returns a 400.

## Migrating from /v1/video/kling/\*

Kling used to live under `/v1/video/kling/*`. It now lives under `/v1/klingai/videos/*`, a faithful mirror of Kling's own API: same request bodies, same response shapes, same error codes. If you call Kling directly today, you can point at us by swapping the base URL and using your Nunchux key as the Bearer token. Existing `/v1/video/kling/*` integrations keep working; migrate when convenient.

<Info>
  Two things that are not a find-and-replace

  * Polling moved from one URL to one per capability. There is no `/v1/klingai/videos/tasks/{task_id}`. Instead of a single `GET /v1/video/kling/tasks/{task_id}`, poll the endpoint you submitted to. For example, a job from `POST /v1/klingai/videos/text2video` is polled at `GET /v1/klingai/videos/text2video/{task_id}`. A shared poll helper needs to carry the submit path through; a `task_id` polled at another capability's URL returns 404.
  * One path was renamed beyond the prefix change: `video-effects` → `effects`. The rest keep their trailing segment.
</Info>

| Legacy (superseded) | Current |
| - | - |
| `POST /v1/video/kling/text2video` | `POST /v1/klingai/videos/text2video` |
| `POST /v1/video/kling/image2video` | `POST /v1/klingai/videos/image2video` |
| `POST /v1/video/kling/omni-video` | `POST /v1/klingai/videos/omni-video` |
| `POST /v1/video/kling/motion-control` | `POST /v1/klingai/videos/motion-control` |
| `POST /v1/video/kling/video-effects` | `POST /v1/klingai/videos/effects` |
| `POST /v1/video/kling/image-recognize` | `POST /v1/klingai/videos/image-recognize` |
| `GET /v1/video/kling/tasks/{task_id}` | `GET /v1/klingai/videos/{capability}/{task_id}` |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.