---
title: "Kling 4.0"
description: "Kling 4.0 makes video from text, from one first frame, or from reference media. It renders at 720p or 1080p, in any whole length from 3 to 30 seconds. It is the Kling model for new video: it keeps a person or a product the same from shot to shot."
url: https://docs.roasos.com/models/video/kling-4
---

# Kling 4.0

<Callout>
  Family 

  `video`

   · model 

  `kling-4`

   · catalog 

  `model-catalog@2026-10-06.v5`

   · pricing 

  `customer-pricing@2026-11-05.v15`

  .
</Callout>

## Endpoint

```http
POST https://api.roasos.com/v1/video/kling-4
```

Send your API key as a bearer token. The key needs the scope `api:write`. Every paid request also needs an `Idempotency-Key` header.

The envelope fields `quote_only`, `quote_id`, `webhook_url` and `wait_timeout_seconds` behave the same on each model endpoint. Refer to [Jobs](/concepts/jobs) and [Quotes and credits](/concepts/quotes-and-credits).

## Good at

Kling 4.0 is the only Kling video model since 2026-10-29. It replaced Kling 3, Kling 3 Pro, Kling O3, Kling O3 Pro and both 4K tiers, and those endpoints are removed.

Use it to keep one or more people, or a product, the same across a clip. Send each person or product as an element: one frontal image and up to 3 more views. The prompt names each element once as `@Element1`, `@Element2`, and so on. Send `avatar_id` to attach a saved person as an element.

An element can also be a video of the person or object (`video_url`, 3 to 8 seconds, at least 700 px on each side) instead of images. Up to 3 elements can be video elements, and their seconds count in the 30-second clip total and in the price. Up to 3 elements can carry a `voice_id`: a Kling voice that you made with fal's create-voice endpoint. The element then speaks in that voice.

To change an existing video, use [Kling 4.0 Edit](/models/video-edit/kling-4).

The reference lane takes up to 10 images (`references`, named `@Image1..N`), 5 video clips (`video_urls`, named `@Video1..N`) and 7 elements. A first frame (`image_url`) on the reference lane is the literal opening frame, and it counts as one of the 10 images. Each reference clip must be 3 to 30 seconds long, and all clips together must be 30 seconds or less. A reference clip shows motion or style to follow. It does not edit that clip.

Use `multi_prompt` for a board of shots in one generation: up to 8 shots, each with its own prompt (512 characters or less) and its own length. The shot lengths must add up to `duration_seconds`.

Text-to-video takes 9:16, 16:9, 1:1 and 21:9. The reference lane also takes `auto`, which follows the references. Image-to-video takes its framing from the first frame, and `end_image_url` sets the last frame.

The provider documents no audio switch. The endpoint does not accept `generate_audio`.

## Pricing

The unit is one **clip**. Pricing key: `kling-4`.

| Request     | Credits |
| ----------- | ------- |
| `3s_1080p`  | 9       |
| `17s_1080p` | 48      |
| `30s_1080p` | 85      |

The credits come from the roasOS pricing table. No other field changes the price. To get the exact price of your request, send it with `quote_only: true` first.

## Modes

* `text-to-video`
* `image-to-video`
* `reference-to-video`

roasOS selects the mode from the media fields of your request. You do not send a mode.

Provider endpoints: `kling/v4/image-to-video` · `kling/v4/reference-to-video` · `kling/v4/text-to-video`.

## Request fields

| Field                     | Type         | Required | Default | Rule                                                                                                                                                            |
| ------------------------- | ------------ | -------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `aspect_ratio`            | enum         | no       | `16:9`  | `9:16`, `16:9`, `1:1`, `21:9`, `auto`; only in `text-to-video`, `reference-to-video`                                                                            |
| `avatar_id`               | string       | no       | —       | UUID; only in `reference-to-video`                                                                                                                              |
| `avatar_outfit`           | string       | no       | —       | ≤ 500 characters                                                                                                                                                |
| `duration_seconds`        | integer enum | no       | `5`     | `3`, `4`, `5`, `6`, `7`, `8`, `9`, `10`, `11`, `12`, `13`, `14`, `15`, `16`, `17`, `18`, `19`, `20`, `21`, `22`, `23`, `24`, `25`, `26`, `27`, `28`, `29`, `30` |
| `elements`                | object\[]    | no       | —       | 1–7 items; each `{ frontal_image_url, reference_image_urls, video_url, voice_id }`; only in `reference-to-video`                                                |
| `end_image_url`           | string       | no       | —       | https URL; ≤ 4,096 characters; only in `image-to-video`                                                                                                         |
| `extend_of_generation_id` | string       | no       | —       | UUID                                                                                                                                                            |
| `image_url`               | string       | no       | —       | https URL; ≤ 4,096 characters; only in `image-to-video`                                                                                                         |
| `mode`                    | enum         | no       | —       | `text-to-video`, `image-to-video`, `reference-to-video`                                                                                                         |
| `multi_prompt`            | object\[]    | no       | —       | 1–8 items; each `{ prompt, duration_seconds }`                                                                                                                  |
| `prompt`                  | string       | no       | —       | ≥ 1 character; ≤ 2,500 characters                                                                                                                               |
| `quote_id`                | string       | no       | —       | UUID                                                                                                                                                            |
| `quote_only`              | boolean      | no       | `false` | —                                                                                                                                                               |
| `references`              | string\[]    | no       | —       | ≤ 10 items; only in `reference-to-video`                                                                                                                        |
| `resolution`              | enum         | no       | `1080p` | `720p`, `1080p`                                                                                                                                                 |
| `scene_id`                | string       | no       | —       | UUID                                                                                                                                                            |
| `video_urls`              | string\[]    | no       | —       | 1–5 items; only in `reference-to-video`                                                                                                                         |
| `wait_timeout_seconds`    | integer      | no       | `0`     | 0–60                                                                                                                                                            |
| `webhook_url`             | string       | no       | —       | https URL; ≤ 4,096 characters                                                                                                                                   |

