How it works: jobs & polling
Every generation runs asynchronously. You create a job, track its status, and download the file once it's ready.
The lifecycle#
1. You POST to the model's endpoint and get a job with status and id. 2. While it isn't done, you call GET /v1/jobs/{id} every few seconds. 3. When status becomes completed, the data array holds the output URLs.
Possible statuses: processing → completed or failed. Cost is charged when the job is accepted; failed jobs are refunded automatically.
Response shape#
Both the POST and the GET return the same job object:
{
"id": "abc123…",
"object": "generation",
"model": "<model-id>",
"output_kind": "video" | "image",
"status": "processing" | "completed" | "failed",
"created_at": "ISO-8601",
"completed_at": "ISO-8601 | null",
"expires_at": "ISO-8601",
"retention_days": 7,
"error": "string | null",
"cost_usd": 0.30,
"input": { /* echo of your request */ },
"timings": { "inference_ms": 1234 },
"urls": { "get": "https://api18.dev/v1/jobs/{id}" },
"data": [ { "url": "https://api18.dev/v1/files/{id}" } ]
}Polling loop#
let job = /* response from the POST */;
while (job.status !== "completed" && job.status !== "failed") {
await new Promise((r) => setTimeout(r, 3000)); // 3s
job = await fetch("https://api18.dev/v1/jobs/" + job.id, {
headers: { Authorization: `Bearer ${process.env.API18_KEY}` },
}).then((r) => r.json());
}
if (job.status === "failed") throw new Error(job.error);
const urls = job.data.map((d) => d.url);Downloading the files#
Each item in data points to /v1/files/{id}, which 302-redirects to the file. For multiple outputs, use ?n=1, ?n=2, etc.
Output URLs expire (see
retention_days). Download and store the files if you need to keep them long-term.See full examples in SDKs.