# Runs, progress and cancellation

> Submit API runs from saved or inline graphs, observe their lifecycle, cancel work, recover uncertain submissions, and retain the outputs you need.

Source: https://kaitoi.io/docs/core/runs-and-files/
Section: Kaitoi Core / REST API
Written for: Developers executing workflows through the REST API.
Prerequisites: An API key with runs:execute to create or cancel runs and runs:read to observe them
Last reviewed: 2026-10-24

---

A public API run is one asynchronous execution of a graph. `POST /api/v1/runs`
returns **202 Accepted** with a run record, rather than waiting for the final
result. Store its `id` so you can observe that attempt without submitting
the work again. For canvas controls, use [Runs in Studio](/docs/concepts/runs/).

All paths below are relative to `/api/v1`. Creating and canceling require
`runs:execute`; reading a run or its events requires `runs:read`. Runs are
account-owned, not restricted to the individual API key that created them.
Another suitably scoped key for the same owner can read the run. Station
visitor runs are exposed through the Station rather than these run routes.

## Track work after the request returns


Create work once from a saved project or inline graph and retain its run identity. Inspect queued, running and terminal states plus any available results. Cancel requests do not reverse provider work. Applying outputs is a separate guarded write for a succeeded saved-project run: use expectedVersion equal to run.projectVersion while the project still matches. Public run details expose source identifiers, not a retrievable graph snapshot body.

Only the succeeded branch of a saved-project run reaches guarded project application. Failed and canceled branches retain available evidence. Inline execution has no saved project to apply to.

*Figure: Keep a run receipt until its outcome and files are known.*

## Submit a precise request

Provide exactly one source:

| Source                 | Request fields | Targeting                                                                           |
| ---------------------- | -------------- | ----------------------------------------------------------------------------------- |
| Saved project          | `projectId`    | Explicit `targetNodeIds`, or the saved graph's execution configuration when omitted |
| Transient inline graph | `graph`        | At least one `targetNodeIds` entry is required                                      |

Use explicit targets when your application needs predictable intent. An inline
graph run does not create a saved project. The server snapshots the graph
document before dispatch, so subsequent project edits do not change that
attempt's graph. Referenced files and external services still have to be
available; the snapshot is not a promise to archive those dependencies.

`inputOverrides` are keyed by node instance ID, then input pin name:

```json
{
	"projectId": "project_example",
	"targetNodeIds": ["prompt_node"],
	"inputOverrides": {
		"prompt_node": {
			"inputPrompt": { "type": "string", "value": "a lighthouse at dusk" }
		}
	}
}
```

The IDs and pin names must exist in your graph; this example is illustrative.
Supported value tags are `string`, `number`, `boolean`, `json`, `file`, and
`null`. A `file` value uses `fileId` instead of `value`. These are transport
tags, separate from canvas pin types. Overrides affect this run without editing
the saved project; `persistInputOverrides: true` is currently unsupported.
See [Projects and graph documents](/docs/core/projects-and-graphs/) for graph
structure and validation.

## Retry submissions safely

Use `Idempotency-Key` on supported submission endpoints. Keep the same key and
same request when recovering from an uncertain network result. Changing the
request needs a different logical submission; reusing a key with conflicting
content is an error.

An idempotency replay can return the original accepted response. Read the run
detail to learn its current status. A key is not an unlimited promise that
every failed, expired, or canceled job can be repeated without cost. Inspect
the recorded run before deciding to start a new attempt.

## Follow the lifecycle

Use `GET /runs/{id}` for current status and results:

| Status      | Meaning                                                                |
| ----------- | ---------------------------------------------------------------------- |
| `accepted`  | A validated run record exists and work is being dispatched.            |
| `queued`    | Execution has been claimed or queued; completion is still pending.     |
| `running`   | The execution layer is working on the graph.                           |
| `succeeded` | The attempt finished successfully; inspect its outputs.                |
| `failed`    | The attempt ended in error; inspect `error` and events.                |
| `canceled`  | The attempt stopped; do not assume it produced every requested output. |

The last three states are terminal. A client polling slowly can miss short
intermediate states. Failure or cancellation can happen before execution starts.

For detailed observation, poll `GET /runs/{id}/events` or stream
`GET /runs/{id}/events/stream` using Server-Sent Events. Events include an `id`,
`runId`, `type`, and `createdAt`; `nodeId`, `progress`, and `message` are optional.
Types include `run.accepted`, `node.progress`, and `node.log`. Progress, when
present, is between 0 and 1.