The schema is strict. A field that is not in this table gets `400 unsupported_option`. The field names are the roasOS role names. One role has one name in every family.

## Media

This endpoint accepts these media roles:

* `avatar_id` — identity; the saved character reference sheet, with a derived portrait only where the model requires a single face or source frame
* `elements` — identity; one frontal image plus more views, per person or object
* `references` — style and appearance references
* `image_url` — first frame (or the single source image)
* `end_image_url` — last frame
* `scene_id` — first frame — a scene this workspace owns
* `video_urls` — reference videos

| Role            | Items | Maximum size | Content types                           | Duration |
| --------------- | ----- | ------------ | --------------------------------------- | -------- |
| `end_image_url` | ≤ 1   | 10 MB        | `image/jpeg`, `image/png`, `image/webp` | —        |
| `image_url`     | ≤ 1   | 10 MB        | `image/jpeg`, `image/png`, `image/webp` | —        |
| `references`    | ≤ 10  | 10 MB        | `image/jpeg`, `image/png`, `image/webp` | —        |
| `video_urls`    | ≤ 5   | 100 MB       | `video/mp4`                             | 3–30 s   |

Every other role is refused: `product_id`, `product_images`, `reference_images`, `scene_image`, `video_url`, `audio_urls`, `audio_url`, `voice_id`. A refused role gets `400 unsupported_option`.

<Callout>
  Each media URL must be an immutable object on 

  `https://cdn.roasos.com`

   that your workspace owns. Any other URL gets 

  `400 reference_media_not_workspace_owned`

  . Refer to 

  [Reference media and uploads](/concepts/reference-media)

   to learn how to put a file there.
</Callout>

## Prompt

Limit 2,500 characters for `prompt`, and 512 characters for each `multi_prompt` shot.

* Think in shots: "Shot 1 (3s): …". Define each subject and the setting in shot 1, and keep them the same.
* In each shot, write the subject, the action and its motion path, the setting, the camera, and the mood. Use one action and one camera move for each shot.
* Write motion as physics: the main movement, then the movement of hair, fabric or particles.
* Write each spoken line in quotes with a speaker label and a voice: \[Mia, warm]: "Okay, this texture."
* In image-to-video, write only what changes. The first frame sets the scene.
* Use each `@Image`, `@Video` and `@Element` tag once. The platform adds a missing `@Image` or `@Video` tag at the end of a single prompt. You must write each `@Element` tag yourself.
* End with "No text overlays, no captions." Add text in post-production.

## Not exposed

These catalog entries describe omitted inputs, pinned values and source constraints. They do not imply that the provider accepts every listed field.

* `video_urls` — these values are not available: `MOV containers`.
* `middle_image_urls` — not accepted by this public endpoint.
* `voice_ids` — not accepted by this public endpoint.

## Result

The result kind is `video`. Always present: `url`.

```json
{
  "data": {
    "id": "gen_…",
    "kind": "video",
    "model": "kling-4",
    "status": "completed",
    "result": {
      "url": "https://cdn.roasos.com/workspaces/ws_01/result"
    }
  }
}
```

The files are immutable objects on `https://cdn.roasos.com`. A result URL is a valid media input for your next request.

## Example

The three steps are always the same: quote, run, poll.

