PL Log inOpen Studio
Preview version. Model catalog, prices, calculator and docs are live. Generation goes live at launch.

Tasks

Docs: every page

A task is what POST /v1/generate creates. Ask GET /v1/tasks/{id} about it at any time: while it runs you see its status, and after it finishes you also get the result files and what it cost.

Fetching a task

bash
curl https://kadraris.com/v1/tasks/task_8f2k... \
  -H "Authorization: Bearer $KADRARIS_API_KEY"

You do not need to build this URL yourself. The /v1/generate response includes it ready to use in poll_url.

Task object

json
{
  "id": "task_8f2k...",
  "status": "succeeded",
  "model": "seedance-2.5",
  "output": [
    { "type": "video", "url": "https://...", "duration": 5, "width": 1280, "height": 720 }
  ],
  "cost": { "amount": "0.77", "currency": "USD" },
  "error": null,
  "created_at": "2026-09-27T10:15:02Z",
  "completed_at": "2026-09-27T10:16:41Z"
}
FieldTypeWhat it holds
idstringThe task identifier
statusstringOne of queued, running, succeeded, failed
modelstringThe model that runs the task
outputarrayResult files, present once status is succeeded
costobject{amount, currency} you were charged; 0 for a failure
errorobject or null{type, message}, only for failed
created_atstringWhen the task was created (ISO 8601)
completed_atstring or nullWhen it ended (ISO 8601), empty until then

Status values

StatusWhat is happeningFinal
queuedWaiting its turn; the cost is already reservedno
runningThe model is working on itno
succeededFiles are ready and cost has been chargedyes
failedIt did not finish; the reservation is returned and the charge is 0yes

Entries in output

FieldForWhat it holds
typeevery entryimage, video or audio
urlevery entryDownload link that expires
width, heightimage, videoSize in pixels
durationvideo, audioLength in seconds

This is how a failure looks:

json
{
  "id": "task_3xq1...",
  "status": "failed",
  "model": "kling-3.0",
  "output": [],
  "cost": { "amount": "0.00", "currency": "USD" },
  "error": { "type": "upstream_error", "message": "The model could not complete this request." },
  "created_at": "2026-09-27T10:20:00Z",
  "completed_at": "2026-09-27T10:21:12Z"
}

Polling until done

Request the task again every few seconds and stop when the status is succeeded or failed. An image is often ready in seconds; a video may need a few minutes. The examples below start at 2 s and lengthen the gap up to 15 s.

python
import os, time, requests

API = "https://kadraris.com/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['KADRARIS_API_KEY']}"}

def wait_for_task(task_id: str, timeout: int = 900) -> dict:
    delay, deadline = 2, time.time() + timeout
    while time.time() < deadline:
        task = requests.get(f"{API}/tasks/{task_id}", headers=HEADERS, timeout=30).json()
        if task["status"] in ("succeeded", "failed"):
            return task
        time.sleep(delay)
        delay = min(delay * 1.5, 15)  # gap grows to at most 15 s
    raise TimeoutError(f"task {task_id} still running after {timeout} s")

task = requests.post(
    f"{API}/generate",
    headers=HEADERS,
    json={"model": "kling-3.0", "input": {"prompt": "A kite tangled in a tree on a windy hill", "duration": 5}},
    timeout=30,
).json()

done = wait_for_task(task["id"])
print(done["status"], done["cost"], done["output"] or done["error"])
javascript
const API = "https://kadraris.com/v1";
const headers = { Authorization: `Bearer ${process.env.KADRARIS_API_KEY}` };
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));

async function waitForTask(id, timeoutMs = 900_000) {
  let delay = 2000;
  const deadline = Date.now() + timeoutMs;
  while (Date.now() < deadline) {
    const task = await (await fetch(`${API}/tasks/${id}`, { headers })).json();
    if (task.status === "succeeded" || task.status === "failed") return task;
    await sleep(delay);
    delay = Math.min(delay * 1.5, 15000);
  }
  throw new Error(`task ${id} still running`);
}

const started = await (await fetch(`${API}/generate`, {
  method: "POST",
  headers: { ...headers, "Content-Type": "application/json" },
  body: JSON.stringify({
    model: "kling-3.0",
    input: { prompt: "A kite tangled in a tree on a windy hill", duration: 5 },
  }),
})).json();

const done = await waitForTask(started.id);
console.log(done.status, done.cost, done.output ?? done.error);

When you run many tasks in parallel or the jobs are long, a webhook is simpler than a polling loop.

Saving the files

The links in output do not last forever. Copy each file you care about to your own storage.

python
import pathlib

def download_outputs(task: dict, folder: str = "out") -> list[pathlib.Path]:
    ext = {"image": "png", "video": "mp4", "audio": "mp3"}
    paths = []
    pathlib.Path(folder).mkdir(exist_ok=True)
    for i, item in enumerate(task["output"]):
        path = pathlib.Path(folder) / f"{task['id']}_{i}.{ext[item['type']]}"
        with requests.get(item["url"], stream=True, timeout=120) as r:
            r.raise_for_status()
            with open(path, "wb") as f:
                for chunk in r.iter_content(1 << 20):
                    f.write(chunk)
        paths.append(path)
    return paths
javascript
import { writeFile, mkdir } from "node:fs/promises";

async function downloadOutputs(task, folder = "out") {
  await mkdir(folder, { recursive: true });
  for (const [i, item] of task.output.entries()) {
    const res = await fetch(item.url);
    const buf = Buffer.from(await res.arrayBuffer());
    await writeFile(`${folder}/${task.id}_${i}`, buf);
  }
}

In the Python version the extension is picked from type only. If the exact format matters, read the Content-Type header of the download response.

Errors from this endpoint

Statuserror.typeSituation
401authentication_errorKey missing or invalid
404not_foundNo such task, or it was created by a different account

All error types: Errors.