Polling returns `data`, `nextCursor`, and `hasMore`. Treat cursors as opaque.
Resume SSE with the last SSE `id` in `Last-Event-ID`, or the `cursor` query
parameter. Reuse a cursor only for the same run and API key owner that issued it.

Use structured error information and retain the run ID for support. Back off
after rate limiting and avoid unbounded polling loops.

## Request cancellation

`POST /runs/{id}/cancel` returns the updated record. If the run is still
`accepted` or `queued`, cancellation makes it terminal immediately. If it is
`running`, the request sets `cancelRequested` and the execution layer attempts
to stop it. Keep observing the final status: cancellation can race with
completion, and it cannot recall provider work already accepted externally.

Canceling an already canceled run returns that record. A succeeded or failed
run cannot be canceled and returns a conflict. Cancellation is not a refund
guarantee and may leave partial outputs.

## Read the cost

Terminal records report `creditsUsed` when reliable accounting is available.
`creditsUsedMicrocredits` is the exact amount in millionths of a credit,
encoded as a string. The optional `spend` summary breaks usage down by node,
provider, and charge type.

`creditsUsed` is null while active. It can also be null on a terminal run when
a reliable summary is unavailable. Treat null as **unknown**, never as zero.
A failed or canceled attempt may still have incurred charges. These fields
describe actual recorded usage, not a price estimate for another attempt.

## Apply results to a saved project

A run's outputs and a saved project's contents are separate. To retain the
server-owned outputs of a **succeeded saved-project run** in that project, call
`POST /runs/{id}/apply-to-project`. Inline runs cannot use this operation.
The key needs all three scopes: `runs:read`, `projects:write`, and `graphs:write`.

Send `expectedVersion` equal to the run's `projectVersion`. You may supply an
`outputKeys` subset; omit it to apply all recorded outputs. The endpoint accepts
output selection, not client-supplied output values. Use an `Idempotency-Key`
for a retryable application request.

If the saved project has changed since the run was created, application fails
with a conflict instead of overwriting newer work. Inspect the current graph
and rerun the intended version before applying results. A successful application
returns the new project `version`, `appliedOutputs`, and `outputKeys`.

## Keep output files

Use the public file workflow to upload or register supported inputs, then pass
their file IDs. Result metadata can supply a short-lived download URL. Download
what your application needs before it expires, or request a fresh authorized
download through the file API.

Check ownership and any external-user guards on file references. A bare file ID
is not proof that an application user should have access to that file. Run
retention, library retention, and a downloaded copy are separate lifecycles.

The [API reference](https://api.studio.kaitoi.io/api/v1/docs) defines the current
request fields, response schemas, errors, and limits.

## Preserve the attempt and its snapshot

**Accept:** POST /runs returns an attempt ID and snapshots its source; it is
not the final result. **Observe:** use that run's status/events and distinguish
succeeded, failed, or canceled with unknown cost kept unknown. **Apply:**
server-recorded outputs can be applied through a separate version-guarded
action where eligible. Temporary URLs and saved-project state are different.

## Checks and next steps

Saved-project run submission loads the project and retains an execution
snapshot internally. It does not accept a document-write-style
`expectedVersion` guard. Keep the returned `sourceType`, `projectId`,
`projectVersion` and targets for provenance. Public run details do not return
the snapshot body or a snapshot download route, and `projectVersion` is not
a certificate of exact bytes during concurrent writes.

For caller-controlled source, retain and submit a supported explicit inline
graph. That still does not freeze provider implementations, external services
or file lifetimes. A previous saved-project read does not lock submission.

Applying outputs later is a separate version-guarded action. It requires a
successful saved-project run, the necessary write scopes, and a compatible
unchanged project context. A run result, a temporary media URL, a saved graph,
and outputs adopted back into that graph are distinct facts.

If output application reports an unresolved or unavailable project save state,
preserve the result and local work. Follow [guarded project recovery](/docs/core/errors-and-retries/#recover-a-guarded-project-save)
instead of treating every refusal as a retryable stale version.

For upload and retention contracts see [Files and outputs](/docs/core/files-and-outputs/).
For an uncertain submission, inspect the original run with its ID and preserve
the same logical retry key before considering another paid attempt.
