PL Log inCreate account
Launching in October 2026. Accounts, model catalog, calculator and docs are live. Studio is available in preview; generation is awaiting activation.

Tasks

All documentation pages

Every call to POST /v1/generate creates a task. GET /v1/tasks/{id} returns its current state and, once it has finished, the output files and the final cost.

Request

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

The poll_url field returned by /v1/generate already contains the full URL.

Response

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"
}
FieldTypeDescription
idstringTask id
statusstringqueued, running, succeeded or failed
modelstringModel id
outputarrayResult files, filled when status is succeeded
costobject{amount, currency} charged for the task. 0 when failed
errorobject or null{type, message} when status is failed
created_atstringISO 8601 timestamp
completed_atstring or nullISO 8601 timestamp, set when the task finishes

Statuses

StatusMeaningFinal
queuedAccepted, waiting to start. Funds are heldno
runningGeneration in progressno
succeededOutput ready, cost chargedyes
failedGeneration did not complete, hold released, you pay 0yes

Output items

FieldPresent forDescription
typeallimage, video or audio
urlallTemporary download URL
width, heightimage, videoPixels
durationvideo, audioSeconds

A failed task:

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

Poll every few seconds until the status is final. Images usually need seconds, video can take several minutes.

python
import os, time, requests

API = "https://jantaris.com/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['JANTARIS_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)  # back off up to 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 paper boat drifting down a rainy street", "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://jantaris.com/v1";
const headers = { Authorization: `Bearer ${process.env.JANTARIS_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 paper boat drifting down a rainy street", duration: 5 } }),
})).json();

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

For long jobs or many parallel tasks, webhooks save you the polling loop.

Downloading output

Output URLs are temporary. Download the files you want to keep and store them on your side.

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);
  }
}

The file extension above is a guess by type. Use the Content-Type header of the download if you need the exact format.

Errors

Statuserror.typeWhen
401authentication_errorBad or missing key
404not_foundTask id does not exist or belongs to another account

See Errors.