Home › Guides › Kling API errors

Every error a Kling API call can return, what usually causes it, and what to do

Updated 2026-10-02

Errors from a Kling call arrive in two places, and confusing them is the most common debugging mistake. A request can be refused at creation with an HTTP error status, or it can be accepted and later fail, in which case the HTTP response was fine and the failure lives in the job's status and error fields. This page covers both, mapped to the situations you meet when calling Kling v3 (kling-v3.0-std, -pro, -4k), Kling O3 and Motion Control through VideoRouter.

The envelope

Every error uses the same OpenAI-style body, whichever host was involved:

{"error": {"message": "...", "type": "invalid_request_error", "code": "invalid_api_key"}}

code is optional, so branch on the HTTP status first and use the code to refine. The statuses below come from VideoRouter's error documentation; the "Kling causes" column is where that table meets the Kling-specific rules documented elsewhere.

Status codes mapped to Kling causes

Status / codeMeaningTypical Kling-side causeAction
400 invalid_request_errorBad request or unknown model idA mistyped tier (kling-v3-std instead of kling-v3.0-std); a model id not in the catalog, which includes any Kling 4.0 id because that model is not available on any host yet; start_image_url sent to a model that has no image input; end_image_url sent at all (documented as rejected on every model); a start image combined with reference arrays on a non-Motion-Control model; input_video_url without a prompt on an editing model; too many reference itemsFix the request. Never retry.
401 invalid_api_keyKey missing, malformed, revoked or expiredHeader typo, or an expired key in a deployed environmentAlert; never retry.
402 spend_cap_exceededThis key's monthly cap reachedA loop over many prompts on kling-v3.0-4k hit a cap you setStop. Raise the cap or wait.
402 insufficient_creditsOrganisation balance is emptyBalance ran out during a batchTop up, then resume.
403 model_not_allowedModel not in this key's allow-listThe key was restricted to Std and your code asked for ProFix key policy or the model id.
429 rate limitPer-key requests per minute exceededToo many parallel submissionsSleep for Retry-After, then retry.
500, 502, 503, 504 upstream_errorEvery candidate in the chain failedAll hosts serving that Kling tier were failing or rejectingRetry with backoff. Not billed.

The "unknown model" 400 deserves its own check

Because Kling tiers are separate model ids, a wrong string is the cheapest bug to make and it fails immediately, before billing. GET /v1/videos/models returns the live list of valid slugs and is unbilled; call it in a health check or when you see an unexpected 400. If you are waiting for Kling 4.0, an id for it will keep returning 400 until it appears in that list, which has not happened yet.

Single-host models have less fallback

The documentation describes Motion Control as served by one provider, with no cross-provider failover pool. A 5xx there means the one host failed, not "every host in a pool", so do not expect automatic rescue. The documented behaviour for failed upstream jobs is that they are not billed, so a bounded retry is reasonable.

Accepted, then failed

A job whose creation returned 202 can still end with status: "failed". The HTTP status of the poll is a normal 200; the failure is in the body, where error carries the provider's message and data is null. For Kling the causes you can verify are mostly input problems: a start image URL the host cannot fetch (expired presigned links are the classic), or a reference video the host cannot read. Test with a short clip first, as the Motion Control guide recommends, so those surface cheaply. The docs state that a job failing because every upstream host failed is not billed, which is what makes a retry after failed reasonable, in contrast to a retry after a lost connection.

Handling code

import time, random, requests

BASE = "https://videorouter.sh/api/v1"
H = {"Authorization": "Bearer llmr_sk_live_..."}

class Permanent(Exception): pass          # fix the request or the key
class Transient(Exception): pass          # safe to retry
class Ambiguous(Exception): pass          # create may have succeeded

def create(payload, attempts=4):
    for n in range(attempts):
        try:
            r = requests.post(f"{BASE}/videos", headers=H, json=payload, timeout=30)
        except (requests.ConnectionError, requests.Timeout):
            raise Ambiguous("connection lost on create")   # do NOT loop here
        if r.ok:
            return r.json()
        err = r.json().get("error", {}) if r.content else {}
        if r.status_code in (400, 401, 402, 403):
            raise Permanent(f"{r.status_code} {err.get('code')}: {err.get('message')}")
        wait = float(r.headers.get("Retry-After") or 0) if r.status_code == 429 else 0
        time.sleep(max(wait, min(30, 2 ** n)) + random.random())
    raise Transient("still failing after retries")

def finish(job_id, deadline_s=900):
    end = time.monotonic() + deadline_s
    while time.monotonic() < end:
        j = requests.get(f"{BASE}/videos/{job_id}", headers=H, timeout=30).json()
        if j["status"] == "completed":
            return j["data"][0]["url"]
        if j["status"] == "failed":
            raise Permanent(f"job failed: {j['error']}")   # read the message first
        time.sleep(5)
    raise TimeoutError(job_id)             # slow is not failed: keep the id

A triage order that saves time

  1. Was it HTTP 4xx at creation? Read code; it is your request, key or budget.
  2. Was it 5xx? Wait and retry; nothing was billed.
  3. Did creation succeed and the job later fail? Read error.message, check the image or video URL is reachable from outside your network, and retry once.
  4. Did the file arrive but look wrong in size? That is not an error at all: an unsupported resolution or aspect ratio is ignored rather than rejected. See the duration and aspect-ratio guide.

The general retry policy that applies to any model is in the Python tutorial, and the curl equivalent is in the curl walkthrough. Create a key and set a monthly cap so the 402 you eventually see is one you chose.

Frequently asked questions

Is a Kling job that fails billed?

VideoRouter documents that a job failing because every upstream host failed is not billed. Charges are made once at creation for jobs that are accepted, so check the job status and error message before assuming either way.

Why do I get a 400 for a Kling model id?

The id is not in the catalog. Kling tiers are separate ids (kling-v3.0-std, kling-v3.0-pro, kling-v3.0-4k), and no Kling 4.0 id is available yet. GET /v1/videos/models lists valid slugs.

Which Kling errors should never be retried?

400, 401, 402 and 403. They need a change to the request, key, budget or key policy. Retry 429 after Retry-After and 5xx with backoff.

Keep reading

Using Kling is one part of the job.

VideoRouter puts it next to dozens of other video and image models behind one API key, so you can compare providers, prices and fail over automatically. Compare providers on VideoRouter →