> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.beatapi.io/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.beatapi.io/_mcp/server.

# Kling 3.0 Motion Control

POST https://api.beatapi.io/v1/videos/tasks
Content-Type: application/json

Make the character in one image repeat the motion of one 3–30 second video at 720p or 1080p, and choose whether the background comes from the video or the image; the result is up to 10 seconds with image orientation and up to 30 seconds with video orientation.- Asynchronous: the create call returns a task ID; the video arrives when the task succeeds.
- Two inputs, both required: one character image and one motion video of 3 to 30 seconds. The character in the image performs the motion of the video.
- `character_orientation` sets what the character faces follow and how long the result can be: at most 10 seconds with `image` (the default), at most 30 seconds with `video`.
- There is no `duration` field: the result is as long as your motion video, up to the limit of the orientation you chose.
- Billed per second of the finished video, settled from the amount held at create, at 720p or 1080p. A failed task costs nothing.Both Kling motion-control models are not on the live `GET /v1/media/models` list at the time of writing. While a model is off that list, creating a task for it is answered with `503 processing_unavailable` and nothing is charged. Check the list before you build on it.POST/v1/videos/tasksTry itOpens this model in the API Explorer. Enter your BeatAPI API key to create a task: the call is real and is billed to your own balance.## Model IDsTwo models share this endpoint and the same inputs. The values below come straight from the gateway contract; only Kling 3.0 Motion Control accepts `background_source`.| Model ID                     | `resolution`    | `character_orientation` | `background_source`          | Character images | Motion videos | Choose it when                                                                                                                                     |
| ---------------------------- | --------------- | ----------------------- | ---------------------------- | ---------------- | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| `kling-2.6-motion-control`   | `720p`, `1080p` | `image`, `video`        | —                            | 1                | 1             | You want the lower price per second, and the background from the motion video is fine.                                                             |
| **`kling-3-motion-control`** | `720p`, `1080p` | `image`, `video`        | `input_video`, `input_image` | 1                | 1             | You want to choose whether the background comes from the motion video or from the character image (`background_source`). It costs more per second. |## Endpoint and authentication`POST https://api.beatapi.io/v1/videos/tasks`Authorization
string
RequiredYour BeatAPI API key, sent as `Bearer <BEATAPI_API_KEY>`. Create keys in the [Dashboard](https://beatapi.io/dashboard). Keep keys on your server; never ship them in a browser or mobile app.Send the body as JSON with `Content-Type: application/json`. `Idempotency-Key` is optional: send a unique value (at most 255 characters) per logical request and reuse it only to retry that exact body. Within 24 hours a retry with the same key and body returns the first response again with `Idempotent-Replayed: true`; the same key with a different body returns `409 idempotency_conflict`. Responses other than 2xx are not stored, so a retry after a 400 or 402 runs again.## How a request works`POST` the parameters below to the endpoint. A `201` means the task was accepted and its price reserved from your balance; the work runs in the background. Save `data.id`.Call `GET /v1/tasks/{task_id}` and wait `data.poll_after_seconds` between calls (5 s for images, 8 s for videos) until `status` is `succeeded` or `failed`. Or register a [webhook](/webhooks) for `task.succeeded` and `task.failed`; keep polling as the fallback, because a task stopped by the processing timeout sends no event.On `succeeded`, download every `data.output.media[].url` and store the files you need; BeatAPI does not promise how long result URLs stay available. On `failed`, read `data.error_code` and `data.error_message`: the reserved amount was returned to your balance, and `data.retryable` says whether sending the same request again can succeed.## Input modes| Mode            | Required inputs                                         | Notes                                                                                                                                                           |
| --------------- | ------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Motion transfer | `model`, `images` (1 link), `reference_videos` (1 link) | The character image performs the motion of the video. `prompt` is optional. Nothing else is accepted: no `duration`, `aspect_ratio`, audio or extra references. |## Request parametersmodel
string
RequiredThe model to run.**Value**: `kling-3-motion-control`**If the value is wrong**- Missing: `"model" is required`
- An image model sent to this endpoint: `"model" nano-banana-2 is served by POST /v1/images/tasks, not this endpoint`prompt
string
OptionalOptional text guidance about the character or the scene. The motion itself comes from the video.**Length**: ≤ 2500 characters- Length is counted in characters (a CJK character counts as one). An empty string is accepted.**If the value is wrong**- Longer than the limit: `"prompt" has an unsupported length`images
string[]
RequiredThe character image: exactly one link. This is who performs the motion.**Items**: 1–1  \
Each item is a public `https://` URL. For a local file, [upload it first](/quick-guide#upload-an-input-file) with `POST /v1/files` and pass the returned `data.url`.- Send a list with one public HTTPS link; a local file can be uploaded with `POST /v1/files` and the returned `data.url` used here.
- Image size, format and dimensions are not checked when the task is created.**If the value is wrong**- Missing: `"images" is required`
- An empty list: `"images" requires at least 1 item`
- More than one link: `"images" accepts at most 1 items`
- Not an https\:// link: `"images[0]" must be a public HTTPS URL`reference_videos
string[]
RequiredThe motion video: exactly one link. The character in `images` repeats the motion in this video.**Items**: 1–1  \
Each item is a public `https://` URL. For a local file, [upload it first](/quick-guide#upload-an-input-file) with `POST /v1/files` and pass the returned `data.url`.- Its length must be 3 to 30 seconds (see Media limits). The result is as long as this video, up to the limit set by `character_orientation`.
- The gateway reads the length from the file header with byte-range requests before the task is created, when the host allows it.
- A local file can be uploaded with `POST /v1/files` (video up to 100 MB) and the returned `data.url` used here.You are billed for the seconds of video that are produced, so trim the clip to the motion you need. With `character_orientation: "image"` the result is at most 10 seconds.**If the value is wrong**- Missing: `"reference_videos" is required`
- An empty list: `"reference_videos" requires at least 1 item`
- More than one link: `"reference_videos" accepts at most 1 items`
- Not an https\:// link: `"reference_videos[0]" must be a public HTTPS URL`
- Shorter than 3 or longer than 30 seconds: `reference_videos[0] is 2 seconds long; this model takes reference video of 3 to 30 seconds.`
- The host answers with an error or an empty file: `reference_videos[0] could not be downloaded: the URL returned HTTP 404.`resolution
string
OptionalDefault: 720pOutput resolution. It is also the price tier: 1080p costs more per second than 720p.**Allowed values**: `720p`, `1080p`  \
**Default**: `720p`- Lower case only. If you leave it out, `720p` is used.**If the value is wrong**- A value not in the list (for example `480p`, `4k` or `1080P`): `"resolution" must be one of: [720p 1080p]`character_orientation
string
OptionalDefault: imageWhether the character’s orientation follows the character image (`image`) or the motion video (`video`).**Allowed values**: `image`, `video`  \
**Default**: `image`- It also sets the longest result: at most 10 seconds with `image`, at most 30 seconds with `video` (see How the orientation sets the length).
- If you leave it out, `image` is used, so a motion video longer than 10 seconds needs `video` explicitly.
- It sets how much is held on your balance when the task is created: the longest result the orientation allows (see Billing).**If the value is wrong**- A value not in the list: `"character_orientation" must be one of: [image video]`background_source
string
OptionalDefault: input_videoWhere the background of the result comes from: `input_video` keeps the background of the motion video, `input_image` keeps the background of the character image.**Allowed values**: `input_video`, `input_image`  \
**Default**: `input_video`- If you leave it out, `input_video` is used.
- Only Kling 3.0 Motion Control accepts this field.**If the value is wrong**- A value not in the list: `"background_source" must be one of: [input_video input_image]`The gateway also checks these combinations before creating the task:- exactly one character image and one uploaded motion video of 3 to 30 seconds; with character\_orientation image the output is at most 10 seconds## Input media limitsThe gateway reads every input before the task is created and refuses one outside these limits with a `400 bad_request` that names the input. Nothing is charged.| Media  | Limits checked before the task is created |
| ------ | ----------------------------------------- |
| Videos | each 3–30 s                               |- Only the motion video is checked against a length limit when the task is created: 3 to 30 seconds, measured from the file header with a 0.1-second tolerance. The message names the input by position, counting from 0 like the field errors: `reference_videos[0] is 45 seconds long; this model takes reference video of 3 to 30 seconds.`
- A clip whose length cannot be read (the host does not allow byte-range requests, or the file is not an MP4 or MOV) is not refused at this step and is not measured. The create call is refused only when the host answers with an error or an empty file, for example `reference_videos[0] could not be downloaded: the URL returned HTTP 404.`
- Use `image` with clips of up to 10 seconds. For a motion video longer than 10 seconds, send `character_orientation: "video"`.
- Image size, format and dimensions are not checked when the task is created.
- Have a local file? Upload it with `POST /v1/files` (png, jpg, jpeg or webp images up to 50 MB; mp4 or mov video up to 100 MB) and pass the returned `data.url`.## How the orientation sets the lengthThere is no `duration` field. The result is as long as your motion video, and `character_orientation` caps it.| `character_orientation` | The character’s orientation follows | Longest result | Held on your balance at create            |
| ----------------------- | ----------------------------------- | -------------- | ----------------------------------------- |
| `image` (default)       | The character image                 | 10 seconds     | 10 seconds at the price of the resolution |
| `video`                 | The motion video                    | 30 seconds     | 30 seconds at the price of the resolution |- The amount is settled when the task finishes from the usage reported for the video that was produced, normally the list rate per second for its length; the difference to the amount held is returned. If no usage is reported, the amount held is charged. See Billing.## Fields this model does not acceptSending one of these is refused before anything is charged, instead of being ignored:- `duration`, `aspect_ratio`, `generate_audio`, `seed`, `task_type`, `output_format`, `reference_images`, `reference_audios`, `quality`, `sound`, `watermark` and any other field of the other video models → `field "duration" is not supported by model kling-3-motion-control` (with the field you sent).
- Any field the API does not know at all → `unknown field: <name>`.## Request examples```bash title="cURL"
curl --request POST \
  --url https://api.beatapi.io/v1/videos/tasks \
  --header "Authorization: Bearer $BEATAPI_API_KEY" \
  --header 'Content-Type: application/json' \
  --data '{
  "model": "kling-3-motion-control",
  "prompt": "Keep the singer’s look and outfit; follow the movement of the video.",
  "images": [
    "https://media.beatapi.io/samples/neon-singer.png"
  ],
  "reference_videos": [
    "https://your-cdn.example.com/clips/source.mp4"
  ],
  "resolution": "720p"
}'
``````python title="Python"
import os
import requests

response = requests.post(
    "https://api.beatapi.io/v1/videos/tasks",
    headers={"Authorization": f"Bearer {os.environ['BEATAPI_API_KEY']}"},
    json={
      "model": "kling-3-motion-control",
      "prompt": "Keep the singer’s look and outfit; follow the movement of the video.",
      "images": [
        "https://media.beatapi.io/samples/neon-singer.png"
      ],
      "reference_videos": [
        "https://your-cdn.example.com/clips/source.mp4"
      ],
      "resolution": "720p"
    },
    timeout=60,
)
body = response.json()
if not response.ok:
    raise RuntimeError(f"{response.status_code} {body['error']['code']}: {body['error']['message']}")
task = body["data"]
print(task["id"], task["status"])
``````javascript title="JavaScript"
const response = await fetch('https://api.beatapi.io/v1/videos/tasks', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.BEATAPI_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "model": "kling-3-motion-control",
    "prompt": "Keep the singer’s look and outfit; follow the movement of the video.",
    "images": [
      "https://media.beatapi.io/samples/neon-singer.png"
    ],
    "reference_videos": [
      "https://your-cdn.example.com/clips/source.mp4"
    ],
    "resolution": "720p"
  }),
});
const body = await response.json();
if (!response.ok) throw new Error(`${response.status} ${body.error.code}: ${body.error.message}`);
console.log(body.data.id, body.data.status);
```### Motion transferReplace the video link with your own MP4 or MOV of 3 to 30 seconds (there is no public sample video). `character_orientation` is left out, so `image` applies and the result is at most 10 seconds.```bash
curl --request POST \
  --url https://api.beatapi.io/v1/videos/tasks \
  --header "Authorization: Bearer $BEATAPI_API_KEY" \
  --header 'Content-Type: application/json' \
  --data '{
  "model": "kling-3-motion-control",
  "prompt": "Keep the singer’s look and outfit; follow the movement of the video.",
  "images": [
    "https://media.beatapi.io/samples/neon-singer.png"
  ],
  "reference_videos": [
    "https://your-cdn.example.com/clips/source.mp4"
  ],
  "resolution": "720p"
}'
```### 1080p, orientation follows the videoFor a clip longer than 10 seconds: `video` allows a result of up to 30 seconds. Replace the video link with your own clip. A longer result holds more on your balance when the task is created.```bash
curl --request POST \
  --url https://api.beatapi.io/v1/videos/tasks \
  --header "Authorization: Bearer $BEATAPI_API_KEY" \
  --header 'Content-Type: application/json' \
  --data '{
  "model": "kling-3-motion-control",
  "images": [
    "https://media.beatapi.io/samples/neon-singer.png"
  ],
  "reference_videos": [
    "https://your-cdn.example.com/clips/source.mp4"
  ],
  "resolution": "1080p",
  "character_orientation": "video"
}'
```### Keep the background of the character imageThe result keeps the background of the character image instead of the one in the motion video. Replace the video link with your own clip.```bash
curl --request POST \
  --url https://api.beatapi.io/v1/videos/tasks \
  --header "Authorization: Bearer $BEATAPI_API_KEY" \
  --header 'Content-Type: application/json' \
  --data '{
  "model": "kling-3-motion-control",
  "images": [
    "https://media.beatapi.io/samples/neon-singer.png"
  ],
  "reference_videos": [
    "https://your-cdn.example.com/clips/source.mp4"
  ],
  "resolution": "720p",
  "background_source": "input_image"
}'
```## Responses### Create a task (201)The task object. `output` is `null` until the task succeeds. Fields marked “while running” disappear once the task finishes.```json
{
  "data": {
    "id": "task_7Qm2Xc9LpR4vT8sKa1Nf6YbH3eWdJ0uZ",
    "object": "task",
    "task_kind": "video",
    "capability_id": "kling-3-motion-control",
    "capability_version": 1,
    "media_type": "video",
    "model": "kling-3-motion-control",
    "status": "queued",
    "stage": "queued",
    "created_at": 1791993600,
    "updated_at": 1791993600,
    "completed_at": null,
    "poll_after_seconds": 8,
    "output": null,
    "usage": {
      "credits_reserved": 1.1,
      "credits_settled": 0,
      "credits_refunded": 0,
      "credits_charged": 1.1,
      "billable_duration_seconds": 10
    },
    "request_id": "req_5f0c2a9e4b7d4e31",
    "error_code": null,
    "error_message": null
  }
}
```| Field                                  | Type              | Description                                                                                                                              |
| -------------------------------------- | ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `data.id`                              | `string`          | Task ID: `task_` followed by 32 letters and digits. Use it to poll and to match webhook events.                                          |
| `data.object`                          | `string`          | Always `task`.                                                                                                                           |
| `data.task_kind`                       | `string`          | `image` or `video`.                                                                                                                      |
| `data.capability_id`                   | `string`          | The model ID you sent.                                                                                                                   |
| `data.capability_version`              | `integer`         | Contract version of the model, currently `1`.                                                                                            |
| `data.media_type`                      | `string`          | `image` or `video`.                                                                                                                      |
| `data.model`                           | `string`          | The model ID you sent (same as `capability_id`).                                                                                         |
| `data.status`                          | `string`          | `queued`, `processing`, `succeeded` or `failed`. Only the last two are final.                                                            |
| `data.stage`                           | `string`          | Repeats `status`; kept for older clients.                                                                                                |
| `data.created_at`                      | `integer`         | When the task was created, Unix seconds.                                                                                                 |
| `data.updated_at`                      | `integer`         | Last change, Unix seconds.                                                                                                               |
| `data.completed_at`                    | `integer \| null` | When the task finished, Unix seconds; `null` while it runs.                                                                              |
| `data.poll_after_seconds`              | `integer`         | While running: how long to wait before the next poll (5 for images, 8 for videos).                                                       |
| `data.typical_seconds`                 | `object`          | While running, when enough recent tasks exist: `p50` and `p90` completion times in seconds over the last `window`, from `samples` tasks. |
| `data.output`                          | `object \| null`  | The result; `null` until `status` is `succeeded`. See the output fields below.                                                           |
| `data.usage.credits_reserved`          | `number`          | USD held from your balance when the task was created.                                                                                    |
| `data.usage.credits_charged`           | `number`          | USD this task costs; `0` on a failed task.                                                                                               |
| `data.usage.credits_settled`           | `number`          | USD finally taken; set when the task succeeds, otherwise `0`.                                                                            |
| `data.usage.credits_refunded`          | `number`          | USD returned to your balance; equals the reservation on a failed task once the refund is recorded.                                       |
| `data.usage.billable_duration_seconds` | `integer`         | Video only: the seconds the price is based on. Absent when the model reports no duration.                                                |
| `data.request_id`                      | `string`          | Request ID. Include it when you contact support.                                                                                         |
| `data.error_code`                      | `string \| null`  | Failure code; `null` unless the task failed.                                                                                             |
| `data.error_message`                   | `string \| null`  | What went wrong and what to change; `null` unless the task failed.                                                                       |### Finished task`GET /v1/tasks/{task_id}` returns HTTP 200 with `status: "succeeded"` and the result in `data.output`.```json
{
  "data": {
    "id": "task_7Qm2Xc9LpR4vT8sKa1Nf6YbH3eWdJ0uZ",
    "object": "task",
    "task_kind": "video",
    "capability_id": "kling-3-motion-control",
    "capability_version": 1,
    "media_type": "video",
    "model": "kling-3-motion-control",
    "status": "succeeded",
    "stage": "succeeded",
    "created_at": 1791993600,
    "updated_at": 1791993690,
    "completed_at": 1791993690,
    "output": {
      "media": [
        {
          "type": "video",
          "url": "https://media.beatapi.io/outputs/task_7Qm2Xc9LpR4vT8sKa1Nf6YbH3eWdJ0uZ/0.mp4",
          "mime_type": "video/mp4"
        }
      ],
      "r2_url": "https://media.beatapi.io/outputs/task_7Qm2Xc9LpR4vT8sKa1Nf6YbH3eWdJ0uZ/0.mp4"
    },
    "usage": {
      "credits_reserved": 1.1,
      "credits_settled": 1.1,
      "credits_refunded": 0,
      "credits_charged": 1.1,
      "billable_duration_seconds": 10
    },
    "request_id": "req_5f0c2a9e4b7d4e31",
    "error_code": null,
    "error_message": null
  }
}
```#### Output fields| Field                      | Type       | Description                                                                            |
| -------------------------- | ---------- | -------------------------------------------------------------------------------------- |
| `output.media`             | `object[]` | The generated files; one video for this model.                                         |
| `output.media[].type`      | `string`   | Always `video`.                                                                        |
| `output.media[].url`       | `string`   | HTTPS link to the stored video. Download and keep it; no retention period is promised. |
| `output.media[].mime_type` | `string`   | MIME type of the stored video, for example `video/mp4`.                                |
| `output.r2_url`            | `string`   | The video URL again, for older clients. Read `media` instead.                          |### Failed taskA failed task is still HTTP 200 on `GET /v1/tasks/{task_id}`: the failure is in the body. The reservation is returned in full, so nothing is charged.```json
{
  "data": {
    "id": "task_7Qm2Xc9LpR4vT8sKa1Nf6YbH3eWdJ0uZ",
    "object": "task",
    "task_kind": "video",
    "capability_id": "kling-3-motion-control",
    "capability_version": 1,
    "media_type": "video",
    "model": "kling-3-motion-control",
    "status": "failed",
    "stage": "failed",
    "created_at": 1791993600,
    "updated_at": 1791993690,
    "completed_at": 1791993690,
    "output": null,
    "usage": {
      "credits_reserved": 1.1,
      "credits_settled": 0,
      "credits_refunded": 1.1,
      "credits_charged": 0,
      "billable_duration_seconds": 10
    },
    "retryable": true,
    "request_id": "req_5f0c2a9e4b7d4e31",
    "error_code": "processing_failed",
    "error_message": "The task failed during processing on our side. Retry; a failed task is refunded."
  }
}
```| Field                | Type      | Description                                                                                                                                                                                                            |
| -------------------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `data.error_code`    | `string`  | One of `bad_request` and `content_policy_violation` (change the request) or `processing_unavailable`, `processing_timeout`, `processing_failed` and `result_transfer_failed` (try again later). See [Errors](/errors). |
| `data.error_message` | `string`  | A sentence that names the problem and the fix.                                                                                                                                                                         |
| `data.retryable`     | `boolean` | Present only on a failed task: `true` when the same request can succeed if you send it again.                                                                                                                          |
| `data.usage`         | `object`  | Once the refund is recorded (normally at the moment the task fails): `credits_charged` and `credits_settled` are `0`, and `credits_refunded` equals `credits_reserved`.                                                |## Complete exampleCreate a task, wait for it with the server’s polling hint, and print the result URLs. Set `BEATAPI_API_KEY` in your environment and install `requests`.```python
import os
import time
import uuid

