Docs
Search docs

Cancel a single job of a workflow run

POST/api/jobs/{job_id}/cancel

cancelWorkflowRunJob

Cancels one job without touching its siblings. A job that never started goes straight to `cancelled` (its transitive dependants are skipped, and the run finalizes if that left no live jobs); a running job transitions to `cancelling` — the executing runner tears it down and confirms. Requires write access on the job's repo. 409 when the job is already finished; cancelling an already-`cancelling` job is a no-op.

Authentication is not described for this operation in the spec — that does not mean it is public. Check tokens and scopes.

curl

curl -X POST 'https://neosource.dev/api/jobs/JOB_ID/cancel'

fetch

fetch("https://neosource.dev/api/jobs/JOB_ID/cancel", {
  method: "POST",
});

Path parameters

job_idrequired

string

Responses

200Job cancelling/cancelled

application/json

WorkflowRunJobResponse

object

cache_transfer
one of
  • JobCacheTransferResponse

    object

    A job's cache transfer, measured by the server as it served the job's cache requests (`workflow_run_job_cache_transfer`). Every byte figure is COMPRESSED object bytes as the server sent or stored them (the actions/cache tarball, the build-cache pool object), never the unpacked size. A download counts when the server builds a hit response, at the entry's full size, even if the client aborts mid-body; a `lookup` or HEAD never counts. An upload counts only once durably stored. Every request that reaches the server counts once (a repeated pool PUT counts each time). Telemetry, not accounting: a crashed API node loses up to 30 s of counts, and nothing is ever counted twice.

    native_restored_bytes

    integer | nullint64

    Bytes those restores sent. `null` exactly when `native_restores` is.

    native_restores

    integer | nullint64

    actions/cache restore hits served to this job. `null` = not measured: the job's row predates restore tracking, so it may have restored. A job that ran during the deploy introducing this count may under-count (a restore served by a not-yet-updated node is missed).

    native_save_skippedrequired

    integerint64

    actions/cache saves refused because the key already existed (keys are immutable); they stored nothing.

    native_saved_bytesrequired

    integerint64

    native_savesrequired

    integerint64

    actions/cache saves that stored a new entry.

    pool_bytes_downrequired

    integerint64

    Bytes the pool's hits sent.

    pool_bytes_uprequired

    integerint64

    pool_getsrequired

    integerint64

    Build-cache pool GETs (a miss counts with 0 bytes).

    pool_hitsrequired

    integerint64

    pool_putsrequired

    integerint64

    Build-cache pool PUTs that stored, and their bytes.

  • null

carried_from
one of
  • CarriedFromResponse

    object

    Where a carried job ([`WorkflowRunJobResponse::carried_from`]) executed.

    attemptrequired

    integerint32

    That run's `attempt`.

    job_idrequired

    string

    The job that executed (UUID hex).

    run_idrequired

    string

    Its run — an earlier attempt of this run (UUID hex).

  • null

compile_cache
one of
  • CompileCacheResponse

    object

    A job's compile-cache result, as SERVED to a reader: the raw counters plus the two figures derived from them. The derived fields are computed at read time rather than stored, so improving the model never means a migration and the inputs stay visible beside the output. Both are `null` where they have no meaning — notably `estimated_saved_ms` on a job that hit on everything and therefore never measured a compile.

    compilationsrequired

    integerint64

    compile_msrequired

    integerint64

    estimated_saved_ms

    integer | nullint64

    Modelled ms the cache saved: `hits × (mean compile − mean cache read)`. `null` when the terms do not exist — see `JobCompileCache`.

    hit_rate

    number | nulldouble

    Hits ÷ (hits + misses), or `null` when there were no requests.

    hitsrequired

    integerint64

    missesrequired

    integerint64

    read_hit_msrequired

    integerint64

    requestsrequired

    integerint64

  • null

definitionrequired

object

Definition slice the runner needs to execute. JSON shape mirrors `neosource_core::model::WorkflowJobDef`.

enqueued_atrequired

integerint64

executed_by_runner

string | null

