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.

View this page as Markdown

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).

Operations: Runs endpoints · Tasks · Fields.

The Run resource

{
  "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).
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).
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).
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.

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.
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).
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:

{
  "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).
  • 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:

{ "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).

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; field writes (PATCH /v1/runs/{id}/fields, all-or-nothing) on Field values & typing.
  • 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).

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.