import requests

API = "https://api.beatapi.io"
HEADERS = {"Authorization": f"Bearer {os.environ['BEATAPI_API_KEY']}"}


def create_task(body: dict) -> dict:
    # One Idempotency-Key per logical request: a retry with the same key cannot create a second task.
    headers = {**HEADERS, "Idempotency-Key": str(uuid.uuid4())}
    for attempt in range(3):
        response = requests.post(f"{API}/v1/videos/tasks", headers=headers, json=body, timeout=60)
        if response.status_code in (429, 503) and attempt < 2:
            time.sleep(int(response.headers.get("Retry-After", 10)))
            continue
        break
    try:
        payload = response.json()
    except ValueError:  # an edge error page is not JSON
        response.raise_for_status()
        raise
    if not response.ok:
        error = payload["error"]
        raise RuntimeError(f"{response.status_code} {error['code']}: {error['message']} (request_id={error.get('request_id')})")
    return payload["data"]


def wait_for_task(task_id: str, timeout_seconds: int = 1800) -> dict:
    deadline = time.monotonic() + timeout_seconds
    while time.monotonic() < deadline:
        response = requests.get(f"{API}/v1/tasks/{task_id}", headers=HEADERS, timeout=30)
        if response.status_code == 429 or response.status_code >= 500:
            time.sleep(int(response.headers.get("Retry-After", 10)))
            continue
        response.raise_for_status()
        task = response.json()["data"]
        if task["status"] in ("succeeded", "failed"):
            return task
        time.sleep(task.get("poll_after_seconds", 5))
    raise TimeoutError(f"task {task_id} is still running; keep polling later")


