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.
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 / label |
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 label. |
due |
The run-level due date (an instant), or null. |
key |
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). |
started |
manual, schedule, api or kickoff; started is the member for a manual start, else null. |
schedule / scheduled |
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). |
deleted |
Set while the run is in Trash. |
version |
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", user, { kind: "group", group, { kind: "role", role } or { kind: "guest", email }, with no names; look names up with GET / and GET / (Directory). |
fields[] |
In builder order, every field of every task except Display Text (which carries no value). task 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; validation 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 |
|---|---|
workflow |
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 + q |
Run-name text, case-insensitive, matched at the start of the name (prefix, the default) or anywhere (q). |
updated |
Incremental sync — see Incremental sync. |
include |
true to include runs in Trash (deleted set). Default false. |
sort / order |
started (default, desc), updated, name, due, key, completed, priority; order is asc or desc. Only started and updated 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"
}nameis required (1–300 characters). Run names are how people find runs; a consistent convention (a date and an identifier) pays off on the board.fieldsmay 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}/schemadescribes exactly what is accepted (Workflows & schema).dueAtis an instant or aYYYY-MM-DDdate (the end of that day in the workspace's time zone); an impossible date such as2026-02-30is 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, … (422validation_failed, orfield_not_writablewhen the first problem is one); then the kickoff fields — a missing required kickoff field is 422required_fields_unsatisfied(each missing one asrequired, plus every visible kickoff value outside its bounds or rules asinvalid_value), and refused kickoff values alone are 422validation_failed(invalid_value). No run is created. The same request with the sameIdempotency-Keyanswers the same 422. - The answer is
201 Createdwith an absoluteLocation(https://app.processcart.com/api/v1/runs/{id}) and the Run (revision: 1). Onerun.createdevent fires. Therevisionis for your next write'sExpected-Revision;POST /v1/runsitself 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" }statusis 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") stampscompletedAt; setting it back toactiveclears it.labelsreplaces the whole list; every id is checked against the workspace's label set (422unknown_labelon an unknown one).- Each changed attribute writes one activity entry. One
run.updatedevent per request carries every changedname/priority/labels/dueAtindata.changes. A status change is never inrun.updated: it firesrun.status_changed(plusrun.completedwhen the new status iscompleted), 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-Revisionis honored; the answer is the Run with its newrevision(Concurrency).
Trash and restore
DELETE /v1/runs/{id}moves the run to Trash (recoverable for 30 days) and answers 200 with the Run (deletedAtset) rather than 204 — the resource still exists. A run already in Trash answers 200 unchanged. Every further write answers 409run_trashed; reads of the run, its tasks, comments, files and activity still work.POST /v1/runs/{id}/restorebrings it back. A live run answers 200 unchanged; a run whose workflow is in Trash is refused with 409workflow_trashed(restore the workflow in the app first).Expected-Revisionis honored as a compare-and-set.
Workflows cannot be trashed or restored through the API in v1.
Tasks and fields
GET /v1/runs/{id}/tasksis the resource'stasks[]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.