Tasks
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
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
{
"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"
}| Field | Type | What it holds |
|---|---|---|
id | string | The task identifier |
status | string | One of queued, running, succeeded, failed |
model | string | The model that runs the task |
output | array | Result files, present once status is succeeded |
cost | object | {amount, currency} you were charged; 0 for a failure |
error | object or null | {type, message}, only for failed |
created_at | string | When the task was created (ISO 8601) |
completed_at | string or null | When it ended (ISO 8601), empty until then |
Status values
| Status | What is happening | Final |
|---|---|---|
queued | Waiting its turn; the cost is already reserved | no |
running | The model is working on it | no |
succeeded | Files are ready and cost has been charged | yes |
failed | It did not finish; the reservation is returned and the charge is 0 | yes |
Entries in output
| Field | For | What it holds |
|---|---|---|
type | every entry | image, video or audio |
url | every entry | Download link that expires |
width, height | image, video | Size in pixels |
duration | video, audio | Length in seconds |
This is how a failure looks:
{
"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.
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"])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.
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 pathsimport { 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
| Status | error.type | Situation |
|---|---|---|
| 401 | authentication_error | Key missing or invalid |
| 404 | not_found | No such task, or it was created by a different account |
All error types: Errors.























