---
title: "List the detached jobs on a machine"
method: GET
path: "/api/v1/jobs"
---

# List the detached jobs on a machine

`GET /api/v1/jobs`

## Request

- Base URL: `https://aicommander.dev`
- URL: `https://aicommander.dev/api/v1/jobs`
- Auth: one of:
  - none
  - HTTP bearer

Returns the machine's jobs — running right now, plus finished ones still retained (about a week; expired jobs are then swept automatically in the background, roughly hourly, driven by job start/list activity — so this very call can be what deletes an older expired job's metadata, log and workspace). Use it to answer 'what is running on the GPU box?', to recover the jobId of work started earlier, or before starting new GPU work so you don't collide with an existing run. Newest first, and by default only the newest 20 — 7 days of retention on a busy machine can mean dozens of records, and returning them all wastes a caller's context; raise `limit` (page cap 200) or narrow with `status` when you need more, and the reply reports how many older entries were omitted. If the reply carries `skippedJobs`/`warning`, the list is INCOMPLETE — some records were dropped and are not shown.

## Query parameters

- `code` string, required
- `operation_id` string
- `status` 'running' | 'exited' | 'unknown'
- `include_command` 'true' | 'false'
- `limit` integer

## Response `200`

The machine's jobs.

- object
  - `ok` true, required
  - `jobs` JobSummary[], required
    - `jobId` string, required: 16 hex characters, generated by the machine (never caller-supplied).
    - `name` string, required: The label you passed, or one the machine generated.
    - `status` 'running' | 'exited' | 'unknown', required: `running` = the process was alive when the machine looked. `exited` = the exit code is authoritative. `unknown` = the process is gone and no exit code was recorded — an agent restart, a `SIGKILL`, the OOM killer, an escalated cancellation, or any cancellation on Windows (`taskkill /F`) all produce it, since no exit marker is written; the outcome is genuinely unknowable and must never be reported as success.
    - `exitCode` integer, nullable, required: Non-null only for `exited`.
    - `startedAt` integer, required: Epoch milliseconds.
    - `endedAt` integer, nullable, required: Epoch milliseconds the job ended, or null while it is still running. NOT null for `unknown` jobs: one that recorded no exit still gets a timestamp, but an INFERRED one — see endedAtApproximate, and check that flag before presenting this value as the moment the job finished.
    - `endedAtApproximate` boolean: Present and true when `endedAt` is an ESTIMATE, not an observed instant. A job that recorded no exit marker (SIGKILL, the OOM killer, an escalated cancel, any Windows cancel, the agent going down) has no true end time, so the machine infers one from the last write to its output log — up to ~2 minutes late on Windows, where NTFS defers last-write-time updates while the log is held open. ABSENT means the timestamp was observed and is exact; absent is also what an agent older than 1.0.50 sends for every job. Never quote an approximate value as the finish time, and never measure a duration from one without saying it is approximate.
    - `unknownReason` 'cancelled' | 'killed_by_signal' | 'job_script_removed' | 'vanished_no_output_windows' | 'vanished_no_output_posix' | 'vanished_with_output_windows' | 'vanished_with_output_posix' | 'undetermined': Present ONLY on `unknown` jobs, and only when the machine recorded one: WHAT THE MACHINE OBSERVED about the ending, captured at the moment it settled the job and never re-derived afterwards. It does NOT soften the status — `unknown` still means no exit code was recorded and success may never be assumed. Every token names evidence, not a cause. `cancelled` = someone asked for this job to stop. `killed_by_signal` = the machine watched the job's shell end and it ended on a signal, the only one of these backed by an observed death. `job_script_removed` = the per-job script files were gone from the job's directory (on Windows, the signature of security software quarantining them mid-run — see https://aicommander.dev/antivirus). `vanished_no_output_*` / `vanished_with_output_*` = nothing watched the ending; the scripts were intact and the log was empty, or the log had output in it. A `vanished_*` job may have been killed OR may have completed normally after bypassing the exit marker (a command that `exec`s away or overrides the wrapper's exit trap) — the evidence does not say which. `undetermined` = the machine could not read the job's own directory. ABSENT is normal and means the machine said nothing: an agent older than 1.2.2, or a token this relay does not recognise (which is dropped rather than passed on). Treat an absent value as no information, never as an error, and do not assume this list is closed — a newer agent may add a token, which arrives as absence until the relay learns it.
    - `gpuIndex` integer, nullable, required: The card reserved for this job, or null if none was requested.
    - `logBytes` integer, required: Current size of the job's output log, so you can page it without a probe read.
    - `truncated` boolean, required: True once the log hit its 256 MiB cap and the agent stopped appending. The job itself was NOT killed.
    - `command` string: The job's command line — present ONLY when the request asked for it with include_command=true.
    - `operationId` string: The `operation_id` the job was started with, if any.
    - `locks` string[]: The named locks the job took, if any.
    - `deduplicated` boolean: Present and true on a start answered with the job an earlier start under the same `operation_id` created: nothing new was started.
  - `omitted` integer: How many older job records are NOT in `jobs`. THE LIST IS A PAGE, NOT THE HISTORY: with no `limit` you get the newest 20, and this field is the only thing that says so — a client that ignores it will read a capped list as the machine's complete job history. 0 means nothing was left out. With a `status` filter it is an UPPER bound (the machine stops walking at the first N matches and cannot know how many of the records behind them would have matched). Raise `limit` (up to 200) or narrow with `status` to reach the rest. Omitted entirely only when the machine is running an agent too old to report it — then the completeness of the list is simply unknown.
  - `skippedJobs` object[]: Present ONLY when the list is INCOMPLETE. Some job records on the machine did not match the protocol and were dropped, so `jobs` is a partial answer — one entry here per dropped record. Never treat a reply carrying this field as the machine's full job list: the missing jobs may include running ones. Absent means nothing was dropped.
    - `field` string, required: Dotted protocol path of the field that made the record unusable, e.g. `job.endedAt`. Every dropped list entry is validated as a single job record, so the path is rooted at `job` (or `logs` for a log slice) and carries no list index — several entries can therefore report the same path.
    - `jobId` string: The dropped job's id, when it was itself intact and safe to echo. Omitted otherwise — the record is then unidentifiable from this reply alone.
  - `warning` string: Present exactly when `skippedJobs` is — the same omission in prose, for a human or a model reading the response. Surface it to the user rather than presenting the shortened list as complete.

## Other responses

- `400`: Missing/invalid fields. On the job endpoints this is also how the MACHINE reports a request it can never run (`reason: "invalid_request"` — an empty command, a relative `cwd`, a `gpu_index` that names no card): the request itself is at fault, so correct the field the message names rather than retrying it.
- `401`: A token was supplied but is not a valid API key/admin/OAuth token.
- `403`: Resolved but refused. Causes include expired anonymous access; Free/anonymous file transfer; `reason: plan_device_limit`; independent operator block/approval; or `error: reactivation_required`. For plan_device_limit, delete an older saved machine to promote the next or upgrade to Pro. For file transfer, use Pro. Do not conflate either with operator approval.
- `404`: Session code or alias not found (anonymous callers get a unified message that does not confirm existence).
- `429`: Per-IP or global tool-call rate limit exceeded.
- `503`: The session exists but the agent is offline or disconnected mid-command.

---

[API](https://skmtc.dev/aicommander/apis/ai-commander-remote-shell-and-long-running-jobs-api.md) · [All operations](https://skmtc.dev/aicommander/apis/ai-commander-remote-shell-and-long-running-jobs-api/llms.txt) · [OpenAPI document](https://skmtc.dev/aicommander/apis/ai-commander-remote-shell-and-long-running-jobs-api/revisions/a6bb89f779a1?raw)