task = create_task({
  "model": "kling-3-motion-control",
  "prompt": "Keep the singer’s look and outfit; follow the movement of the video.",
  "images": [
    "https://media.beatapi.io/samples/neon-singer.png"
  ],
  "reference_videos": [
    "https://your-cdn.example.com/clips/source.mp4"
  ],
  "resolution": "720p"
})
print("created", task["id"])
task = wait_for_task(task["id"])
if task["status"] == "failed":
    raise SystemExit(f"{task['error_code']}: {task['error_message']}")
for item in task["output"]["media"]:
    print(item["url"])
```## Billing- These prices apply once the model is listed; until then a create request is refused and nothing is charged.
- Billed per second of the finished video, by `resolution`. `character_orientation` and the prompt do not change the rate. The table below prices an 8-second result.
- The result is as long as your motion video, so the final price is not known when the task is created. The gateway holds the most the orientation allows: 10 seconds with `image`, 30 seconds with `video`, at the rate of the resolution. That is about $1.10 (720p) or $1.40 (1080p) with `image`, and about $3.30 (720p) or $4.20 (1080p) with `video`. The balance must cover that amount, or the create call fails with `402 insufficient_credits`.
- When the task finishes, the amount is settled from the usage reported for the video that was produced, normally the list rate per second for its length, and the rest of the amount held is returned. If no usage is reported, the amount held is charged. `usage.credits_charged` and `usage.credits_settled` show what was charged. `usage.billable_duration_seconds` on this model reports the seconds held at create (10 or 30), not the length of the finished video; read the credits fields for what you paid.
- A failed task, including one refused by moderation after submission, is refunded in full. A request refused before a task exists costs nothing.Current list prices, the same as on [Pricing](https://beatapi.io/pricing). An account with a negotiated discount pays less; `data.usage` on the task always shows the amount actually charged.| Option        | List price (USD) |
| ------------- | ---------------- |
| 8 sec · 720p  | \$0.88           |
| 8 sec · 1080p | \$1.12           |## Common errors| Error or symptom                                                                                  | Cause                                                                                                                                      | Fix                                                                                                |
| ------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------- |
| 400 `field "duration" is not supported by model kling-3-motion-control`                           | A field this model does not accept (also `aspect_ratio`, `seed`, `generate_audio`, `task_type` and the rest).                              | Remove the field. The length follows your motion video.                                            |
| 400 `"images" accepts at most 1 items` or `"reference_videos" accepts at most 1 items`            | More than one character image or motion video.                                                                                             | Send exactly one link in each list.                                                                |
| 400 `"reference_videos" is required`                                                              | The motion video is missing; this model has no text-only or image-only mode.                                                               | Send one link in `reference_videos` and one in `images`.                                           |
| 400 `reference_videos[0] is 2 seconds long; this model takes reference video of 3 to 30 seconds.` | The motion video is shorter than 3 or longer than 30 seconds.                                                                              | Trim or replace the clip. Nothing was charged.                                                     |
| 400 `reference_videos[0] could not be downloaded: the URL returned HTTP 404.`                     | The link answers with an error or an empty file.                                                                                           | Host the clip at a public HTTPS link, or upload it with `POST /v1/files`.                          |
| 400 `"resolution" must be one of: [720p 1080p]`                                                   | `480p`, `4k` or upper case such as `1080P`.                                                                                                | Send `720p` or `1080p`.                                                                            |
| 400 `"background_source" must be one of: [input_video input_image]`                               | A value other than `input_video` or `input_image`.                                                                                         | Send one of the two, or leave the field out for `input_video`.                                     |
| 402 `insufficient_credits`                                                                        | The balance is below the amount held at create, which is the longest result the orientation allows (10 s with `image`, 30 s with `video`). | Top up, or use `character_orientation: "image"` with a clip of up to 10 seconds, which holds less. |
| 503 `processing_unavailable` when creating the task                                               | The model is not offered right now: it is missing from `GET /v1/media/models`.                                                             | Nothing was charged. Check the list, then retry with backoff.                                      |
| Task `failed` with `content_policy_violation`                                                     | The prompt, an input or the result was refused by moderation.                                                                              | Change the input. Retrying unchanged fails again; nothing is charged.                              |## HTTP errors at a glanceErrors before a task exists arrive as a non-2xx response, and nothing is charged. Branch on `error.code` or `error.retryable`; `message` names the problem. The full list and retry policy are on [Errors](/errors).```json
{
  "error": {
    "code": "bad_request",
    "message": "\"prompt\" is required",
    "retryable": false,
    "request_id": "req_5f0c2a9e4b7d4e31"
  }
}
```| HTTP status | `error.code`                                                                                  | What to do                                                                                                            |
| ----------- | --------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
| 400         | `bad_request`, `content_policy_violation`                                                     | Fix the request; the message names the field. Do not retry unchanged.                                                 |
| 401         | `missing_api_key`, `invalid_api_key` (older clients may see `unauthorized`)                   | Send a valid key as `Authorization: Bearer <key>`.                                                                    |
| 402         | `insufficient_credits`                                                                        | Top up, or raise the key’s quota, then send it again.                                                                 |
| 403         | `forbidden`                                                                                   | The key or account may not use this model (for example, free credit only covers some models). Do not retry unchanged. |
| 404         | `not_found`                                                                                   | On `GET /v1/tasks/{task_id}`: the task does not exist for this key’s account. Check the ID.                           |
| 409         | `idempotency_conflict`                                                                        | Use a new `Idempotency-Key`, or wait for the first request with this key to finish.                                   |
| 429         | `rate_limit_exceeded`                                                                         | Wait `Retry-After` seconds (also `error.retry_after_seconds`), then retry.                                            |
| 500         | `internal_error`                                                                              | Retry with backoff; contact support with `request_id` if it continues.                                                |
| 503         | `processing_unavailable`, `processing_timeout`, `processing_failed`, `result_transfer_failed` | Nothing was charged. Retry with exponential backoff.                                                                  |## Differences between the two models|                         | Kling 2.6 Motion Control                        | Kling 3.0 Motion Control                  |
| ----------------------- | ----------------------------------------------- | ----------------------------------------- |
| Inputs                  | One character image, one motion video of 3–30 s | Same                                      |
| `resolution`            | `720p`, `1080p`                                 | Same                                      |
| `character_orientation` | `image` (default), `video`                      | Same                                      |
| `background_source`     | Not accepted                                    | `input_video` (default), `input_image`    |
| Price per second        | Lower                                           | Higher (see the price table on each page) |

Reference: https://docs.beatapi.io/api-reference/video-api/kling-3-motion-control

## Authentication

- `Authorization` header (bearer token, required) — Send your API key as `Authorization: Bearer <BEATAPI_API_KEY>`.

## Servers

- `https://api.beatapi.io` (Direct, default)
- `https://api.beatapi.io` (API)

