Sociaro

kling/kling-4.0-omni

Kling 4.0 Omni, served direct from Kling, which opened Kling 4.0 on 2026-10-23.

One model for text-to-video, key frames, reference images, reference videos, video editing, elements and voices: up to 10 images, 5 input videos, 7 elements and 3 voices, 3 to 30 seconds at 720p or 1080p (the default).

Send Kling's own body: contents, settings and options.

Billed per second of the duration you ask for, which is required here: 720p $0.157, 1080p $0.2355; with input videos, $0.0942 or $0.1413 a billed second, on the input and the output seconds (Kling's 4.0 list price, which Kling still labels a preview).

Slugkling/kling-4.0-omni
Kindvideo
Vendorkling
EndpointPOST /v1/kling/generations

Charged per job from what the generator reports it rendered — your Logs show the exact figure for each one; see balance and billing.

Calling it

curl https://api.sociaro.com/v1/kling/generations \
  -H "Authorization: Bearer $SOCIARO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
        "model": "kling/kling-4.0-omni",
        "contents": [
          {"type": "prompt", "text": "a paper boat drifts down a rain-soaked street"},
          {"type": "first_frame", "url": "https://your-cdn.example.com/first.png"}
        ],
        "settings": {"duration": 5}
      }'

# then poll the job id it returns:
curl https://api.sociaro.com/v1/kling/generations/JOB_ID \
  -H "Authorization: Bearer $SOCIARO_API_KEY"

Parameters

Kling 4.0 Omni — text, key frames, reference images or input videos to video

FieldTypeRequiredDefaultValuesWhat it does
contentsobject[]yes—prompt · first_frame · middle_frame · last_frame · refer_image · feature_video · base_video · element · voiceThe inputs, each a part with a `type`: `{"type": "prompt", "text": "…"}`, an image with a public https `url` (jpg, jpeg or png, up to 30 MB, at least 300 px), or a video, `feature_video` (a reference to follow) or `base_video` (the video to edit), with a public https `url` (never base64). Give an input an `id` to name it in the prompt as `@id` (required on every `middle_frame` and `refer_image` when there are several). Up to 10 images; up to 5 videos, at most one `base_video` (the gateway counts it among the 5); 15 inputs in all. Each video, and all of them together, must be 3 to 30 seconds: the gateway reads each one's length from the file's own header when you submit (an MP4 or MOV that is not fragmented), and Kling judges the rest (up to 300 MB, 700 to 4553 px a side and at most 8,294,400 pixels in all, an aspect ratio between 2:5 and 5:2, 24 to 60 fps). An `element` (`{"type": "element", "element_id": "…", "id": "…"}`, up to 7) or a `voice` (`{"type": "voice", "voice_id": "…", "id": "…"}`, up to 3) is one your organisation made on `/v1/kling/elements` or `/v1/kling/voices`, or one of Kling's presets; any other id is a `404`.
settings.durationintegeryes—3–30Whole seconds of output, sent as a JSON integer, and the number you are charged for (with input videos, with the input seconds). Required here, a `base_video` included: left out, Kling takes the length from the prompt, and the price must be known at submit.
settings.resolutionstringno1080p720p · 1080pSets the per-second rate. Kling's 4.0 default is 1080p, not 720p. There is no 4k on 4.0.
settings.aspect_ratiostringnoadaptive21:9 · 16:9 · 9:16 · 1:1 · adaptive`adaptive` follows the prompt, else the `base_video`, the key frames (the first frame first) and the other input videos, in that order, else 16:9; Kling does not list a reference image as a source. Does not change the price.
options.watermark_infoobjectno—{"enabled": false}Only `false` is accepted: a watermarked copy is not available on this gateway yet.

Anything not listed here is refused rather than ignored, so a typo fails loudly instead of quietly producing something else.

Worked example

curl https://api.sociaro.com/v1/kling/generations \
  -H "Authorization: Bearer $SOCIARO_API_KEY" -H "Content-Type: application/json" \
  -d '{ "model": "kling/kling-4.0-omni",
        "contents": [ { "type": "prompt", "text": "the jacket from @image_1, worn on a windy pier" },
                      { "type": "refer_image", "url": "https://your-cdn.example.com/jacket.png", "id": "image_1" } ],
        "settings": { "duration": 10, "aspect_ratio": "9:16" } }'

# A feature reference video: follow the moves in @video_1. With an 8.03 s clip: 30 billed seconds, $4.239
curl https://api.sociaro.com/v1/kling/generations \
  -H "Authorization: Bearer $SOCIARO_API_KEY" -H "Content-Type: application/json" \
  -d '{ "model": "kling/kling-4.0-omni",
        "contents": [ { "type": "prompt", "text": "the fighters in @image_1 move with the rhythm and attacks of @video_1" },
                      { "type": "feature_video", "url": "https://your-cdn.example.com/fight.mp4", "id": "video_1" },
                      { "type": "refer_image", "url": "https://your-cdn.example.com/fighters.png", "id": "image_1" } ],
        "settings": { "duration": 15 } }'