<CodeBlockTabs defaultValue="cURL">
  <CodeBlockTabsList>
    <CodeBlockTabsTrigger value="cURL">
      cURL
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="JavaScript">
      JavaScript
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="Python">
      Python
    </CodeBlockTabsTrigger>
  </CodeBlockTabsList>

  <CodeBlockTab value="cURL">
    ```bash
    # 1. Quote. Nothing is spent.
    curl -X POST https://api.roasos.com/v1/video/kling-4 \
      -H "Authorization: Bearer roasos_sk_live_..." \
      -H "Idempotency-Key: 0f1c9e2a-kling-4-0001" \
      -H "Content-Type: application/json" \
      -d '{"quote_only":true,"prompt":"A woman holds the bottle at eye level in a bright kitchen, morning light, static camera."}'
    # 200 { "data": { "kind": "quote", "quote_id": "q_7Hk...", "credits": 9, "expires_at": "...",
    #        "confirmation": { "model": "kling-4", "example": "3s_1080p" } } }

    # 2. Run. Same body, same Idempotency-Key, quote_id in place of quote_only.
    curl -X POST https://api.roasos.com/v1/video/kling-4 \
      -H "Authorization: Bearer roasos_sk_live_..." \
      -H "Idempotency-Key: 0f1c9e2a-kling-4-0001" \
      -H "Content-Type: application/json" \
      -d '{"quote_id":"q_7Hk...","prompt":"A woman holds the bottle at eye level in a bright kitchen, morning light, static camera."}'
    # 201 { "data": { "id": "gen_...", "status": "queued", "credits_reserved": 9 } }

    # 3. Poll, then read the result.
    curl https://api.roasos.com/v1/jobs/gen_.../status -H "Authorization: Bearer roasos_sk_live_..."
    curl https://api.roasos.com/v1/jobs/gen_.../result -H "Authorization: Bearer roasos_sk_live_..."
    ```
  </CodeBlockTab>

  <CodeBlockTab value="JavaScript">
    ```javascript
    const key = process.env.ROASOS_API_KEY;
    const url = "https://api.roasos.com/v1/video/kling-4";
    const idempotencyKey = "0f1c9e2a-kling-4-0001";
    const input = {
      "prompt": "A woman holds the bottle at eye level in a bright kitchen, morning light, static camera."
    };

    const post = (payload) =>
      fetch(url, {
        method: "POST",
        headers: {
          Authorization: `Bearer ${key}`,
          "Idempotency-Key": idempotencyKey,
          "Content-Type": "application/json",
        },
        body: JSON.stringify(payload),
      }).then((response) => response.json());

    // 1. Quote. Nothing is spent.
    const quote = await post({ quote_only: true, ...input });
    console.log(quote.data.credits);

    // 2. Run.
    const job = await post({ quote_id: quote.data.quote_id, ...input });

    // 3. Poll, then read the result.
    let status = "queued";
    while (status !== "completed" && status !== "failed" && status !== "canceled") {
      await new Promise((done) => setTimeout(done, 3000));
      const poll = await fetch(`${url.split("/v1")[0]}/v1/jobs/${job.data.id}/status`, {
        headers: { Authorization: `Bearer ${key}` },
      }).then((response) => response.json());
      status = poll.data.status;
    }

    const result = await fetch(`${url.split("/v1")[0]}/v1/jobs/${job.data.id}/result`, {
      headers: { Authorization: `Bearer ${key}` },
    }).then((response) => response.json());
    console.log(result.data.result);
    ```
  </CodeBlockTab>

  <CodeBlockTab value="Python">
    ```python
    import os, time, requests

    key = os.environ["ROASOS_API_KEY"]
    url = "https://api.roasos.com/v1/video/kling-4"
    headers = {
        "Authorization": f"Bearer {key}",
        "Idempotency-Key": "0f1c9e2a-kling-4-0001",
    }
    payload = {
        "prompt": "A woman holds the bottle at eye level in a bright kitchen, morning light, static camera."
    }

    # 1. Quote. Nothing is spent.
    quote = requests.post(url, headers=headers, json={"quote_only": True, **payload}).json()
    print(quote["data"]["credits"])

    # 2. Run.
    job = requests.post(url, headers=headers, json={"quote_id": quote["data"]["quote_id"], **payload}).json()
    job_id = job["data"]["id"]

    # 3. Poll, then read the result.
    jobs = "https://api.roasos.com/v1/jobs"
    status = "queued"
    while status not in ("completed", "failed", "canceled"):
        time.sleep(3)
        status = requests.get(f"{jobs}/{job_id}/status", headers=headers).json()["data"]["status"]

    result = requests.get(f"{jobs}/{job_id}/result", headers=headers).json()
    print(result["data"]["result"])
    ```
  </CodeBlockTab>
</CodeBlockTabs>

## See also

* [`llms.txt` for this model](https://docs.roasos.com/models/video/kling-4/llms.txt) — the same facts as one plain-text file for an agent.
* `GET https://api.roasos.com/v1/catalog/kling-4` — the machine-readable form: enums, defaults, limits and prices. Refer to the [API reference](/api-reference/overview).
* [Jobs](/concepts/jobs) — how to poll a job and read its result.
* Provider endpoints: `kling/v4/image-to-video` · `kling/v4/reference-to-video` · `kling/v4/text-to-video`.