The runner that claimed this job (UUID hex), materialized at claim like `executed_on`. `null` until first claim, on carried jobs, and ALWAYS when `executed_on` is `actor` (a stable id would link one person's machine across public runs).

executed_on
one of
  • RunnerBindingKind

    string

    Which trust binding a job executed under (ADR 0056 §1/§7). Serialized on the wire and in `workflow_run_jobs.executed_on` as the `snake_case` strings `hosted` / `workspace` / `actor`.

    "hosted""workspace""actor"

  • null

executed_runner_name

string | null

Display name of the runner that executed this job, as it was at claim (a later rename does not rewrite history). Set ONLY when `executed_on` is `workspace`: a personal runner's name defaults to its hostname and run pages can be public, so an actor-bound execution is shown as "ran on @handle's machine" instead, and a hosted one as "hosted". Also `null` for a runner that never reported a name.

exit_code

integer | nullint32

finished_at

integer | nullint64

idrequired

string

job_keyrequired

string

log_offsetrequired

integerint64

log_truncated

boolean

The job's log hit its per-job size cap (ADR 0029 §"Log size per job / retention"). The log ends with a server-authored truncation marker line and nothing more will ever be appended. This flag, not that line, is the authoritative signal — a job can echo the same text.

matrix_values

object | null

Matrix combination, when this job row came from matrix expansion.

needsrequired

array

Other jobs that must succeed before this one is eligible.

items

string

pod_scheduled_at

integer | nullint64

When this job's pod was scheduled onto a node (epoch ms; Kubernetes reports it at 1 s precision). `null` until the runner reports it, for a job that did not run on a k8s pod, and for every job that ran before the field existed. `started_at` → this is time spent waiting for capacity.

pod_started_at

integer | nullint64

When this job's runner container started (epoch ms; 1 s precision). `pod_scheduled_at` → this is the pod's boot (image pull, Nix store, sandbox). Same `null` cases as `pod_scheduled_at`.

repo_idrequired

string

run_idrequired

string

runs_on

array

Labels the job's `runs-on:` requires (a stored legacy `runs-on: nix` job reports `["ubuntu-latest"]`, ADR 0174).

items

string

started_at

integer | nullint64

statusrequired

WorkflowRunJobStatus

string

Lifecycle of an individual job inside a run. Distinct from the run-level status because `needs:` ordering means some jobs are `Waiting` while others are `Running`. The serde `snake_case` strings are the wire vocabulary the API emits (pinned by the `enum_wire_consistency` tests).

"waiting""ready""running""cancelling""succeeded""failed""cancelled""skipped"

steps

array

Per-step records reported by the runner. Empty until the runner posts its first snapshot (and always empty on the claim payload).

items

WorkflowRunStepResponse

object

One step of a job, reported by the executing runner. Offsets index into the job's log byte stream (`GET /api/jobs/{job_id}/logs`), so the UI can slice the job log per step. `log_end_offset` is `null` while the step is in flight.

cache
finished_at

integer | nullint64

log_end_offset

integer | nullint64

log_start_offsetrequired

integerint64

namerequired

string

started_at

integer | nullint64

statusrequired
WorkflowRunStepStatus
step_indexrequired

integerint32

403Forbidden — one of: forbidden, needs_scope

application/json

one of
  • ErrorForbidden
  • NeedsScopeError

    object

    `403` body returned when a classic OAuth token is asked for something its granted scopes do not cover — an explicit private listing without `repo`, or the one-click GitHub mirror without `admin:public_key`. `scope` names the missing grant so the client can say exactly what the token lacks. There is no elevation route to widen it any more (a re-link goes through the GitHub App, whose tokens carry no scopes), so the client's answer is to say so, never to start a handshake.

    errorrequired

    string

    Always `needs_scope` — this body exists to carry the extra fields that kind needs.

    "needs_scope"

    scoperequired

    string

    The provider scope to request via elevation (e.g. `"repo"`).

Standard errors

Bodies documented once for the whole API — see standard errors.

  • 400Bad Request — one of: invalid_input
  • 404Not Found — one of: not_found
  • 409Conflict — one of: already_exists, conflict
  • 429Rate limited — retry after the `Retry-After` header
  • 500Internal server error
  • 503Service temporarily unavailable / at capacity — retry after the `Retry-After` header
  • 504Gateway timeout — the request exceeded the server's handling budget

Schemas

Referenced above. Listed here rather than expanded inline, so the same definition is not repeated at every level.

StepCacheReportDto

object

What a step's cache RESTORE got back. `null` on the step it belongs to means "this step said nothing about caching" — never "miss". Every step that predates the feature, and every step that is not a cache restore, reads null, so a reader that renders absence as a miss libels most of the platform's history.

bytes

integer | nullint64

Compressed size of the restored entry, when the transport knew one.

matched_key

string | null

The key that MATCHED — the primary key on an exact hit, the restore-key-matched entry's key on a partial one. `null` on a miss.

outcomerequired

StepCacheOutcome

string

What a cache RESTORE got back (ADR 0016; phase 2 of `plans/ci-cache-and-step-visibility-2026-08.md`). Scoped to restores on purpose. A restore is one-to-one with a step, so the fact has an unambiguous owner and an unambiguous meaning — "did the cache help here". A SAVE is not: the combined `actions/cache@v4` saves from a job-end post-hook that runs after every step has finished, so attributing it to a step index would mean inventing one. Adding a save outcome later is additive to this enum and to the column that stores it.

"exact""partial""miss"

WorkflowRunStepStatus

string

Lifecycle of one step inside a job. Written by the runner (it knows the boundaries exactly) via an idempotent full-snapshot upsert; never derived server-side from log parsing.

"queued""running""succeeded""failed""skipped"