# An element and a voice: your own (POST /v1/kling/elements, /v1/kling/voices) or Kling's presets (their ids from
# GET /v1/kling/elements/presets and /v1/kling/voices/presets), in place of YOUR_ELEMENT_ID and YOUR_VOICE_ID
curl https://api.sociaro.com/v1/kling/generations \
  -H "Authorization: Bearer $SOCIARO_API_KEY" -H "Content-Type: application/json" \
  -d '{ "model": "kling/kling-4.0-omni",
        "contents": [ { "type": "prompt", "text": "@element_1 waves at the camera and says hello in @voice_1" },
                      { "type": "element", "element_id": "YOUR_ELEMENT_ID", "id": "element_1" },
                      { "type": "voice", "voice_id": "YOUR_VOICE_ID", "id": "voice_1" } ],
        "settings": { "duration": 5 } }'

# Video editing: change @video_1, keep the rest. With a 12.5 s clip: 22.5 billed seconds, $3.17925
curl https://api.sociaro.com/v1/kling/generations \
  -H "Authorization: Bearer $SOCIARO_API_KEY" -H "Content-Type: application/json" \
  -d '{ "model": "kling/kling-4.0-omni",
        "contents": [ { "type": "prompt", "text": "edit @video_1: when the box opens, a cat is asleep inside; keep everything else" },
                      { "type": "base_video", "url": "https://your-cdn.example.com/box.mp4", "id": "video_1" } ],
        "settings": { "duration": 10 } }'

curl https://api.sociaro.com/v1/kling/generations/{job_id} -H "Authorization: Bearer $SOCIARO_API_KEY"

Submit and poll. Kling opened Kling 4.0 on 2026-10-23. Send kling/kling-4.0-omni to POST /v1/kling/generations and poll GET /v1/kling/generations/{job_id}. It is also served on the Jobs API (/v2/jobs), with the same body as input.

Input videos. A feature_video is a reference to follow (its motion, its rhythm, its camera); a base_video is the video to edit: say in the prompt what to change and what to keep. Each is a public https URL, never base64. At most 5 videos, at most one of them a base_video (Kling allows up to 5 reference videos and one base_video; the gateway counts the base_video among the 5). Each video, and all of them together, must be 3 to 30 seconds: the gateway reads each one's length from the file's own header when you submit (an MP4 or MOV that is not fragmented, at a URL that answers without a redirect), and a length it cannot read, or outside 3–30 s, is a 400 before anything is sent. Kling judges the rest when the job is submitted: .mp4 or .mov, up to 300 MB, 700 to 4553 px a side and at most 8,294,400 pixels in all, an aspect ratio between 2:5 and 5:2, 24 to 60 fps.

