roasOS
Concepts

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 | canceled

completed, 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 pathGives you
GET /v1/jobsa 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}/resultthe result object
GET /v1/jobs/{id}/eventsthe 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 is 50.

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 jobs is the object that GET /v1/jobs/{id}/status gives. The items are in the order of ids.
  • 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_terminal is false, 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 becomes canceled.
  • 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.

WorkStarted byRead at
An analysisPOST /v1/workflows/analyze, POST /v1/workflows/extract-brandGET /v1/workflows/analyses/{id}
An agent runPOST /v1/agent/completions, POST /v1/skills/{id}/runs, POST /v1/workflows/remix, POST /v1/workflows/staticsGET /v1/agent-runs/{id}
A research jobPOST /v1/research/jobsGET /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.

HeaderValue
Deprecationtrue
SunsetMon, 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.

On this page