## Request

### Headers

- `Idempotency-Key` (string, optional) — Send the body as JSON with `Content-Type: application/json`. `Idempotency-Key` is optional: send a unique value (at most 255 characters) per logical request and reuse it only to retry that exact body. Within 24 hours a retry with the same key and body returns the first response again with `Idempotent-Replayed: true`; the same key with a different body returns `409 idempotency_conflict`. Responses other than 2xx are not stored, so a retry after a 400 or 402 runs again.

### Body (application/json)

This endpoint expects a Kling3MotionControlVideoRequest.

- `images` (list of string, required) — The character image: exactly one link. This is who performs the motion. **Items**: 1–1 Each item is a public `https://` URL. For a local file, [upload it first](/quick-guide#upload-an-input-file) with `POST /v1/files` and pass the returned `data.url`. - Send a list with one public HTTPS link; a local file can be uploaded with `POST /v1/files` and the returned `data.url` used here. - Image size, format and dimensions are not checked when the task is created. **If the value is wrong** - Missing: `"images" is required` - An empty list: `"images" requires at least 1 item` - More than one link: `"images" accepts at most 1 items` - Not an https:// link: `"images[0]" must be a public HTTPS URL`
- `model` (enum, required) — The model to run. **Value**: `kling-3-motion-control` **If the value is wrong** - Missing: `"model" is required` - An image model sent to this endpoint: `"model" nano-banana-2 is served by POST /v1/images/tasks, not this endpoint`
  - Allowed values: `kling-3-motion-control`
- `reference_videos` (list of string, required) — The motion video: exactly one link. The character in `images` repeats the motion in this video. **Items**: 1–1 Each item is a public `https://` URL. For a local file, [upload it first](/quick-guide#upload-an-input-file) with `POST /v1/files` and pass the returned `data.url`. - Its length must be 3 to 30 seconds (see Media limits). The result is as long as this video, up to the limit set by `character_orientation`. - The gateway reads the length from the file header with byte-range requests before the task is created, when the host allows it. - A local file can be uploaded with `POST /v1/files` (video up to 100 MB) and the returned `data.url` used here. **Notes**: You are billed for the seconds of video that are produced, so trim the clip to the motion you need. With `character_orientation: "image"` the result is at most 10 seconds. **If the value is wrong** - Missing: `"reference_videos" is required` - An empty list: `"reference_videos" requires at least 1 item` - More than one link: `"reference_videos" accepts at most 1 items` - Not an https:// link: `"reference_videos[0]" must be a public HTTPS URL` - Shorter than 3 or longer than 30 seconds: `reference_videos[0] is 2 seconds long; this model takes reference video of 3 to 30 seconds.` - The host answers with an error or an empty file: `reference_videos[0] could not be downloaded: the URL returned HTTP 404.`
- `background_source` (enum, optional, default: input_video) — Where the background of the result comes from: `input_video` keeps the background of the motion video, `input_image` keeps the background of the character image. **Allowed values**: `input_video`, `input_image` **Default**: `input_video` - If you leave it out, `input_video` is used. - Only Kling 3.0 Motion Control accepts this field. **If the value is wrong** - A value not in the list: `"background_source" must be one of: [input_video input_image]`
  - Allowed values: `input_video`, `input_image`
- `character_orientation` (enum, optional, default: image) — Whether the character’s orientation follows the character image (`image`) or the motion video (`video`). **Allowed values**: `image`, `video` **Default**: `image` - It also sets the longest result: at most 10 seconds with `image`, at most 30 seconds with `video` (see How the orientation sets the length). - If you leave it out, `image` is used, so a motion video longer than 10 seconds needs `video` explicitly. - It sets how much is held on your balance when the task is created: the longest result the orientation allows (see Billing). **If the value is wrong** - A value not in the list: `"character_orientation" must be one of: [image video]`
  - Allowed values: `image`, `video`
- `prompt` (string, optional) — Optional text guidance about the character or the scene. The motion itself comes from the video. **Length**: ≤ 2500 characters - Length is counted in characters (a CJK character counts as one). An empty string is accepted. **If the value is wrong** - Longer than the limit: `"prompt" has an unsupported length`
- `resolution` (enum, optional, default: 720p) — Output resolution. It is also the price tier: 1080p costs more per second than 720p. **Allowed values**: `720p`, `1080p` **Default**: `720p` - Lower case only. If you leave it out, `720p` is used. **If the value is wrong** - A value not in the list (for example `480p`, `4k` or `1080P`): `"resolution" must be one of: [720p 1080p]`
  - Allowed values: `720p`, `1080p`

## Response

### 201

The task object. `output` is `null` until the task succeeds. Fields marked “while running” disappear once the task finishes.

- `map from string to any`

## Errors

### 400 Bad Request Error

`bad_request`, `content_policy_violation` Fix the request; the message names the field. Do not retry unchanged.

- `map from string to any`

### 401 Unauthorized Error

`missing_api_key`, `invalid_api_key` (older clients may see `unauthorized`) Send a valid key as `Authorization: Bearer <key>`.

- `map from string to any`

### 402 Payment Required Error

`insufficient_credits` Top up, or raise the key’s quota, then send it again.

- `map from string to any`

### 403 Forbidden Error

`forbidden` The key or account may not use this model (for example, free credit only covers some models). Do not retry unchanged.

- `map from string to any`

### 409 Conflict Error

`idempotency_conflict` Use a new `Idempotency-Key`, or wait for the first request with this key to finish.

- `map from string to any`

### 429 Too Many Requests Error

`rate_limit_exceeded` Wait `Retry-After` seconds (also `error.retry_after_seconds`), then retry.

- `map from string to any`

### 500 Internal Server Error

`internal_error` Retry with backoff; contact support with `request_id` if it continues.

- `map from string to any`

### 503 Service Unavailable Error

`processing_unavailable`, `processing_timeout`, `processing_failed`, `result_transfer_failed` Nothing was charged. Retry with exponential backoff.

- `map from string to any`

## Examples

### Motion transfer

**Request**

```json
{
  "images": [
    "https://media.beatapi.io/samples/neon-singer.png"
  ],
  "model": "kling-3-motion-control",
  "reference_videos": [
    "https://your-cdn.example.com/clips/source.mp4"
  ],
  "prompt": "Keep the singer’s look and outfit; follow the movement of the video.",
  "resolution": "720p"
}
```

**Response**

```json
{
  "data": {
    "capability_id": "kling-3-motion-control",
    "capability_version": 1,
    "completed_at": null,
    "created_at": 1791993600,
    "error_code": null,
    "error_message": null,
    "id": "task_7Qm2Xc9LpR4vT8sKa1Nf6YbH3eWdJ0uZ",
    "media_type": "video",
    "model": "kling-3-motion-control",
    "object": "task",
    "output": null,
    "poll_after_seconds": 8,
    "request_id": "req_5f0c2a9e4b7d4e31",
    "stage": "queued",
    "status": "queued",
    "task_kind": "video",
    "updated_at": 1791993600,
    "usage": {
      "billable_duration_seconds": 10,
      "credits_charged": 1.1,
      "credits_refunded": 0,
      "credits_reserved": 1.1,
      "credits_settled": 0
    }
  }
}
```

**SDK Code**

```python Motion transfer
import requests

url = "https://api.beatapi.io/v1/videos/tasks"

payload = {
    "images": ["https://media.beatapi.io/samples/neon-singer.png"],
    "model": "kling-3-motion-control",
    "reference_videos": ["https://your-cdn.example.com/clips/source.mp4"],
    "prompt": "Keep the singer’s look and outfit; follow the movement of the video.",
    "resolution": "720p"
}
headers = {
    "Authorization": "Bearer <token>",
    "Content-Type": "application/json"
}

response = requests.post(url, json=payload, headers=headers)

print(response.json())
```

```javascript Motion transfer
const url = 'https://api.beatapi.io/v1/videos/tasks';
const options = {
  method: 'POST',
  headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
  body: '{"images":["https://media.beatapi.io/samples/neon-singer.png"],"model":"kling-3-motion-control","reference_videos":["https://your-cdn.example.com/clips/source.mp4"],"prompt":"Keep the singer’s look and outfit; follow the movement of the video.","resolution":"720p"}'
};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go Motion transfer
package main

import (
	"fmt"
	"strings"
	"net/http"
	"io"
)

func main() {

	url := "https://api.beatapi.io/v1/videos/tasks"

	payload := strings.NewReader("{\n  \"images\": [\n    \"https://media.beatapi.io/samples/neon-singer.png\"\n  ],\n  \"model\": \"kling-3-motion-control\",\n  \"reference_videos\": [\n    \"https://your-cdn.example.com/clips/source.mp4\"\n  ],\n  \"prompt\": \"Keep the singer’s look and outfit; follow the movement of the video.\",\n  \"resolution\": \"720p\"\n}")

	req, _ := http.NewRequest("POST", url, payload)

	req.Header.Add("Authorization", "Bearer <token>")
	req.Header.Add("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby Motion transfer
require 'uri'
require 'net/http'

url = URI("https://api.beatapi.io/v1/videos/tasks")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n  \"images\": [\n    \"https://media.beatapi.io/samples/neon-singer.png\"\n  ],\n  \"model\": \"kling-3-motion-control\",\n  \"reference_videos\": [\n    \"https://your-cdn.example.com/clips/source.mp4\"\n  ],\n  \"prompt\": \"Keep the singer’s look and outfit; follow the movement of the video.\",\n  \"resolution\": \"720p\"\n}"

response = http.request(request)
puts response.read_body
```

```java Motion transfer
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.post("https://api.beatapi.io/v1/videos/tasks")
  .header("Authorization", "Bearer <token>")
  .header("Content-Type", "application/json")
  .body("{\n  \"images\": [\n    \"https://media.beatapi.io/samples/neon-singer.png\"\n  ],\n  \"model\": \"kling-3-motion-control\",\n  \"reference_videos\": [\n    \"https://your-cdn.example.com/clips/source.mp4\"\n  ],\n  \"prompt\": \"Keep the singer’s look and outfit; follow the movement of the video.\",\n  \"resolution\": \"720p\"\n}")
  .asString();
```

```php Motion transfer
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://api.beatapi.io/v1/videos/tasks', [
  'body' => '{
  "images": [
    "https://media.beatapi.io/samples/neon-singer.png"
  ],
  "model": "kling-3-motion-control",
  "reference_videos": [
    "https://your-cdn.example.com/clips/source.mp4"
  ],
  "prompt": "Keep the singer’s look and outfit; follow the movement of the video.",
  "resolution": "720p"
}',
  'headers' => [
    'Authorization' => 'Bearer <token>',
    'Content-Type' => 'application/json',
  ],
]);

echo $response->getBody();
```

```csharp Motion transfer
using RestSharp;

var client = new RestClient("https://api.beatapi.io/v1/videos/tasks");
var request = new RestRequest(Method.POST);
request.AddHeader("Authorization", "Bearer <token>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{\n  \"images\": [\n    \"https://media.beatapi.io/samples/neon-singer.png\"\n  ],\n  \"model\": \"kling-3-motion-control\",\n  \"reference_videos\": [\n    \"https://your-cdn.example.com/clips/source.mp4\"\n  ],\n  \"prompt\": \"Keep the singer’s look and outfit; follow the movement of the video.\",\n  \"resolution\": \"720p\"\n}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
```

```swift Motion transfer
import Foundation

let headers = [
  "Authorization": "Bearer <token>",
  "Content-Type": "application/json"
]
let parameters = [
  "images": ["https://media.beatapi.io/samples/neon-singer.png"],
  "model": "kling-3-motion-control",
  "reference_videos": ["https://your-cdn.example.com/clips/source.mp4"],
  "prompt": "Keep the singer’s look and outfit; follow the movement of the video.",
  "resolution": "720p"
] as [String : Any]

let postData = JSONSerialization.data(withJSONObject: parameters, options: [])

let request = NSMutableURLRequest(url: NSURL(string: "https://api.beatapi.io/v1/videos/tasks")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```

### 1080p, orientation follows the video

**Request**

```json
{
  "images": [
    "https://media.beatapi.io/samples/neon-singer.png"
  ],
  "model": "kling-3-motion-control",
  "reference_videos": [
    "https://your-cdn.example.com/clips/source.mp4"
  ],
  "character_orientation": "video",
  "resolution": "1080p"
}
```

**Response**

```json
{
  "data": {
    "capability_id": "kling-3-motion-control",
    "capability_version": 1,
    "completed_at": null,
    "created_at": 1791993600,
    "error_code": null,
    "error_message": null,
    "id": "task_7Qm2Xc9LpR4vT8sKa1Nf6YbH3eWdJ0uZ",
    "media_type": "video",
    "model": "kling-3-motion-control",
    "object": "task",
    "output": null,
    "poll_after_seconds": 8,
    "request_id": "req_5f0c2a9e4b7d4e31",
    "stage": "queued",
    "status": "queued",
    "task_kind": "video",
    "updated_at": 1791993600
  }
}
```

**SDK Code**

```python 1080p, orientation follows the video
import requests

url = "https://api.beatapi.io/v1/videos/tasks"

payload = {
    "images": ["https://media.beatapi.io/samples/neon-singer.png"],
    "model": "kling-3-motion-control",
    "reference_videos": ["https://your-cdn.example.com/clips/source.mp4"],
    "character_orientation": "video",
    "resolution": "1080p"
}
headers = {
    "Authorization": "Bearer <token>",
    "Content-Type": "application/json"
}

response = requests.post(url, json=payload, headers=headers)

print(response.json())
```

```javascript 1080p, orientation follows the video
const url = 'https://api.beatapi.io/v1/videos/tasks';
const options = {
  method: 'POST',
  headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
  body: '{"images":["https://media.beatapi.io/samples/neon-singer.png"],"model":"kling-3-motion-control","reference_videos":["https://your-cdn.example.com/clips/source.mp4"],"character_orientation":"video","resolution":"1080p"}'
};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go 1080p, orientation follows the video
package main

import (
	"fmt"
	"strings"
	"net/http"
	"io"
)

func main() {

	url := "https://api.beatapi.io/v1/videos/tasks"

	payload := strings.NewReader("{\n  \"images\": [\n    \"https://media.beatapi.io/samples/neon-singer.png\"\n  ],\n  \"model\": \"kling-3-motion-control\",\n  \"reference_videos\": [\n    \"https://your-cdn.example.com/clips/source.mp4\"\n  ],\n  \"character_orientation\": \"video\",\n  \"resolution\": \"1080p\"\n}")

	req, _ := http.NewRequest("POST", url, payload)

	req.Header.Add("Authorization", "Bearer <token>")
	req.Header.Add("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby 1080p, orientation follows the video
require 'uri'
require 'net/http'

url = URI("https://api.beatapi.io/v1/videos/tasks")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n  \"images\": [\n    \"https://media.beatapi.io/samples/neon-singer.png\"\n  ],\n  \"model\": \"kling-3-motion-control\",\n  \"reference_videos\": [\n    \"https://your-cdn.example.com/clips/source.mp4\"\n  ],\n  \"character_orientation\": \"video\",\n  \"resolution\": \"1080p\"\n}"

response = http.request(request)
puts response.read_body
```

```java 1080p, orientation follows the video
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.post("https://api.beatapi.io/v1/videos/tasks")
  .header("Authorization", "Bearer <token>")
  .header("Content-Type", "application/json")
  .body("{\n  \"images\": [\n    \"https://media.beatapi.io/samples/neon-singer.png\"\n  ],\n  \"model\": \"kling-3-motion-control\",\n  \"reference_videos\": [\n    \"https://your-cdn.example.com/clips/source.mp4\"\n  ],\n  \"character_orientation\": \"video\",\n  \"resolution\": \"1080p\"\n}")
  .asString();
```

```php 1080p, orientation follows the video
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://api.beatapi.io/v1/videos/tasks', [
  'body' => '{
  "images": [
    "https://media.beatapi.io/samples/neon-singer.png"
  ],
  "model": "kling-3-motion-control",
  "reference_videos": [
    "https://your-cdn.example.com/clips/source.mp4"
  ],
  "character_orientation": "video",
  "resolution": "1080p"
}',
  'headers' => [
    'Authorization' => 'Bearer <token>',
    'Content-Type' => 'application/json',
  ],
]);

echo $response->getBody();
```

```csharp 1080p, orientation follows the video
using RestSharp;

var client = new RestClient("https://api.beatapi.io/v1/videos/tasks");
var request = new RestRequest(Method.POST);
request.AddHeader("Authorization", "Bearer <token>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{\n  \"images\": [\n    \"https://media.beatapi.io/samples/neon-singer.png\"\n  ],\n  \"model\": \"kling-3-motion-control\",\n  \"reference_videos\": [\n    \"https://your-cdn.example.com/clips/source.mp4\"\n  ],\n  \"character_orientation\": \"video\",\n  \"resolution\": \"1080p\"\n}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
```

```swift 1080p, orientation follows the video
import Foundation

let headers = [
  "Authorization": "Bearer <token>",
  "Content-Type": "application/json"
]
let parameters = [
  "images": ["https://media.beatapi.io/samples/neon-singer.png"],
  "model": "kling-3-motion-control",
  "reference_videos": ["https://your-cdn.example.com/clips/source.mp4"],
  "character_orientation": "video",
  "resolution": "1080p"
] as [String : Any]

let postData = JSONSerialization.data(withJSONObject: parameters, options: [])

let request = NSMutableURLRequest(url: NSURL(string: "https://api.beatapi.io/v1/videos/tasks")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```

### Keep the background of the character image

**Request**

```json
{
  "images": [
    "https://media.beatapi.io/samples/neon-singer.png"
  ],
  "model": "kling-3-motion-control",
  "reference_videos": [
    "https://your-cdn.example.com/clips/source.mp4"
  ],
  "background_source": "input_image",
  "resolution": "720p"
}
```

**Response**

```json
{
  "data": {
    "capability_id": "kling-3-motion-control",
    "capability_version": 1,
    "completed_at": null,
    "created_at": 1791993600,
    "error_code": null,
    "error_message": null,
    "id": "task_7Qm2Xc9LpR4vT8sKa1Nf6YbH3eWdJ0uZ",
    "media_type": "video",
    "model": "kling-3-motion-control",
    "object": "task",
    "output": null,
    "poll_after_seconds": 8,
    "request_id": "req_5f0c2a9e4b7d4e31",
    "stage": "queued",
    "status": "queued",
    "task_kind": "video",
    "updated_at": 1791993600
  }
}
```

**SDK Code**

```python Keep the background of the character image
import requests

url = "https://api.beatapi.io/v1/videos/tasks"

payload = {
    "images": ["https://media.beatapi.io/samples/neon-singer.png"],
    "model": "kling-3-motion-control",
    "reference_videos": ["https://your-cdn.example.com/clips/source.mp4"],
    "background_source": "input_image",
    "resolution": "720p"
}
headers = {
    "Authorization": "Bearer <token>",
    "Content-Type": "application/json"
}

response = requests.post(url, json=payload, headers=headers)

print(response.json())
```

```javascript Keep the background of the character image
const url = 'https://api.beatapi.io/v1/videos/tasks';
const options = {
  method: 'POST',
  headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
  body: '{"images":["https://media.beatapi.io/samples/neon-singer.png"],"model":"kling-3-motion-control","reference_videos":["https://your-cdn.example.com/clips/source.mp4"],"background_source":"input_image","resolution":"720p"}'
};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go Keep the background of the character image
package main

import (
	"fmt"
	"strings"
	"net/http"
	"io"
)

func main() {

	url := "https://api.beatapi.io/v1/videos/tasks"

	payload := strings.NewReader("{\n  \"images\": [\n    \"https://media.beatapi.io/samples/neon-singer.png\"\n  ],\n  \"model\": \"kling-3-motion-control\",\n  \"reference_videos\": [\n    \"https://your-cdn.example.com/clips/source.mp4\"\n  ],\n  \"background_source\": \"input_image\",\n  \"resolution\": \"720p\"\n}")

	req, _ := http.NewRequest("POST", url, payload)

	req.Header.Add("Authorization", "Bearer <token>")
	req.Header.Add("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby Keep the background of the character image
require 'uri'
require 'net/http'

url = URI("https://api.beatapi.io/v1/videos/tasks")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n  \"images\": [\n    \"https://media.beatapi.io/samples/neon-singer.png\"\n  ],\n  \"model\": \"kling-3-motion-control\",\n  \"reference_videos\": [\n    \"https://your-cdn.example.com/clips/source.mp4\"\n  ],\n  \"background_source\": \"input_image\",\n  \"resolution\": \"720p\"\n}"

response = http.request(request)
puts response.read_body
```

```java Keep the background of the character image
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.post("https://api.beatapi.io/v1/videos/tasks")
  .header("Authorization", "Bearer <token>")
  .header("Content-Type", "application/json")
  .body("{\n  \"images\": [\n    \"https://media.beatapi.io/samples/neon-singer.png\"\n  ],\n  \"model\": \"kling-3-motion-control\",\n  \"reference_videos\": [\n    \"https://your-cdn.example.com/clips/source.mp4\"\n  ],\n  \"background_source\": \"input_image\",\n  \"resolution\": \"720p\"\n}")
  .asString();
```

```php Keep the background of the character image
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://api.beatapi.io/v1/videos/tasks', [
  'body' => '{
  "images": [
    "https://media.beatapi.io/samples/neon-singer.png"
  ],
  "model": "kling-3-motion-control",
  "reference_videos": [
    "https://your-cdn.example.com/clips/source.mp4"
  ],
  "background_source": "input_image",
  "resolution": "720p"
}',
  'headers' => [
    'Authorization' => 'Bearer <token>',
    'Content-Type' => 'application/json',
  ],
]);

echo $response->getBody();
```

```csharp Keep the background of the character image
using RestSharp;

var client = new RestClient("https://api.beatapi.io/v1/videos/tasks");
var request = new RestRequest(Method.POST);
request.AddHeader("Authorization", "Bearer <token>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{\n  \"images\": [\n    \"https://media.beatapi.io/samples/neon-singer.png\"\n  ],\n  \"model\": \"kling-3-motion-control\",\n  \"reference_videos\": [\n    \"https://your-cdn.example.com/clips/source.mp4\"\n  ],\n  \"background_source\": \"input_image\",\n  \"resolution\": \"720p\"\n}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
```

```swift Keep the background of the character image
import Foundation

let headers = [
  "Authorization": "Bearer <token>",
  "Content-Type": "application/json"
]
let parameters = [
  "images": ["https://media.beatapi.io/samples/neon-singer.png"],
  "model": "kling-3-motion-control",
  "reference_videos": ["https://your-cdn.example.com/clips/source.mp4"],
  "background_source": "input_image",
  "resolution": "720p"
] as [String : Any]

let postData = JSONSerialization.data(withJSONObject: parameters, options: [])

let request = NSMutableURLRequest(url: NSURL(string: "https://api.beatapi.io/v1/videos/tasks")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```