Async Jobs API
Manage long-running media generation jobs across image, video, and audio modalities. The Jobs API lets you poll job status, list all jobs for your tenant, and cancel in-flight jobs before they consume credits.
?async=true). Video generation is always async. Sync endpoints (chat, embeddings) are unaffected and always return HTTP 200 inline.Job lifecycle
All async jobs move through the same state machine:
queued β in_progress β succeeded
β failed
β cancelled (via DELETE)
β expired (artifact TTL reached)The job object
| Name | Type | Required | Description |
|---|---|---|---|
id | string | Required | Unique job identifier. Prefix encodes modality: img_* (image), vid_* (video), aud_* (audio). |
object | string | Required | Modality discriminator: "image_job" | "video_job" | "audio_job". |
status | string | Required | queued | in_progress | succeeded | failed | cancelled | expired |
model | string | Required | Standard model alias used to create the job (e.g. openai/gpt-image-2). |
provider | string | Upstream provider that processed the job. May be null while queued. | |
modality | string | Required | image | video | audio |
created_at | number | Required | Unix timestamp when the job was submitted. |
completed_at | number | null | Unix timestamp when the job reached a terminal state. | |
expires_at | number | null | Unix timestamp after which artifact URLs are no longer accessible (typically 1 hour post-completion). | |
result | object | null | Modality-specific result payload. Present only when status=succeeded. | |
error | object | null | Error details when status=failed. Contains code and message. | |
usage | object | null | Token or unit consumption used for billing. |
Endpoints
Retrieve a job
GET /v1/jobs/:id
Returns the current state of an async job. Works for all modalities β the id prefix (img_, vid_, aud_) routes to the correct table automatically.
curl https://api.therouter.ai/v1/jobs/img_01J9Z7QP8BT4EWHF6V3KDMSR99 \
-H "Authorization: Bearer $THEROUTER_API_KEY"{
"id": "img_01J9Z7QP8BT4EWHF6V3KDMSR99",
"object": "job",
"status": "succeeded",
"modality": "image",
"model": "openai/gpt-image-2",
"provider": "openai-api",
"created_at": 1746700800,
"completed_at": 1746700890,
"expires_at": 1746704490,
"result": {
"data": [{ "url": "https://..." }]
},
"usage": { "image_count": 1 }
}List jobs
GET /v1/customer/jobs
Returns a paginated list of all async jobs for the authenticated tenant, across all three modalities. Supports filtering by modality, status, model, and date range.
| Name | Type | Required | Description |
|---|---|---|---|
modality | string | Filter by modality: image | video | audio. Omit for all. | |
status | string | Filter by status: queued | in_progress | succeeded | failed | cancelled | expired. | |
model | string | Filter by model alias (exact match). | |
start_date | string | ISO 8601 date lower bound for created_at. | |
end_date | string | ISO 8601 date upper bound for created_at. | |
limit | number | Page size. Range: 1β200. Default: 50. | |
offset | number | Pagination offset. Default: 0. |
# List most recent succeeded image jobs
curl "https://api.therouter.ai/v1/customer/jobs?modality=image&status=succeeded&limit=20" \
-H "Authorization: Bearer $THEROUTER_API_KEY"{
"jobs": [
{
"id": "img_01J9Z7QP8BT4EWHF6V3KDMSR99",
"modality": "image",
"model": "openai/gpt-image-2",
"status": "succeeded",
"created_at": "2026-05-08T10:00:00Z",
"actual_cost": 0.04,
"reserved_credits": 0.05,
"error_message": null,
"expires_at": "2026-05-08T11:01:30Z"
}
],
"pagination": {
"offset": 0,
"limit": 20,
"has_more": false
}
}Modality-specific aliases
OpenAI-compatible alias routes delegate to the same underlying job resolver:
GET /v1/images/:idβ alias for image jobsGET /v1/videos/:idβ alias for video jobs
Polling pattern
Poll GET /v1/jobs/:id until status is a terminal state (succeeded, failed, cancelled, or expired).
async function pollJob(id, apiKey) {
let delay = 2000;
while (true) {
const res = await fetch(`https://api.therouter.ai/v1/jobs/${id}`, {
headers: { Authorization: `Bearer ${apiKey}` },
});
const job = await res.json();
if (['succeeded', 'failed', 'cancelled', 'expired'].includes(job.status)) {
return job;
}
await new Promise((r) => setTimeout(r, delay));
delay = Math.min(delay * 1.5, 30_000);
}
}Billing
Credits are reserved at submission time (shown as reserved_credits in the job list). The final actual_cost is settled on completion. Failed, cancelled, or expired jobs are fully refunded β no credits are consumed.