Skip to main content

Kling O3 (Kling 3.0 Omni) reference-to-video casts up to 7 reference images, such as a person, an outfit or a place, together in one new shot. Clips run 3 to 15 seconds at 720p, 1080p or 4K, with optional native audio.

From $0.067/s

Workbench

Send a request

Quickstart

Examples use the selected endpoint’s public parameter definitions. Replace the API key and example media, then send the request from your server.

SDKs & integrations
POSThttps://api.spicyapi.ai/api/v1/jobs/createTask

API authentication

Keep the API key in the server environment variable SPICY_API_KEY. Use Bearer authentication and Content-Type: application/json for JSON requests.

Authorization: Bearer $SPICY_API_KEY
Sample outputUse this example
# Generate the idempotency key once; reuse it when retrying after a lost response.
export SPICY_IDEMPOTENCY_KEY="${SPICY_IDEMPOTENCY_KEY:-$(uuidgen)}"
curl -X POST https://api.spicyapi.ai/api/v1/jobs/createTask \
  -H "Authorization: Bearer $SPICY_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $SPICY_IDEMPOTENCY_KEY" \
  -d '{
  "model": "kling/o3/reference-to-video",
  "input": {
    "aspect_ratio": "16:9",
    "duration_seconds": 5,
    "generate_audio": false,
    "prompt": "A slow aerial shot drifting over a misty pine forest at sunrise",
    "reference_image_urls": [
      "https://example.com/reference.png"
    ],
    "resolution": "720p",
    "shot_type": "single"
  }
}'

# Copy data.taskId from the response into TASK_ID, then poll the status.
TASK_ID="TASK_ID_FROM_RESPONSE"
curl "https://api.spicyapi.ai/api/v1/jobs/recordInfo?taskId=$TASK_ID" \
  -H "Authorization: Bearer $SPICY_API_KEY"

Generated from this model’s input schema — the same one the API validates every request against.

Input parameters

Place these fields inside the request body’s input object. Names and types match the API. Do not assume a default when none is specified.

This model has dependencies between fields. Fields marked conditionally required depend on other inputs; see the complete JSON Schema below for the rules.

reference_image_urlsarray<string>Required

Up to 7 reference photos for characters, objects, outfits or places. JPEG or PNG. Refer to each one in the prompt as <<<image_1>>>, <<<image_2>>>, etc.

Default
Not specified
Constraints
minItems:1
maxItems:7
promptstringRequired

Say what each numbered reference is (<<<image_1>>>, <<<image_2>>> and so on) and describe the shot they should produce together. Naming the job each photo does is what keeps it from blending into the others.

Default
Not specified
Constraints
minLength:1
maxLength:2500
multi_promptarray<object>Conditionally required

The shot list for shot_type customize: up to 6 shots in order, each with its own prompt (up to 512 characters) and length in whole seconds. The lengths must add up to duration_seconds, which is still what you are billed on.

Default
Not specified
Constraints
minItems:1
maxItems:6
shot_typestringOptional

single asks for one continuous take (not a hard guarantee). multi lets the model cut the shot into several angles on its own. customize follows the shot list you give in multi_prompt. None of them changes the price.

Default
"single"
Allowed values
"single""multi""customize"
duration_secondsintegerOptionalAffects price

Whole seconds, 3 to 15. Drives the price along with resolution and audio.

Default
5
Constraints
minimum:3
maximum:15
keep_original_audiobooleanOptional

Keeps the reference video's own sound in the result. On by default; it only matters when you give a reference video. It does not change the price.

Default
true
generate_audiobooleanOptionalAffects price

Adds a generated soundtrack in the same pass - dialogue in quotes, effects, ambience. At 720p and 1080p it costs more per second than a silent run; at 4K the price is the same either way. Not available together with a reference video.

Default
false
resolutionstringOptionalAffects price

720p, 1080p or 4K. Higher tiers cost more per second.

Default
"720p"
Allowed values
"720p""1080p""4k"
aspect_ratiostringOptional

16:9, 9:16 or 1:1. Pick it before writing the shot so the composition fits.

Default
"16:9"
Allowed values
"16:9""9:16""1:1"
elementsarray<object>Optional

One named subject for 4K runs: give it a name, a one-line description and 2 to 4 photos of the same person or object (voice samples of 5 to 30 seconds are optional), then write @name in the prompt or the shot list where the subject should appear. One subject per request; it counts toward the 7-reference limit shared with reference_image_urls. It does not change the price.

