Jobs
One envelope and one read model for every kind of work that roasOS does.
Each model endpoint starts a job. An image, a scene, an avatar, a video, a UGC clip, audio and lipsync are all jobs. They have the same envelope and the same read endpoints.
An analysis, an agent run and a research job are different. Each one has its own envelope and its own read path. Refer to Work that is not a job.
The envelope
A create request returns 201 with the job:
{
"data": {
"id": "6f31c07d-...",
"kind": "video",
"model": "kling-4",
"status": "queued",
"credits_reserved": 9,
"created_at": "2026-08-19T10:04:11Z",
"links": {
"self": "/v1/jobs/6f31c07d-...",
"status": "/v1/jobs/6f31c07d-.../status",
"result": "/v1/jobs/6f31c07d-.../result",
"events": "/v1/jobs/6f31c07d-.../events"
}
}
}The id is a UUID. Every public identifier of roasOS uses that one format: a job, a quote, an analysis, an agent run, a research job. An id in another format is a 400 invalid_resource_id.
The reserved credits are in credits_reserved. Read the four links values, and do not build a path of your own.
Status
A job has one of five statuses:
queued -> processing -> completed | failed | canceledcompleted, failed and canceled are terminal. A job does not change after it is terminal.
The kinds that GET /v1/jobs reads
Seven kinds appear in the list: image, scene, avatar, video, ugc, audio and lipsync.
Each family reports the kind of the media that it gives you. An image edit, a product shot and an Ideogram reframe are all image. A dub, a caption, a video reframe and an assembly operation are all video. Music is audio. So a filter of kind=assembly does not exist, and kind=image finds the product shot.
The filter also accepts voice, batch and research. Each of those three answers an empty page, because each one has its own resource:
- A voice is at
GET /v1/voices. - A batch item appears in the list under the kind of its media, and never under
batch. - A research job is at
GET /v1/research/jobs/{id}.
Read a job
| Method and path | Gives you |
|---|---|
GET /v1/jobs | a list of jobs; filter with kind, status, cursor and limit |
GET /v1/jobs/{id} | the envelope, plus result when the job is terminal and error when it failed |
GET /v1/jobs/{id}/status | { id, status, updated_at } — the light poll |
GET /v1/jobs/wait?ids=… | the light poll of 1 to 12 jobs, after they finish or after the timeout |
GET /v1/jobs/{id}/result | the result object |
GET /v1/jobs/{id}/events | the steps of the run, in order |
The list is paged. Refer to Pagination.
Poll for the result
Poll GET /v1/jobs/{id}/status every few seconds. When the status is completed, read GET /v1/jobs/{id}/result.
curl https://api.roasos.com/v1/jobs/6f31c07d-.../status \
-H "Authorization: Bearer roasos_sk_..."GET /v1/jobs/{id}/result gives 409 run_not_completed while the job is not terminal. This is not an error in your code. Poll the status endpoint first.
Wait for many jobs
A video job can run for some minutes. To wait for up to 12 jobs in one call, use GET /v1/jobs/wait.
curl "https://api.roasos.com/v1/jobs/wait?ids=6f31c07d-...,9a2b44e0-...&timeout_seconds=50" \
-H "Authorization: Bearer roasos_sk_..."ids— 1 to 12 different job IDs, separated by commas.timeout_seconds— 1 to 55. The default is50.
roasOS reads the jobs every 2 to 5 seconds. The call returns when all the jobs are completed, failed or canceled, or before timeout_seconds is over.
{ "data": {
"jobs": [
{ "id": "6f31c07d-...", "status": "completed", "updated_at": "2026-09-28T10:02:11Z" },
{ "id": "9a2b44e0-...", "status": "processing", "updated_at": "2026-09-28T10:00:03Z" }
],
"all_terminal": false,
"waited_ms": 49250 } }- Each item in
jobsis the object thatGET /v1/jobs/{id}/statusgives. The items are in the order ofids. - A job ID that is not in your workspace gives
{ "id": "…", "error": { "code": "job_not_found", "message": "Job not found" } }in its place. roasOS does not wait for it. - When
all_terminalisfalse, send the call again with the IDs that are not finished.
The call is free. A bad ids or timeout_seconds value gives 400 validation_error.
Wait inside the create call
To wait inside the create call, send wait_timeout_seconds (0 to 60). roasOS then waits up to that time and returns the terminal job if the run finishes. The default is 0.
Events
GET /v1/jobs/{id}/events gives the steps of the run, in order: queued, provider_submitted, provider_completed, settled, failed and canceled. A delivered webhook adds webhook_delivered or webhook_failed.
roasOS records these events while the job runs. A job that ran before the timeline existed has no recorded events. For one of those, roasOS calculates the steps from the times on the job, and it shows no webhook event. Use the events to see how long each step took. Do not use them as a live stream.
Cancel a job
POST /v1/jobs/{id}/cancel records a cancellation on the job. It answers 409 job_not_cancelable when the job is already terminal.
The response carries cancel_honoured.
true— the job had not reached the model provider. roasOS stops the run, refunds the whole reservation, and the job becomescanceled.false— the provider already holds the request. roasOS records the cancellation, but the run finishes and you are billed for it. A provider charges for work that it has started.
A cancel is therefore free only before the provider submit, which is usually the first few seconds of a job.
Failure and refunds
If the status is failed, the job carries an error object with a stable code. roasOS releases the credit reservation. It does not charge you for a job that failed. Refer to Errors and to Quotes and credits.
Results are files
A result is a set of immutable objects on https://cdn.roasos.com. The URL does not change, and the bytes do not change. You can therefore send a result URL as the media input of your next request. Refer to Reference media and uploads.
Work that is not a job
Three kinds of work do not use the job envelope. Read each one at its own path.
| Work | Started by | Read at |
|---|---|---|
| An analysis | POST /v1/workflows/analyze, POST /v1/workflows/extract-brand | GET /v1/workflows/analyses/{id} |
| An agent run | POST /v1/agent/completions, POST /v1/skills/{id}/runs, POST /v1/workflows/remix, POST /v1/workflows/statics | GET /v1/agent-runs/{id} |
| A research job | POST /v1/research/jobs | GET /v1/research/jobs/{id} |
Each of the three publishes its own status words, and the words are not the five above. Read them on the page of that family: Analyze an ad, Agent runs and Research ads.
An agent run and an analysis start jobs of their own while they work. Those jobs are ordinary jobs, and you read them here.
The older paths
GET /v1/generations and GET /v1/generations/{id} still read image, video, audio and lipsync jobs. They are the older shape of the same data. Use /v1/jobs for new work: it reads every kind, and it has the status, result and events endpoints.
Two create paths are deprecated: POST /v1/generations and POST /v1/scenes. Each one answers with three headers.
| Header | Value |
|---|---|
Deprecation | true |
Sunset | Mon, 30 Nov 2026 00:00:00 GMT |
Link | </v1/catalog>; rel="successor-version" |
The Sunset date is the date after which the operation can stop answering. Read GET /v1/catalog to find the model endpoint that replaces your call. Move to it before that date.