# Runs

> The central resource — every field value (canonical + formatted) and every task's state in one document — and how to list, start, update, trash and restore runs.

Source: https://developers.processcart.com/guides/runs

A **run** is one execution of a workflow's published version. The API returns it as one document with full context, so an integration never needs a second fetch: the header, every task with its state and assignees, and every field with its canonical `value` and display `formatted`. The same document is what a webhook delivery carries as `data.run` (the one exception: a workflow moved to or from Trash sends its runs' `run.trashed` / `run.restored` with a lite header — see [Events](https://developers.processcart.com/reference/events)).

Operations: [Runs endpoints](https://developers.processcart.com/reference/runs) · [Tasks](https://developers.processcart.com/reference/tasks) · [Fields](https://developers.processcart.com/reference/fields).

## The Run resource

```json
{
  "id": "9b7d2c1e-…",
  "name": "Order 1042",
  "status": "active",
  "priority": "high",
  "labels": ["lbl_rush"],
  "labelDetails": [{ "id": "lbl_rush", "name": "Rush", "color": "#dc2626" }],
  "dueAt": "2026-10-10T06:59:59.000Z",
  "keyDate": "2026-10-09",
  "revision": 7,
  "updatedAt": "2026-10-07T18:42:11.000Z",
  "startedAt": "2026-10-07T17:03:11.000Z",
  "startedBy": "api",
  "startedByUserId": null,
  "schedule": null,
  "scheduledFor": null,
  "completedAt": null,
  "deletedAt": null,
  "workflow": { "id": "3c1d9e2a-…", "name": "Order fulfillment" },
  "versionId": "a0b1c2d3-…",
  "url": "https://app.processcart.com/runs/9b7d2c1e-…",
  "tasks": [
    { "key": "details", "title": "Order details", "kind": "standard", "section": "Intake",
      "state": "done", "visible": true, "locked": false, "dueAt": null,
      "assignees": [{ "kind": "user", "userId": "11111111-…" }],
      "completedAt": "2026-10-07T17:40:00.000Z", "completedByUserId": "11111111-…" },
    { "key": "review", "title": "Manager review", "kind": "approval", "section": "Intake",
      "state": "todo", "visible": true, "locked": false, "dueAt": "2026-10-09T06:59:59.000Z",
      "assignees": [], "completedAt": null, "completedByUserId": null }
  ],
  "fields": [
    { "key": "amount", "label": "Amount", "type": "number", "format": "currency", "taskKey": "details",
      "value": 1700.5, "formatted": "$1,700.50", "visible": true },
    { "key": "discount", "label": "Discount", "type": "number", "format": "percent", "taskKey": "details",
      "value": 0.125, "formatted": "12.5%", "visible": true },
    { "key": "approved", "label": "Approved", "type": "checkbox", "format": null, "taskKey": "review",
      "value": false, "formatted": "No", "visible": true },
    { "key": "shipping", "label": "Shipping", "type": "dropdown", "format": null, "taskKey": "details",
      "value": "Ground", "formatted": "Ground", "visible": true, "choices": ["Ground", "Air"] }
  ]
}
```

| Member | Meaning |
| --- | --- |
| `status` | `active`, `completed`, `on_hold` or `archived`. A run completes when every task is done (or by hand); an archived run accepts only a status change. |
| `priority` | `none`, `low`, `medium`, `high`, `urgent`. |
| `labels` / `labelDetails` | The status-label **ids** the run wears, and the same labels resolved to `{ id, name, color }` from the workspace's label set. Writes take ids; an unknown id is a 422 (`errors[].code` `unknown_label`). An id with no match is kept in `labels` and absent from `labelDetails`. |
| `dueAt` | The run-level due date (an instant), or null. |
| `keyDate` | The value of the workflow's designated Key Date field (`YYYY-MM-DD`), or null. |
| `revision` | The concurrency token — send it back as `Expected-Revision`. Bumped on every persisted change ([Concurrency](https://developers.processcart.com/guides/concurrency)). |
| `startedBy` | `manual`, `schedule`, `api` or `kickoff`; `startedByUserId` is the member for a manual start, else null. |
| `schedule` / `scheduledFor` | The schedule that created the run, as `{ id, name }` (null for a run no schedule started, and once that schedule is deleted), and the occurrence instant it was created for (null for every other run, a schedule's **Run now** included) ([Schedules](https://developers.processcart.com/guides/schedules)). |
| `deletedAt` | Set while the run is in Trash. |
| `versionId` | The immutable workflow version the run is pinned to — a later publish never changes a running run. |
| `tasks[]` | In checklist order. `state` is `todo`, `done` or `rejected_redo`; `visible` and `locked` are the engine's current verdicts (a hidden or locked task cannot be acted on); `assignees` are resolved people and groups by id — `{ kind: "user", userId }`, `{ kind: "group", groupId }`, `{ kind: "role", role }` or `{ kind: "guest", email }`, with no names; look names up with `GET /v1/users` and `GET /v1/groups` ([Directory](https://developers.processcart.com/guides/directory)). |
| `fields[]` | In builder order, every field of every task except Display Text (which carries no value). `taskKey` says which task the field belongs to; `visible` follows the field's and its task's visibility; `choices` rides dropdown and multi-choice fields, `items` subtasks fields; `validationError` appears when the current value breaks the field's rules (completion will refuse until it is fixed). |

The typing of `value` and `formatted` is the subject of [Field values & typing](https://developers.processcart.com/guides/field-values).

## Listing runs

`GET /v1/runs` returns **full Run resources** (fields and tasks included), newest started first, at most 100 per page — one request replaces a hundred reads. Filters (every parameter is optional and camelCase; unknown parameters are a 422):

| Parameter | Matches |
| --- | --- |
| `workflowId` | One workflow. |
| `status` | Comma-separated statuses; **the default is any status**, so a sync does not miss completed runs. |
| `priority` | Comma-separated priorities. |
| `labels` | Comma-separated label ids; a run matches when it wears **any** of them. |
| `assignee` | `u:<user id>` or `g:<group id>` — runs with an open task assigned to that member (directly, through a group or a role) or group. `me` is refused with a 422 (`errors[].code` `viewer_relative`): a key is nobody. |
| `due` | `overdue`, `today`, `week` or `none`, on the run's due date in the workspace's time zone. |
| `q` + `qMode` | Run-name text, case-insensitive, matched at the **start** of the name (`prefix`, the default) or anywhere (`qMode=contains`). |
| `updatedSince` | Incremental sync — see [Incremental sync](https://developers.processcart.com/guides/incremental-sync). |
| `includeTrashed` | `true` to include runs in Trash (`deletedAt` set). Default false. |
| `sort` / `order` | `startedAt` (default, `desc`), `updatedAt`, `name`, `dueAt`, `keyDate`, `completedAt`, `priority`; `order` is `asc` or `desc`. Only `startedAt` and `updatedAt asc` traverse a changing collection stably ([Pagination](https://developers.processcart.com/guides/pagination)). |
| `limit` / `cursor` | Paging: default 50, at most 100. |

A key with a workflow or folder allowlist sees only the runs of those workflows; others are absent, never a 403.

## Starting a run

`POST /v1/runs` starts a run of a workflow's published version **in one transaction**:

```json
{
  "workflowId": "3c1d9e2a-…",
  "name": "Order 1042",
  "fields": { "customer_email": "ada@example.com", "amount": "$1,700.50", "shipping": "Ground" },
  "priority": "high",
  "labels": ["lbl_rush"],
  "dueAt": "2026-10-10"
}
```

- `name` is required (1–300 characters). Run names are how people find runs; a consistent convention (a date and an identifier) pays off on the board.
- `fields` may set **any writable field** of the version, not only kickoff fields; every visible kickoff field must be valid, and the ones marked required must be present. `GET /v1/workflows/{id}/schema` describes exactly what is accepted ([Workflows & schema](https://developers.processcart.com/guides/workflows)).
- `dueAt` is an instant or a `YYYY-MM-DD` date (the end of that day in the workspace's time zone); an impossible date such as `2026-02-30` is a 422.
- Everything is validated **before** the run exists, in three passes that each list every problem they find in `errors[]`: the label ids (`unknown_label`); then the field values — an unknown field, a non-writable field, an unparseable value, … (422 `validation_failed`, or `field_not_writable` when the first problem is one); then the kickoff fields — a missing required kickoff field is 422 `required_fields_unsatisfied` (each missing one as `required`, plus every visible kickoff value outside its bounds or rules as `invalid_value`), and refused kickoff values alone are 422 `validation_failed` (`invalid_value`). No run is created. The same request with the same `Idempotency-Key` answers the same 422.
- The answer is `201 Created` with an absolute `Location` (`https://app.processcart.com/api/v1/runs/{id}`) and the Run (`revision: 1`). One `run.created` event fires. The `revision` is for your next write's `Expected-Revision`; `POST /v1/runs` itself takes none.

A workflow without a published version is a 409 `workflow_not_published`; one outside the key's allowlist a 403 `not_allowed_for_key`; one in Trash or in another workspace a 404.

## Reading a run

`GET /v1/runs/{id}` returns the Run with its `revision`. A run in Trash still loads (with `deletedAt` set) so a sync can see what happened to it; a run of another workspace, or an id that is not a uuid, is a 404.

## Updating the header

`PATCH /v1/runs/{id}` changes run-level attributes under the run lock — any subset of:

```json
{ "name": "Order 1042 — expedited", "priority": "urgent", "labels": ["lbl_rush", "lbl_vip"], "dueAt": null, "status": "on_hold" }
```

- `status` is applied first; then the archived guard governs the rest (an archived run refuses every other change until it is active again). Completing a run by hand (`status: "completed"`) stamps `completedAt`; setting it back to `active` clears it.
- `labels` replaces the whole list; every id is checked against the workspace's label set (422 `unknown_label` on an unknown one).
- Each changed attribute writes one activity entry. One `run.updated` event per request carries every changed `name` / `priority` / `labels` / `dueAt` in `data.changes`. A status change is never in `run.updated`: it fires `run.status_changed` (plus `run.completed` when the new status is `completed`), so a request that changes the status and the name fires both. An unchanged request is a 200 no-op and emits no event.
- `Expected-Revision` is honored; the answer is the Run with its new `revision` ([Concurrency](https://developers.processcart.com/guides/concurrency)).

## Trash and restore

- `DELETE /v1/runs/{id}` moves the run to **Trash** (recoverable for 30 days) and answers **200 with the Run** (`deletedAt` set) rather than 204 — the resource still exists. A run already in Trash answers 200 unchanged. Every further write answers 409 `run_trashed`; reads of the run, its tasks, comments, files and activity still work.
- `POST /v1/runs/{id}/restore` brings it back. A live run answers 200 unchanged; a run whose **workflow** is in Trash is refused with 409 `workflow_trashed` (restore the workflow in the app first). `Expected-Revision` is honored as a compare-and-set.

Workflows cannot be trashed or restored through the API in v1.

## Tasks and fields

- `GET /v1/runs/{id}/tasks` is the resource's `tasks[]` as a bounded list; it takes no query parameters (an unknown one is a 422).
- Task actions (`complete`, `reopen`, `approve`, `reject`) and files are on [Tasks, approvals & files](https://developers.processcart.com/guides/tasks-approvals-files); field writes (`PATCH /v1/runs/{id}/fields`, all-or-nothing) on [Field values & typing](https://developers.processcart.com/guides/field-values).
- Every write to a run is server-authoritative: the engine re-evaluates formulas, visibility and locks from the persisted values, applies the gates, and only then writes — exactly as the app does. The run's activity trail records every change with the key as its actor ([Comments & activity](https://developers.processcart.com/guides/comments-activity)).

## Events

`run.created`, `run.updated`, `run.status_changed`, `run.completed`, `run.trashed`, `run.restored`, `task.ready`, `task.assigned`, `task.completed`, `task.uncompleted`, `field.updated` (opt-in), `approval.requested`, `approval.decided` — every one carries the full Run, except that `run.trashed` / `run.restored` carry only the lite header `{ id, name, revision, workflow, deletedAt }` when a workflow trash or restore cascaded (`data.withWorkflow: true`; the key is absent on a single run's trash or restore). Fetch `GET /v1/runs/{id}` when you need the body — it answers for a run in Trash too. See [Events](https://developers.processcart.com/reference/events).