Default
Not specified
Constraints
maxItems:1
reference_video_urlsarray<string>OptionalAffects price

Optional reference video, one clip, whose motion, camera work or characters the new shot draws on; refer to it in the prompt as <<<video_1>>>. With a video, use up to 4 reference images, and generate_audio has to stay off. A run with a reference video is billed at a higher per-second rate than one without; it is not available at 4K.

Default
Not specified
Constraints
minItems:1
maxItems:1

Track the task

HTTP 202 means the task was accepted, not completed. Save the returned taskId and query that task. Do not create another task while waiting for its result.

Accepted response
HTTP/1.1 202 Accepted
Location: /api/v1/jobs/recordInfo?taskId=job_01k3m8x9q2z4v7n5p6r8s0t1w3

{
  "code": 200,
  "msg": "success",
  "request_id": "req_example_create",
  "data": {
    "taskId": "job_01k3m8x9q2z4v7n5p6r8s0t1w3",
    "state": "queued",
    "estimatedCost": "0.750000"
  }
}

The accepted response includes the estimate so your app can show the same amount in task history and billing.

Read the job
curl --get "https://api.spicyapi.ai/api/v1/jobs/recordInfo" \
  --data-urlencode "taskId=$TASK_ID" \
  -H "Authorization: Bearer $SPICY_API_KEY"

Start polling at about 2 seconds and gradually increase the interval. Stop at a terminal state. After a connection failure, query the original taskId. Retry creation only with the original idempotency key and body.

Or let the callback find you
import { verifyWebhook } from "@spicyapi/sdk";

// express.raw keeps the bytes we signed; express.json never matches.
app.post("/hooks/spicy", express.raw({ type: "application/json" }), (req, res) => {
  const { payload } = verifyWebhook({
    rawBody: req.body,
    timestamp: req.header("X-Webhook-Timestamp"),
    signature: req.header("X-Webhook-Signature"),
    payloadVersion: Number(req.header("X-Webhook-Payload-Version")),
    secret: process.env.SPICY_WEBHOOK_SECRET,
  });
  res.status(200).end();
  void handleFinishedTask(payload);
});

Pass callBackUrl when you create the task and the finished record is POSTed to you — no polling loop at all. Every delivery carries X-Webhook-Timestamp, X-Webhook-Signature and X-Webhook-Payload-Version. The signature is a base64 HMAC-SHA256 over taskId.timestamp.sha256(body), so it must be checked against the raw bytes: parsing the JSON and re-serialising it changes them and the check will fail. Failed deliveries are retried, so the same taskId can arrive twice — make the handler idempotent. Answer 2xx first and do the work afterwards; a slow handler is treated as a failed delivery.

Read the result

Consume output only when state is succeeded. On failure, use errorCode, errorMessage and request_id to investigate. Example costs and states do not represent an actual bill.

Completed response example
{
  "code": 200,
  "msg": "success",
  "request_id": "req_example_record",
  "data": {
    "taskId": "job_01k3m8x9q2z4v7n5p6r8s0t1w3",
    "model": "kling/o3/reference-to-video",
    "state": "succeeded",
    "output": {
      "assets": [
        {
          "key": "tasks/2026/08/28/job_01k3m8x9q2z4v7n5p6r8s0t1w3/3f7a1c9e5b2d48f0a6c1e3d5b7f9a1c3.mp4",
          "mime": "video/mp4",
          "url": "https://example.r2.cloudflarestorage.com/results/job_01k3m8x9q2z4v7n5p6r8s0t1w3/result.mp4?X-Amz-Signature=SIGNATURE_FROM_RESPONSE",
          "expiresAt": "2026-08-28T12:20:08.000Z"
        }
      ]
    },
    "cost": "0.750000",
    "settled": true,
    "createdAt": "2026-08-28T12:00:00Z",
    "completedAt": "2026-08-28T12:00:30Z"
  }
}

For each available asset, open or download assets[].url directly without an API Authorization header. The signed URL expires at expiresAt; poll the task again to refresh it while the asset remains within its retention period. A pending or unavailable asset has no URL. Treat signed URLs as private.

SDKs & integrations

Use SpicyAPI in your app, terminal, AI assistant or visual workflow. Choose a setup guide below.

Use it in ComfyUI

Install the SpicyAPI nodes from ComfyUI Manager or with the command below, then search for Kling O3 · Reference to Video on the canvas. The node has the same options as this page and shows its price before it runs.

cd ComfyUI/custom_nodes
git clone https://github.com/spicyapi-ai/comfyui-spicyapi