Elements and voices. An element is a character or an object Kling keeps from your images; a voice is cloned from a recording. Make them on POST /v1/kling/elements and POST /v1/kling/voices (Kling's own bodies), get the task until it succeeds, and name the element_id or voice_id here, with an id to cite it in the prompt as @id: at most 7 elements and 3 voices. They are your organisation's, shared by its keys and nobody else's; Kling's presets (GET /v1/kling/elements/presets, GET /v1/kling/voices/presets) are everyone's. Any other id is a 404 and nothing is sent, whether it is another customer's or nobody's. An element made from a video is not available on this gateway yet. A voice costs $0.007 once, when it is made; an element is free; naming either here adds nothing to the price. There is no delete route: to remove an element or voice, ask support: an operator deletes it at Kling. The routes: Generations API.

Refused here, each a 400 before anything is sent: a request without settings.duration; more than 10 images, 5 videos, 7 elements, 3 voices or 15 inputs in all, or more than one base_video; a video that is not a public https URL; an element made from a video; settings.audio, settings.multi_shot and 4k, which Kling 4.0 does not have; options.callback_url (poll instead), options.external_task_id (the gateway sets its own), options.watermark_info.enabled: true; a top-level prompt (the prompt is a contents part) and any other field Kling does not define. The body is capped at 1 MiB, so images and videos go in as URLs.

How it is billed. Kling's 4.0 list price, billed once when the job completes; a failed job is not billed. Kling's pricing page still labels the 4.0 price a preview (read 2026-10-23), so it may change when Kling publishes final pricing. Without an input video: settings.duration seconds at the rate of the resolution you chose. A 10 second clip is $1.57 at 720p and $2.355 at 1080p, the default.

720p 1080p (default)
no input video, per second of output $0.157/s $0.2355/s
with input videos, per billed second $0.0942/s $0.1413/s

With input videos the billed seconds are Kling's: twice the output when the input videos together are no longer than it, else input plus output. The input seconds are the ones Kling reports on the finished job (usage.input_duration), never rounded to whole seconds, and only those: the length the gateway reads when you submit only holds each video to 3–30 s. Until Kling reports them the finished job answers processing; the poll that completes it passes Kling's usage on as Kling sends it. The feature-video example above, a 15 second 1080p clip following an 8.03 second video: 8.03 ≤ 15, so 15 × 2 = 30 billed seconds × $0.1413 = $4.239. The editing example, 10 seconds of a 12.5 second video: 12.5 > 10, so 12.5 + 10 = 22.5 billed seconds × $0.1413 = $3.17925. Kling's page rounds the two rates to $0.094 and $0.141.

How a job runs

Generation takes longer than a request should wait, so it goes in three moves: submit, then poll, then collect.

# 1. submit — the model id plus the fields above
curl -sS https://api.sociaro.com/v1/alibaba/generations \
  -H "Authorization: Bearer $SOCIARO_API_KEY" -H "Content-Type: application/json" \
  -d '{ "model": "…", "prompt": "…" }'
# -> { "job_id": "<id>", "status": "submitted" }

# 2. poll every 3-5 s for images, 5-10 s for video
curl -sS https://api.sociaro.com/v1/alibaba/generations/<id> \
  -H "Authorization: Bearer $SOCIARO_API_KEY"
# -> { "job_id": "…", "status": "processing" }
# -> { "job_id": "…", "status": "completed", "model": "…",
#      "media_url": "https://api.sociaro.com/v1/media/<media_id>" }
# -> { "job_id": "…", "status": "failed", "model": "…", "error": { "code": …, "message": … } }

# 3. collect within the hour — the opaque id IS the credential, no header needed
curl -sSL "https://api.sociaro.com/v1/media/<media_id>" -o out.mp4

Use the job_id verbatim as the path segment when polling: the two names are the same value. Which submit and poll URL this model uses is at the top of this page, under Calling it.

Reading the poll

status is one of processing · completed · failed. Only completed carries media_url, only failed carries error.

Gate on status, never on the poll's HTTP code. A poll-time failure is HTTP 200 with status: "failed" and an error object of { "code", "message" }, plus provider_code when the generator supplied one — on every failure branch, including one where the result could not be delivered. A submit-time rejection is different: it comes back as its own HTTP status with the reason in the body.

code is ours and stable; message is the generator's. Match on code: it comes from a small fixed set, and provider_failed still means what it always meant. A rejection of what you sent — a size out of range, an unusable input — additionally sets code: "invalid_request", because that is a different thing for your code to do. message now carries the generator's own sentence, and provider_code its own code (OutputVideoSensitiveContentDetected.PolicyViolation, IPInfringementSuspect), so a moderated generation, a copyright refusal and a broken renderer are finally distinguishable. Treat provider_code as informational: the vendors change these strings without telling us, so branch on ours.

message is variable text, capped at 400 characters. It used to be a single fixed 43-character sentence, so if you compare it by equality or keep it in a narrower column, that needs changing — match on code, and on provider_code when you need the finer distinction.

Two things that text is not. It does not name the generator behind the model: the names of the services and hosts that actually run your job are substituted out before you see it, so do not parse it for one. What it does not hide is a name you already have — the halves of the model id you called (alibaba, bytedance, wan, qwen, seedance, seedream, happyhorse) stay as written, because blanking those would garble the message exactly where it is useful: Model alibaba/wan-2-7-image not found has to survive intact. And the text can quote your own request back: a moderation message often contains the fragment it objected to. That is why we do not write it into our own logs, and why forwarding or storing a failed poll's message is your decision to make rather than something to do by default.

Collecting the result

media_url points at our host, not the generator's. It is a capability: the opaque id is the credential, so no Authorization header is needed and anyone holding the link can fetch it. It expires within the hour — download rather than store the link.

Several images are not optional extras. An Alibaba image model asked for more than one picture (n above 1) is charged for the number of pictures the generator reports making, and every link it returns is delivered, never fewer than that number: media_urls lists all of them in the generator's order (present on every image result, even a single one) and media_url is the first. Each has its own link and its own expiry. If any one of them cannot be delivered the job is reported failed and nothing is charged — unlike a still or a layer, an image you paid for is never left out.

Rules the gateway adds

Two fields are ours and never reach the generator:

Field Notes
model which model to run
user your own end-user id. Recorded against this job's spend so you can attribute cost per end user, and stripped before the request leaves us, so the generator never sees it. Validated BEFORE the job is created, so a rejection is free: a string of at most 128 characters, no control characters, valid UTF-8. Omitting it, or sending null, is fine and simply records no end user

An unknown field is always a 400. Most generators reject one themselves; some accept it, ignore it, render something other than what you asked for and bill you — so we refuse it for you. Either way you get 400 unknown field(s): [...] naming the offender, never a surprise render.

Ranges and enums are the generator's own and we do not re-validate most of them, so an out-of-range value comes back as its rejection, in its wording and with its numbers. The one thing we DO check ourselves is resolution, because an unsupported tier is silently ignored rather than refused: you would ask for the higher tier, be handed a lower one, and be charged. The tiers on this page are read from what the vendor prices, so they change when the vendor publishes one.

Authentication

Authorization: Bearer <your-api-key> on submit and on poll. The key must be valid AND granted this model — 403 otherwise.

One deliberate exception on poll: a key that is over budget may still poll and collect a job it already submitted, so work you have already paid for is never stranded by a budget that ran out mid-generation. While the key is over budget the gateway cannot read its model list at all, so the per-model grant is not re-checked on those polls — a grant revoked after submit still collects that job. Ownership is always enforced: a job can only be polled by the key that created it, and submit is always refused in that state.