Kling 4.0 is the next-generation video model from Kuaishou's Kling. Submit an async job with POST /v1/videos/generations: no references for text-to-video, or up to 10 reference images, 5 reference videos and 3 reference audio clips together. 3-30 seconds, 720P / 1080P, 16:9 or 9:16, always with sound. Billed per second; with reference videos the billed seconds are the output seconds plus the reference-video seconds.
Quick start
Three steps: submit a job, poll it, take the video link. Replace YOUR_API_KEY with your key and the commands below run as they are.
Step 1 · Submit (returns a job id at once, HTTP 202)
curl https://nezhagate.com/v1/videos/generations \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model": "kling-4.0", "prompt": "a dancer spins through falling maple leaves while the camera circles her slowly", "duration": 5, "resolution": "720P", "size": "16:9"}'Response (HTTP 202)
{
"id": "img_5c2e8f1a9b3d7e6f0a4c8b2d",
"object": "video.generation.job",
"status": "queued",
"created": 1791281400,
"model": "kling-4.0"
}Step 2 · Poll every 5–10 seconds until status is succeeded or failed
curl https://nezhagate.com/v1/videos/jobs/img_5c2e8f1a9b3d7e6f0a4c8b2d \ -H "Authorization: Bearer YOUR_API_KEY"
Response when it succeeds
{
"id": "img_5c2e8f1a9b3d7e6f0a4c8b2d",
"object": "video.generation.job",
"status": "succeeded",
"created": 1791281400,
"model": "kling-4.0",
"data": [
{"url": "https://img.nezhagate.com/i/8c1f0e5d2b7a49e3a6d0c4f1b9e2a7d5.mp4"}
],
"usage": {"clips": 1, "model": "kling-4.0"}
}Step 3 · data[0].url is the clip (MP4). It is kept for 15 days; download it to your own storage to keep it longer.
Complete example (Python: submit, wait, save to disk)
import time
import requests
BASE = "https://nezhagate.com/v1"
HEADERS = {"Authorization": "Bearer YOUR_API_KEY"}
# 1. submit
job = requests.post(f"{BASE}/videos/generations", headers=HEADERS, json={
"model": "kling-4.0",
"prompt": "a dancer spins through falling maple leaves while the camera circles her slowly",
"duration": 5,
"resolution": "720P",
"size": "16:9",
}).json()
if "error" in job:
raise SystemExit(job["error"]["message"])
# 2. poll every 10 seconds, for up to an hour
deadline = time.time() + 3600
while job["status"] not in ("succeeded", "failed") and time.time() < deadline:
time.sleep(10)
job = requests.get(f"{BASE}/videos/jobs/{job['id']}", headers=HEADERS).json()
# 3. download the video
if job["status"] == "succeeded":
url = job["data"][0]["url"]
with open("kling-4.0.mp4", "wb") as f:
f.write(requests.get(url).content)
print("saved", url)
elif job["status"] == "failed":
print("failed:", job["error"]["code"], job["error"]["message"])
else:
print("still", job["status"], job["id"])While queued, the poll also returns queue_position (jobs ahead of yours) and eta_seconds (expected wait). If your time limit passes, do not resubmit: the job is still there, so keep polling the same id later.
Authentication
Authorization: Bearer YOUR_API_KEY Content-Type: application/json
Create an API Key in the console to start.
At a glance
| Duration | 3-30 s in one-second steps (default 5 s) |
| Resolution | 720P / 1080P |
| Aspect ratio | 16:9 / 9:16 |
| Reference images | up to 10 · all used as references (characters, outfits, props, settings), never a first frame |
| Reference videos | up to 5 · each 2-30 s and 700-4553 px high, 30 s in all · counted in the billed seconds |
| Reference audio | up to 3 · each 5-30 s of clear human speech, sets the voice · not billed |
| Sound | always on, cannot be switched off (audio: false is refused) |
| Prompt length | up to 2,500 characters (Chinese and English count the same) |
| Billing | per second: (output seconds + reference-video seconds) x the rate of the resolution |
Request parameters
The JSON body of POST /v1/videos/generations:
| Parameter | Type | Required | Description |
|---|---|---|---|
| model | string | Yes | The model id: kling-4.0. |
| prompt | string | Yes | What happens, the camera, the action and the sound, up to 2,500 characters. With references, say what each one is for, e.g. "the character in image 1 swings the spear in the forest of image 2, following the moves in the reference video, and says in the voice of the reference audio: come on!". |
| duration | integer | No | Output length, an integer 3-30, default 5; alias seconds. |
| resolution | string | No | 720P (default) or 1080P. |
| size | string | No | 16:9 (default) or 9:16; alias aspect_ratio. Other ratios are refused (400). |
| images | array | No | Up to 10 reference images (public links, data: URIs or base64, each up to 16 MB); also image (one) or reference_images. All are references, never a first frame; start_frame, end_frame and image_role=first_frame are refused. |
| video_references | array | No | Up to 5 reference videos (mp4 / mov, each 2-30 s, 700-4553 px high and up to 50 MB, 30 s in all): direct download links, base64 (data:video/mp4;base64,...) or links from a Playground upload. Billed by the second, see Pricing below. |
| audio_reference | array | No | Up to 3 reference audio clips (mp3 / wav / m4a, each 5-30 s and up to 15 MB) that set a character's voice: clear human speech only (music or sound effects fail); http(s) links only, with a reference image or video as well. Not billed. |
| audio | boolean | No | Every clip has sound and it cannot be switched off; false is refused (400), so leave it out. |
| callback_url | string | No | Where to push the result when the job ends; see Webhooks below. |
| n | integer | No | Each job makes one clip; leave it out. A value above 1 still makes one clip, but n clips are held at submit and the extra is returned when the job ends. |
Other fields (seed, negative_prompt, fps, watermark and the like) are not passed on and do not change the price; results are always MP4, returned as a url.
Price
Billed per second: cost = the per-second rate of the resolution x (output seconds + the seconds of every reference video). The same reference-video link counts once; reference images and audio are free. The amount is held at submit, settled at the same figure on success and refunded in full on failure. 1 credit = $0.005.
| Resolution | Credits / s | USD / s | A 5-second clip | 10 s output + 5 s reference video (15 billed s) |
|---|---|---|---|---|
| 720P | 18 | $0.09 | $0.45 | $1.35 |
| 1080P | 25 | $0.125 | $0.625 | $1.875 |
Reference material
- Reference images are references, never a first frame. Say in the prompt what each one is for by its number, e.g. "the character in image 1", "the setting in image 2".
- Reference videos set motion and camera, and combine with reference images. Each 2-30 s and 700-4553 px high (e.g. 1280x720 landscape or 720x1280 portrait; a 480P video fails), 30 s in all. We download and measure each one at submit: a clip whose length cannot be read, one that is too short or not high enough gets a 400 and is not charged.
- Reference audio sets a character's voice and must be 5-30 s of clear human speech; music, sound effects or clips under 5 s are refused. Write the lines to be spoken in the prompt.
- Each image is a public image URL, a
data:URI or base64, up to 16 MB; send several as an array inimages. We store the images before the job is queued; one that cannot be read is refused with 400,messagesays which, and nothing is charged. - Reference files we store are kept for 7 days; results for 15 days.
With reference images, a reference video and reference audio
curl https://nezhagate.com/v1/videos/generations \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model": "kling-4.0", "prompt": "the character in image 1 swings the spear in the forest of image 2, following the moves in the reference video, and shouts in the voice of the reference audio: come on", "images": ["https://example.com/character.png", "https://example.com/forest.png"], "video_references": ["https://example.com/moves-1280x720.mp4"], "audio_reference": ["https://example.com/voice-8s.mp3"], "duration": 10, "resolution": "720P", "size": "16:9"}'This job outputs 10 s with a 5 s reference video, so 15 seconds are billed; the reference audio is free.
Job status and fields
GET /v1/videos/jobs/{id} reports these statuses; every field is described in Async jobs. Poll every 5–10 seconds, or let a webhook (below) push the result. Polling is free, and any key of the same account can read the job.
| Status | Meaning |
|---|---|
| queued | Waiting in line. Carries queue_position (jobs ahead of yours) and eta_seconds (expected wait). |
| processing | Rendering. |
| succeeded | Done. The result is in data[0].url, the billing detail in usage. |
| failed | Failed. The reason is in error; the credits held for the job are refunded in full. |
processingcan go back toqueued, for example while every line is busy, while a finished clip is fetched again, or after a restart. If a line cannot finish a job, the gateway may hand it to the next line to render again; however many lines it passes through, a job is charged once.- One account runs up to 50 jobs at a time; further jobs wait in submission order, and a job that has not started after 15 minutes fails and is refunded in full. Job records are kept for 3 days and video links for 15 days.
How long it takes
Our tests (720P / 1080P, 3-5 s) usually rendered in 3-5 minutes; it takes longer when the queue is busy. We wait up to 120 minutes and refund in full on a timeout.
Median render time in our tests: 3.3 min.
Errors and refunds
An error at submit comes back at once as HTTP 4xx / 5xx: no job is created and nothing is charged. A failure after the job was accepted is reported by the job endpoint, and the hold is refunded in full. Every code is listed under Error codes.
Errors at submit
| HTTP | code | Cause | What to do |
|---|---|---|---|
| 400 | invalid_request | A parameter is not valid: the duration, resolution, ratio or number of references is outside this model's range, the prompt is too long, a reference image or video cannot be read, audio: false was sent to a model whose clips always have sound, and so on. message says which. | Fix what message says and submit again. |
| 400 | invalid_callback_url | callback_url is not a public http(s) address, or is longer than 2000 characters. | Use an address that can be reached from the internet. |
| 400 | model_not_found | The model id is misspelled, model is missing, or the model cannot be used on this endpoint (a chat model on the image endpoint, for example). | Use a model id from GET /v1/models or the API reference. |
| 401 | invalid_api_key | The key does not exist, was deleted or is disabled, or the account is disabled. | Check the key on the API Keys page of the console. |
| 402 | insufficient_quota | Not enough balance to hold this request. The gateway holds the highest possible cost of the request first, so a burst of concurrent calls can trigger it too. | After a burst, wait a few seconds and retry; otherwise top up. |
| 429 | rate_limit_exceeded | Over the requests-per-minute limit set on this key (counted over the last 60 seconds). No Retry-After header. | Wait a few seconds and retry, or raise the key's limit in the console. |
| 503 | model_maintenance | The model is under maintenance and takes no requests; no other model answers in its place meanwhile. | Retry when maintenance ends; see Status. |
| 503 | service_unavailable | A temporary failure on our side, such as storing a reference file. No job was created and nothing was charged. | Retry shortly. |
Failed jobs (status failed)
| code | When you see it | What to do |
|---|---|---|
| content_policy | Video: the prompt, a reference, or the generated picture or sound did not pass content review (param is audio when the sound was flagged). Wan Standard and Prime: input material did not pass review. error.reason is FaceDetectionSuspect (possible real human face) or DataInspectionFailed (input content review failed). error.message explains the cause and recommended change. Failed tasks are not billed. | Rewrite the prompt or replace the reference before you submit again; the same input is refused again. |
| duration_rejected | A reference was not accepted: the reference audio is not 5-30 s of clear human speech, or a reference video has the wrong length. With param audio it is the audio. | Use a voice clip of at least 5 s (or drop the reference audio) and trim reference videos to 2-30 s, then resubmit; unchanged it fails again. |
| render_failed | Video: no clip came out this time, usually a one-off. | Submitting the same request again usually works. |
| render_timeout | Video: queueing or rendering took too long and the upstream gave the job up. | Submit again. |
| result_fetch_failed | The video was rendered, but downloading or storing it kept failing on our side. | Submit again. |
| upstream_error | Any other failure: every line was tried, the job waited 15 minutes without starting, the upstream refused without a reason, and so on; message says which. | Submitting again usually works. |
Branch on code, not on the text of message. Treat any code you do not recognise (or a missing one) as upstream_error: resubmitting usually works. Every failed job is refunded in full.
Webhooks instead of polling
Add callback_url when you submit and the moment the job ends, success or failure, the gateway pushes exactly what the poll endpoint returns to that URL, signed and retried on failure. You can also set an account-wide address under Settings → Webhook in the console. Webhook docs →
curl https://nezhagate.com/v1/videos/generations \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model": "kling-4.0", "prompt": "a dancer spins through falling maple leaves while the camera circles her slowly", "duration": 5, "resolution": "720P", "size": "16:9", "callback_url": "https://your-domain.com/webhook"}'