# Process Cart API — full documentation > The Process Cart REST API: workflows, runs, tasks, field values, comments, files, saved views, webhooks, schedules, automation rules and the member directory of a workspace. Base URL https://app.processcart.com/api/v1; API keys with action scopes; RFC 9457 errors; cursor pagination with incremental sync; Standard Webhooks; OpenAPI 3.1. Source: https://developers.processcart.com/llms.txt --- # Process Cart API > Start a run, set its fields and complete a task from curl in five minutes — then read the conventions every endpoint follows. Source: https://developers.processcart.com/ The Process Cart REST API gives your code, your no-code tools and your AI agents the same workspace your team uses: workflows, runs, tasks, field values, comments, files, saved views, webhooks, schedules, automation rules and the member directory. It is a plain JSON API over HTTPS with API keys, cursor pagination, RFC 9457 errors and Standard Webhooks, described by an [OpenAPI 3.1 document](https://developers.processcart.com/openapi.json). - **Base URL:** `https://app.processcart.com/api/v1` - **Authentication:** `Authorization: Bearer ` — keys are created in **Settings → API** by an Admin ([Authentication & API keys](https://developers.processcart.com/guides/authentication)). - **Included on every plan**, no approval needed ([Plans, limits & access](https://developers.processcart.com/guides/plans)). - **Reference:** [every endpoint](https://developers.processcart.com/reference), [errors](https://developers.processcart.com/errors), [events](https://developers.processcart.com/reference/events), [limits](https://developers.processcart.com/reference/limits). AI tools: [llms.txt](https://developers.processcart.com/llms.txt) and a `.md` twin of every page. **Prefer TypeScript?** The [SDK](https://developers.processcart.com/guides/sdk) wraps every call below in a typed method — `npm install @processcart/sdk` — with pagination, idempotency keys, retries and webhook verification built in. ## Quickstart Everything below is curl. Replace `pc_live_…` with a key from Settings → API; the key needs the `runs:read`, `runs:write` and `workflows:read` scopes (or full access). ### 1. Identify your key ```bash curl https://app.processcart.com/api/v1/me \ -H "Authorization: Bearer pc_live_…" ``` ```json { "org": { "id": "0f7c4b3e-…", "name": "Acme", "slug": "acme", "timezone": "America/Los_Angeles", "sandbox": false }, "key": { "id": "6a1f0c2d-…", "name": "Zapier", "prefix": "pc_live_ab12", "readOnly": false, "scopes": ["runs:read", "runs:write", "workflows:read"], "createdAt": "2026-10-01T17:03:11.000Z", "lastUsedAt": null }, "rateLimit": { "limit": 600, "windowSeconds": 60 } } ``` Every response carries `X-Request-Id` (quote it to support) and, once authenticated, the `RateLimit-*` headers. ### 2. Find a workflow ```bash curl "https://app.processcart.com/api/v1/workflows" \ -H "Authorization: Bearer pc_live_…" ``` Pick a workflow with a `publishedVersion`; its `id` goes into the next call. `GET /v1/workflows/{id}/schema` tells you which fields a run accepts at start and what each one takes. ### 3. Start a run ```bash curl -X POST https://app.processcart.com/api/v1/runs \ -H "Authorization: Bearer pc_live_…" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: start-7f2c1c2e" \ -d '{ "workflowId": "3c1d9e2a-…", "name": "Order 1042", "fields": { "customer_email": "ada@example.com", "amount": 1700.5 } }' ``` The answer is `201 Created` with a `Location` header and the full Run (`revision: 1`): every task with its state, every field with its canonical `value` and display `formatted`. A number is sent as a JSON number (currency in whole units, percent as a fraction); a string is parsed by the field's format — `"$1,700.50"` works too ([Field values & typing](https://developers.processcart.com/guides/field-values)). The `Idempotency-Key` makes a retry safe: the same request again replays the stored answer instead of starting a second run ([Idempotency](https://developers.processcart.com/guides/idempotency)). ### 4. Set field values ```bash curl -X PATCH https://app.processcart.com/api/v1/runs/{id}/fields \ -H "Authorization: Bearer pc_live_…" \ -H "Content-Type: application/json" \ -H "Expected-Revision: 1" \ -d '{ "ship_date": "2026-10-09", "approved": true }' ``` One transaction, all-or-nothing: a bad key fails the whole request with every problem listed in `errors[]`. `Expected-Revision` refuses the write if someone changed the run since you read it (409 `revision_mismatch`, whose body names `currentRevision`) — [Concurrency](https://developers.processcart.com/guides/concurrency). ### 5. Complete a task ```bash curl -X POST "https://app.processcart.com/api/v1/runs/{id}/tasks/{taskKey}/complete" \ -H "Authorization: Bearer pc_live_…" \ -H "Idempotency-Key: complete-7f2c1c2e-review" ``` The engine applies the same gates the app does: the task must be visible and unlocked, and its required fields satisfied — otherwise a 422 `required_fields_unsatisfied` names every offender. Approvals are decided with `/approve` and `/reject` under the `approvals:decide` scope ([Tasks, approvals & files](https://developers.processcart.com/guides/tasks-approvals-files)). ### 6. Hear about changes Subscribe a webhook to `*` (or to specific events) and receive signed deliveries, retried on failure ([Webhooks](https://developers.processcart.com/webhooks)) — or poll `GET /v1/runs?updatedSince=…` ([Incremental sync](https://developers.processcart.com/guides/incremental-sync)). ## Where to go next | If you want to… | Read | | --- | --- | | Understand keys, scopes, read-only keys and allowlists | [Authentication & API keys](https://developers.processcart.com/guides/authentication) | | Test without touching live data | [Sandbox workspaces](https://developers.processcart.com/guides/sandbox) | | Read and write runs, tasks and fields | [Runs](https://developers.processcart.com/guides/runs) · [Field values & typing](https://developers.processcart.com/guides/field-values) · [Tasks, approvals & files](https://developers.processcart.com/guides/tasks-approvals-files) | | Page through and sync large collections | [Pagination](https://developers.processcart.com/guides/pagination) · [Incremental sync](https://developers.processcart.com/guides/incremental-sync) | | Retry safely and avoid lost updates | [Idempotency](https://developers.processcart.com/guides/idempotency) · [Concurrency](https://developers.processcart.com/guides/concurrency) | | Handle failures | [Errors](https://developers.processcart.com/errors) · [Rate limits](https://developers.processcart.com/guides/rate-limits) | | Call the API from TypeScript | [TypeScript SDK](https://developers.processcart.com/guides/sdk) | | Generate a client | [OpenAPI](https://developers.processcart.com/guides/openapi) | | Know what will change, and when | [Versioning & deprecation](https://developers.processcart.com/guides/versioning) · [Changelog](https://developers.processcart.com/changelog) | --- # Plans, limits & access > The API and webhooks are included for every workspace at no additional cost; keys are self-serve; the limits are the same for everyone. Source: https://developers.processcart.com/guides/plans **API access and webhooks are included for every workspace at no additional cost.** No approval or sales contact is needed: an Admin creates keys self-serve in **Settings → API**, and the key works the moment it is created. ## What every workspace gets - The full `/api/v1` surface: workflows, runs, tasks, field values, comments, files, saved views, webhooks, schedules, automation rules, the member directory and CSV export — nothing is tier-gated. - Webhooks for every event type, with signed deliveries and retries. - **600 requests per minute per key** (a fixed 60-second window). Create separate keys for separate integrations; each has its own window. - As many keys as you need, each with its own scopes, read-only flag and workflow or folder allowlist. - Sandbox workspaces for testing ([Sandbox workspaces](https://developers.processcart.com/guides/sandbox)). ## Who can do what | Action | Who | | --- | --- | | Create, rotate and revoke API keys | Admins, in Settings → API | | Use a key | Anything that holds the secret — keys are not tied to a person's session | | Manage webhooks | Builders and Admins in the app; any key with `webhooks:manage` through the API | | Manage schedules | Builders and Admins in the app (Automations → Schedules); any key with `schedules:manage` | | Manage automation rules | Builders and Admins in the app (Automations → Rules; a rule for every workflow is Admins only); any key with `rules:manage` (a rule for every workflow needs a key without an allowlist) | | Flip a workspace to a sandbox | Admins, in Settings → API | Keys outlive their creator: a key keeps working after the Admin who made it leaves the workspace — except for creating or changing a saved view, which needs the key's creator to still be a Builder or Admin. Revoke keys when their creator leaves. ## Limits Every limit is published on [Limits and schedules](https://developers.processcart.com/reference/limits); the rate limit is explained on [Rate limits](https://developers.processcart.com/guides/rate-limits). There is no separate quota for webhooks. --- # Authentication & API keys > Bearer keys with action scopes, read-only keys, workflow and folder allowlists, rotation with a 24-hour grace period, and revocation — all self-serve. Source: https://developers.processcart.com/guides/authentication Every request to `/api/v1` (except `GET /v1/health`) carries an API key. Keys are created by an Admin in **Settings → API**, shown exactly once, and stored only as a hash — Process Cart cannot show a key again. ## Sending the key The canonical form is a Bearer token: ```http GET /api/v1/me HTTP/1.1 Host: app.processcart.com Authorization: Bearer pc_live_8Jq3… ``` Tools that can only set a custom header may send `X-API-Key: pc_live_8Jq3…` instead. Sending both headers with **different** values is refused (401) — a proxy that injects one while the client sends the other must not silently pick a winner. A key looks like `pc_live_` (or `pc_test_` on a [sandbox workspace](https://developers.processcart.com/guides/sandbox)) followed by 43 URL-safe characters. The first twelve characters (`pc_live_8Jq3`) are the key's display prefix, the only part ever shown again. ### When authentication fails A 401 is an `application/problem+json` body with a stable `code` and a `WWW-Authenticate: Bearer realm="processcart"` header: ```json { "type": "https://developers.processcart.com/errors#key_revoked", "title": "API key revoked", "status": 401, "detail": "This API key was revoked, or its rotation grace period has ended.", "code": "key_revoked", "requestId": "req_5f1c…" } ``` | `code` | Meaning | | --- | --- | | `unauthorized` | No credential, or two different credentials. | | `invalid_api_key` | The key is malformed or unknown. | | `key_revoked` | The key was revoked, or its rotation grace period has ended. | ## Scopes A key holds one or more action scopes. Each operation names the scope it needs (it is in every reference page and in the OpenAPI document as `x-processcart-scope`); a key without it gets 403 `insufficient_scope` with the scope in `detail`. | Scope | Label | Allows | | --- | --- | --- | | `runs:read` | Read runs | List and read runs, tasks, field values, comments and activity. | | `runs:write` | Write runs | Start runs, set field values, complete / reopen tasks, comment, upload files, trash and restore. | | `approvals:decide` | Decide approvals | Approve or reject approval tasks (acts with Admin authority on approvals). | | `workflows:read` | Read workflows | List workflows, read their published schema and export the portable template. | | `workflows:write` | Write workflows | Create workflows from a template JSON and change workflow settings (name, description, folder, Key Date field, archive / unarchive). | | `views:read` | Read saved views | List saved views and query the runs board. | | `views:write` | Write saved views | Create saved views and update views owned by the key's creator; delete any shared view in the workspace (Admin power, audited). | | `webhooks:manage` | Manage webhooks | Create, update, test, disable and delete webhooks; read and redeliver deliveries. | | `schedules:manage` | Manage schedules | Create, update, pause, resume, preview and delete recurrence schedules, and start a schedule's run now (the response is the full Run with its field values). | | `rules:manage` | Manage automation rules | Create, update, enable, disable and delete automation rules, and read their execution log. A rule's actions change runs (fields, status, priority, labels, task assignees), notify members and call webhooks. | | `users:read` | Read users & groups | List members and groups (ids, names, roles). | | `*` | Full access | Every scope, present and future. | `*` is every scope, present and future — an Admin's "full access" key. `approvals:decide` is never implied by `runs:write`: a key that holds it decides approvals with Admin authority. `schedules:manage` covers [Schedules](https://developers.processcart.com/guides/schedules) and `rules:manage` [Automation rules](https://developers.processcart.com/guides/automation-rules); an automation rule for every workflow also needs a key without a workflow or folder allowlist. `GET /v1/me` needs no particular scope — any valid key may identify itself — and `GET /v1/health` needs no key at all. ## Read-only keys A key created as **read-only** may only make `GET` and `HEAD` requests, whatever scopes it holds. A read-only key with `webhooks:manage` can list webhooks and read deliveries but never create, change or redeliver one; any other verb is a 403 `read_only_key`. Hand a read-only key to anything that only needs to look. ## Allowlists A key may be restricted to some workflows, to the workflows inside some folders, or both. The allowlist is the API's restriction — a key minted by an Admin otherwise sees everything the Admin sees. - **Run-scoped operations** (anything under `/v1/runs/{id}`, `POST /v1/runs`, the export) resolve the run's or body's workflow and refuse one outside the allowlist with **403 `not_allowed_for_key`**. - **Lists** (`GET /v1/runs`, `GET /v1/workflows`, `GET /v1/views/{id}/runs`, `GET /v1/schedules`, `GET /v1/automation-rules`) fold the allowlist into the query: rows outside it are simply absent, never a 403. A restricted key's rules list also omits every rule for every workflow, and its execution list (`GET /v1/automation-rules/{id}/executions`) shows only executions on runs of its allowed workflows. - **Schedules and automation rules:** an item, or a create, for a workflow outside the allowlist is 403 `not_allowed_for_key`, and so is any rule for every workflow — that takes a key without an allowlist ([Schedules](https://developers.processcart.com/guides/schedules#allowlists) · [Automation rules](https://developers.processcart.com/guides/automation-rules#allowlists)). - **Workflows:** `GET /v1/workflows/{id}` outside the allowlist is a 403; `POST /v1/workflows` is refused for a workflow-restricted key (the new workflow could never be in its list), and a folder-restricted key must create inside one of its folders (`folderId`). - **Webhooks:** a key restricted by **folder** cannot use the webhook routes at all (403, "mint a workflow-restricted key for webhooks"). A key restricted by **workflow** may create or update a webhook only when the webhook's `workflowIds` is a non-empty subset of the key's workflows, lists and reads only such webhooks, and sees only deliveries about those workflows (plus `webhook.test` deliveries). - An unfoldered workflow is outside every folder allowlist. Allowlists are checked on every request against the workspace, so a workflow moved out of an allowed folder stops matching at once. ## Rotation **Rotate** in Settings → API mints a replacement key and keeps the old secret working for **24 hours** (`graceExpiresAt` on the old key), so a deploy can swap secrets without an outage. After the grace period the old key answers 401 `key_revoked`. An `Idempotency-Key` stored under the old key replays for the replacement too ([Idempotency](https://developers.processcart.com/guides/idempotency)). ## Revocation **Revoke** stops a key immediately: the next request answers 401 `key_revoked`. Revocation cannot be undone — create a new key instead. Keys outlive their creator, so revoke a departing member's keys when they leave; the request log on the same page shows what each key has been doing. ## Request log and audit Settings → API keeps a log of every authenticated request (route template, status, latency, request id — never the body or the query) and the audit trail records every key created, rotated or revoked. Quote a response's `X-Request-Id` to support and it can be found there. ## `GET /v1/me` The first call every integration should make: it returns the workspace and the key behind the credential, with its scopes and the published rate limit. Use it to confirm a key before relying on it. ```bash curl https://app.processcart.com/api/v1/me -H "Authorization: Bearer pc_live_…" ``` `org.labels` lists the workspace's status labels, in its own order, as `{ "id": "rush", "name": "Rush", "color": "#dc2626" }` — the ids a run's `labels`, the runs filter `labels=`, a view's `query.labels` and an automation rule's label actions take. It is empty when the workspace defines none; labels are managed in the app (Settings → Organization), not through the API. --- # TypeScript SDK > The official TypeScript client — typed methods for every operation, cursor pagination, idempotency keys, retries and webhook verification. Source: https://developers.processcart.com/guides/sdk `@processcart/sdk` is the official TypeScript client for this API. It has one method per operation, named by the operation's `operationId` (the same name the [reference](https://developers.processcart.com/reference) shows), and its request and response types are typed from the [OpenAPI document](https://developers.processcart.com/guides/openapi). It also walks cursor pages, sends an `Idempotency-Key` on every write, retries what is safe to retry, and verifies webhook signatures. ## Install ```bash npm install @processcart/sdk ``` The package is ESM with type declarations; it has no runtime dependencies. ## Create a client ```typescript import { ProcessCart } from "@processcart/sdk"; const client = new ProcessCart({ apiKey: process.env.PROCESSCART_API_KEY! }); ``` An Admin creates keys under **Settings → API** ([Authentication & API keys](https://developers.processcart.com/guides/authentication)). The client accepts a `pc_live_…` or `pc_test_…` key and refuses anything else before sending a request. A [sandbox](https://developers.processcart.com/guides/sandbox) uses the same base URL; the key decides which workspace you reach. | Option | Default | What it does | | ----------- | -------------------------------------------------------------- | -------------------------------------------------------------------------------- | | `apiKey` | — | Your API key (required). | | `baseUrl` | `https://app.processcart.com/api` | The server; paths add `/v1/…`. Change it only to reach a proxy or a test double. | | `timeoutMs` | `30000` | The timeout of one attempt, in milliseconds. | | `retry` | `{ maxAttempts: 3, maxDelayMs: 8000, retryAfterCapMs: 60000 }` | See [Retries](#retries). | | `fetch` | `globalThis.fetch` | Any WHATWG-compatible `fetch`. | | `userAgent` | `processcart-sdk/` | The `User-Agent` header. | Every method also takes a last `options` argument for that one call: `idempotencyKey`, `expectedRevision`, `requestId` (sent as `X-Request-Id`), `signal` (an `AbortSignal`), `timeoutMs` and `retry`. ## Quickstart The six steps of the [quickstart](https://developers.processcart.com), in TypeScript. The key needs the `runs:read`, `runs:write` and `workflows:read` scopes (or full access). ```typescript // 1. Identify your key. const me = await client.getMe(); console.log(`Connected to ${me.org.name} with the key "${me.key.name}"`); // 2. Find a published workflow. const workflows = await client.listWorkflows(); const workflow = workflows.data.find((w) => w.publishedVersion !== null); if (!workflow) throw new Error("Publish a workflow first"); // 3. Start a run, with field values in the same transaction. const run = await client.startRun({ workflowId: workflow.id, name: "Order 1042", fields: { customer_email: "ada@example.com", amount: 1700.5 }, }); // 4. Set field values; refused with 409 if someone changed the run since you read it. const updated = await client.setRunFields( run.id, { ship_date: "2026-10-09" }, { expectedRevision: run.revision }, ); // 5. Complete the first open task. const task = updated.tasks.find((t) => t.visible && !t.locked && t.state === "todo"); if (task) await client.completeTask(run.id, task.key, { expectedRevision: updated.revision }); ``` Then the sixth step, hear about changes: every run updated since your last sync, across every page ([Incremental sync](https://developers.processcart.com/guides/incremental-sync)). ```typescript for await (const changed of client.listRuns({ updatedSince: "2026-10-08T00:00:00Z" })) { console.log(changed.name, changed.revision); } ``` A method resolves to the response body, so `run.tasks` and `run.fields` are typed. Path parameters come first, in path order, then the request body (or the query), then `options`. ## Pagination A list method returns a request you can use three ways ([Pagination](https://developers.processcart.com/guides/pagination)): ```typescript // The first page: { data, nextCursor, hasMore }. const first = await client.listRuns({ limit: 50 }); // Every item across every page, following nextCursor with the same query. for await (const run of client.listRuns({ status: ["active"] })) { console.log(run.name); } // Every page, each with its own $meta. for await (const page of client.listRuns({ limit: 100 }).pages()) { console.log(page.data.length, page.$meta.requestId); } ``` Nothing is sent until you `await` or iterate, and the iterator owns `cursor`. For [incremental sync](https://developers.processcart.com/guides/incremental-sync), walk `client.listRuns({ updatedSince })` from the instant of your last sync. ## Writes, idempotency and concurrency Every `POST`, `PATCH` and `DELETE` carries an `Idempotency-Key`: a random UUID generated once per call and reused on each of that call's retries, so a retried call replays the stored answer instead of applying the change again ([Idempotency](https://developers.processcart.com/guides/idempotency)). Pass `options.idempotencyKey` to make a call safe to repeat across processes — derived from your own order number, say. Every Run the API returns carries its `revision`. Send the one you last saw as `expectedRevision` (the client sends it as the `Expected-Revision` header) and a write that raced another one is refused with `409 revision_mismatch` instead of overwriting it ([Concurrency](https://developers.processcart.com/guides/concurrency)). Each write resolves to the updated run, so chain the next write from its `revision`: ```ts const run = await client.getRun(runId); const updated = await client.setRunFields(runId, { amount: 125 }, { expectedRevision: run.revision }); await client.completeTask(runId, "review", { expectedRevision: updated.revision }); ``` `expectedRevision` takes the revision number (a non-negative integer; anything else throws a `TypeError` before a request is sent). On a `409 revision_mismatch` the error's `currentRevision` is the run's revision now: read the run again, merge, and retry with it. `deleteRunFile` answers `204`, so read the run before the next conditional write. Every method accepts the option, but only the writes that change an existing run read it: on schedules, automation rules, views, webhooks and workflows the server ignores it, and the last write wins. The API sends no `ETag` and refuses `If-Match` — CDN layers evaluate `If-Match` against the response and turn a successful write into a 412. Every result also carries a non-enumerable `$meta` (`JSON.stringify` and deep-equality checks skip it): | `$meta` field | What it holds | | ------------- | ------------------------------------------------------------------------------ | | `status` | The HTTP status. | | `requestId` | `X-Request-Id` — quote it to support. | | `revision` | The run's revision, from the body (set on every Run read, create and write). | | `replayed` | `true` when the API replayed the stored answer for the same `Idempotency-Key`. | | `rateLimit` | `{ limit, remaining, reset }` from the `RateLimit-*` headers. | | `headers` | The raw response headers. | An operation that answers `204 No Content` (`deleteView`, `deleteWebhook`, `deleteRunFile`) resolves to an object holding only `$meta`. ## Retries The client makes up to `maxAttempts` attempts in all, the first included (3 by default; `retry: { maxAttempts: 1 }` turns retries off). It retries: - **429 `rate_limited`** — after `Retry-After` seconds, else after `RateLimit-Reset`, else with backoff ([Rate limits](https://developers.processcart.com/guides/rate-limits)). - **409 `idempotency_in_progress`** — after `Retry-After`, or 2 seconds without one. - **502, 503 and 504 on a `GET`** — after `Retry-After` when the response has one, else with backoff. A write that meets one is not retried (see below); the error carries `retryAfter`. - **A network failure** (`fetch` itself threw) and **a timed-out attempt**. A wait the server asked for is capped at `retryAfterCapMs` (60 seconds). Backoff waits 500 ms before the first retry and doubles each time, plus up to 250 ms of jitter, capped at `maxDelayMs` (8 seconds). It never retries a 500, a 502 / 503 / 504 on a write (a crash or a database outage after the commit can release the API's idempotency claim, so a retry with the same key could apply the change twice — re-running it is your call), any other 4xx, or a call you aborted with `signal`. When the attempts run out it throws the last failure. `getHealth` is the one call that resolves a 503: a degraded API answers `{ status: "degraded" }`, read `$meta.status` to tell it from a 200. ## Errors Every API or transport failure rejects with a `ProcessCartError`; check for one with `isProcessCartError`: ```typescript import { isProcessCartError } from "@processcart/sdk"; try { await client.completeTask(runId, "review"); } catch (error) { if (!isProcessCartError(error)) throw error; console.error(error.status, error.code, error.requestId); for (const e of error.errors ?? []) console.error(e.field, e.code, e.message); } ``` The error carries `status` (`0` when no response arrived), `code` (an [error code](https://developers.processcart.com/errors), or `network_error`, `timeout` or `non_problem_response` from the client itself), `title`, `detail`, `requestId`, `errors` (the per-field problems of a 422), `retryAfter`, `headers`, the `problem` document as received, and `raw` (the response text when the body was not a problem document, such as a proxy's HTML page). On a `409 revision_mismatch`, `currentRevision` is the run's revision now (the problem body's `currentRevision`). A call you abort rejects with your abort reason, not a `ProcessCartError`. The code list is open: handle codes this version does not know. ## Files `uploadRunFile` runs the three-step upload ([Files](https://developers.processcart.com/guides/tasks-approvals-files#files)) in one call: it requests an upload ticket, PUTs the bytes to the signed URL with exactly the ticket's headers and no API key, then finalizes. ```typescript import { readFile } from "node:fs/promises"; const { attachment, run } = await client.uploadRunFile(runId, { target: { kind: "field", fieldKey: "packing_slip" }, filename: "packing-slip.pdf", contentType: "application/pdf", bytes: await readFile("./packing-slip.pdf"), }); ``` `bytes` is a `Blob`, an `ArrayBuffer` or a `Uint8Array` (a Node `Buffer` is one); `size` defaults to its length. `options.expectedRevision` applies to the finalize step, and each API step gets its own `Idempotency-Key`. A refused PUT rejects with a `ProcessCartError` (`code: "non_problem_response"`, the storage answer in `raw`) and nothing is finalized. ## Webhooks `@processcart/sdk/webhooks` verifies [Standard Webhooks](https://developers.processcart.com/webhooks) signatures with WebCrypto, without the client. Pass the RAW body — the exact bytes received, never a re-serialized object: ```typescript import { createServer } from "node:http"; import { headersFromRequest, verifyWebhookSignature } from "@processcart/sdk/webhooks"; import type { WebhookEvent } from "@processcart/sdk/webhooks"; const secret = process.env.PROCESSCART_WEBHOOK_SECRET!; // whsec_… createServer(async (req, res) => { const chunks: Buffer[] = []; for await (const chunk of req) chunks.push(chunk as Buffer); const body = Buffer.concat(chunks); const result = await verifyWebhookSignature(secret, headersFromRequest(req.headers), body); if (!result.ok) return void res.writeHead(400).end(result.reason); const event = JSON.parse(body.toString("utf8")) as WebhookEvent; res.writeHead(204).end(); // answer within 10 seconds, then do the work console.log(event.type, result.id); // dedupe on result.id }).listen(3000); ``` With a `Request` (Workers, Deno, Bun, Next.js route handlers), read `await request.text()` and pass `headersFromRequest(request.headers)`. The check accepts any `v1` entry of `webhook-signature` (both secrets sign for 24 hours after a rotation) and refuses a `webhook-timestamp` more than 300 seconds from now (`toleranceSeconds` changes it). A failure resolves to `{ ok: false, reason }`: `missing_headers`, `bad_timestamp`, `timestamp_outside_tolerance`, `no_matching_signature` or `bad_secret`. ## Types Every request and response type comes from the OpenAPI document, so the SDK types match what the API validates: ```typescript import type { Run, RunTask, OpBody, OpQuery } from "@processcart/sdk"; type NewRun = OpBody<"startRun">; // the startRun body type RunFilter = OpQuery<"listRuns">; // the listRuns query ``` The component schemas a method takes or returns are exported under their OpenAPI names (`Run`, `RunTask`, `RunField`, `WorkflowSummary`, `View`, `Webhook`, …); the raw `paths`, `components` and `webhooks` interfaces are exported too. `WebhookEvent` (from `@processcart/sdk/webhooks`) is the union of every event envelope, narrowed by `type`. Field values are typed per workflow at runtime — `getWorkflowSchema(id)` returns the JSON Schema of one workflow's run bodies ([Field values & typing](https://developers.processcart.com/guides/field-values)). ## Methods | Method | HTTP | What it does | | --- | --- | --- | | [`getMe(…)`](https://developers.processcart.com/reference#tag/me/GET/v1/me) | GET /v1/me | Identify the API key and its workspace | | [`getHealth(…)`](https://developers.processcart.com/reference#tag/health/GET/v1/health) | GET /v1/health | Check that the API is up | | [`listRuns(…)`](https://developers.processcart.com/reference#tag/runs/GET/v1/runs) | GET /v1/runs | List runs | | [`startRun(…)`](https://developers.processcart.com/reference#tag/runs/POST/v1/runs) | POST /v1/runs | Start a run | | [`getRun(…)`](https://developers.processcart.com/reference#tag/runs/GET/v1/runs/{id}) | GET /v1/runs/{id} | Get a run | | [`updateRun(…)`](https://developers.processcart.com/reference#tag/runs/PATCH/v1/runs/{id}) | PATCH /v1/runs/{id} | Update a run's name, priority, labels, due date or status | | [`trashRun(…)`](https://developers.processcart.com/reference#tag/runs/DELETE/v1/runs/{id}) | DELETE /v1/runs/{id} | Move a run to Trash | | [`restoreRun(…)`](https://developers.processcart.com/reference#tag/runs/POST/v1/runs/{id}/restore) | POST /v1/runs/{id}/restore | Restore a run from Trash | | [`listRunTasks(…)`](https://developers.processcart.com/reference#tag/tasks/GET/v1/runs/{id}/tasks) | GET /v1/runs/{id}/tasks | List a run's tasks | | [`completeTask(…)`](https://developers.processcart.com/reference#tag/tasks/POST/v1/runs/{id}/tasks/{key}/complete) | POST /v1/runs/{id}/tasks/{key}/complete | Complete a task | | [`reopenTask(…)`](https://developers.processcart.com/reference#tag/tasks/POST/v1/runs/{id}/tasks/{key}/reopen) | POST /v1/runs/{id}/tasks/{key}/reopen | Reopen a completed task | | [`approveTask(…)`](https://developers.processcart.com/reference#tag/tasks/POST/v1/runs/{id}/tasks/{key}/approve) | POST /v1/runs/{id}/tasks/{key}/approve | Approve an approval task | | [`rejectTask(…)`](https://developers.processcart.com/reference#tag/tasks/POST/v1/runs/{id}/tasks/{key}/reject) | POST /v1/runs/{id}/tasks/{key}/reject | Reject an approval task | | [`setRunFields(…)`](https://developers.processcart.com/reference#tag/fields/PATCH/v1/runs/{id}/fields) | PATCH /v1/runs/{id}/fields | Set field values | | [`listRunComments(…)`](https://developers.processcart.com/reference#tag/comments/GET/v1/runs/{id}/comments) | GET /v1/runs/{id}/comments | List a run's comments | | [`createRunComment(…)`](https://developers.processcart.com/reference#tag/comments/POST/v1/runs/{id}/comments) | POST /v1/runs/{id}/comments | Comment on a task | | [`getRunComment(…)`](https://developers.processcart.com/reference#tag/comments/GET/v1/runs/{id}/comments/{commentId}) | GET /v1/runs/{id}/comments/{commentId} | Get a comment | | [`requestRunFile(…)`](https://developers.processcart.com/reference#tag/files/POST/v1/runs/{id}/files) | POST /v1/runs/{id}/files | Request an upload ticket | | [`finalizeRunFile(…)`](https://developers.processcart.com/reference#tag/files/POST/v1/runs/{id}/files/{attachmentId}/finalize) | POST /v1/runs/{id}/files/{attachmentId}/finalize | Finalize an upload | | [`listRunFiles(…)`](https://developers.processcart.com/reference#tag/files/GET/v1/runs/{id}/files) | GET /v1/runs/{id}/files | List a run's files | | [`getRunFile(…)`](https://developers.processcart.com/reference#tag/files/GET/v1/runs/{id}/files/{attachmentId}) | GET /v1/runs/{id}/files/{attachmentId} | Get a file with a download link | | [`deleteRunFile(…)`](https://developers.processcart.com/reference#tag/files/DELETE/v1/runs/{id}/files/{attachmentId}) | DELETE /v1/runs/{id}/files/{attachmentId} | Remove a file | | [`listRunActivity(…)`](https://developers.processcart.com/reference#tag/activity/GET/v1/runs/{id}/activity) | GET /v1/runs/{id}/activity | List a run's activity | | [`listWorkflows(…)`](https://developers.processcart.com/reference#tag/workflows/GET/v1/workflows) | GET /v1/workflows | List workflows | | [`createWorkflow(…)`](https://developers.processcart.com/reference#tag/workflows/POST/v1/workflows) | POST /v1/workflows | Create a workflow from a template | | [`getWorkflow(…)`](https://developers.processcart.com/reference#tag/workflows/GET/v1/workflows/{id}) | GET /v1/workflows/{id} | Get a workflow | | [`updateWorkflow(…)`](https://developers.processcart.com/reference#tag/workflows/PATCH/v1/workflows/{id}) | PATCH /v1/workflows/{id} | Update a workflow's name, description, folder, Key Date field or status | | [`getWorkflowSchema(…)`](https://developers.processcart.com/reference#tag/workflows/GET/v1/workflows/{id}/schema) | GET /v1/workflows/{id}/schema | Get the JSON Schema for a workflow's run bodies | | [`getWorkflowTemplate(…)`](https://developers.processcart.com/reference#tag/workflows/GET/v1/workflows/{id}/template) | GET /v1/workflows/{id}/template | Export a workflow as a portable template | | [`listViews(…)`](https://developers.processcart.com/reference#tag/views/GET/v1/views) | GET /v1/views | List shared views | | [`createView(…)`](https://developers.processcart.com/reference#tag/views/POST/v1/views) | POST /v1/views | Create a shared view | | [`getView(…)`](https://developers.processcart.com/reference#tag/views/GET/v1/views/{id}) | GET /v1/views/{id} | Get a shared view | | [`updateView(…)`](https://developers.processcart.com/reference#tag/views/PATCH/v1/views/{id}) | PATCH /v1/views/{id} | Update a shared view | | [`deleteView(…)`](https://developers.processcart.com/reference#tag/views/DELETE/v1/views/{id}) | DELETE /v1/views/{id} | Delete a shared view | | [`listViewRuns(…)`](https://developers.processcart.com/reference#tag/views/GET/v1/views/{id}/runs) | GET /v1/views/{id}/runs | List the runs a view selects | | [`listWebhooks(…)`](https://developers.processcart.com/reference#tag/webhooks/GET/v1/webhooks) | GET /v1/webhooks | List webhooks | | [`createWebhook(…)`](https://developers.processcart.com/reference#tag/webhooks/POST/v1/webhooks) | POST /v1/webhooks | Create a webhook | | [`getWebhook(…)`](https://developers.processcart.com/reference#tag/webhooks/GET/v1/webhooks/{id}) | GET /v1/webhooks/{id} | Get a webhook | | [`updateWebhook(…)`](https://developers.processcart.com/reference#tag/webhooks/PATCH/v1/webhooks/{id}) | PATCH /v1/webhooks/{id} | Update a webhook | | [`deleteWebhook(…)`](https://developers.processcart.com/reference#tag/webhooks/DELETE/v1/webhooks/{id}) | DELETE /v1/webhooks/{id} | Delete a webhook | | [`enableWebhook(…)`](https://developers.processcart.com/reference#tag/webhooks/POST/v1/webhooks/{id}/enable) | POST /v1/webhooks/{id}/enable | Enable a webhook | | [`disableWebhook(…)`](https://developers.processcart.com/reference#tag/webhooks/POST/v1/webhooks/{id}/disable) | POST /v1/webhooks/{id}/disable | Disable a webhook | | [`rotateWebhookSecret(…)`](https://developers.processcart.com/reference#tag/webhooks/POST/v1/webhooks/{id}/rotate-secret) | POST /v1/webhooks/{id}/rotate-secret | Rotate a webhook's signing secret | | [`testWebhook(…)`](https://developers.processcart.com/reference#tag/webhooks/POST/v1/webhooks/{id}/test) | POST /v1/webhooks/{id}/test | Send a test event | | [`listWebhookDeliveries(…)`](https://developers.processcart.com/reference#tag/webhooks/GET/v1/webhooks/{id}/deliveries) | GET /v1/webhooks/{id}/deliveries | List a webhook's deliveries | | [`getWebhookDelivery(…)`](https://developers.processcart.com/reference#tag/webhooks/GET/v1/webhooks/{id}/deliveries/{deliveryId}) | GET /v1/webhooks/{id}/deliveries/{deliveryId} | Get a delivery | | [`redeliverWebhookDelivery(…)`](https://developers.processcart.com/reference#tag/webhooks/POST/v1/webhooks/{id}/deliveries/{deliveryId}/redeliver) | POST /v1/webhooks/{id}/deliveries/{deliveryId}/redeliver | Send a delivery again | | [`listSchedules(…)`](https://developers.processcart.com/reference#tag/schedules/GET/v1/schedules) | GET /v1/schedules | List schedules | | [`createSchedule(…)`](https://developers.processcart.com/reference#tag/schedules/POST/v1/schedules) | POST /v1/schedules | Create a schedule | | [`getSchedule(…)`](https://developers.processcart.com/reference#tag/schedules/GET/v1/schedules/{id}) | GET /v1/schedules/{id} | Get a schedule | | [`updateSchedule(…)`](https://developers.processcart.com/reference#tag/schedules/PATCH/v1/schedules/{id}) | PATCH /v1/schedules/{id} | Update a schedule | | [`deleteSchedule(…)`](https://developers.processcart.com/reference#tag/schedules/DELETE/v1/schedules/{id}) | DELETE /v1/schedules/{id} | Delete a schedule | | [`pauseSchedule(…)`](https://developers.processcart.com/reference#tag/schedules/POST/v1/schedules/{id}/pause) | POST /v1/schedules/{id}/pause | Pause a schedule | | [`resumeSchedule(…)`](https://developers.processcart.com/reference#tag/schedules/POST/v1/schedules/{id}/resume) | POST /v1/schedules/{id}/resume | Resume a schedule | | [`runScheduleNow(…)`](https://developers.processcart.com/reference#tag/schedules/POST/v1/schedules/{id}/run) | POST /v1/schedules/{id}/run | Start a run from a schedule now | | [`previewSchedule(…)`](https://developers.processcart.com/reference#tag/schedules/GET/v1/schedules/{id}/preview) | GET /v1/schedules/{id}/preview | Preview a schedule's next occurrences | | [`listAutomationRules(…)`](https://developers.processcart.com/reference#tag/automation-rules/GET/v1/automation-rules) | GET /v1/automation-rules | List automation rules | | [`createAutomationRule(…)`](https://developers.processcart.com/reference#tag/automation-rules/POST/v1/automation-rules) | POST /v1/automation-rules | Create an automation rule | | [`getAutomationRule(…)`](https://developers.processcart.com/reference#tag/automation-rules/GET/v1/automation-rules/{id}) | GET /v1/automation-rules/{id} | Get an automation rule | | [`updateAutomationRule(…)`](https://developers.processcart.com/reference#tag/automation-rules/PATCH/v1/automation-rules/{id}) | PATCH /v1/automation-rules/{id} | Update an automation rule | | [`deleteAutomationRule(…)`](https://developers.processcart.com/reference#tag/automation-rules/DELETE/v1/automation-rules/{id}) | DELETE /v1/automation-rules/{id} | Delete an automation rule | | [`enableAutomationRule(…)`](https://developers.processcart.com/reference#tag/automation-rules/POST/v1/automation-rules/{id}/enable) | POST /v1/automation-rules/{id}/enable | Turn an automation rule on | | [`disableAutomationRule(…)`](https://developers.processcart.com/reference#tag/automation-rules/POST/v1/automation-rules/{id}/disable) | POST /v1/automation-rules/{id}/disable | Turn an automation rule off | | [`listAutomationRuleExecutions(…)`](https://developers.processcart.com/reference#tag/automation-rules/GET/v1/automation-rules/{id}/executions) | GET /v1/automation-rules/{id}/executions | List a rule's executions | | [`listUsers(…)`](https://developers.processcart.com/reference#tag/directory/GET/v1/users) | GET /v1/users | List members | | [`listGroups(…)`](https://developers.processcart.com/reference#tag/directory/GET/v1/groups) | GET /v1/groups | List groups | | [`exportRunsCsv(…)`](https://developers.processcart.com/reference#tag/exports/GET/v1/exports/runs.csv) | GET /v1/exports/runs.csv | Export a workflow's runs as CSV | `uploadRunFile(id, { target, filename, contentType, bytes, size? }, options?)` adds the one-call upload described under [Files](#files). ## Runtime support The SDK needs `fetch` and WebCrypto: Node.js 22.12 or later, Deno, Bun and Cloudflare Workers. Use it from server-side code only — an API key acts on your whole workspace, so it never belongs in code that runs in a browser (the API sends no CORS headers). ## Versioning The package follows semantic versioning, starting at 0.1.0; `SDK_VERSION` holds the installed version and rides the `User-Agent` header. It targets version 1 of the API (every path under `/v1`), which changes only additively ([Versioning & deprecation](https://developers.processcart.com/guides/versioning)). --- # Sandbox workspaces > Build and test an integration against a separate workspace flagged as a sandbox — pc_test_ keys, the same base URL, data isolated from your live workspace. Source: https://developers.processcart.com/guides/sandbox A sandbox is a **separate workspace** you build and test an integration in before it touches live data. It is a whole workspace of its own — its own workflows, runs, members and keys — with one flag set in Settings → API. ## The two-account flow 1. **Sign up a second account** (or have a colleague do it) and create a workspace for testing — "Acme sandbox". 2. In that workspace, open **Settings → API** and switch on **This workspace is a sandbox**. An Admin can flip it; the change is audited. 3. Create API keys there. Keys minted while the flag is on carry the **`pc_test_`** prefix, and `GET /v1/me` reports `"sandbox": true` for them. Existing keys keep the prefix they were minted with. 4. Import your real workflows into the sandbox with `GET /v1/workflows/{id}/template` on the live workspace and `POST /v1/workflows` on the sandbox — the portable template carries the whole buildable workflow ([Workflows & schema](https://developers.processcart.com/guides/workflows)). 5. Point your integration at the sandbox key. **The base URL is the same** (`https://app.processcart.com/api/v1`); only the key decides which workspace you are in. Data stays isolated per workspace: a sandbox never sees another workspace's runs, members or webhooks, and nothing you do there reaches your live workspace. Webhooks registered in the sandbox deliver sandbox events only. ## What a sandbox does not change - Limits are the same (600 requests per minute per key). - Email still sends if the sandbox workspace has email configured — leave it on the shared sending mode or point it nowhere while testing. - A key always acts on the workspace that minted it, whatever its prefix: the prefix only records whether that workspace was flagged as a sandbox when the key was minted. A key minted in the sandbox can never read or change the live workspace's data, and a key minted in the live workspace never touches the sandbox — so a sandbox key minted before the flag was switched on still reads `pc_live_`, and still acts on the sandbox only. When you are ready, mint a `pc_live_` key in the live workspace and swap it in; the requests do not change. --- # 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:` or `g:` — 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). --- # Field values & typing > Every field reads as a canonical value plus a formatted string, typed by its format — numbers stay numbers, percents are fractions, dates are YYYY-MM-DD — and writes take the canonical or a human string. Source: https://developers.processcart.com/guides/field-values Every field in a Run (and in every webhook payload) is one entry of `fields[]`: ```json { "key": "amount", "label": "Amount", "type": "number", "format": "currency", "taskKey": "details", "value": 1700.5, "formatted": "$1,700.50", "visible": true } ``` - **`value`** is the canonical value — use it for math, comparisons and filters. - **`formatted`** is the display text exactly as the app shows it (en-US) — use it in documents and messages. It is `""` when the field is blank. - **`type`** is the field type from the builder; **`format`** is the field's effective format — the axis that decides the JSON shape of `value`. A `text` field with a currency format reads and writes as a number, like a `number` field would. ## Rules - **Blank is `null`.** A field with no value reads `value: null, formatted: ""`. A checkbox is the exception: it never reads `null` — a never-set checkbox reads `false`. - **Numbers are JSON numbers.** A currency `value` is a number in **whole currency units** (`1700.5` reads `"$1,700.50"`), never cents and never a string. A percent `value` is a **fraction**: `0.125` means 12.5 % and reads `"12.5%"`. A plain number is the number. - **Dates are `YYYY-MM-DD`** with no time zone; times are `HH:mm` (24-hour); run-level instants (`dueAt`, `updatedAt`, …) are ISO 8601 in UTC. - **Strings are normalized:** emails lowercased, URLs parsed and re-serialized (a missing scheme is added), US phone numbers as E.164 (`+15551234567`), text as entered. - **Lists are arrays of strings:** multi-choice values are the chosen labels, subtasks the checked item keys, members the member user ids, files the attachment ids. - **Formulas and hidden fields** read whatever the engine computed: a number, a string, a boolean, or `null` (also for a formula error). A formula's `formatted` follows its format (a currency formula reads `"$2,400.00"`). - **Display Text never appears** in `fields[]` — it carries no value. - **Normalization:** after a write, compare against the returned `value`, not against what you sent. `"$1,700.50"` comes back as `1700.5`; `"Jun 25 2026"` as `"2026-06-25"`; `"ADA@Example.com"` as `"ada@example.com"`. ## Writing values `PATCH /v1/runs/{id}/fields` takes `{ "": value, … }` (1–200 keys); `POST /v1/runs` takes the same map as `fields`. For every key: - A **JSON number is the canonical**: send the fraction for a percent (`0.125`), whole units for a currency (`1700.5`). It must be finite. - A **string is a human entry parsed by the field's format**: `"$1,700.50"` → `1700.5`; `"12.5%"` → `0.125`; `"06/25/2026"` → `"2026-06-25"`; `"2:30 PM"` → `"14:30"`. A comma is always a thousands separator. For a percent field, a bare `"12.5"` follows the field's entry mode (whole percents or decimals) — send a JSON number or an explicit `%` from code. For a text-family field a non-string is refused (never coerced to junk). - A **boolean** writes a checkbox (`false` clears it). - An **array of strings** writes a multi-choice (every entry must be one of `choices`), subtasks (every key one of `items`), or members field (every id a current member — see [Directory](https://developers.processcart.com/guides/directory)); an unknown entry is a 422, never silently dropped. - `null` or `""` **clears** the field (a whitespace-only string clears too). - **Hidden fields** (not shown in the app, not secret) are writable when no formula computes them — the place to stash an external id. - **Not writable:** formula fields, any field a formula computes, Display Text, and File fields (upload through `POST /v1/runs/{id}/files` — [Tasks, approvals & files](https://developers.processcart.com/guides/tasks-approvals-files#files)). A write to one is a 422 `field_not_writable` whose `errors[]` names the reason (`computed`, `file` or `display_text`). - **Out-of-bounds values are accepted and flagged** on `PATCH /v1/runs/{id}/fields` (and for non-kickoff fields at start), exactly as in the app: the value is stored, the field's `validationError` names the problem, and completing the task refuses until it is fixed (422 `required_fields_unsatisfied`). A dropdown value outside `choices` and an unparseable string are refused outright. At start, a visible kickoff field that breaks its bounds or rules is refused instead (422, `errors[].code` `invalid_value` — see [Runs](https://developers.processcart.com/guides/runs)). - Strings are capped at 10,000 characters and arrays at 200 items of 200 characters; over the cap is refused, never truncated. The write is **all-or-nothing** inside the run's lock: one bad key fails the whole request with every problem listed in `errors[]` (`unknown_field`, `field_not_writable`, `invalid_value`, `unparseable`, `not_a_choice`, `not_a_member`, …) and nothing is written. A field of a completed task is refused with 409 `task_completed` — reopen the task first. After the write the engine recomputes formulas, visibility, locks and due dates, voids approvals whose reviewed values changed, and records the trail; the answer is the Run with its new `revision` — send it as `Expected-Revision` on the next write ([Concurrency](https://developers.processcart.com/guides/concurrency)). ```bash curl -X PATCH https://app.processcart.com/api/v1/runs/{id}/fields \ -H "Authorization: Bearer pc_live_…" \ -H "Content-Type: application/json" \ -H "Expected-Revision: 7" \ -d '{ "amount": 1800, "discount": "10%", "ship_date": "2026-10-09", "shipping": "Air", "approved": true, "external_ref": "crm-88213" }' ``` ## Per-workflow JSON Schema `GET /v1/workflows/{id}/schema` turns these rules into JSON Schema (2020-12) for the workflow's published version — one document for the `POST /v1/runs` body and one for `PATCH /v1/runs/{id}/fields` — with each property's type, bounds, `enum` of choices and the custom `x-processcart-type` / `x-processcart-format` annotations ([Workflows & schema](https://developers.processcart.com/guides/workflows)). Generate your types from it rather than from the static OpenAPI document, where `value` is the union of every canonical shape. ## The typing table | Field type | Format | `value` (read) | Blank | Write accepts | Example `value` → `formatted` | Writable | | --- | --- | --- | --- | --- | --- | --- | | `text` | `none` | string | null | a string; an empty or whitespace-only string clears | `"88 Birch Court"` → `"88 Birch Court"` | yes | | `text` | `custom` | string matching the field's mask or pattern | null | a string the mask / pattern accepts | `"AB-1234"` → `"AB-1234"` | yes | | `textarea` | — | string (multi-line) | null | a string; whitespace-only clears | `"Keys handed over at 3 pm."` → `"Keys handed over at 3 pm."` | yes | | `number` | `number` | number | null | a JSON number (the canonical) or a string parsed by the format ("1,234.5" — a comma is always a thousands separator) | `1234.5` → `"1,234.5"` | yes | | `number` | `currency` | number in whole currency units | null | a JSON number (the canonical) or a string parsed by the format ("$1,700.50" → 1700.5) | `1700.5` → `"$1,700.50"` | yes | | `number` | `percent` | number — a FRACTION (0.125 means 12.5 %) | null | a JSON number (the canonical) or a string parsed by the format ("12.5%" → 0.125; a bare "12.5" follows the field's entry mode) | `0.125` → `"12.5%"` | yes | | `date` | `date` | string, YYYY-MM-DD (no time zone) | null | a date string in any common form ("2026-06-25", "06/25/2026", "Jun 25 2026") | `"2026-06-25"` → `"Jun 25, 2026"` | yes | | `time` | `time` | string, HH:mm (24-hour, no seconds, no time zone) | null | a time string ("14:30", "2:30 PM") | `"14:30"` → `"2:30 PM"` | yes | | `dropdown` | — | string — one of the field's `choices` | null | one of `choices`, exactly | `"Yes"` → `"Yes"` | yes | | `multi_choice` | — | string[] — labels from `choices`, in the field's order | null | an array of `choices` entries (an unknown entry is a 422, never dropped) | `["A","C"]` → `"A, C"` | yes | | `checkbox` | — | boolean — never null; a never-set checkbox reads false | false | a boolean (false clears) | `true` → `"Yes"` | yes | | `email` | `email` | string, lowercased | null | an email address | `"ada@example.com"` → `"ada@example.com"` | yes | | `url` | `url` | string, normalized by the URL parser (a scheme is added) | null | an http(s) URL | `"https://processcart.com/"` → `"https://processcart.com/"` | yes | | `phone` | `phone` | string, E.164 | null | a US phone number in any common form ("(555) 123-4567") | `"+15551234567"` → `"(555) 123-4567"` | yes | | `members` | — | string[] of member user ids | null | an array of current member user ids (an unknown id is a 422) | `["11111111-1111-4111-8111-111111111111"]` → `"Ada Lovelace"` | yes | | `subtasks` | — | string[] of checked item keys, in the field's order | null | an array of `items[].key` entries | `["photos"]` → `"1 of 2: Photos"` | yes | | `file` | — | string[] of attachment ids | null | not writable here — upload through `POST /v1/runs/{id}/files` | `["22222222-2222-4222-8222-222222222222"]` → `"contract.pdf"` | no | | `formula` | — | number \| string \| boolean \| null (engine output; dates as YYYY-MM-DD, date-times as YYYY-MM-DDTHH:mm:ss in the org's time zone) | null (also for a formula error) | not writable (computed) | `2400` → `"$2,400.00"` | no | | `hidden` | — | whatever canonical shape was written (number \| string \| boolean \| string[]) | null | any canonical value — writable through the API when the field carries no formula (not shown in the app, not secret) | `"zapier"` → `"zapier"` | yes | | `display_text` | — | — (never appears in `fields[]`; it carries no value) | — | not writable | — | no | The table is generated from the same typing rule the API serializes and validates with ([Field typing](https://developers.processcart.com/reference/field-typing) carries it on its own page). --- # Tasks, approvals & files > Complete, reopen, approve and reject tasks under the engine's gates, and attach files to File fields with a two-phase upload. Source: https://developers.processcart.com/guides/tasks-approvals-files Operations: [Tasks endpoints](https://developers.processcart.com/reference/tasks) · [Files endpoints](https://developers.processcart.com/reference/files). ## Tasks A run's tasks are in `tasks[]` on the Run (and at `GET /v1/runs/{id}/tasks`). Each has a stable `key` from the template, a `kind` (`standard`, `approval` or `email`), a `state` (`todo`, `done`, `rejected_redo`) and the engine's current `visible` and `locked` verdicts. Every action is `POST /v1/runs/{id}/tasks/{key}/`, honors `Expected-Revision`, answers the Run with its new `revision` ([Concurrency](https://developers.processcart.com/guides/concurrency)), and is idempotent where it can be. | Action | Scope | What happens | | --- | --- | --- | | `complete` | `runs:write` | Marks a standard task done after the engine's gates: the task must be visible and unlocked, and its required fields satisfied (every offender is listed in `errors[]` with `code: "required"`, 422 `required_fields_unsatisfied`). A task already done answers 200 unchanged. An approval task cannot be completed this way (409 `wrong_task_kind`); an email task with no send configuration is 409 `task_not_configured`. | | `reopen` | `runs:write` (+ `approvals:decide` for an approval) | Puts a done task back to to-do; a completed run becomes active again. A task that is not done answers 200 unchanged. | | `approve` | `approvals:decide` | Records an approval with an optional `comment` (send `{}` when there is nothing to add). An approval already approved is refused (409 `task_completed`) until it is reopened. | | `reject` | `approvals:decide` | Records a rejection — the `comment` is **required** (1–5,000 characters) — and sends the configured earlier tasks back for redo (`rejected_redo`). A duplicate rejection answers 200 unchanged. An approval already approved is refused (409 `task_completed`) until it is reopened. | Gates the actions share: on `complete`, `approve` and `reject`, a hidden task is 409 `task_hidden` and a task behind an incomplete stop task or a sent-back approval is 409 `task_locked` (`reopen` has neither gate, so a hidden or locked done task can still be reopened); on every action, an archived run is 409 `run_archived`, a run in Trash is 409 `run_trashed` and a key missing the scope is 403. ### Approvals and the key's authority In the app an approval is decided only by its assigned approvers. An API key is never an assigned approver, so the **`approvals:decide` scope** is what grants it the authority — a key that holds it decides as an Admin would. `runs:write` alone cannot approve, reject or reopen an approval (403 `approver_only`). Grant the scope deliberately, to the integration that genuinely decides. When an approval's reviewed values change after it was approved, the approval is voided and must be decided again — the same rule as in the app. A field write that voids an approval records it in the activity trail. ```bash curl -X POST "https://app.processcart.com/api/v1/runs/{id}/tasks/review/approve" \ -H "Authorization: Bearer pc_live_…" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: order-1042-approve" \ -d '{ "comment": "Looks right." }' ``` ### Email tasks An email task's send happens in the app (its own send flow, or a mail client for `mailto:` tasks); the API does not send email. `complete` on an email task records the "mark as sent" confirmation — the task is done, nothing is sent — under the same gates as a standard task. An email task with no send configuration cannot be completed at all (409 `task_not_configured`). ## Files Files live on **File fields**. Because a single request to the API is capped at 1 MiB, an upload is a three-step flow: ask for a ticket, PUT the bytes straight to storage, then finalize. Files on comments are app-only; a key can list and download them but cannot add or remove them. ### 1. Request an upload ticket ```bash curl -X POST https://app.processcart.com/api/v1/runs/{id}/files \ -H "Authorization: Bearer pc_live_…" \ -H "Content-Type: application/json" \ -d '{ "filename": "contract.pdf", "size": 184233, "contentType": "application/pdf", "target": { "kind": "field", "fieldKey": "contract" } }' ``` ```json { "attachmentId": "7d0e…", "upload": { "method": "PUT", "url": "https://…/storage/v1/object/upload/sign/…", "headers": { "Content-Type": "application/pdf" }, "expiresAt": "2026-10-07T20:42:11.000Z" }, "finalize": { "method": "POST", "url": "https://app.processcart.com/api/v1/runs/{id}/files/7d0e…/finalize", "body": { "filename": "contract.pdf", "target": { "kind": "field", "fieldKey": "contract" } } } } ``` The answer is **200** (nothing exists yet — an abandoned ticket leaves no trace on the run). `size` is at most **25 MB**; `contentType` becomes the stored type. A hidden field (422 `field_not_writable`, `errors[].code` `hidden`), a non-File field or a comment target is refused. The upload URL is a credential: never log it. It is valid **two hours**; an `Idempotency-Key` replay past that hands back the expired URL — request a new ticket under a new key. ### 2. PUT the bytes ```bash curl -X PUT "" \ -H "Content-Type: application/pdf" \ --data-binary @contract.pdf ``` Send exactly the headers in `upload.headers` and **no other credential** (no API key, no bearer). Finalized bytes are immutable: a second PUT to the same URL is refused — storage answers **HTTP 400** with a JSON body like `{ "statusCode": "409", "error": "Duplicate", "code": "KeyAlreadyExists" }`, and the stored bytes stay the first upload's. **Key on the body's `code` (`KeyAlreadyExists`), not the HTTP status**: it means the bytes are already there — go on to finalize. Any other refusal (an expired or altered URL) means: request a new ticket. ### 3. Finalize ```bash curl -X POST "https://app.processcart.com/api/v1/runs/{id}/files/7d0e…/finalize" \ -H "Authorization: Bearer pc_live_…" \ -H "Content-Type: application/json" \ -H "Expected-Revision: 7" \ -d '{ "filename": "contract.pdf", "target": { "kind": "field", "fieldKey": "contract" } }' ``` Send the **same `filename` and `target`** as the ticket. The server verifies the stored object (its size and type are read from storage, never from you) and writes the file into the field under the run lock: a single-file field replaces its current file, a multi-file field appends. The answer is **200 `{ attachment, run }`** — the one composite response in the API — with the Run's new revision in `run.revision`. Before the bytes arrived it is 409 `upload_incomplete`; a stored object that is empty or over 25 MB is 413 `payload_too_large` (`errors[].code` `too_large` on `size`) and is removed; a field of a completed task is 409 `task_completed` — reopen the task first; finalizing the same ticket twice is a no-op. An `attachment.created` event fires. ### Reading and removing - `GET /v1/runs/{id}/files` lists every attachment (field files and comment files), oldest first, without download links; a bounded list that takes no query parameters (an unknown one is a 422). - `GET /v1/runs/{id}/files/{attachmentId}` returns the attachment with a signed **`downloadUrl` valid one hour** (`Content-Disposition: attachment`). Mint a fresh one per download rather than storing it. - `DELETE /v1/runs/{id}/files/{attachmentId}` removes a **field** file (the field's value drops it under the run lock; the required-field gate reacts like any other change) and the stored object; 204 with no body (read the run for its new revision), `Expected-Revision` honored. A file in a field of a completed task is 409 `task_completed` (reopen the task first). A comment's file cannot be removed through the API (403 `not_allowed_for_key`). Every file write — a ticket, finalize, remove — is refused on an archived run (409 `run_archived`) and on a run in Trash (409 `run_trashed`). The Attachment resource: `{ id, runId, taskKey, fieldKey, commentId, filename, size, contentType, uploadedByUserId, uploadedBy, createdAt }` — `uploadedBy` is an actor (`user`, `api`, `guest` or `unknown`), `fieldKey` is null for a comment's file and `commentId` null for a field's. --- # Comments & activity > Post and read task comments (plain text or a rich document, strictly sanitized, with mentions) and read the run's append-only activity trail. Source: https://developers.processcart.com/guides/comments-activity Operations: [Comments endpoints](https://developers.processcart.com/reference/comments) · [Activity endpoints](https://developers.processcart.com/reference/activity). ## Comments A comment sits on one task of a run. Through the API a comment is authored **by the key** (`author.kind` = `api`, `name` = the key's name) and is shown in the app that way. ### Posting `POST /v1/runs/{id}/comments` takes the task and **exactly one** of `text` or `body`: ```json { "taskKey": "review", "text": "Payment confirmed by the bank.\n\n@Ada Lovelace please release the order." } ``` - `text` is plain text (1–20,000 characters): blank lines separate paragraphs, a single newline is a line break, and `@Name` becomes a mention when it matches exactly one current member. - `body` is a rich document in ProseMirror JSON (`{ "type": "doc", "content": [ … ] }`). It is sanitized **strictly**: the allowed node types are paragraph, text, heading, bulletList, orderedList, listItem, blockquote, codeBlock, hardBreak, horizontalRule, image, table, tableRow, tableCell, tableHeader, mention, mergeTag (an inline field reference, `{ "type": "mergeTag", "attrs": { "fieldKey": "" } }` — a malformed key drops the tag) and embed (a video embed whose `attrs.src` is an https URL on YouTube, Loom or Vimeo — any other source is removed); the allowed marks bold, italic, code, link, textStyle and highlight. Anything else is dropped on save, never refused. A mention is `{ "type": "mention", "attrs": { "id": "" } }` — its label is always re-derived from the member list, and a mention of a non-member is removed. - Mentioned members are notified as they would be in the app. - A `taskKey` the run does not have is a 422 (`errors[].code` `unknown_task`). - The answer is `201 Created` with an absolute `Location` and the Comment. A comment **never changes the run's revision**, so `Expected-Revision` plays no part. An archived run refuses with 409 `run_archived`, a run in Trash with 409 `run_trashed`. ### Reading - `GET /v1/runs/{id}/comments` lists the run's live comments, oldest first, paged (`limit` up to 200). A run in Trash still answers. - `GET /v1/runs/{id}/comments/{commentId}` returns one comment; a deleted comment is a 404. The Comment resource: ```json { "id": "c1d2…", "runId": "9b7d…", "taskKey": "review", "runTaskId": "e4f5…", "author": { "kind": "api", "keyId": "6a1f…", "name": "Zapier" }, "body": { "type": "doc", "content": [ { "type": "paragraph", "content": [ { "type": "text", "text": "Payment confirmed by the bank." } ] } ] }, "text": "Payment confirmed by the bank.\n\n@Ada Lovelace please release the order.", "mentions": ["11111111-…"], "createdAt": "2026-10-07T18:42:11.000Z", "editedAt": null, "url": "https://app.processcart.com/runs/9b7d…?task=review" } ``` `author` is `{ kind: "user", userId, name }` for a member, `{ kind: "api", keyId, name }` for a key, `{ kind: "guest", email }` for a guest, or `{ kind: "unknown" }` when the author can no longer be named. `text` is the body as plain text (mentions read `@Name`). **Editing and deleting comments are not yet available through the API** (they are in the app); see the [changelog](https://developers.processcart.com/changelog). Files attached to comments are app-only too — a key can list and download them ([files](https://developers.processcart.com/guides/tasks-approvals-files#files)). ## Activity `GET /v1/runs/{id}/activity` is the run's append-only trail: every recorded change with who did it, the raw payload the app stores, and the human sentence the Activity tab shows. ```json { "id": "a1b2…", "runId": "9b7d…", "type": "field_changed", "category": "fields", "actor": { "kind": "api", "keyId": "6a1f…", "name": "Zapier" }, "taskKey": "details", "payload": { "fieldKey": "amount", "label": "Amount", "from": 1700.5, "to": 1800 }, "description": "API key Zapier set Amount: $1,700.50 → $1,800.00", "createdAt": "2026-10-07T18:42:11.000Z" } ``` - `type` is an **open** vocabulary (`run_started`, `field_changed`, `formula_recalculated`, `task_completed`, `task_uncompleted`, `approval_decided`, `approval_invalidated`, `comment_added`, `attachment_added`, `run_status_changed`, …); tolerate values you do not know. `category` files it under one of `fields`, `tasks`, `approvals`, `files`, `comments` or `run`. - `actor` is who did it, by `kind`: `user` (a member, with `name` when known), `api` (a key: `keyId`, `name`), `guest` (`email`), `schedule` (a recurrence schedule: `scheduleId`, `name`), `rule` (an automation rule: `ruleId`, `name`), `system` for the engine's own work, and `unknown` for an author that can no longer be resolved. Like `type`, the vocabulary is open: tolerate kinds you do not know. - `description` is rendered the way the app renders it; branch on `type` and `payload` when you need the machine fields. Query parameters: `type` (one event type), `category`, `since` (strictly after an instant), `order` (`desc` — newest first — by default; `asc` reads forward), `limit` (up to 200) and `cursor`. Events written in one transaction share a `createdAt`; the cursor orders them by id as well, so none is skipped or repeated across a page boundary. To **tail** a run, keep the last page's cursor with `order=asc` and ask again — `since` is the coarse filter, the cursor the exact one. A run in Trash still answers. --- # Workflows & schema > List and read workflows, read a published version's tree and its JSON Schema, update workflow-level settings, and import or export the portable template. Source: https://developers.processcart.com/guides/workflows Operations: [Workflows endpoints](https://developers.processcart.com/reference/workflows). A **workflow** is a template; runs are started from its **published version**. The builder owns the tree (sections, tasks, fields, rules); the API reads it, describes it as JSON Schema, moves it between workspaces as a portable template, and changes the workflow-level settings around it. ## Summary and detail `GET /v1/workflows` lists the workflows the key may see (its allowlist applied; trashed workflows absent), by name, 50 per page up to 200. Each item is the library header: ```json { "id": "3c1d9e2a-…", "name": "Order fulfillment", "description": "Every order from payment to shipment.", "status": "active", "icon": "package", "color": "#2563eb", "folder": { "id": "f0f1…", "name": "Operations" }, "keyDateFieldKey": "ship_date", "kickoffFormEnabled": false, "publishedVersion": { "id": "a0b1…", "number": 4, "publishedAt": "2026-09-30T21:10:00.000Z" }, "taskCount": 9, "createdAt": "2026-06-26T15:00:00.000Z", "updatedAt": "2026-09-30T21:10:00.000Z" } ``` - `publishedVersion` is null until the workflow is published — such a workflow cannot start runs and has no schema. - `updatedAt` moves on every change to the header, folder, status or published pointer; `updatedSince` turns the list into an [incremental sync](https://developers.processcart.com/guides/incremental-sync). - `status` is `active` or `archived`; an archived workflow is hidden from the Run menu while its runs stay. `GET /v1/workflows/{id}` adds the **published version's tree**: `sections[] → tasks[] → fields[]` plus `rules[]` — every task key and field key a run will carry. A task carries its `kind`, `isStop`, `defaultVisibility`, `dueRule`, `assignment` (by ids: users, groups, roles, the run starter, a Members field, a guest) and `guestConfig`; a field its `type`, `label`, `help`, `required`, `options` (choices, items, file `multiple`), `formatConfig`, `validation`, a static `defaultValue`, `formula`, `inKickoff`, `kickoffPosition` and `readOnly` (formula, file and Display Text fields, and any field carrying a formula, are read-only through the API). An unpublished workflow answers with `publishedVersion: null` and no sections; a workflow in Trash is a 404. ## JSON Schema `GET /v1/workflows/{id}/schema` returns two JSON Schema (2020-12) documents generated from the published version: ```json { "workflowId": "3c1d9e2a-…", "versionId": "a0b1…", "versionNumber": 4, "start": { "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Start a run", "type": "object", "properties": { "workflowId": …, "name": …, "fields": { … }, "priority": …, "labels": …, "dueAt": … }, "required": ["workflowId", "name", "fields"] }, "fields": { "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Set field values", "type": "object", "properties": { … }, "minProperties": 1, "maxProperties": 200 } } ``` This example's workflow has a required kickoff field, so `start.required` lists `fields`; for a workflow without one it is `["workflowId", "name"]` and `fields` may be omitted — the per-workflow form of the reference, where `fields` is optional. - `start` describes the `POST /v1/runs` body: every writable field under `fields`, with **`required` naming only the required kickoff fields no rule can hide** (a required field a rule may hide is described as "required at start unless a rule hides it"); a required checkbox is `const: true`. - `fields` describes the `PATCH /v1/runs/{id}/fields` body: every writable field. - Each property carries the field's `title`, its JSON type per the [typing rules](https://developers.processcart.com/guides/field-values), `enum` for dropdown choices / multi-choice entries / subtask keys, `minimum` / `maximum` bounds, `format` (`date`, `email`, `uri`), and the annotations `x-processcart-type`, `x-processcart-format`, `x-task` and `x-section`. Hidden fields are writable and marked so (never shown in the app, not secret). - `versionId` is the **published** version; runs pin the version they started on, so an older run may carry fields this schema no longer lists. - An unpublished workflow is a 409 `workflow_not_published`. ## Updating a workflow `PATCH /v1/workflows/{id}` changes workflow-level settings — never the tree, which stays the builder's: ```json { "name": "Order fulfillment (US)", "description": "…", "folderId": "f0f1…", "keyDateFieldKey": "ship_date", "status": "active" } ``` Any subset. `folderId` must be a folder of your workspace (`null` for the main Workflows page; a key restricted to folders may only move within its folders) — any other id is a 422 (`errors[].code` `unknown_folder`); `keyDateFieldKey` must name a date-typed field of the published version — of the draft while the workflow has never been published (`null` clears it; a key the version lacks is `unknown_field`, a field that is not date-typed `not_a_date_field`) — and re-derives every live run's Key Date; `status: "archived"` hides the workflow from the Run menu. The answer is the summary. Audited. ## Template export and import The **portable template** (specs §10.6) is the buildable workflow as JSON — sections, tasks, fields, formulas, rules, due rules and assignments by group or role name — with no ids, no people, no workspace settings, no secrets and no embedded images. Its envelope carries `formatVersion` and `engineVersion`, `exportedAt` and `source` (the workspace slug). - `GET /v1/workflows/{id}/template` exports the published version (409 `workflow_not_published` otherwise). Keep it in version control, or feed it to another workspace — the [sandbox](https://developers.processcart.com/guides/sandbox) flow. - `POST /v1/workflows` creates a workflow from one: ```json { "template": { "formatVersion": 1, "engineVersion": 1, "workflow": { … } }, "name": "Order fulfillment", "description": "…", "folderId": "f0f1…", "publish": true } ``` The flow, in order: the envelope's versions are checked (a template from a newer build is a 422, `errors[].code` `unsupported_version`); the template is validated and sanitized; group assignments are matched to your workspace's groups by **name, case-insensitively** (an unknown group clears that assignment — counted in `clearedAssignments`); when `publish` is true (the default) the publish gate runs **before anything is written**, so a 422 creates nothing (a template that fails validation answers `invalid_template` entries in `errors[]`, one that fails the gate `publish_gate` entries, one that repeats a section, task or field key `duplicate_key`, and a `folderId` outside your workspace `unknown_folder`); then the workflow is created, published, and answered as `201 Created` with `Location`, the detail resource, an `import` report (`errors` — always empty on a 201 —, `warnings`, `counts`, `clearedAssignments`, `flaggedEmails` — static recipient addresses kept from the template, worth a review) and `publishWarnings` (the gate's non-blocking notes). `publish: false` leaves a draft to finish in the builder. A key restricted to workflows cannot create one; a key restricted to folders must pass a `folderId` among its folders. Workflows cannot be moved to Trash or restored through the API in v1; run-start restrictions by group (folder restrictions in the app) are replaced, for keys, by the key's allowlist. --- # Views & exports > Read the shared saved views of a workspace and the runs they select, create and change views owned by your key's creator, and export a workflow's runs as CSV. Source: https://developers.processcart.com/guides/views-exports Operations: [Views endpoints](https://developers.processcart.com/reference/views) · [Exports endpoints](https://developers.processcart.com/reference/exports). ## Saved views A saved view is a board query — filters, sort and the board's column choice — with a name. Through the API a key sees every **shared** view of the workspace (`visibility: "org"` or `"groups"`) and never a private one: a private id is a 404, indistinguishable from a missing view. ```json { "id": "v1e2…", "name": "Open rush orders", "query": { "workflowId": "3c1d…", "status": ["active", "on_hold"], "priority": ["urgent", "high"], "labels": ["lbl_rush"], "assignee": null, "due": null, "q": "", "qMode": "prefix", "sort": { "key": "due", "dir": "asc" }, "columns": ["status", "priority", "due", "assignees"] }, "visibility": "org", "groupIds": [], "owner": { "userId": "11111111-…", "name": "Ada Lovelace" }, "createdAt": "2026-09-24T16:00:00.000Z", "updatedAt": "2026-09-24T16:00:00.000Z" } ``` - `GET /v1/views` lists shared views by name (`updatedSince` for a sync); `GET /v1/views/{id}` reads one. - `GET /v1/views/{id}/runs` applies the view's stored query exactly as the board does and returns **full Run resources**, paged like `GET /v1/runs` (up to 100 per page) and limited to the key's allowlist. Two queries cannot be resolved for a key: a view that filters by the viewer (`assignee: { "kind": "me" }`) — call `GET /v1/runs?assignee=u:` instead — and a view sorted by `progress` (computed in the app); both are a 422 (`errors[].code` `viewer_relative` and `unsupported_sort`). `updatedSince` orders by `updatedAt` ascending instead of the view's sort. `columns` is presentation only and ignored. ### Owner rules A saved view always has a person behind it. A view the API creates is **owned by the member who created the key**, who must still be a Builder or Admin of the workspace (else 403 `not_allowed_for_key` — mint a key as a current Admin). Each owner keeps at most 30 views (a 31st is a 422, `errors[].code` `limit_reached`). - `POST /v1/views` — `{ name, query, visibility?: "org" (default) | "groups", groupIds? }`. **Send only the `query` members you set; the rest take the board's defaults** — any workflow, status `active` + `on_hold`, any priority, label, assignee and due date, no name filter, sorted by `started` newest first, and the board's default columns. `sort: { "key": "name" }` without a `dir` takes that key's natural direction (`asc` for `name`, `key_date` and `due`; `desc` for the rest). The 201 carries the complete query, with an absolute `Location`. `groups` needs at least one group of the workspace (a group id from another workspace is a 422, `errors[].code` `unknown_group`). - `PATCH /v1/views/{id}` — any subset of the same members. A `query` changes **only the members it sends**; the rest keep the view's stored values, and `null` clears `workflowId`, `assignee` or `due`. Only a view owned by the key's creator can be changed through the API (403 `not_allowed_for_key` otherwise — create a view of your own), and the creator must still be a Builder or Admin of the workspace (403 `not_allowed_for_key` otherwise). Switching to `org` drops the group list. - In both, every `query` member is checked: a bad value is a 422 naming it (`errors[].field` `query.status.0`), and a member the query does not have (`"workflow"` for `workflowId`) is a 422 `unrecognized_keys` rather than a silent default. - `DELETE /v1/views/{id}` — removes **any** shared view, whoever owns it (the same power an Admin has in the app); audited under the key's name. 204. ```bash curl -X POST https://app.processcart.com/api/v1/views \ -H "Authorization: Bearer pc_live_…" -H "Content-Type: application/json" \ -d '{ "name": "Active by name", "query": { "workflowId": "3c1d…", "status": ["active"], "sort": { "key": "name" } } }' ``` The status labels a `query.labels` filter takes are listed by `GET /v1/me` (`org.labels`). ## CSV export `GET /v1/exports/runs.csv?workflowId=…` returns one workflow's runs as a CSV file, newest first: ```bash curl "https://app.processcart.com/api/v1/exports/runs.csv?workflowId=3c1d…&status=completed&cols=status,started,completed&fields=amount,ship_date" \ -H "Authorization: Bearer pc_live_…" -o orders.csv ``` - `workflowId` is required — a CSV has one set of field columns. The run filters are `GET /v1/runs`'s (`status`, `priority`, `labels`, `assignee`, `due`, `q` + `qMode`, `updatedSince`, `includeTrashed`). - `cols` picks the run columns after "Run name": `status`, `priority`, `labels`, `progress`, `key_date`, `due`, `started`, `completed` (default: status, started, completed). `fields` picks field columns by key, in the workflow's own order (default: the first six fields); an unknown key is a 422. - Every cell is formatted as the app's own export formats it — currency with its symbol, dates in the workspace's time zone, members by name — and cells that could be read as formulas by a spreadsheet are defused. - `text/csv; charset=utf-8` with a BOM and CRLF line ends (so it opens cleanly in Excel) and a `Content-Disposition: attachment` header. - The whole file is built before it is sent; a selection above **10,000 rows** is refused with a 422 (`errors[].code` `too_many_rows`) — narrow the filter (by status, by `updatedSince`, by label). - The usual pre-checks answer Problem Details: 404 for an unknown workflow, 409 `workflow_not_published`, 403 outside the key's allowlist. For a machine-readable copy of every run, prefer `GET /v1/runs` with `updatedSince` — it carries canonical values rather than display text ([Incremental sync](https://developers.processcart.com/guides/incremental-sync)). --- # Webhooks > Signed, retried deliveries of every event (Standard Webhooks) — create endpoints, verify signatures in Node or Python, understand the retry schedule and auto-disable, redeliver, and dedupe. Source: https://developers.processcart.com/webhooks Operations: [Webhooks endpoints](https://developers.processcart.com/reference/webhooks) · [Events](https://developers.processcart.com/reference/events). A webhook is an https endpoint subscribed to events. Every event arrives in one signed JSON POST, retried on failure, and carries what it is about: a run event the full Run, a workflow event the workflow, a [configuration event](#configuration-events) (a webhook, schedule or automation rule created, changed or deleted) the webhook, schedule or rule as the API returns it. The one exception: the cascade variants of `run.trashed` / `run.restored` (a workflow moved to or from Trash, `data.withWorkflow: true`) carry only the lite header `{ id, name, revision, workflow, deletedAt }` — fetch `GET /v1/runs/{id}` when you need the body. Webhooks are managed in the app (Automations → Webhooks) and through the API with the `webhooks:manage` scope. ## Creating an endpoint ```bash curl -X POST https://app.processcart.com/api/v1/webhooks \ -H "Authorization: Bearer pc_live_…" \ -H "Content-Type: application/json" \ -d '{ "url": "https://hooks.example.com/processcart", "description": "Order sync", "events": ["*"], "workflowIds": null, "filters": {} }' ``` ```json { "id": "w1b2…", "url": "https://hooks.example.com/processcart", "description": "Order sync", "events": ["*"], "workflowIds": null, "filters": {}, "enabled": true, "disabledReason": null, "disabledAt": null, "consecutiveFailures": 0, "lastDeliveryAt": null, "createdAt": "2026-10-07T18:00:00.000Z", "createdBy": null, "updatedAt": "2026-10-07T18:00:00.000Z", "secret": "whsec_…" } ``` - `events` lists concrete event types and/or `*`. **`*` covers everything except the opt-in types** `field.updated` and `guest.task_viewed`, which must be named explicitly. The catalog is on [Events](https://developers.processcart.com/reference/events). A `*` subscription is chatty by design: one run start delivers `run.created`, then a `task.ready` for every task that became actionable and, for each of those that has assignees, a `task.assigned` (an approval task sends `approval.requested` in its place) — a dozen signed requests within a second or two on a five-task workflow. Subscribe to the events you act on. - `workflowIds` narrows the subscription to some workflows (`null` or `[]` = every workflow): run, workflow and schedule events about other workflows are not sent, nor are the events of a rule scoped to another workflow (a rule for every workflow passes any filter); webhook events have no workflow and pass it. `filters.fieldKeys` narrows `field.updated` to some fields. - An automation rule's `call_webhook` action can also send its triggering event to this endpoint, outside its `events`, `workflowIds` and `filters` ([Automation rules](https://developers.processcart.com/guides/automation-rules#call_webhook)) — by design, so a rule can route one event wherever it is needed. Route on `type` and `data.run.workflow` rather than trusting the subscription alone. Such a delivery is the one a subscription would send: one per event per endpoint, the same payload and `webhook-id`, and nothing that names the rule. A receiver cannot tell which of the two sent it, and an endpoint that both subscribes and is called by a rule gets the event once; dedupe on `webhook-id` as for any delivery. - The **`secret` is shown once**, in the 201 — store it now. Rotate to get a new one. Every API read omits it (in the app, an Admin or Builder can reveal it again on the webhook's page under Automations, and every reveal is audited). The one exception is an idempotent retry: for 24 hours, a byte-identical retry of the create (or of `POST …/rotate-secret`) with the same `Idempotency-Key`, from the same API key or its rotation, replays the stored response — secret included ([Idempotency](https://developers.processcart.com/guides/idempotency)). Treat that `Idempotency-Key` as secret material. - The URL must be a public https address: the SSRF policy refuses private and link-local networks, loopback, cloud metadata addresses, Process Cart's own hosts, credentials in the URL and `#fragments`, and re-checks the resolved address before every attempt. - A refused body is a 422 `validation_failed` whose `errors[]` names the member: `url_policy` on `url`, `unknown_workflow` on a `workflowIds` entry that is not a workflow of your workspace, `unknown_key` on `enabled` (use the enable / disable operations), and `unrecognized_keys` on `body` (the message names the key) for any other member the body does not take. Nothing is created. - Then: `GET`, `PATCH` (any subset of the configuration; `enabled` is not part of it), `DELETE` (204 — pending deliveries are dropped), `POST …/enable`, `POST …/disable` (reason `manual`; events during the pause are not queued), `POST …/rotate-secret`, `POST …/test` (202; queues a `webhook.test` delivery and sends it right away — read the delivery for the outcome; refused while disabled). Allowlisted keys: a key restricted by **folder** cannot use the webhook operations at all (403); a key restricted by **workflow** may create or update a webhook only when its `workflowIds` is a non-empty subset of the key's workflows, lists and reads only such webhooks, and sees only deliveries about those workflows (plus test events). ## Configuration events Your automations announce their own lifecycle, so an integration learns by webhook when an endpoint, a schedule or a rule appears, changes, is switched off or goes away: | Resource | Created | Changed | Deleted | `data.change` on the change event | |---|---|---|---|---| | Webhook | `webhook.created` | `webhook.updated` | `webhook.deleted` | `settings`, `enabled`, `disabled`, `secret_rotated`, `auto_disabled` | | Schedule | `schedule.created` | `schedule.updated` | `schedule.deleted` | `settings`, `paused`, `resumed`, `auto_disabled` | | Automation rule | `rule.created` | `rule.updated` | `rule.deleted` | `settings`, `enabled`, `disabled`, `auto_disabled` | - `data.webhook`, `data.schedule` or `data.rule` is the resource exactly as `GET` returns it; a `*.deleted` event carries its last state. **No payload ever carries a signing secret or key material** — `secret_rotated` says that the secret changed, never what it is. - `settings` is any configuration edit; `enabled` / `disabled` and `paused` / `resumed` are switches made by hand (in the app or through the API); `auto_disabled` means Process Cart turned it off: a webhook after a `410` or repeated exhausted deliveries, a rule after repeated failures, a schedule that ended, could not start its runs (three failures in a row, or at once when a start can never succeed), or whose workflow was moved to Trash, archived or unpublished. The resource's `disabledReason` says which. Switching something to the state it is already in announces nothing. - `actor` is whoever made the change: `user` in the app, `api` (with the key's name) through the API, `system` for an automatic change. - They are in `*` and can be subscribed to by name like every other type. Automation rules never trigger on them. - Like every delivery they are unordered: when two changes to one resource arrive close together, `GET` the resource for its current state. - A webhook receives the events about itself, too — except that a disabled or deleted endpoint is not delivered to. Its own `disabled` / `auto_disabled` change and its `webhook.deleted` reach the workspace's **other** enabled endpoints only, so to hear about an endpoint switching off, subscribe a second one. ## The delivery Every delivery is a `POST` of one JSON envelope ([Events](https://developers.processcart.com/reference/events) has the shape) with the Standard Webhooks headers: ```http POST /processcart HTTP/1.1 Host: hooks.example.com Content-Type: application/json User-Agent: ProcessCart-Webhooks/1.0 (+https://developers.processcart.com/webhooks) webhook-id: evt_7f2c1c2e-0f3a-4f0e-9d6c-2b7a4c9e1f00 webhook-timestamp: 1791830531 webhook-signature: v1,K2Z…= x-processcart-event: task.completed ``` - `webhook-id` is the event id (`evt_`) — **stable across retries and redeliveries**; dedupe on it. - `webhook-timestamp` is the Unix time (seconds) the attempt was signed. Reject a delivery more than **300 seconds** from your clock as a replay. - `webhook-signature` is `v1,` of `..`, keyed with the secret's bytes (base64-decode the part after `whsec_`). After a secret rotation it carries two space-separated entries for 24 hours; accept any one that matches, and compare in constant time. - `x-processcart-event` repeats the body's `type` for cheap routing. Answer **any 2xx within 10 seconds**. Do the work after answering, not before — a slow handler is a failed attempt. ### Verifying in Node ```javascript import { createHmac, timingSafeEqual } from "node:crypto"; export function verifyProcessCart(secret, headers, rawBody, toleranceSeconds = 300) { const id = headers["webhook-id"]; const timestamp = headers["webhook-timestamp"]; const signature = headers["webhook-signature"]; if (!id || !timestamp || !signature) return false; if (Math.abs(Date.now() / 1000 - Number(timestamp)) > toleranceSeconds) return false; const key = Buffer.from(secret.replace(/^whsec_/, ""), "base64"); const expected = Buffer.from( createHmac("sha256", key).update(`${id}.${timestamp}.${rawBody}`).digest("base64"), ); return signature.split(" ").some((entry) => { const [version, value] = entry.split(",", 2); if (version !== "v1" || !value) return false; const given = Buffer.from(value); return given.length === expected.length && timingSafeEqual(given, expected); }); } ``` `rawBody` must be the exact bytes received (read the body as text before any JSON parsing — a re-serialized body will not verify). In TypeScript, `verifyWebhookSignature` from `@processcart/sdk/webhooks` runs the same check on WebCrypto ([TypeScript SDK](https://developers.processcart.com/guides/sdk#webhooks)). ### Verifying in Python ```python import base64, hashlib, hmac, time def verify_processcart(secret: str, headers: dict, raw_body: bytes, tolerance: int = 300) -> bool: msg_id = headers.get("webhook-id") timestamp = headers.get("webhook-timestamp") signature = headers.get("webhook-signature") if not (msg_id and timestamp and signature): return False if abs(time.time() - int(timestamp)) > tolerance: return False key = base64.b64decode(secret.removeprefix("whsec_")) signed = f"{msg_id}.{timestamp}.".encode() + raw_body expected = base64.b64encode(hmac.new(key, signed, hashlib.sha256).digest()).decode() for entry in signature.split(" "): version, _, value = entry.partition(",") if version == "v1" and hmac.compare_digest(value, expected): return True return False ``` ## Retries, failures and auto-disable - First attempt immediately; after a failure, retries at **1 min → 5 min → 15 min → 30 min → 60 min** after the previous attempt — 6 attempts in all over about 1 h 51 min; then the delivery is `exhausted`. - Per-attempt timeout 10 seconds; any 2xx counts as delivered, anything else (or a timeout, or a refused connection) as a failed attempt. - A `410 Gone` disables the endpoint at once (`disabledReason: "gone"`); 5 consecutive exhausted deliveries disable it (`"failures"`). Either way the workspace's Builders and Admins are notified; enable it again from Automations → Webhooks or `POST /v1/webhooks/{id}/enable`. - Deliveries are kept 30 days. Deliveries are kept with their status (`pending`, `delivered`, `failed`, `exhausted`), attempt count, last response code (`lastStatusCode`), last and next attempt times (`lastAttemptAt`, `nextAttemptAt`): `GET /v1/webhooks/{id}/deliveries` (newest first, filter by `status`) and `GET /v1/webhooks/{id}/deliveries/{deliveryId}` (with the full `payload` as it was or will be POSTed). `POST …/deliveries/{deliveryId}/redeliver` queues a finished delivery (delivered, failed or exhausted) again with the **same `webhook-id`** and sends it right away (202); a delivery still in flight is 409 `delivery_not_terminal`, a disabled webhook 409 `webhook_disabled`. When an endpoint is disabled automatically, the workspace's Builders and Admins are notified, and every other enabled endpoint subscribed to `webhook.updated` (or `*`) receives it with `data.change: "auto_disabled"` ([Configuration events](#configuration-events)); `POST /v1/webhooks/{id}/enable` (or enabling it from Automations → Webhooks) turns it back on and resets the failure counter. Events that happened while it was disabled were not queued — catch up with `GET /v1/runs?updatedSince=…` ([Incremental sync](https://developers.processcart.com/guides/incremental-sync)). ## Consuming deliveries safely - **Dedupe on `webhook-id`** (`id` in the body): a retry and a redelivery carry the same id. - **Deliveries are unordered.** When order matters, order by `data.run.revision` and treat a revision older than the one you hold as stale. - **Overlap and dedupe runs by `(id, revision)`** when you combine webhooks with `updatedSince` polling — the same rule the sync recipe uses. - **Both `task.completed` (with a guest actor) and `guest.task_completed` fire** when a guest submits their task; handle one of them, or both idempotently. - **`field.updated` is opt-in** (excluded from `*`); subscribe to it explicitly, and narrow it with `filters.fieldKeys` on a busy workflow. - **A formula moves too.** A write that changes the result of a formula field also emits `field.updated` for the computed field, so one request can produce more than one `field.updated`. - **An automation rule can send you events outside your subscription** ([`call_webhook`](https://developers.processcart.com/guides/automation-rules#call_webhook)), `field.updated` included: route on `type` and `data.run.workflow`. - **Not every event has a run.** `workflow.published`, `webhook.test` and the [configuration events](#configuration-events) carry no `data.run`; route on `type` before reading it. - Verify the signature **before** parsing JSON, and return 2xx before doing slow work. ## Events | Event | When | `data` | In `*` | | --- | --- | --- | --- | | `run.created` | A run was started (by a member, the API, a schedule or a public kickoff form). | `run`; `kickoffLinkId` when a public form started it. | yes | | `run.updated` | A run's name, priority, labels or due date changed. | `run` + `changes` keyed by attribute (`name`, `priority`, `labels`, `dueAt`), each `{ from, to }`. | yes | | `run.status_changed` | A run's status changed (active / completed / on_hold / archived), by hand or automatically. | `run` + `changes.status { from, to }`. | yes | | `run.completed` | Every task is done, or the run was marked completed by hand. | `run`. | yes | | `run.trashed` | A run moved to Trash — on its own, or with its workflow. | `run` (the lite header `{ id, name, revision, workflow, deletedAt }` when a workflow trash cascaded) + `withWorkflow`. | yes | | `run.restored` | A run came back from Trash — on its own, or with its workflow. | `run` (lite when a workflow restore cascaded) + `withWorkflow`. | yes | | `task.ready` | A task became actionable: visible, unlocked and not done. | `run` + `task`. | yes | | `task.assigned` | A task's assignees were resolved or changed, or the task became actionable for them. | `run` + `task` + `assignees`. | yes | | `task.completed` | A task was completed. | `run` + `task`. | yes | | `task.uncompleted` | A done task was reopened, by hand or by a rejected approval. | `run` + `task`; `byApproval` names the approval task whose rejection reopened it. | yes | | `field.updated` | A field value changed (opt-in: excluded from `*`; `filters.fieldKeys` narrows it). | `run` + `field` (the resource's field entry: key, label, type, format, value, formatted, …) + `field.previous`. | no (opt-in) | | `approval.requested` | An approval task became actionable for its approvers (again when a decided approval is voided by a reviewed-value change). | `run` + `task`. | yes | | `approval.decided` | An approval was approved or rejected. | `run` + `task` + `approval { id, decision, comment, decidedBy, decidedByEmail, decidedAt }`. | yes | | `comment.created` | A comment was posted on a task. | `run` + `task` + `comment { id, authorId, text, mentions }`. | yes | | `attachment.created` | A file finished uploading to a field or a comment. | `run` + `task` + `attachment { id, filename, size, mime, fieldKey, taskKey, commentId }`. | yes | | `guest.task_viewed` | A guest first opened their task link (opt-in: excluded from `*`). | `run` + `task` + `guest { email, linkId }`. | no (opt-in) | | `guest.task_completed` | A guest submitted their task (fires beside `task.completed`, whose actor is the guest). | `run` + `task` + `guest { email, linkId }`. | yes | | `workflow.published` | A workflow version was published. | `workflow { id, name }` + `version { id, number }`. | yes | | `schedule.run_created` | A recurrence schedule started a run. | `run` + `schedule { id, name }`. | yes | | `schedule.created` | A recurrence schedule was created. | `schedule` — the Schedule resource, as `GET /v1/schedules/{id}` returns it. | yes | | `schedule.updated` | A schedule changed: its settings were edited, it was paused or resumed, or the system paused it. | `schedule` + `change`: `settings`, `paused`, `resumed` or `auto_disabled` (`auto_disabled`: the system paused it, and `schedule.disabledReason` says why). | yes | | `schedule.deleted` | A schedule was deleted. | `schedule` — its last state. | yes | | `rule.created` | An automation rule was created. | `rule` — the AutomationRule resource, as `GET /v1/automation-rules/{id}` returns it. | yes | | `rule.updated` | An automation rule changed: its settings were edited, it was turned on or off, or the system turned it off after repeated failures. | `rule` + `change`: `settings`, `enabled`, `disabled` or `auto_disabled` (`auto_disabled`: the system turned it off, and `rule.disabledReason` says why). | yes | | `rule.deleted` | An automation rule was deleted. | `rule` — its last state. | yes | | `webhook.created` | A webhook endpoint was created. | `webhook` — the Webhook resource, as `GET /v1/webhooks/{id}` returns it (never the signing secret). | yes | | `webhook.updated` | A webhook endpoint changed: its settings were edited, it was enabled or disabled, its secret was rotated, or the system disabled it. | `webhook` + `change`: `settings`, `enabled`, `disabled`, `secret_rotated` or `auto_disabled` (`auto_disabled`: the system disabled it, and `webhook.disabledReason` says why). Never the secret. | yes | | `webhook.deleted` | A webhook endpoint was deleted. | `webhook` — its last state. | yes | | `webhook.test` | A test event sent from Settings or `POST /v1/webhooks/{id}/test`. | `webhook { id }`. | yes | --- # Directory > The workspace's members (ids, names, roles, avatars — never email addresses) and groups, the ids that assignments, Members fields and assignee filters use. Source: https://developers.processcart.com/guides/directory Operations: [Directory endpoints](https://developers.processcart.com/reference/directory). The directory is read-only through the API (scope `users:read`). Its user ids are what every `assignees[].userId`, `startedByUserId`, `completedByUserId`, Members-field value and `assignee=u:` filter refers to; its group ids are what `{ "kind": "group" }` assignees and `assignee=g:` use. ## Members `GET /v1/users` lists the workspace's members in the order they joined: ```json { "id": "11111111-…", "name": "Ada Lovelace", "role": "builder", "avatarUrl": "https://…/avatars/…", "joinedAt": "2026-06-22T15:00:00.000Z" } ``` - `role` is `member`, `builder` or `admin`. - **Email addresses are not exposed.** Addresses live only in the authentication store, which is not scoped to a workspace; a member is identified to an integration by id and display name. Match your own records to Process Cart members by id, captured once (from a Members field, an assignee or this list). - `name` is null for a member who has not set one; `avatarUrl` null when there is no avatar. ## Groups `GET /v1/groups` lists the workspace's groups by name, each with the ids of its current members: ```json { "id": "g2h3…", "name": "Fulfillment", "memberIds": ["11111111-…", "22222222-…"], "createdAt": "2026-08-08T20:00:00.000Z" } ``` Groups are managed by Admins in Settings → Groups. Through the API they are the way to resolve a group assignee into people, and to filter runs by `assignee=g:`. Both lists page with `limit` (up to 200) and `cursor`. --- # Schedules > Start runs of a workflow on a recurrence — daily, weekly, monthly or yearly at a set time — with a run-name pattern, preset field values and per-task assignees; pause, resume, run now and preview. Source: https://developers.processcart.com/guides/schedules Operations: [Schedules endpoints](https://developers.processcart.com/reference/schedules) · scope `schedules:manage`. A schedule starts runs of one published workflow on a recurrence. Each run gets a name from the schedule's pattern, the schedule's preset field values, and — for the tasks you name — the schedule's assignees instead of the task's own assignment rule. Schedules are managed in the app (Automations → Schedules, Builders and Admins) and through the API with the `schedules:manage` scope. | Operation | HTTP | SDK method | | --- | --- | --- | | [List schedules](https://developers.processcart.com/reference/schedules#listSchedules) | GET /v1/schedules | `listSchedules(…)` | | [Create a schedule](https://developers.processcart.com/reference/schedules#createSchedule) | POST /v1/schedules | `createSchedule(…)` | | [Get a schedule](https://developers.processcart.com/reference/schedules#getSchedule) | GET /v1/schedules/{id} | `getSchedule(…)` | | [Update a schedule](https://developers.processcart.com/reference/schedules#updateSchedule) | PATCH /v1/schedules/{id} | `updateSchedule(…)` | | [Delete a schedule](https://developers.processcart.com/reference/schedules#deleteSchedule) | DELETE /v1/schedules/{id} | `deleteSchedule(…)` | | [Pause a schedule](https://developers.processcart.com/reference/schedules#pauseSchedule) | POST /v1/schedules/{id}/pause | `pauseSchedule(…)` | | [Resume a schedule](https://developers.processcart.com/reference/schedules#resumeSchedule) | POST /v1/schedules/{id}/resume | `resumeSchedule(…)` | | [Start a run from a schedule now](https://developers.processcart.com/reference/schedules#runScheduleNow) | POST /v1/schedules/{id}/run | `runScheduleNow(…)` | | [Preview a schedule's next occurrences](https://developers.processcart.com/reference/schedules#previewSchedule) | GET /v1/schedules/{id}/preview | `previewSchedule(…)` | ## Create a schedule ```bash curl -X POST https://app.processcart.com/api/v1/schedules \ -H "Authorization: Bearer pc_live_…" \ -H "Content-Type: application/json" \ -d '{ "workflowId": "3c1d…", "name": "Weekly order review", "recurrence": { "freq": "weekly", "interval": 1, "time": "09:00", "startDate": "2026-10-12", "weekly": { "days": ["mon"] } }, "namePattern": "{{workflow}} — {{date:YYYY-MM-DD}}", "presetValues": { "customer": "Acme Corp", "amount": 250 }, "assignmentOverrides": { "review": [{ "kind": "group", "groupId": "7f0e…" }] } }' ``` ```typescript const schedule = await client.createSchedule({ workflowId: "3c1d…", name: "Weekly order review", recurrence: { freq: "weekly", interval: 1, time: "09:00", startDate: "2026-10-12", weekly: { days: ["mon"] }, }, presetValues: { customer: "Acme Corp", amount: 250 }, }); console.log(schedule.description, schedule.nextOccurrences); ``` The answer is the schedule (201, with an absolute `Location`): its recurrence in words (`description`), `nextRunAt` and the next three occurrences. A recurrence with no future occurrence is refused (422). - **`timezone`** is an IANA zone such as `America/New_York`; leave it out to use the workspace's zone. - **`presetValues`** are canonical values keyed by field key, checked against the published version like `POST /v1/runs` fields ([Field values & typing](https://developers.processcart.com/guides/field-values)). A preset its field's format rules refuse — outside a minimum or maximum, or failing a pattern — is refused when the schedule is saved (422, `errors[].code` `invalid_value` at `presetValues.`), so a schedule never starts runs with it. Every required kickoff field needs a preset (422, `errors[].code` `required`; a required checkbox needs `true`) — except a File field, a field the API cannot write (a formula or other computed field), and a field whose default value fills it. - **`assignmentOverrides`** maps a task key to the members and groups that get that task in every run — `{ "kind": "user", "userId" }` or `{ "kind": "group", "groupId" }` (1–20 per task), the same spelling the Run resource uses for those two kinds. Only these two kinds are accepted: a Run's `role` or `guest` assignee cannot be an override, and an entry carrying any other member is refused (422). An override replaces the task's own assignment rule for those runs; an override whose members have all left the workspace, or whose group was deleted, falls back to that rule. - A schedule stays on its workflow; create another schedule for a different one. The workflow must be published, not archived and not in Trash. ## Recurrence | Member | Meaning | | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `freq` | `daily`, `weekly`, `monthly` or `yearly`. | | `interval` | Every N days (1–365), weeks (1–52), months (1–24) or years (1–10), counted from `startDate`. | | `time` | `HH:mm`, 24-hour, minutes on 5-minute steps (`00`, `05`, …, `55`), in the schedule's zone. | | `startDate` / `endDate` | The first and last local dates an occurrence may fall on (`endDate` inclusive; omit it or send `null` for no end). | | `weekly` | `{ "days": ["mon", "wed", "fri"] }` — weeks start on Monday; the week holding `startDate` is the first. | | `monthly` | `{ "kind": "day", "day": 31 }` (a day past the end of a shorter month runs on its last day) or `{ "kind": "ordinal", "ordinal": "last", "target": "weekday" }` — `ordinal` is `first`, `second`, `third`, `fourth` or `last`; `target` is a weekday (`mon` … `sun`), `day`, `weekday` or `weekend_day` (“the last weekday”, “the first Monday”). | | `yearly` | `{ "month": 3, "day": 15 }`; February 29 runs on February 28 in other years. | Only the member that matches `freq` may be present. `GET /v1/schedules/{id}/preview?count=5` answers the next occurrences (1–10, default 3) as UTC instants; for a paused schedule, the ones a resume would produce. ## When runs are created - Runs are created at the scheduled time, usually within a minute: the scheduler checks for due schedules every 5 minutes, and schedule times sit on 5-minute steps. - **Daylight saving time.** A time that does not exist on a spring-forward day (02:30 when clocks jump from 02:00 to 03:00) runs at the same offset into the new hour (03:30). A time that happens twice on a fall-back day runs once, at the first. - **Catch-up.** If runs could not be created while the schedule was on (an outage), the next check creates ONE run, for the latest missed occurrence, and records how many it skipped in `lastError`. It never creates one run per missed occurrence. - **Resuming never catches up.** Occurrences that fall while a schedule is paused are skipped; after a resume, the next run is the first occurrence after now. - **Time zones.** The schedule's zone decides when a run is created and what `{{date}}` and `{{time}}` read. Inside the run, `TODAY()`, date-field defaults and due rules use the workspace's zone, as for any run. - **Outages.** A start that fails for a passing reason — the database did not answer, or the start failed unexpectedly — is not a refusal: `lastError` records it, the next check retries it, and the schedule is never paused for it. **Run now** answers 503 `unavailable` with `Retry-After` instead (500 `internal_error` for an unexpected failure). Any other error while creating a run counts toward a pause: the third in a row pauses the schedule (`failed`). - **Which server fires it.** A schedule fires only on the kind of server (production, preview or local) that created it or last edited or resumed it. When an edit or a resume moves it to another kind, its next run is recomputed from now, so the new server never creates a catch-up run for occurrences the old one missed. ## Run names `namePattern` (default `{{workflow}} — {{date}}`) takes these tokens; anything else is kept literally, and the name is cut to 200 characters. | Token | Renders | | -------------------- | --------------------------------------------------------------------------------------------------------------------------- | | `{{workflow}}` | The workflow's name. | | `{{date}}` | The occurrence's local date, `MMM D, YYYY`. | | `{{date:}}` | The date in a Date-field pattern: `YYYY-MM-DD`, `MM/DD/YYYY`, `DD/MM/YYYY`, `DD-MMM-YYYY`, `MMM D, YYYY` or `MMMM D, YYYY`. | | `{{time}}` | The occurrence's local time, `HH:mm`. | ## Runs a schedule creates Every such run has `startedBy: "schedule"` and `schedule: { id, name }` ([Runs](https://developers.processcart.com/guides/runs)). `scheduledFor` is the occurrence it was created for; it is `null` for a run started with **Run now**. Each occurrence creates at most one run. After a schedule is deleted its runs stay, and their `schedule` reads `null`. Subscribe to `schedule.run_created` to hear about them ([Webhooks](https://developers.processcart.com/webhooks)). ```bash curl -X POST https://app.processcart.com/api/v1/schedules/5b2a…/run \ -H "Authorization: Bearer pc_live_…" ``` **Run now** (`POST /v1/schedules/{id}/run`) starts one run right away — paused or not — with the schedule's pattern (rendered at the start instant), presets and overrides. It answers the new Run (201, its `revision` in the body) and an absolute `Location`. Presets the published version no longer accepts are skipped (the schedule's `presetMismatches` names them); a required kickoff field without a preset is refused with 422 `required_fields_unsatisfied`. ## Pause, resume, update, delete - `POST /v1/schedules/{id}/pause` and `/resume` answer the schedule (200), unchanged when it already was. Resuming a schedule whose end date has passed is 409 `schedule_ended` — change its recurrence first. Creating, resuming or running a schedule now — or a `PATCH` that changes its `presetValues` or `assignmentOverrides` — on an unpublished workflow is 409 `workflow_not_published`, on one in Trash 409 `workflow_trashed`, on an archived one 403 `workflow_not_runnable`. - `PATCH /v1/schedules/{id}` changes any of `name`, `recurrence`, `timezone`, `namePattern`, `presetValues` and `assignmentOverrides`; a new recurrence or zone recomputes the next run from now. `enabled` is refused (422, `errors[].code` `unknown_key` on `enabled`) — pause and resume own it. - `DELETE /v1/schedules/{id}` answers 204. - `GET /v1/schedules` lists schedules newest first; filter by `workflowId` or `enabled`, and use `updatedSince` for a sync ([Incremental sync](https://developers.processcart.com/guides/incremental-sync)). Schedules carry no `revision`: no schedule operation reads `Expected-Revision` (the SDK's `expectedRevision` option is ignored here), and an update applies to the schedule as it is stored, so the last write wins. Read the schedule again before an update that depends on what another client may have changed. Every change is recorded in the workspace's audit log under the key's name. ### Why a schedule is paused `disabledReason` says why `enabled` is `false`: | Reason | Meaning | | ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `manual` | Paused by a person or a key. | | `ended` | The recurrence's end date passed, or it has no occurrence left to schedule (`lastError` then reads “This schedule has no future runs.”). | | `unpublished` | The workflow has no published version. | | `failed` | A start was refused (a preset or required field problem), three attempts in a row to create a run failed for a reason other than an outage, or the recurrence can no longer be read; `lastError` says why. | | `workflow_trashed` | The workflow moved to Trash. Restoring it does not resume the schedule. | | `workflow_archived` | The workflow was archived. Unarchiving it does not resume the schedule. | When the scheduler itself pauses a schedule as `failed` or `unpublished`, the workspace's Builders and Admins are notified. Moving the workflow to Trash or archiving it pauses its schedules at once, without a notification. ## Allowlists A key restricted to workflows or folders lists only the schedules of workflows inside its allowlist; any other schedule answers 403 `not_allowed_for_key`, and so does creating one for a workflow outside it. --- # Automation rules > When-then rules on a workflow's runs — a trigger event, an optional task or field filter and field conditions, then actions that set fields, status, priority, labels and assignees, notify people or send the event to a webhook. Source: https://developers.processcart.com/guides/automation-rules Operations: [Automation rules endpoints](https://developers.processcart.com/reference/automation-rules) · scope `rules:manage`. An automation rule reads “when **this happens** on a run of **this workflow** (and these conditions hold), **do these things**”. Rules are managed in the app (Automations → Rules, Builders and Admins) and through the API with the `rules:manage` scope. | Operation | HTTP | SDK method | | --- | --- | --- | | [List automation rules](https://developers.processcart.com/reference/automation-rules#listAutomationRules) | GET /v1/automation-rules | `listAutomationRules(…)` | | [Create an automation rule](https://developers.processcart.com/reference/automation-rules#createAutomationRule) | POST /v1/automation-rules | `createAutomationRule(…)` | | [Get an automation rule](https://developers.processcart.com/reference/automation-rules#getAutomationRule) | GET /v1/automation-rules/{id} | `getAutomationRule(…)` | | [Update an automation rule](https://developers.processcart.com/reference/automation-rules#updateAutomationRule) | PATCH /v1/automation-rules/{id} | `updateAutomationRule(…)` | | [Delete an automation rule](https://developers.processcart.com/reference/automation-rules#deleteAutomationRule) | DELETE /v1/automation-rules/{id} | `deleteAutomationRule(…)` | | [Turn an automation rule on](https://developers.processcart.com/reference/automation-rules#enableAutomationRule) | POST /v1/automation-rules/{id}/enable | `enableAutomationRule(…)` | | [Turn an automation rule off](https://developers.processcart.com/reference/automation-rules#disableAutomationRule) | POST /v1/automation-rules/{id}/disable | `disableAutomationRule(…)` | | [List a rule's executions](https://developers.processcart.com/reference/automation-rules#listAutomationRuleExecutions) | GET /v1/automation-rules/{id}/executions | `listAutomationRuleExecutions(…)` | ## Create a rule ```bash curl -X POST https://app.processcart.com/api/v1/automation-rules \ -H "Authorization: Bearer pc_live_…" \ -H "Content-Type: application/json" \ -d '{ "name": "Flag large orders", "workflowId": "3c1d…", "triggerEvent": "task.completed", "filter": { "taskKey": "details" }, "conditions": { "combinator": "and", "items": [{ "type": "field", "fieldKey": "amount", "op": "greater", "value": 1000 }] }, "actions": [{ "type": "add_labels", "labelIds": ["rush"] }, { "type": "set_priority", "priority": "high" }, { "type": "notify", "recipients": ["g:7f0e…"], "message": "A large order is ready for review." }] }' ``` ```typescript const rule = await client.createAutomationRule({ name: "Flag large orders", workflowId: "3c1d…", triggerEvent: "task.completed", filter: { taskKey: "details" }, conditions: { combinator: "and", items: [{ type: "field", fieldKey: "amount", op: "greater", value: 1000 }], }, actions: [{ type: "set_priority", priority: "high" }], }); ``` The answer is the rule, turned on (201, with an absolute `Location`). Task and field keys are checked against the workflow's **published** version (`errors[].code` `unknown_task` / `unknown_field` / `field_not_writable`); members, groups, labels and webhooks against the workspace (`not_a_member`, `unknown_group`, `unknown_label`, `unknown_webhook`). A rule that names tasks or fields of a workflow with no published version is 409 `workflow_not_published`; a rule of a workflow in Trash is 409 `workflow_trashed` (on create, update and enable). A `set_field` value is checked when the rule is saved, like a field write ([Field values & typing](https://developers.processcart.com/guides/field-values)): a refused value is 422 at `actions..value` with the field-write code (`wrong_type`, `not_a_choice`, …; a Members value naming someone outside the workspace is `not_a_member`; a value its field's format rules refuse — outside a minimum or maximum, or failing a pattern — is `invalid_value`), and the rule stores the canonical value. A condition's `value` for `equals`, `not_equals`, `greater`, `greater_equal`, `less` or `less_equal` on a writable field is read the same way (422 at `conditions.items..value` with the field-write code) and stored canonical, so `"1,700"` on a number field is kept as `1700`. A workspace holds at most 100 rules that are on. `keyMismatches` on a rule lists what it names that no longer resolves, each with a `reason`: for a field or task, `missing` (a later published version no longer has the key) or `not_published` (the workflow has no published version); for a field, also `not_writable` (a `set_field` target is now computed or read-only); for a webhook, `deleted` or `disabled`. ## When: trigger, filter, conditions | Member | Meaning | | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `triggerEvent` | One run event: `run.created`, `run.updated`, `run.status_changed`, `run.completed`, `task.ready`, `task.assigned`, `task.completed`, `task.uncompleted`, `field.updated`, `approval.requested`, `approval.decided`, `comment.created`, `attachment.created`, `guest.task_completed` or `schedule.run_created` ([Events](https://developers.processcart.com/reference/events)). Workflow, webhook, trash and guest-view events, and the configuration events (`schedule.*`, `rule.*` and `webhook.*` created / updated / deleted), cannot trigger a rule. | | `filter.taskKey` | Only that task (task, approval, comment and guest triggers). | | `filter.fieldKey` | Only that field (`field.updated`). | | `conditions` | Up to 10 field conditions — `{ "type": "field", "fieldKey", "op", "value" }` with `op` one of `equals`, `not_equals`, `contains`, `not_contains`, `greater`, `greater_equal`, `less`, `less_equal`, `is_filled`, `is_empty` — combined with `and` (all) or `or` (any). No conditions = always. | Conditions read the run's values **after** the triggering change (a `field.updated` rule sees the new value; a `task.completed` rule sees the task done), exactly like a visibility rule in the workflow builder. ## Then: actions Actions run in order (except `call_webhook`, see below), through the same checks as a person's change (locks, required fields, approval voiding, the activity trail). Up to 10 per rule. | `type` | Does | | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `set_field` | `{ fieldKey, value }` — sets a field to a canonical value; `null` clears a non-required field. | | `set_status` / `set_priority` | `{ status }` / `{ priority }` on the run. A rule that fires on the last task's completion runs after the run has completed, so `set_status` can put a completed run `on_hold`: its tasks stay complete and it keeps its `completedAt`, and `PATCH /v1/runs/{id}` with `"status": "completed"` completes it again (a new `completedAt` and another `run.completed` event). | | `add_labels` / `remove_labels` | `{ labelIds }` — label ids from the workspace's label set. | | `assign_task` | `{ taskKey, assignees }` — `{ "kind": "user", "userId" }` / `{ "kind": "group", "groupId" }`. Replaces the task's own assignment rule for that run. | | `notify` | `{ recipients, message }` — recipients are `builders` (Builders and Admins), `admins`, `assignees` (the members assigned to the run's open, visible tasks: assigned members, the members of assigned groups, and the members holding an assigned role or a higher one; guests are not notified), `starter` (the member who started the run), `u:` or `g:`. Each recipient gets a bell notification unless they turned “Messages from automation rules” off in their notification settings, and an email when that setting includes email. A `field.updated` rule emails each recipient at most once per hour per run; that cap never holds back the bell notification. | | `call_webhook` | `{ webhookId }` — sends the triggering event to that endpoint too (at most one per rule; see below). | A rule for **every workflow** (`workflowId: null`) can only use `set_status`, `set_priority`, `add_labels`, `remove_labels`, `notify` and `call_webhook`, and takes no filter or conditions — keys belong to one workflow. Only Admins create one in the app; through the API it needs a key without a workflow or folder allowlist (403 `not_allowed_for_key` otherwise). ### `call_webhook` The rule sends the event that triggered it, with its usual payload, to one webhook of the workspace — even if that endpoint does not subscribe to the event type or to the run's workflow. An endpoint that already receives the event gets it once, not twice, and the delivery carries nothing that names the rule: the receiver sees the same payload and `webhook-id` a subscription would send. `call_webhook` is not an ordered step. The event is queued when the rule matches, in the same change that triggered it, so it is sent whatever the rule's other actions do, even when one of them fails. Its payload is the run as of the triggering change, before the rule's other actions; fetch `GET /v1/runs/{id}` (or subscribe to the events those changes emit) to see them. A disabled or deleted webhook records the action as `skipped`. The delivery is signed and retried like any other ([Webhooks](https://developers.processcart.com/webhooks)). ## Executions and outcomes Every event a rule matches creates one **execution**, except the matches the loop guard drops ([Loops and failures](#loops-and-failures)). Executions are listed by `GET /v1/automation-rules/{id}/executions` (newest first, filter by `status`; kept 30 days) and go `queued`, then `running`, then `done` or `failed`. An execution's `result` lists each action attempted with an `outcome`: Each action that changes the run is its own write under the run lock, so a rule with two such actions emits two `run.updated` events — one per action, not one per execution. - `ok` — done. - `skipped` — nothing to do, and not an error: a key the run's version does not have, a field on a task that is already complete, an unchanged value, a change to a run that is archived (a rule that archives the run skips the changes after it), labels that no longer exist, a recipient or assignee list left empty when its group was deleted, a Members value or an assignee list whose members have all left the workspace (the field is never cleared for that), a disabled or deleted webhook. A skipped action never stops the rule. - `failed` — the change was refused; `error` says why. The rule stops at the first failed action, so a later `notify` never announces a change that did not happen (a `call_webhook` was already sent when the rule matched). An execution also fails when the rule was pointed at another workflow while it waited (“The rule no longer applies to this run's workflow.”), and when its job stopped part-way: after 15 minutes a sweep marks it failed (“Interrupted: …”), and it performs no further actions. `ok` in a `result` entry is `true` only for the outcome `ok`. Changes a rule makes are recorded with the rule as the actor (`{ "kind": "rule", "ruleId", "name" }`) in the run's activity and in event payloads. ## Loops and failures - **Rules never trigger rules.** A change a rule makes does not trigger any rule (webhooks still hear about it). - **Loop guard.** A loop through something outside Process Cart (a rule calls a webhook, the receiver changes the run through the API, which triggers the rule again) is stopped: once a rule has run 20 times on one run within the past 60 minutes, its next match records a failed execution (“Loop guard: …”). Later matches on that run leave no execution at all while the rule has more than 20 executions on it in the past 60 minutes (a rolling window). When the count falls back to 20, the next match records another loop-guard failure, and the rule runs again only once the count is below 20. - **Auto-disable.** A failed execution notifies the workspace's Builders and Admins (at most once an hour per rule). After 10 failed executions in a row the rule turns itself off (`disabledReason` `failures`, or `loop_guard` when the last one was the loop guard). `POST /v1/automation-rules/{id}/enable` turns it back on and resets the counter; so does any edit of the rule. An execution the sweep marks “Interrupted” counts nothing toward the turn-off — the job stopped, not the rule. ## Turn on, turn off, update, delete - `POST /v1/automation-rules/{id}/disable` turns a rule off (`disabledReason` `manual`); events while it is off are not queued for it. `/enable` turns it back on. Both answer the rule (200), unchanged when it already was. - `PATCH /v1/automation-rules/{id}` changes any of `name`, `workflowId`, `triggerEvent`, `filter`, `conditions` and `actions`; the result is checked as a whole, like a create, and the failure counter resets. `enabled` is refused (422, `errors[].code` `unknown_key` on `enabled`) — enable and disable own it. Executions still waiting when the rule moves to another workflow fail rather than act on runs outside it. - `DELETE /v1/automation-rules/{id}` deletes the rule and its executions (204). - `GET /v1/automation-rules` lists rules newest first; filter by `workflowId` or `enabled`, and use `updatedSince` for a sync ([Incremental sync](https://developers.processcart.com/guides/incremental-sync)). Rules carry no `revision`: no rule operation reads `Expected-Revision` (the SDK's `expectedRevision` option is ignored here), and an update applies to the rule as it is stored, so the last write wins. Read the rule again before an update that depends on what another client may have changed. Every change is recorded in the workspace's audit log under the key's name. ## Allowlists A key restricted to workflows or folders lists only the rules of workflows inside its allowlist — rules for every workflow are omitted for it. Reading, changing or creating any other rule answers 403 `not_allowed_for_key`. A restricted key's execution list shows only executions on runs of its allowed workflows: a rule moved from another workflow keeps its older executions, and those stay hidden from the key. The allowlist is checked again inside each write, so a rule moved between the key's request and the write is refused (403) rather than changed. --- # Pagination > Every collection answers { data, nextCursor, hasMore } with opaque cursors, a published page-size ceiling, strict query parameters and a stable order per endpoint. Source: https://developers.processcart.com/guides/pagination Every list in the API answers the same envelope: ```json { "data": [ … ], "nextCursor": "eyJ2IjoiMjAyNi0xMC0wN1QxODo0MjoxMS4wMDBaIiwiaWQiOiIuLi4iLCJoIjoiOWI3ZCJ9", "hasMore": true } ``` - `data` is the page. - `nextCursor` is the opaque token for the next page, or `null` on the last one. - `hasMore` says whether another page exists. Bounded lists that always fit in one answer (a run's tasks, a run's files) use the same envelope with `nextCursor: null` and `hasMore: false`, so one loop handles everything. ## Walking a collection ```bash curl "https://app.processcart.com/api/v1/runs?limit=100" -H "Authorization: Bearer pc_live_…" # … then, while hasMore: curl "https://app.processcart.com/api/v1/runs?limit=100&cursor=" -H "Authorization: Bearer pc_live_…" ``` Send the **same filters and sort** on every page. A cursor is bound to the query that produced it: a cursor sent with a different filter or sort is refused with 422 `validation_failed` (`field: "cursor"`, `code: "cursor_mismatch"`), never silently applied to the wrong list. A cursor the API did not hand out, or one that was altered, is refused the same way with `code: "invalid_cursor"`. ## Page size `limit` defaults to **50**. The ceiling is **200** on ordinary lists and **100** on `GET /v1/runs` and `GET /v1/views/{id}/runs` (every item there is a full Run with its fields and tasks). A `limit` above the ceiling is a 422 — it is never clamped, so a client cannot silently get less than it asked for. ## Stable order Each endpoint documents its order. The cursor is a keyset (the sort value and the row id), so a page never repeats or skips a row that did not change. Two orders traverse a changing collection stably: `startedAt` (the default on runs) and `updatedAt asc` (what `updatedSince` forces). Other sorts — `name`, `dueAt`, `keyDate`, `completedAt`, `priority` — may skip or repeat a row whose sort value changes while you are paging. For a full copy, use `startedAt`; for a sync, use [Incremental sync](https://developers.processcart.com/guides/incremental-sync). ## Strict query parameters Every query parameter is validated and **unknown parameters are refused** with 422 `validation_failed` so a typo cannot silently widen a filter: ```json { "code": "validation_failed", "errors": [ { "field": "updated_since", "code": "unknown_parameter", "message": "Unknown query parameter \"updated_since\". Did you mean \"updatedSince\"? Query parameters are camelCase." } ] } ``` Parameters are camelCase everywhere (`updatedSince`, `workflowId`, `includeTrashed`); the message suggests the camelCase twin of the common snake_case spellings (`updated_since`, `workflow_id`, `page_size`, `per_page`, `include_trashed`, …). Comma-separated parameters (`status=active,on_hold`) may also be repeated (`status=active&status=on_hold`). --- # Incremental sync > updatedSince on the six lists — inclusive, ordered by updatedAt ascending, with an overlap-and-dedupe recipe keyed by (id, revision) on runs and (id, updatedAt) elsewhere. Source: https://developers.processcart.com/guides/incremental-sync Six lists accept `updatedSince=`: `GET /v1/runs`, `GET /v1/workflows`, `GET /v1/views`, `GET /v1/webhooks`, `GET /v1/schedules` and `GET /v1/automation-rules`. It is the one rule for keeping a copy of a collection current without reading every record. ## The rule - `updatedSince` keeps the items whose `updatedAt` is **at or after** the instant (inclusive, `>=`). - The order becomes **`updatedAt` ascending**, with the id as the tiebreaker. An explicit `sort` or `order` that conflicts with it is a 422 (`errors[].code = "conflicts_with_updated_since"`). - Pagination works as usual: follow `nextCursor` with the same `updatedSince`. A cursor sent without it, or with a different filter, is refused (422, `errors[].code` `cursor_mismatch`); a cursor the API did not hand out is `invalid_cursor`. - `updatedAt` is the server's write time of the record's last change. On runs it moves on every persisted change (a field, a task, the header, a file); comments do not move it. ```bash curl "https://app.processcart.com/api/v1/runs?updatedSince=2026-10-07T18:00:00Z&limit=100&includeTrashed=true" \ -H "Authorization: Bearer pc_live_…" ``` ## The recipe 1. Remember the **start instant** of your previous sync (your clock, taken before the first request). 2. Request with `updatedSince = that instant minus one minute` — the overlap covers clock skew and in-flight writes. 3. Page to the end. 4. **Dedupe.** Runs: by `(id, revision)` — the same number you send back as `Expected-Revision` ([Concurrency](https://developers.processcart.com/guides/concurrency)); the same `revision` is unchanged, a higher one replaces yours. Workflows, views, webhooks, schedules and automation rules carry no revision: compare `updatedAt` — a later value replaces yours, an equal one is unchanged. 5. Store the start instant of this sync for the next one. Two writes can share an `updatedAt` instant, and the revision can jump by more than one between two reads (the engine may bump it twice for one logical change). Neither matters to the recipe: on runs, compare revisions, not timestamps. ## Trash and deletions `GET /v1/runs` hides trashed runs unless you send `includeTrashed=true`. A sync that must notice deletions sends it: a trashed run then arrives with `deletedAt` set, and a restored one with `deletedAt: null`. (A webhook for a run trashed or restored with its workflow carries only a lite header — `{ id, name, revision, workflow, deletedAt }` with `data.withWorkflow: true` — whose `deletedAt` and `revision` are enough to mark the row; fetch `GET /v1/runs/{id}` when you need the body.) Runs are purged from Trash after 30 days; a run you never saw trashed and never see again was purged — treat an id that stops appearing as gone only after that window. ## Webhooks instead If you would rather be told than ask, a webhook on `run.updated`, `run.status_changed`, `task.completed` and friends carries the full Run in every delivery, except the lite header on a workflow-trash cascade ([Webhooks](https://developers.processcart.com/webhooks)). Deliveries are unordered, so apply the same `(id, revision)` rule there; a sync with `updatedSince` is the way to catch up after an outage. --- # Idempotency > Send Idempotency-Key on any POST, PATCH or DELETE to make retries safe — a byte-identical retry replays the stored answer; a different body with the same key is refused. Source: https://developers.processcart.com/guides/idempotency A network timeout leaves you not knowing whether a write landed. Send an `Idempotency-Key` and retry the identical request: you get the original answer back instead of a second run, a second comment or a second webhook. ## How it works ```bash curl -X POST https://app.processcart.com/api/v1/runs \ -H "Authorization: Bearer pc_live_…" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: crm-order-1042-start" \ -d '{ "workflowId": "…", "name": "Order 1042" }' ``` - The key is any string of 1–255 printable ASCII characters without spaces — a uuid, or something meaningful to you. - Keys are stored **per workspace**, and a stored response is replayed only to the API key that made the request (or to the key one rotation away from it, in either direction). A **second API key** sending the same `Idempotency-Key` value is refused with 422 `idempotency_key_reused` — so prefix keys with something unique to each integration. - The first request runs normally and its response is stored for **24 hours**. - A second request with the same key **and a byte-identical body** (same method, path and `Expected-Revision` too) replays the stored response with the header `Idempotent-Replayed: true` — the same status, body and `Location`. A replay carries a fresh `X-Request-Id`, and a replayed problem body is rewritten to match it. - A second request with the same key but a **different** method, path, body or `Expected-Revision` — or from a different API key — is refused with 422 `idempotency_key_reused`, and nothing is created. - While the first request is still running, a duplicate answers 409 `idempotency_in_progress` with `Retry-After: 2`. The key applies to `POST`, `PATCH` and `DELETE`. `GET` requests are naturally idempotent and ignore it. ## What is stored, and what is not - Successes (2xx) and request-shaped refusals (most 4xx) are stored and replayed, so a retry of a request that was wrong gets the same honest answer. - Answers that describe the **state at that moment** are never stored: every 409 (`revision_mismatch`, `idempotency_in_progress`, `run_trashed`, `task_locked`, `task_completed`, …); the 422s that read the run or its workflow — `required_fields_unsatisfied` (a required field still blank) and `field_not_writable` (among others, a field the run's rules hide right now); the 403s that read the current setup — `approver_only`, `workflow_not_runnable`, `not_allowed_for_key`; every 429; every 5xx. A retry re-executes against the current state — the point of retrying. Because nothing was stored, the key is free again: a corrected request may reuse it. - Responses over 256 KB are not stored; a retry re-executes. - A request that crashes releases the key, so the retry runs. ## The upload ticket `POST /v1/runs/{id}/files` returns a signed upload URL that lives two hours. A replay of that request past the ticket's lifetime hands back the stored, now-expired ticket — ask for a new ticket with a new key instead of retrying the old one ([Tasks, approvals & files](https://developers.processcart.com/guides/tasks-approvals-files#files)). ## One-time secrets A replay returns the stored response as it was — including a webhook's signing secret: the 201 of `POST /v1/webhooks` and the answer of `POST /v1/webhooks/{id}/rotate-secret` come back, secret included, to the same key (or its rotation) for 24 hours. Treat the `Idempotency-Key` on those two calls as secret material ([Webhooks](https://developers.processcart.com/webhooks)). ## Recommendation Generate the key once per logical operation (before the first attempt), reuse it across every retry of that operation, and never reuse it for a different one. A good shape is `--` — `crm-order-1042-start`, `crm-order-1042-complete-review` — so two integrations in one workspace never send the same string. --- # Concurrency > Every Run carries its revision; every write that changes an existing run accepts Expected-Revision and refuses a stale revision with 409 revision_mismatch, whose body names currentRevision — checked inside the run's lock. The API sends no ETag and refuses If-Match. Source: https://developers.processcart.com/guides/concurrency Two people, an automation and your integration can all touch the same run. The API prevents lost updates with optimistic concurrency on the run's **revision**. ## Revision Every Run the API returns carries its `revision` in the body — reads, starts and every write alike (a new run starts at `1`; a finalized upload answers it as `run.revision`). It is an opaque, monotonic integer bumped on every persisted change to the run: a field value, a task's state, the header, a file, a trash or restore. It tracks the run's **persisted state**; derived display data (a formatted value, a due date computed from today's date) may differ at the same revision. Comments and the upload ticket never move it. `revision` is the one version field. The API sends no `ETag` header — not on a read, not on a create, not on a write. The revision may jump by more than one between two reads: the engine can record a logical change as two bumps. Only equality matters. ## Expected-Revision Send the `revision` you last saw — from a read or from the body of your last write — as the `Expected-Revision` header on any write that changes the run: field writes, task actions, `PATCH /v1/runs/{id}`, trash and restore, finalizing or removing a file. Those are the only operations that read the header: a read (`GET /v1/runs/{id}`) and a start (`POST /v1/runs`) answer a `revision` but have nothing to compare it to, and comments and upload tickets never touch the revision. Removing a file answers `204` with no body; read the run for its new revision before the next conditional write. Workflows, views, webhooks, schedules and automation rules carry no revision at all: their writes ignore `Expected-Revision`, and the last write wins. ```bash curl -X PATCH https://app.processcart.com/api/v1/runs/{id}/fields \ -H "Authorization: Bearer pc_live_…" \ -H "Content-Type: application/json" \ -H "Expected-Revision: 7" \ -d '{ "amount": 1800 }' ``` - The check happens **inside the run's lock**, after the row is locked and before anything is evaluated, so two racing writers cannot both pass. - A match applies the write and answers the updated Run; its `revision` is the value to send on your next write. - A mismatch is refused with **409 `revision_mismatch`**. The problem body names the run's current revision in `currentRevision` (and repeats it in `detail`): ```json { "type": "https://developers.processcart.com/errors#revision_mismatch", "title": "Revision mismatch", "status": 409, "detail": "The run changed; its current revision is 9. Read it again, merge, and retry with Expected-Revision: 9.", "code": "revision_mismatch", "requestId": "req_…", "currentRevision": 9 } ``` Read the run again, merge, and retry with `Expected-Revision: `. A 409 is never stored for idempotent replay, so a retry with the same `Idempotency-Key` and a fresh `Expected-Revision` re-executes. Accepted forms: a bare integer (`Expected-Revision: 7`, the canonical form) or the same in double quotes (`"7"`); `*` matches any revision, the same as omitting the header. Anything else — an empty value, a list, a weak `W/"7"`, a negative number, a fraction, text — is a 422 `validation_failed` with `errors[].field: "Expected-Revision"`. ## Why not If-Match CDN layers evaluate `If-Match` against the response and turn a successful write into a 412. A hosting edge treats `If-Match` as a standard HTTP precondition and compares it with the `ETag` of the response the API is about to send — so a write that succeeded, and moved the revision, would reach you as `412 Precondition Failed`. A header of the API's own is passed through untouched. So the API does not use `If-Match` at all: a request that carries it is refused with 422 `validation_failed` (`errors[].field: "If-Match"`, message "This API does not use If-Match; send Expected-Revision: instead"), on every authenticated operation, reads included — never silently honored or ignored. A client that sends `If-Match` sees `412 Precondition Failed` from the hosting layer on some deployments and the 422 from the API on others; both mean "stop sending `If-Match`" — send `Expected-Revision` instead. `If-None-Match` is not supported either: a `GET` always returns the current document. ## Without Expected-Revision Omit the header and the write is applied to whatever the run holds at that moment, last writer wins per field. That is fine for appending a comment or setting a field nobody else edits; use `Expected-Revision` whenever you read, compute and write back. ## Lock semantics Every run write runs under a row lock: the run is locked, `Expected-Revision` is checked, the engine re-evaluates formulas, visibility and locks from the persisted values, the gates are applied, and only then is anything written — in one transaction. Concurrent writes to one run therefore serialize; writes to different runs do not contend. The 600-requests-per-minute limit is the only concurrency limit per key. --- # Errors > Every failure is an RFC 9457 problem with a stable machine-readable code, a human message, the right HTTP status, a request id and field-level errors[]. Source: https://developers.processcart.com/errors Every error answers `application/problem+json` (RFC 9457) with the same shape: ```json { "type": "https://developers.processcart.com/errors#validation_failed", "title": "Validation failed", "status": 422, "detail": "The request body failed validation.", "code": "validation_failed", "requestId": "req_1f3c…", "errors": [ { "field": "amount", "code": "unparseable", "message": "Enter a number." } ] } ``` - `code` is the **stable, machine-readable** reason — branch on it, never on `detail`. Every code is listed below and is the anchor its `type` URL points at. - `type` is this page plus the code; `title` is constant per code; `detail` is a human sentence that may change. - `status` repeats the HTTP status. The code's default status is listed below; a few codes can appear under more than one status where noted. - `requestId` is the same value as the `X-Request-Id` header. Quote it to support. - `errors[]` appears when the problem is field-level: each entry names the `field` (a body member such as `name` or a dotted path such as `target.fieldKey`, a query parameter, a header name, or — for a field value — the field's key, as above), a `code` from the vocabulary below and a `message`. A top-level code can also appear as an `errors[].code` when it concerns one field (`task_completed` on a field of a completed task); it is described under [Error codes](#error-codes). - Request bodies are strict: a key an operation does not take is refused with `unrecognized_keys` on `body` (on `target` for one inside a file upload's `target`; the message names the key) instead of being dropped. `enabled` in a webhook, schedule or rule body answers `unknown_key` on `enabled` instead, and its message names the operations that turn the resource on and off. A map keyed by field or task keys answers its own code too: `PATCH /v1/runs/{id}/fields` (whose whole body is that map) and a schedule's `presetValues` refuse a key that names no field as `unknown_field` on that key, and a schedule's `assignmentOverrides` a key that names no task as `unknown_task`. - A **404 `not_found`** means the key may not see the thing, not only that it does not exist: an id that is not a uuid or belongs to another workspace, a task or field key the run's version does not define, a private saved view, a webhook outside the key's allowlist. The answer never says which, so it reveals nothing about other workspaces. A run in Trash is not a 404: it can still be read, and a write to it answers 409 `run_trashed`. - A **409 `revision_mismatch`** carries one extension member, `currentRevision`: the run's revision now. Read the run again, merge, and retry with `Expected-Revision: ` ([Concurrency](https://developers.processcart.com/guides/concurrency)). - A request that carries **`If-Match`** is refused with 422 `validation_failed` (`errors[].field: "If-Match"`) on every authenticated operation: the API's concurrency header is `Expected-Revision`. Some hosting layers answer such a request with `412 Precondition Failed` before it reaches the API; both mean "send `Expected-Revision` instead". Everything under `/api` answers this way — an unknown path is a 404 problem, a wrong verb a 405 problem with an `Allow` header, a non-JSON body a 415. The `code` vocabulary is **open**: new codes may be added without a version bump, so treat an unknown code as its HTTP status would suggest. ## Error codes | Code | Status | Title | | --- | --- | --- | | [`unauthorized`](#unauthorized) | 401 | Authentication required | | [`invalid_api_key`](#invalid_api_key) | 401 | Invalid API key | | [`key_revoked`](#key_revoked) | 401 | API key revoked | | [`insufficient_scope`](#insufficient_scope) | 403 | Insufficient scope | | [`read_only_key`](#read_only_key) | 403 | Read-only key | | [`not_allowed_for_key`](#not_allowed_for_key) | 403 | Not allowed for this key | | [`rate_limited`](#rate_limited) | 429 | Too many requests | | [`idempotency_key_reused`](#idempotency_key_reused) | 422 | Idempotency key reused | | [`idempotency_in_progress`](#idempotency_in_progress) | 409 | Request in progress | | [`validation_failed`](#validation_failed) | 422 | Validation failed | | [`unsupported_media_type`](#unsupported_media_type) | 415 | Unsupported media type | | [`payload_too_large`](#payload_too_large) | 413 | Payload too large | | [`method_not_allowed`](#method_not_allowed) | 405 | Method not allowed | | [`not_found`](#not_found) | 404 | Not found | | [`revision_mismatch`](#revision_mismatch) | 409 | Revision mismatch | | [`run_archived`](#run_archived) | 409 | Run is archived | | [`run_trashed`](#run_trashed) | 409 | Run is in Trash | | [`task_locked`](#task_locked) | 409 | Task is locked | | [`task_hidden`](#task_hidden) | 409 | Task is hidden | | [`task_completed`](#task_completed) | 409 | Task already completed | | [`field_not_writable`](#field_not_writable) | 422 | Field is not writable | | [`required_fields_unsatisfied`](#required_fields_unsatisfied) | 422 | Required fields unsatisfied | | [`workflow_not_published`](#workflow_not_published) | 409 | Workflow not published | | [`workflow_not_runnable`](#workflow_not_runnable) | 403 | Workflow not runnable | | [`approver_only`](#approver_only) | 403 | Approver only | | [`workflow_trashed`](#workflow_trashed) | 409 | Workflow is in Trash | | [`wrong_task_kind`](#wrong_task_kind) | 409 | Wrong task kind | | [`task_not_configured`](#task_not_configured) | 409 | Task not configured | | [`upload_incomplete`](#upload_incomplete) | 409 | Upload incomplete | | [`webhook_disabled`](#webhook_disabled) | 409 | Webhook disabled | | [`delivery_not_terminal`](#delivery_not_terminal) | 409 | Delivery not finished | | [`schedule_ended`](#schedule_ended) | 409 | Schedule ended | | [`internal_error`](#internal_error) | 500 | Internal error | | [`unavailable`](#unavailable) | 503 | Service unavailable | ### unauthorized **401 Authentication required.** No credential was sent, or `Authorization` and `X-API-Key` carried two different values. Send the key as `Authorization: Bearer `. The response carries `WWW-Authenticate: Bearer realm="processcart"`. ### invalid_api_key **401 Invalid API key.** The key is not recognized — malformed, or not one this deployment issued. Check for stray whitespace and that the whole secret was copied. ### key_revoked **401 API key revoked.** The key was revoked in Settings → API, or it was rotated and its 24-hour grace period has ended. Switch to the replacement key. ### insufficient_scope **403 Insufficient scope.** The key does not hold the scope the operation needs; `detail` names it. Mint a key with that scope (see [Scopes](https://developers.processcart.com/reference/scopes)). ### read_only_key **403 Read-only key.** A read-only key sent a request other than `GET` / `HEAD`. Read-only keys may only read, whatever scopes they hold. ### not_allowed_for_key **403 Not allowed for this key.** The workflow (or its folder) is outside the key's allowlist. Also answers: a folder-restricted key on the webhook routes; a webhook whose `workflowIds` is not a non-empty subset of a workflow-restricted key's list; deleting a comment's file through the API; a view write by a key whose creator is no longer a Builder or Admin; an automation rule for every workflow (`workflowId: null`) used by a key with an allowlist. ### rate_limited **429 Too many requests.** More than 600 requests in the key's current 60-second window. Wait `Retry-After` seconds (`RateLimit-Reset` says the same) and retry. ### idempotency_key_reused **422 Idempotency key reused.** The same `Idempotency-Key` arrived with a different method, path, body or `Expected-Revision` — or from a different API key. Use a fresh key for every distinct request; nothing was changed. ### idempotency_in_progress **409 Request in progress.** A request with this `Idempotency-Key` is still running. Retry after `Retry-After` seconds; the stored answer is then replayed. ### validation_failed **422 Validation failed.** The query, body or a header failed validation — a malformed `Expected-Revision`, or any `If-Match` header (the API refuses it; send `Expected-Revision`). `errors[]` lists every problem with its `field` and a `code` from the vocabulary below; nothing was changed. ### unsupported_media_type **415 Unsupported media type.** The request body is not `application/json` (the `Content-Type` header decides). ### payload_too_large **413 Payload too large.** The request body exceeds 1,048,576 bytes, or a file exceeds 25 MB. ### method_not_allowed **405 Method not allowed.** The path exists but not for this verb; the `Allow` header lists the verbs it supports. ### not_found **404 Not found.** No such resource in your workspace: an id that is not a uuid, a run or workflow of another workspace, a task or field key the run's version does not define, a private saved view, a workflow or run outside what the key may see. The API never reveals whether the id exists elsewhere. ### revision_mismatch **409 Revision mismatch.** `Expected-Revision` named a revision the run no longer has. The problem body's `currentRevision` (and its `detail`) carry the current revision — read the run again, merge, and retry with `Expected-Revision: `. ### run_archived **409 Run is archived.** The run is archived. Only a status change (`PATCH /v1/runs/{id}` with `status`) is accepted until it is active again. ### run_trashed **409 Run is in Trash.** The run is in Trash. Restore it (`POST /v1/runs/{id}/restore`) before writing to it. ### task_locked **409 Task is locked.** The task sits behind an incomplete stop task, or behind an approval that sent it back. Finish the earlier work first. ### task_hidden **409 Task is hidden.** A visibility rule hides the task right now; it cannot be acted on until the rule's condition changes. ### task_completed **409 Task already completed.** The task is already done: a field of a completed task was written, or an approval already approved was approved again. Reopen the task first (`/reopen`). ### field_not_writable **422 Field is not writable.** The field cannot be written through the API: a formula, file or Display Text field, a field a formula computes, or a hidden field carrying a formula. `errors[]` names each such field with the reason as its `code` (`computed`, `file`, `display_text` — or `field_not_writable` itself). Files go through `POST /v1/runs/{id}/files`. ### required_fields_unsatisfied **422 Required fields unsatisfied.** Completing the task would leave a required field blank, or keep a value its format rules refuse (out of bounds, a failed pattern): `errors[]` lists every offender with `code: "required"`. Starting a run (`POST /v1/runs`) answers it when a required kickoff field is missing: `errors[]` lists each missing one as `required` and, in the same response, every kickoff value its rules refuse as `invalid_value` (refused values alone at start are 422 `validation_failed`). ### workflow_not_published **409 Workflow not published.** The workflow has no published version yet; publish it in the builder before starting runs, reading its schema, scheduling it (create, resume or run a schedule now, or change a schedule's presets or assignment overrides) or naming its tasks and fields in an automation rule. ### workflow_not_runnable **403 Workflow not runnable.** The workflow is archived, so a schedule cannot start runs of it: creating a schedule of it, changing a schedule's presets or assignment overrides, resuming one or running one now answers this. Unarchive the workflow first. (A workflow outside the key's allowlist answers 403 `not_allowed_for_key` instead.) ### approver_only **403 Approver only.** Deciding (or reopening) an approval needs the `approvals:decide` scope; the key does not hold it. ### workflow_trashed **409 Workflow is in Trash.** The workflow is in Trash: restore it in the app before restoring its runs; before creating, resuming or running a schedule of it, or changing a schedule's presets or assignment overrides; or before creating, changing or turning on an automation rule of it. ### wrong_task_kind **409 Wrong task kind.** The verb does not fit the task's kind: `approve` / `reject` on a standard task, `complete` on an approval task. ### task_not_configured **409 Task not configured.** The task cannot be completed through the API as configured — an email task with no send configuration. ### upload_incomplete **409 Upload incomplete.** Finalize was called before the file's bytes reached the upload URL. PUT the bytes first (see [Tasks, approvals & files](https://developers.processcart.com/guides/tasks-approvals-files#files)). ### webhook_disabled **409 Webhook disabled.** The webhook is disabled; enable it (`POST /v1/webhooks/{id}/enable`) before sending a test event or redelivering. ### delivery_not_terminal **409 Delivery not finished.** The delivery is still pending or mid-retry; only a delivered, failed or exhausted delivery can be redelivered. ### schedule_ended **409 Schedule ended.** The schedule's end date has passed, so it has no future runs to resume; change its recurrence (a later end date, or none) before resuming. ### internal_error **500 Internal error.** Something failed on our side. Retry once; if it persists, quote the `requestId` to support. ### unavailable **503 Service unavailable.** A dependency did not answer — the database (the answer then carries `Retry-After`), storage signing a download link, or the encryption key a webhook secret needs. Retry with backoff; the status page shows whether a wider incident is open. ## The `errors[].code` vocabulary | `errors[].code` | Meaning | | --- | --- | | `required` | A required value is missing (a body, a field, a comment, a group list). | | `invalid_json` | The request body is not valid JSON. | | `invalid` | A header or body value is malformed (`Expected-Revision`, `Idempotency-Key`, a filter, a webhook body), or a header this API refuses (`If-Match`). | | `invalid_value` | The value is the right type but not acceptable (an impossible date, an out-of-bounds number, a failing custom rule). | | `invalid_input` | The input is not acceptable as a whole (a comment body that is empty or too large). | | `unknown_parameter` | A query parameter this endpoint does not take; the message suggests the camelCase spelling. | | `unknown_key` | A body key that belongs to another operation: `enabled` on a webhook, schedule or rule body (the message names the operations that turn it on and off). | | `invalid_cursor` | The cursor is not one this API handed out (or it was altered). | | `cursor_mismatch` | The cursor belongs to a different filter or sort than this request. | | `conflicts_with_updated_since` | `updatedSince` forces `updatedAt` ascending; an explicit conflicting `sort` / `order` is refused. | | `unsupported_sort` | This list cannot be sorted by that key. | | `viewer_relative` | A viewer-relative filter ("me") has no meaning for an API key. | | `too_many_rows` | The export would exceed its row cap; narrow the filter. | | `unknown_field` | No field with this key exists on the run's workflow version. | | `field_not_writable` | The field cannot be written through this endpoint. | | `computed` | The field is a formula; its value is computed, never written. | | `file` | A File field is written through the files endpoints, not set-fields. | | `too_large` | The stored file is empty or over the 25 MB limit; PUT the bytes again, or upload a smaller file. | | `display_text` | A Display Text field holds no value. | | `hidden` | The field is hidden by the workflow's rules right now. | | `wrong_type` | The JSON type does not match the field (a string where a number belongs, …). | | `unparseable` | The text could not be parsed in the field's format (a date, a phone number, …). | | `not_finite` | A number must be finite. | | `too_long` | The text exceeds the field's maximum length. | | `not_a_choice` | The value is not one of the field's choices. | | `not_an_item` | The value names a checklist item the field does not have. | | `not_a_member` | The user id is not a member of this workspace. | | `unknown_task` | No task with this key exists on the run. | | `unknown_label` | A label id is not in the workspace's status-label set. | | `unknown_workflow` | A workflow id is not in this workspace. | | `unknown_folder` | No folder with this id is in the workspace. | | `unknown_group` | No group with this id is in the workspace. | | `unknown_webhook` | No webhook with this id is in the workspace. | | `not_a_date_field` | The Key Date field must be date-typed. | | `invalid_template` | The template JSON does not match the portable template format. | | `unsupported_version` | The template's `formatVersion` / `engineVersion` is not one this API reads. | | `publish_gate` | The template fails the publish checks a hand-built workflow must pass. | | `duplicate_key` | The template repeats a section, task or field key; keys must be unique within a workflow. | | `limit_reached` | A workspace or per-person maximum is reached (saved views per creator, schedules or enabled rules per workspace); delete or disable one first. | | `url_policy` | The webhook URL is refused (https only; no private, loopback or own-host addresses). | | `invalid_type` | The JSON type is wrong for this member (or a required member is missing). | | `invalid_string` | The string fails its format (a uuid, an instant, a date shape, an email, a pattern). | | `invalid_enum_value` | The value is not one of the allowed values. | | `invalid_literal` | The value must be one exact constant. | | `invalid_union` | The value matches none of the allowed shapes. | | `invalid_union_discriminator` | The discriminator member names no known variant. | | `invalid_date` | Not a valid date. | | `too_small` | Below the minimum (length, count or value). | | `too_big` | Above the maximum (length, count or value). | | `not_multiple_of` | The number is not a multiple of the required step. | | `unrecognized_keys` | The body carries a key this endpoint does not take. | | `custom` | The value fails a rule this endpoint applies (the message says which). | ## Getting help Every response — success or failure — carries `X-Request-Id`. The same id is in the request log under Settings → API, so an Admin can see what the key did. Include it when you write to support. --- # Rate limits > 600 requests per minute per key, RateLimit-* headers on every response to an authenticated request, a 429 with Retry-After when exceeded, and the unauthenticated health probe. Source: https://developers.processcart.com/guides/rate-limits Each API key may make **600 requests per 60-second window**. The window is fixed (it starts on the minute boundary the first request falls in), the count is per key, and every plan has the same limit — create separate keys for separate integrations and each gets its own window. | Limit | Value | | --- | --- | | Requests per key | 600 per 60 seconds (a fixed window) | | `GET /v1/health` | 60 per 60 seconds per client address (unauthenticated) | | Headers on every authenticated response | `RateLimit-Limit`, `RateLimit-Remaining`, `RateLimit-Reset` (seconds), `RateLimit-Policy` | | When exceeded | 429 `rate_limited` with `Retry-After` (seconds) | ## Headers Every response to a request that passed authentication carries the IETF RateLimit header fields — a success, a refusal, a 429: ```http RateLimit-Limit: 600 RateLimit-Remaining: 412 RateLimit-Reset: 23 RateLimit-Policy: 600;w=60 ``` `RateLimit-Reset` is the number of seconds until the window rolls over (never 0). A response given before a key is accepted carries none of them: a 401 (a missing, unknown or revoked key), and a 405 `method_not_allowed` (a verb the path does not support) or a 404 `not_found` for a path no route matches. Those last two are answered before authentication is attempted, by design: they never count toward the window and are not logged. The health probe is unauthenticated and carries none of them either. ## When the limit is hit The request is refused with **429 `rate_limited`** and a `Retry-After` header in seconds — the same number as `RateLimit-Reset`: ```http HTTP/1.1 429 Too Many Requests Retry-After: 23 RateLimit-Limit: 600 RateLimit-Remaining: 0 RateLimit-Reset: 23 Content-Type: application/problem+json ``` ```json { "type": "https://developers.processcart.com/errors#rate_limited", "title": "Too many requests", "status": 429, "detail": "This key is limited to 600 requests per 60 seconds.", "code": "rate_limited", "requestId": "req_…" } ``` A 429 is never stored for idempotent replay: retry the same request with the same `Idempotency-Key` after `Retry-After` and it executes. Back off on 429 (sleep `Retry-After`, then resume) rather than spreading requests across keys — a refused request still counts toward the window. ## Reducing requests - `GET /v1/runs` returns full Runs (fields and tasks included) — one request per 100 runs, no per-run follow-ups. - `updatedSince` fetches only what changed ([Incremental sync](https://developers.processcart.com/guides/incremental-sync)). - Webhooks deliver the full Run with every run event (a lite header on a workflow-trash cascade) ([Webhooks](https://developers.processcart.com/webhooks)) — no polling at all. ## Health `GET /v1/health` is unauthenticated and answers `{ "status": "ok" }` with 200 when the database responds, or `{ "status": "degraded" }` with 503. It is limited per client address (60 per minute), cached for ten seconds on each server, and never logged. The public status page at [status.processcart.com](https://status.processcart.com) checks it continuously — see [Status & uptime](https://developers.processcart.com/status). --- # Versioning & deprecation > The version is in the path (/v1); additive changes ship without a bump; breaking changes get /v2 with at least twelve months of overlap, Deprecation and Sunset headers and a changelog entry. Source: https://developers.processcart.com/guides/versioning The API version is in the path: `https://app.processcart.com/api/v1/…`. `v1` is the current and only version. ## Non-breaking changes ship within v1 These may happen at any time and are announced in the [changelog](https://developers.processcart.com/changelog): - new endpoints, new optional query parameters and new optional body members; - new members in responses and in webhook payloads; - new event types, new activity types, new error codes, new enum values (statuses, field types, formats); - loosened validation (accepting something that was refused), new limits that are higher than today's. A client must therefore **tolerate unknown members and unknown enum values**: ignore a member it does not know, and treat an unknown `code`, event `type` or status the way its HTTP status or context suggests. The OpenAPI document marks every growable enum with `x-processcart-open-enum: true`. ## Breaking changes get a new version Removing or renaming a member, changing a type or a canonical format, tightening validation, changing the meaning of a status code, or dropping an endpoint is a breaking change. It ships as `/v2`, and: - `/v1` keeps working for **at least twelve months** after `/v2` is announced; - the old operations send `Deprecation` (RFC 9745, the date the deprecation took effect) and `Sunset` (RFC 8594, the date they stop answering — never less than **90 days** after `Deprecation`) headers on every response; - the changelog carries the migration notes the day the headers appear. Within a version, a single operation may also be deprecated the same way (headers + a changelog entry + the 90-day minimum) when a replacement exists. **Before the public launch** the API is pre-release: a breaking change may ship inside `/v1` with a [changelog](https://developers.processcart.com/changelog) entry and no `/v2` (the 2026-10-12 move from `If-Match` / `ETag` to `Expected-Revision` is the one such change so far). The policy above applies in full from the launch date the changelog announces. ## What never changes without a version bump - The canonical value shapes on [Field values & typing](https://developers.processcart.com/guides/field-values) — a number stays a number, a percent stays a fraction, a date stays `YYYY-MM-DD`. - The envelope `{ data, nextCursor, hasMore }`, the Problem Details shape and the `code` values already published. - The Standard Webhooks headers and signature scheme. ## Staying informed The [changelog](https://developers.processcart.com/changelog) lists every change with its date; it is written by hand and updated with every release, including the migration notes on the day any `Deprecation` / `Sunset` header appears. The OpenAPI document's `info.version` names the API version and comes straight from the code that serves the API, as does every reference page. --- # API reference > Every operation by resource, the OpenAPI document and the interactive reference. Source: https://developers.processcart.com/reference Every `/api/v1` operation, generated from the same operation registry the routes are built on. Base URL `https://app.processcart.com/api/v1` — send your key as `Authorization: Bearer ` ([Authentication](https://developers.processcart.com/guides/authentication)). | Resource | Operations | What it covers | | --- | --- | --- | | [Me](https://developers.processcart.com/reference/me) | 1 | The key and workspace behind a credential — the first call every integration should make. | | [Workflows](https://developers.processcart.com/reference/workflows) | 6 | Published workflow templates: list, read, create from a portable template JSON, read the per-workflow field schema. | | [Runs](https://developers.processcart.com/reference/runs) | 6 | Runs of a workflow — the central resource: every field value (canonical + formatted) and every task's state in one document. | | [Tasks](https://developers.processcart.com/reference/tasks) | 5 | Complete, reopen, approve and reject a run's tasks under the engine's gates. | | [Fields](https://developers.processcart.com/reference/fields) | 1 | Set a run's field values in one transaction; a JSON number is the canonical, a string is parsed by the field's format. | | [Comments](https://developers.processcart.com/reference/comments) | 3 | Task comments on a run. | | [Files](https://developers.processcart.com/reference/files) | 5 | Two-phase file upload to a run's File fields, and signed downloads. | | [Activity](https://developers.processcart.com/reference/activity) | 1 | A run's append-only activity trail. | | [Views](https://developers.processcart.com/reference/views) | 6 | Shared saved views (filters + sort) and the runs they select. | | [Webhooks](https://developers.processcart.com/reference/webhooks) | 12 | Webhook endpoints, their signing secrets and the delivery log. | | [Schedules](https://developers.processcart.com/reference/schedules) | 9 | Recurrence schedules that start runs of a workflow: create, pause, resume, run now and preview the next occurrences. | | [Automation rules](https://developers.processcart.com/reference/automation-rules) | 8 | When-then rules on a workflow's runs (set fields, status, labels, assignees; notify; send to a webhook) and their execution history. | | [Directory](https://developers.processcart.com/reference/directory) | 2 | Members and groups — ids, names, roles; never email addresses. | | [Exports](https://developers.processcart.com/reference/exports) | 1 | CSV export of a workflow's runs. | | [Health](https://developers.processcart.com/reference/health) | 1 | The unauthenticated status probe behind the status page. | Also generated from the code: - [Scopes](https://developers.processcart.com/reference/scopes) — what each key scope allows. - [Events](https://developers.processcart.com/reference/events) — every webhook event type and its `data`. - [Errors](https://developers.processcart.com/errors) — every Problem Details `code`, the `type` target. - [Field typing](https://developers.processcart.com/reference/field-typing) — how each field type reads and writes. - [Limits and schedules](https://developers.processcart.com/reference/limits) — every published number. - [Schemas](https://developers.processcart.com/reference/schemas) — every component schema of the OpenAPI document. Machine-readable: - [OpenAPI 3.1 — JSON](https://developers.processcart.com/openapi.json) · [YAML](https://developers.processcart.com/openapi.yaml) (the interactive reference below renders the same document) - [llms.txt](https://developers.processcart.com/llms.txt) · [llms-full.txt](https://developers.processcart.com/llms-full.txt) — this site as Markdown for AI tools; every page also has a `.md` twin at its own URL ## Interactive reference The reference below is rendered from the OpenAPI document in your browser. Try-it is off (the API sets no CORS headers — keys never belong in browser code); use the per-language snippets and the worked examples instead. --- # Me endpoints > The key and workspace behind a credential — the first call every integration should make. Every operation with parameters, body, responses, errors and examples. Source: https://developers.processcart.com/reference/me The key and workspace behind a credential — the first call every integration should make. Base URL `https://app.processcart.com/api/v1`. Every operation below is also in the [OpenAPI document](https://developers.processcart.com/openapi.json) and the [interactive reference](https://developers.processcart.com/reference). | Operation | Method and path | Scope | | --- | --- | --- | | [Identify the API key and its workspace](#getMe) | GET /v1/me | Any valid key | ## GET /v1/me **Identify the API key and its workspace.** Any valid key. Operation id `getMe` · [open in the API reference](https://developers.processcart.com/reference#tag/me/GET/v1/me). Returns the organization (workspace) and the API key behind the credential, with the key's scopes and the published rate limit. `org.labels` lists the workspace's status labels — the ids a run's `labels`, the runs filter and automation rules take. Any valid key may call it — the first request every integration should make. **Responses** | Status | Meaning | Body | | --- | --- | --- | | 200 | OK. | [Me](https://developers.processcart.com/reference/schemas#me) | | 401 | No usable credential. `unauthorized` — Authentication required; `invalid_api_key` — Invalid API key; `key_revoked` — API key revoked. | [Problem](https://developers.processcart.com/errors) | | 405 | The verb is not supported on this path. `method_not_allowed` — Method not allowed. | [Problem](https://developers.processcart.com/errors) | | 422 | `validation_failed` — Validation failed. | [Problem](https://developers.processcart.com/errors) | | 429 | `rate_limited` — Too many requests. | [Problem](https://developers.processcart.com/errors) | | 500 | `internal_error` — Internal error. | [Problem](https://developers.processcart.com/errors) | | 503 | `unavailable` — Service unavailable. | [Problem](https://developers.processcart.com/errors) | **Errors** (every code this operation can answer; see [Errors](https://developers.processcart.com/errors)) - 401 [`invalid_api_key`](https://developers.processcart.com/errors#invalid_api_key) — Invalid API key - 401 [`key_revoked`](https://developers.processcart.com/errors#key_revoked) — API key revoked - 401 [`unauthorized`](https://developers.processcart.com/errors#unauthorized) — Authentication required - 405 [`method_not_allowed`](https://developers.processcart.com/errors#method_not_allowed) — Method not allowed - 422 [`validation_failed`](https://developers.processcart.com/errors#validation_failed) — Validation failed - 429 [`rate_limited`](https://developers.processcart.com/errors#rate_limited) — Too many requests - 500 [`internal_error`](https://developers.processcart.com/errors#internal_error) — Internal error - 503 [`unavailable`](https://developers.processcart.com/errors#unavailable) — Service unavailable **Example response** (200) ```json { "org": { "id": "8602787e-fdb1-4a7e-982f-09ea5b3005cd", "name": "Example workspace", "slug": "example-workspace", "timezone": "America/Los_Angeles", "sandbox": false, "labels": [ { "id": "rush", "name": "Rush", "color": "#dc2626" }, { "id": "waiting", "name": "Waiting", "color": "#f59e0b" } ] }, "key": { "id": "07ea300f-832f-4f75-8859-0c318deec6b1", "name": "Example key", "prefix": "pc_live_ynpQ", "readOnly": false, "scopes": [ "*" ], "createdAt": "2026-10-13T00:39:13.702Z", "lastUsedAt": null }, "rateLimit": { "limit": 600, "windowSeconds": 60 } } ``` --- # Workflows endpoints > Published workflow templates: list, read, create from a portable template JSON, read the per-workflow field schema. Every operation with parameters, body, responses, errors and examples. Source: https://developers.processcart.com/reference/workflows Published workflow templates: list, read, create from a portable template JSON, read the per-workflow field schema. Base URL `https://app.processcart.com/api/v1`. Every operation below is also in the [OpenAPI document](https://developers.processcart.com/openapi.json) and the [interactive reference](https://developers.processcart.com/reference). | Operation | Method and path | Scope | | --- | --- | --- | | [List workflows](#listWorkflows) | GET /v1/workflows | Scope `workflows:read` | | [Create a workflow from a template](#createWorkflow) | POST /v1/workflows | Scope `workflows:write` | | [Get a workflow](#getWorkflow) | GET /v1/workflows/{id} | Scope `workflows:read` | | [Update a workflow's name, description, folder, Key Date field or status](#updateWorkflow) | PATCH /v1/workflows/{id} | Scope `workflows:write` | | [Get the JSON Schema for a workflow's run bodies](#getWorkflowSchema) | GET /v1/workflows/{id}/schema | Scope `workflows:read` | | [Export a workflow as a portable template](#getWorkflowTemplate) | GET /v1/workflows/{id}/template | Scope `workflows:read` | ## GET /v1/workflows **List workflows.** Scope `workflows:read` · the key's allowlist narrows the rows (never a 403 — rows outside it are absent) · paginated (`{ data, nextCursor, hasMore }`). Operation id `listWorkflows` · [open in the API reference](https://developers.processcart.com/reference#tag/workflows/GET/v1/workflows). Lists the workflows the key may see (its allowlist is applied; trashed workflows are absent), by name. Each item is the library header: folder, status, the published version, a task count and whether a public kickoff form is live. `updatedSince` turns the list into an incremental sync ordered by `updatedAt` ascending (inclusive). **Query parameters** (an unknown parameter is a 422 `validation_failed` with `errors[].code = "unknown_parameter"`) | Name | Type | Required | Description | | --- | --- | --- | --- | | `updatedSince` | string (date-time) | no | Incremental sync: only items whose `updatedAt` is at or after this instant (inclusive), ordered by `updatedAt` ascending; an explicit conflicting `sort` / `order` is a 422. Overlap by a minute and dedupe: a Run by `(id, revision)`, any other item (workflow, view, webhook, schedule, automation rule) by `(id, updatedAt)` — a higher `revision` or a later `updatedAt` replaces the copy you hold, an equal one is unchanged. | | `limit` | integer | no | | | `cursor` | string | no | The `nextCursor` of the previous page. Opaque, and bound to the query that produced it — a cursor from a different filter or sort is a 422. | **Responses** | Status | Meaning | Body | | --- | --- | --- | | 200 | OK. | [ListWorkflowsResponse](https://developers.processcart.com/reference/schemas#listworkflowsresponse) | | 401 | No usable credential. `unauthorized` — Authentication required; `invalid_api_key` — Invalid API key; `key_revoked` — API key revoked. | [Problem](https://developers.processcart.com/errors) | | 403 | `insufficient_scope` — Insufficient scope. | [Problem](https://developers.processcart.com/errors) | | 405 | The verb is not supported on this path. `method_not_allowed` — Method not allowed. | [Problem](https://developers.processcart.com/errors) | | 422 | `validation_failed` — Validation failed. | [Problem](https://developers.processcart.com/errors) | | 429 | `rate_limited` — Too many requests. | [Problem](https://developers.processcart.com/errors) | | 500 | `internal_error` — Internal error. | [Problem](https://developers.processcart.com/errors) | | 503 | `unavailable` — Service unavailable. | [Problem](https://developers.processcart.com/errors) | **Errors** (every code this operation can answer; see [Errors](https://developers.processcart.com/errors)) - 401 [`invalid_api_key`](https://developers.processcart.com/errors#invalid_api_key) — Invalid API key - 401 [`key_revoked`](https://developers.processcart.com/errors#key_revoked) — API key revoked - 401 [`unauthorized`](https://developers.processcart.com/errors#unauthorized) — Authentication required - 403 [`insufficient_scope`](https://developers.processcart.com/errors#insufficient_scope) — Insufficient scope - 405 [`method_not_allowed`](https://developers.processcart.com/errors#method_not_allowed) — Method not allowed - 422 [`validation_failed`](https://developers.processcart.com/errors#validation_failed) — Validation failed - 429 [`rate_limited`](https://developers.processcart.com/errors#rate_limited) — Too many requests - 500 [`internal_error`](https://developers.processcart.com/errors#internal_error) — Internal error - 503 [`unavailable`](https://developers.processcart.com/errors#unavailable) — Service unavailable **Example response** (200) ```json { "data": [ { "id": "791e68d6-2bd9-4c47-a7a7-17eeefddd5e0", "name": "Draft only", "description": null, "status": "active", "icon": null, "color": null, "folder": null, "keyDateFieldKey": "ship_date", "kickoffFormEnabled": false, "publishedVersion": null, "taskCount": 4, "createdAt": "2026-10-10T17:43:36.934Z", "updatedAt": "2026-10-10T17:43:39.132Z" }, { "id": "ee70a035-0602-43a6-a8bd-959116312fce", "name": "Filed", "description": null, "status": "active", "icon": null, "color": null, "folder": { "id": "bd17f69c-dc2d-4465-81e5-3893b4b95e60", "name": "Sales" }, "keyDateFieldKey": "ship_date", "kickoffFormEnabled": false, "publishedVersion": { "id": "fe5cf349-4c95-43b9-abfe-37d375c5c308", "number": 1, "publishedAt": "2026-10-10T17:43:40.650Z" }, "taskCount": 4, "createdAt": "2026-10-10T17:43:40.308Z", "updatedAt": "2026-10-10T17:43:40.672Z" } ], "nextCursor": null, "hasMore": false } ``` ## POST /v1/workflows **Create a workflow from a template.** Scope `workflows:write` · accepts `Idempotency-Key`. Operation id `createWorkflow` · [open in the API reference](https://developers.processcart.com/reference#tag/workflows/POST/v1/workflows). Creates a workflow from a portable template (the same JSON `GET /v1/workflows/{id}/template` returns) and publishes it, so it is runnable at once. Group assignments are matched by name (case-insensitive); person assignees never travel. Everything is validated BEFORE anything is written: a template problem or a publish-gate error is a 422 with every message listed and no workflow is created. `publish: false` leaves a draft. A key restricted to certain workflows cannot create one; a key restricted to folders must file the new workflow into one of them (`folderId`). **Request headers** | Name | Type | Required | Description | | --- | --- | --- | --- | | `Idempotency-Key` | string | no | Makes the request safe to retry: a second request with the same key and a byte-identical body (and the same `Expected-Revision`) replays the stored response (`Idempotent-Replayed: true`); a different body with the same key is refused (422 `idempotency_key_reused`) and no second resource is created. Keys are stored per workspace for 24 hours and replayed only to the API key that made the request (or the key one rotation away from it); another API key sending the same value is refused (422 `idempotency_key_reused`), so prefix keys per integration. 409, 429 and 5xx answers are never stored. | **Request body** — `application/json`, [CreateWorkflowBody](https://developers.processcart.com/reference/schemas#createworkflowbody) | Property | Type | Required | Description | | --- | --- | --- | --- | | `template` | [WorkflowTemplate](https://developers.processcart.com/reference/schemas#workflowtemplate) | yes | | | `name` | string | no | | | `description` | string \\| null | no | | | `folderId` | [Uuid](https://developers.processcart.com/reference/schemas#uuid) \\| null | no | | | `publish` | boolean | no | | **Responses** | Status | Meaning | Body | | --- | --- | --- | | 201 | Created. `Location` names the new resource. | [CreateWorkflowResponse](https://developers.processcart.com/reference/schemas#createworkflowresponse) | | 401 | No usable credential. `unauthorized` — Authentication required; `invalid_api_key` — Invalid API key; `key_revoked` — API key revoked. | [Problem](https://developers.processcart.com/errors) | | 403 | `insufficient_scope` — Insufficient scope; `not_allowed_for_key` — Not allowed for this key; `read_only_key` — Read-only key. | [Problem](https://developers.processcart.com/errors) | | 405 | The verb is not supported on this path. `method_not_allowed` — Method not allowed. | [Problem](https://developers.processcart.com/errors) | | 409 | `idempotency_in_progress` — Request in progress. | [Problem](https://developers.processcart.com/errors) | | 413 | `payload_too_large` — Payload too large. | [Problem](https://developers.processcart.com/errors) | | 415 | `unsupported_media_type` — Unsupported media type. | [Problem](https://developers.processcart.com/errors) | | 422 | `idempotency_key_reused` — Idempotency key reused; `validation_failed` — Validation failed. | [Problem](https://developers.processcart.com/errors) | | 429 | `rate_limited` — Too many requests. | [Problem](https://developers.processcart.com/errors) | | 500 | `internal_error` — Internal error. | [Problem](https://developers.processcart.com/errors) | | 503 | `unavailable` — Service unavailable. | [Problem](https://developers.processcart.com/errors) | **Errors** (every code this operation can answer; see [Errors](https://developers.processcart.com/errors)) - 401 [`invalid_api_key`](https://developers.processcart.com/errors#invalid_api_key) — Invalid API key - 401 [`key_revoked`](https://developers.processcart.com/errors#key_revoked) — API key revoked - 401 [`unauthorized`](https://developers.processcart.com/errors#unauthorized) — Authentication required - 403 [`insufficient_scope`](https://developers.processcart.com/errors#insufficient_scope) — Insufficient scope - 403 [`not_allowed_for_key`](https://developers.processcart.com/errors#not_allowed_for_key) — Not allowed for this key - 403 [`read_only_key`](https://developers.processcart.com/errors#read_only_key) — Read-only key - 405 [`method_not_allowed`](https://developers.processcart.com/errors#method_not_allowed) — Method not allowed - 409 [`idempotency_in_progress`](https://developers.processcart.com/errors#idempotency_in_progress) — Request in progress - 413 [`payload_too_large`](https://developers.processcart.com/errors#payload_too_large) — Payload too large - 415 [`unsupported_media_type`](https://developers.processcart.com/errors#unsupported_media_type) — Unsupported media type - 422 [`idempotency_key_reused`](https://developers.processcart.com/errors#idempotency_key_reused) — Idempotency key reused - 422 [`validation_failed`](https://developers.processcart.com/errors#validation_failed) — Validation failed - 429 [`rate_limited`](https://developers.processcart.com/errors#rate_limited) — Too many requests - 500 [`internal_error`](https://developers.processcart.com/errors#internal_error) — Internal error - 503 [`unavailable`](https://developers.processcart.com/errors#unavailable) — Service unavailable **Example request** ```json { "template": { "formatVersion": 1, "engineVersion": 1, "exportedAt": "2026-10-10T17:43:34.523Z", "source": "example-workspace", "workflow": { "name": "Order fulfillment", "keyDateFieldKey": "ship_date", "sections": [ { "key": "intake", "title": "Intake", "defaultVisibility": "visible", "tasks": [ { "key": "details", "title": "Details", "instructions": null, "defaultVisibility": "visible", "isStop": false, "kind": "standard", "fields": [ { "key": "customer", "type": "text", "label": "Customer", "help": null, "required": true, "defaultVisibility": "visible", "options": null, "formatConfig": { "type": "none" }, "validation": null, "defaultValue": null, "onFocusPopulate": null, "formula": null, "content": null, "inKickoff": true, "kickoffPosition": 0 }, { "key": "order_code", "type": "text", "label": "Order code", "help": null, "required": false, "defaultVisibility": "visible", "options": null, "formatConfig": { "type": "custom", "regex": "^[A-Z]{2}-\\d{4}$", "errorMessage": "Use AB-1234." }, "validation": null, "defaultValue": null, "onFocusPopulate": null, "formula": null, "content": null, "inKickoff": false, "kickoffPosition": null }, { "key": "memo", "type": "textarea", "label": "Memo", "help": null, "required": false, "defaultVisibility": "visible", "options": null, "formatConfig": null, "validation": null, "defaultValue": null, "onFocusPopulate": null, "formula": null, "content": null, "inKickoff": false, "kickoffPosition": null }, { "key": "quantity", "type": "number", "label": "Quantity", "help": null, "required": false, "defaultVisibility": "visible", "options": null, "formatConfig": { "type": "number", "decimals": 0, "thousands": true }, "validation": null, "defaultValue": null, "onFocusPopulate": null, "formula": null, "content": null, "inKickoff": false, "kickoffPosition": null }, { "key": "amount", "type": "number", "label": "Amount", "help": null, "required": false, "defaultVisibility": "visible", "options": null, "formatConfig": { "type": "currency" }, "validation": null, "defaultValue": null, "onFocusPopulate": null, "formula": null, "content": null, "inKickoff": true, "kickoffPosition": 1 }, { "key": "fee", "type": "number", "label": "Fee", "help": null, "required": false, "defaultVisibility": "visible", "options": null, "formatConfig": { "max": 500, "type": "currency" }, "validation": null, "defaultValue": null, "onFocusPopulate": null, "formula": null, "content": null, "inKickoff": false, "kickoffPosition": null }, { "key": "rate", "type": "number", "label": "Rate", "help": null, "required": false, "defaultVisibility": "visible", "options": null, "formatConfig": { "type": "percent" }, "validation": null, "defaultValue": null, "onFocusPopulate": null, "formula": null, "content": null, "inKickoff": false, "kickoffPosition": null }, { "key": "rate_text", "type": "text", "label": "Rate (text)", "help": null, "required": false, "defaultVisibility": "visible", "options": null, "formatConfig": { "type": "percent" }, "validation": null, "defaultValue": null, "onFocusPopulate": null, "formula": null, "content": null, "inKickoff": false, "kickoffPosition": null }, { "key": "ship_date", "type": "date", "label": "Ship date", "help": null, "required": false, "defaultVisibility": "visible", "options": null, "formatConfig": { "type": "date" }, "validation": null, "defaultValue": null, "onFocusPopulate": null, "formula": null, "content": null, "inKickoff": false, "kickoffPosition": null }, { "key": "start_time", "type": "time", "label": "Start time", "help": null, "required": false, "defaultVisibility": "visible", "options": null, "formatConfig": { "type": "time", "hour12": true }, "validation": null, "defaultValue": null, "onFocusPopulate": null, "formula": null, "content": null, "inKickoff": false, "kickoffPosition": null }, { "key": "gift_wrap", "type": "dropdown", "label": "Gift wrap", "help": null, "required": false, "defaultVisibility": "visible", "options": { "choices": [ "Yes", "No" ] }, "formatConfig": null, "validation": null, "defaultValue": null, "onFocusPopulate": null, "formula": null, "content": null, "inKickoff": false, "kickoffPosition": null }, { "key": "tags", "type": "multi_choice", "label": "Tags", "help": null, "required": false, "defaultVisibility": "visible", "options": { "choices": [ "A", "B", "C" ] }, "formatConfig": null, "validation": null, "defaultValue": null, "onFocusPopulate": null, "formula": null, "content": null, "inKickoff": false, "kickoffPosition": null }, { "key": "agree", "type": "checkbox", "label": "Agree", "help": null, "required": false, "defaultVisibility": "visible", "options": null, "formatConfig": null, "validation": null, "defaultValue": null, "onFocusPopulate": null, "formula": null, "content": null, "inKickoff": false, "kickoffPosition": null }, { "key": "contact_email", "type": "email", "label": "Contact email", "help": null, "required": false, "defaultVisibility": "visible", "options": null, "formatConfig": { "type": "email" }, "validation": null, "defaultValue": null, "onFocusPopulate": null, "formula": null, "content": null, "inKickoff": false, "kickoffPosition": null }, { "key": "website", "type": "url", "label": "Website", "help": null, "required": false, "defaultVisibility": "visible", "options": null, "formatConfig": { "type": "url" }, "validation": null, "defaultValue": null, "onFocusPopulate": null, "formula": null, "content": null, "inKickoff": false, "kickoffPosition": null }, { "key": "phone", "type": "phone", "label": "Phone", "help": null, "required": false, "defaultVisibility": "visible", "options": null, "formatConfig": { "type": "phone" }, "validation": null, "defaultValue": null, "onFocusPopulate": null, "formula": null, "content": null, "inKickoff": false, "kickoffPosition": null }, { "key": "owner", "type": "members", "label": "Owner", "help": null, "required": false, "defaultVisibility": "visible", "options": null, "formatConfig": null, "validation": null, "defaultValue": null, "onFocusPopulate": null, "formula": null, "content": null, "inKickoff": false, "kickoffPosition": null }, { "key": "steps", "type": "subtasks", "label": "Steps", "help": null, "required": false, "defaultVisibility": "visible", "options": { "items": [ { "key": "photos", "label": "Photos" }, { "key": "invoice", "label": "Invoice" } ], "allRequired": false }, "formatConfig": null, "validation": null, "defaultValue": null, "onFocusPopulate": null, "formula": null, "content": null, "inKickoff": false, "kickoffPosition": null }, { "key": "contract", "type": "file", "label": "Contract", "help": null, "required": false, "defaultVisibility": "visible", "options": { "multiple": true }, "formatConfig": null, "validation": null, "defaultValue": null, "onFocusPopulate": null, "formula": null, "content": null, "inKickoff": false, "kickoffPosition": null }, { "key": "double_amount", "type": "formula", "label": "Double amount", "help": null, "required": false, "defaultVisibility": "visible", "options": null, "formatConfig": { "type": "currency" }, "validation": null, "defaultValue": null, "onFocusPopulate": null, "formula": { "expr": "{{amount}} * 2", "mode": "spreadsheet" }, "content": null, "inKickoff": false, "kickoffPosition": null }, { "key": "source", "type": "hidden", "label": "Source", "help": null, "required": false, "defaultVisibility": "hidden", "options": null, "formatConfig": null, "validation": null, "defaultValue": null, "onFocusPopulate": null, "formula": null, "content": null, "inKickoff": false, "kickoffPosition": null }, { "key": "note", "type": "display_text", "label": "Note", "help": null, "required": false, "defaultVisibility": "visible", "options": null, "formatConfig": null, "validation": null, "defaultValue": null, "onFocusPopulate": null, "formula": null, "content": { "type": "doc", "content": [ { "type": "paragraph", "content": [ { "text": "Review the details.", "type": "text" } ] } ] }, "inKickoff": false, "kickoffPosition": null } ] }, { "key": "review", "title": "Review", "instructions": null, "defaultVisibility": "visible", "isStop": false, "kind": "approval", "approvalConfig": { "summaryFieldKeys": [ "amount" ], "rejectReopensTaskKeys": [ "details" ] }, "fields": [] }, { "key": "stop", "title": "Sign-off", "instructions": null, "defaultVisibility": "visible", "isStop": true, "kind": "standard", "fields": [] }, { "key": "wrapup", "title": "Wrap up", "instructions": null, "defaultVisibility": "visible", "isStop": false, "kind": "standard", "fields": [] } ] } ], "rules": [] } }, "name": "Zeta intake", "description": "From the API." } ``` **Example response** (201) ```json { "id": "8b8b4bb9-e062-4a5b-ba75-a3564da9e606", "name": "Zeta intake", "description": "From the API.", "status": "active", "icon": null, "color": null, "folder": null, "keyDateFieldKey": "ship_date", "kickoffFormEnabled": false, "publishedVersion": { "id": "2f84d122-4c0f-4be5-910d-43a117e05c4a", "number": 1, "publishedAt": "2026-10-10T17:43:35.785Z" }, "taskCount": 4, "createdAt": "2026-10-10T17:43:35.602Z", "updatedAt": "2026-10-10T17:43:35.866Z", "sections": [ { "key": "intake", "title": "Intake", "defaultVisibility": "visible", "tasks": [ { "key": "details", "title": "Details", "kind": "standard", "isStop": false, "defaultVisibility": "visible", "dueRule": null, "assignment": null, "guestConfig": null, "fields": [ { "key": "customer", "type": "text", "label": "Customer", "help": null, "required": true, "defaultVisibility": "visible", "options": null, "formatConfig": { "type": "none" }, "validation": null, "defaultValue": null, "onFocusPopulate": null, "formula": null, "inKickoff": true, "kickoffPosition": 0, "readOnly": false }, { "key": "order_code", "type": "text", "label": "Order code", "help": null, "required": false, "defaultVisibility": "visible", "options": null, "formatConfig": { "type": "custom", "regex": "^[A-Z]{2}-\\d{4}$", "errorMessage": "Use AB-1234." }, "validation": null, "defaultValue": null, "onFocusPopulate": null, "formula": null, "inKickoff": false, "kickoffPosition": null, "readOnly": false }, { "key": "memo", "type": "textarea", "label": "Memo", "help": null, "required": false, "defaultVisibility": "visible", "options": null, "formatConfig": null, "validation": null, "defaultValue": null, "onFocusPopulate": null, "formula": null, "inKickoff": false, "kickoffPosition": null, "readOnly": false }, { "key": "quantity", "type": "number", "label": "Quantity", "help": null, "required": false, "defaultVisibility": "visible", "options": null, "formatConfig": { "type": "number", "decimals": 0, "thousands": true }, "validation": null, "defaultValue": null, "onFocusPopulate": null, "formula": null, "inKickoff": false, "kickoffPosition": null, "readOnly": false }, { "key": "amount", "type": "number", "label": "Amount", "help": null, "required": false, "defaultVisibility": "visible", "options": null, "formatConfig": { "type": "currency" }, "validation": null, "defaultValue": null, "onFocusPopulate": null, "formula": null, "inKickoff": true, "kickoffPosition": 1, "readOnly": false }, { "key": "fee", "type": "number", "label": "Fee", "help": null, "required": false, "defaultVisibility": "visible", "options": null, "formatConfig": { "max": 500, "type": "currency" }, "validation": null, "defaultValue": null, "onFocusPopulate": null, "formula": null, "inKickoff": false, "kickoffPosition": null, "readOnly": false }, { "key": "rate", "type": "number", "label": "Rate", "help": null, "required": false, "defaultVisibility": "visible", "options": null, "formatConfig": { "type": "percent" }, "validation": null, "defaultValue": null, "onFocusPopulate": null, "formula": null, "inKickoff": false, "kickoffPosition": null, "readOnly": false }, { "key": "rate_text", "type": "text", "label": "Rate (text)", "help": null, "required": false, "defaultVisibility": "visible", "options": null, "formatConfig": { "type": "percent" }, "validation": null, "defaultValue": null, "onFocusPopulate": null, "formula": null, "inKickoff": false, "kickoffPosition": null, "readOnly": false }, { "key": "ship_date", "type": "date", "label": "Ship date", "help": null, "required": false, "defaultVisibility": "visible", "options": null, "formatConfig": { "type": "date" }, "validation": null, "defaultValue": null, "onFocusPopulate": null, "formula": null, "inKickoff": false, "kickoffPosition": null, "readOnly": false }, { "key": "start_time", "type": "time", "label": "Start time", "help": null, "required": false, "defaultVisibility": "visible", "options": null, "formatConfig": { "type": "time", "hour12": true }, "validation": null, "defaultValue": null, "onFocusPopulate": null, "formula": null, "inKickoff": false, "kickoffPosition": null, "readOnly": false }, { "key": "gift_wrap", "type": "dropdown", "label": "Gift wrap", "help": null, "required": false, "defaultVisibility": "visible", "options": { "choices": [ "Yes", "No" ] }, "formatConfig": null, "validation": null, "defaultValue": null, "onFocusPopulate": null, "formula": null, "inKickoff": false, "kickoffPosition": null, "readOnly": false }, { "key": "tags", "type": "multi_choice", "label": "Tags", "help": null, "required": false, "defaultVisibility": "visible", "options": { "choices": [ "A", "B", "C" ] }, "formatConfig": null, "validation": null, "defaultValue": null, "onFocusPopulate": null, "formula": null, "inKickoff": false, "kickoffPosition": null, "readOnly": false }, { "key": "agree", "type": "checkbox", "label": "Agree", "help": null, "required": false, "defaultVisibility": "visible", "options": null, "formatConfig": null, "validation": null, "defaultValue": null, "onFocusPopulate": null, "formula": null, "inKickoff": false, "kickoffPosition": null, "readOnly": false }, { "key": "contact_email", "type": "email", "label": "Contact email", "help": null, "required": false, "defaultVisibility": "visible", "options": null, "formatConfig": { "type": "email" }, "validation": null, "defaultValue": null, "onFocusPopulate": null, "formula": null, "inKickoff": false, "kickoffPosition": null, "readOnly": false }, { "key": "website", "type": "url", "label": "Website", "help": null, "required": false, "defaultVisibility": "visible", "options": null, "formatConfig": { "type": "url" }, "validation": null, "defaultValue": null, "onFocusPopulate": null, "formula": null, "inKickoff": false, "kickoffPosition": null, "readOnly": false }, { "key": "phone", "type": "phone", "label": "Phone", "help": null, "required": false, "defaultVisibility": "visible", "options": null, "formatConfig": { "type": "phone" }, "validation": null, "defaultValue": null, "onFocusPopulate": null, "formula": null, "inKickoff": false, "kickoffPosition": null, "readOnly": false }, { "key": "owner", "type": "members", "label": "Owner", "help": null, "required": false, "defaultVisibility": "visible", "options": null, "formatConfig": null, "validation": null, "defaultValue": null, "onFocusPopulate": null, "formula": null, "inKickoff": false, "kickoffPosition": null, "readOnly": false }, { "key": "steps", "type": "subtasks", "label": "Steps", "help": null, "required": false, "defaultVisibility": "visible", "options": { "items": [ { "key": "photos", "label": "Photos" }, { "key": "invoice", "label": "Invoice" } ], "allRequired": false }, "formatConfig": null, "validation": null, "defaultValue": null, "onFocusPopulate": null, "formula": null, "inKickoff": false, "kickoffPosition": null, "readOnly": false }, { "key": "contract", "type": "file", "label": "Contract", "help": null, "required": false, "defaultVisibility": "visible", "options": { "multiple": true }, "formatConfig": null, "validation": null, "defaultValue": null, "onFocusPopulate": null, "formula": null, "inKickoff": false, "kickoffPosition": null, "readOnly": true }, { "key": "double_amount", "type": "formula", "label": "Double amount", "help": null, "required": false, "defaultVisibility": "visible", "options": null, "formatConfig": { "type": "currency" }, "validation": null, "defaultValue": null, "onFocusPopulate": null, "formula": { "expr": "{{amount}} * 2", "mode": "spreadsheet" }, "inKickoff": false, "kickoffPosition": null, "readOnly": true }, { "key": "source", "type": "hidden", "label": "Source", "help": null, "required": false, "defaultVisibility": "hidden", "options": null, "formatConfig": null, "validation": null, "defaultValue": null, "onFocusPopulate": null, "formula": null, "inKickoff": false, "kickoffPosition": null, "readOnly": false }, { "key": "note", "type": "display_text", "label": "Note", "help": null, "required": false, "defaultVisibility": "visible", "options": null, "formatConfig": null, "validation": null, "defaultValue": null, "onFocusPopulate": null, "formula": null, "inKickoff": false, "kickoffPosition": null, "readOnly": true } ] }, { "key": "review", "title": "Review", "kind": "approval", "isStop": false, "defaultVisibility": "visible", "dueRule": null, "assignment": null, "guestConfig": null, "fields": [] }, { "key": "stop", "title": "Sign-off", "kind": "standard", "isStop": true, "defaultVisibility": "visible", "dueRule": null, "assignment": null, "guestConfig": null, "fields": [] }, { "key": "wrapup", "title": "Wrap up", "kind": "standard", "isStop": false, "defaultVisibility": "visible", "dueRule": null, "assignment": null, "guestConfig": null, "fields": [] } ] } ], "rules": [], "import": { "errors": [], "warnings": [], "counts": { "sections": 1, "tasks": 4, "fields": 22, "rules": 0 }, "clearedAssignments": 0, "flaggedEmails": [] }, "publishWarnings": [ "\"Source\" (source) has no formula yet." ] } ``` ## GET /v1/workflows/{id} **Get a workflow.** Scope `workflows:read` · checked against the key's workflow / folder allowlist (403 `not_allowed_for_key` outside it). Operation id `getWorkflow` · [open in the API reference](https://developers.processcart.com/reference#tag/workflows/GET/v1/workflows/{id}). The workflow's header plus its PUBLISHED version's sections, tasks, fields and rules — enough to know every task key and field key a run will carry. An unpublished workflow answers with `publishedVersion: null` and no sections; a workflow in Trash is a 404. **Path parameters** | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | string (uuid) | yes | | **Responses** | Status | Meaning | Body | | --- | --- | --- | | 200 | OK. | [WorkflowDetail](https://developers.processcart.com/reference/schemas#workflowdetail) | | 401 | No usable credential. `unauthorized` — Authentication required; `invalid_api_key` — Invalid API key; `key_revoked` — API key revoked. | [Problem](https://developers.processcart.com/errors) | | 403 | `insufficient_scope` — Insufficient scope; `not_allowed_for_key` — Not allowed for this key. | [Problem](https://developers.processcart.com/errors) | | 404 | `not_found` — Not found. | [Problem](https://developers.processcart.com/errors) | | 405 | The verb is not supported on this path. `method_not_allowed` — Method not allowed. | [Problem](https://developers.processcart.com/errors) | | 422 | `validation_failed` — Validation failed. | [Problem](https://developers.processcart.com/errors) | | 429 | `rate_limited` — Too many requests. | [Problem](https://developers.processcart.com/errors) | | 500 | `internal_error` — Internal error. | [Problem](https://developers.processcart.com/errors) | | 503 | `unavailable` — Service unavailable. | [Problem](https://developers.processcart.com/errors) | **Errors** (every code this operation can answer; see [Errors](https://developers.processcart.com/errors)) - 401 [`invalid_api_key`](https://developers.processcart.com/errors#invalid_api_key) — Invalid API key - 401 [`key_revoked`](https://developers.processcart.com/errors#key_revoked) — API key revoked - 401 [`unauthorized`](https://developers.processcart.com/errors#unauthorized) — Authentication required - 403 [`insufficient_scope`](https://developers.processcart.com/errors#insufficient_scope) — Insufficient scope - 403 [`not_allowed_for_key`](https://developers.processcart.com/errors#not_allowed_for_key) — Not allowed for this key - 404 [`not_found`](https://developers.processcart.com/errors#not_found) — Not found - 405 [`method_not_allowed`](https://developers.processcart.com/errors#method_not_allowed) — Method not allowed - 422 [`validation_failed`](https://developers.processcart.com/errors#validation_failed) — Validation failed - 429 [`rate_limited`](https://developers.processcart.com/errors#rate_limited) — Too many requests - 500 [`internal_error`](https://developers.processcart.com/errors#internal_error) — Internal error - 503 [`unavailable`](https://developers.processcart.com/errors#unavailable) — Service unavailable **Example response** (200) ```json { "id": "86504e07-177b-45a5-aad7-843ca07111b9", "name": "Order fulfillment", "description": "Every field type.", "status": "active", "icon": null, "color": null, "folder": { "id": "bd17f69c-dc2d-4465-81e5-3893b4b95e60", "name": "Sales" }, "keyDateFieldKey": "ship_date", "kickoffFormEnabled": false, "publishedVersion": { "id": "c32e30ba-e3ff-4df8-88f6-cd5a15bd0e8b", "number": 1, "publishedAt": "2026-10-10T17:43:32.943Z" }, "taskCount": 4, "createdAt": "2026-10-10T17:43:33.192Z", "updatedAt": "2026-10-10T17:43:33.303Z", "sections": [ { "key": "intake", "title": "Intake", "defaultVisibility": "visible", "tasks": [ { "key": "details", "title": "Details", "kind": "standard", "isStop": false, "defaultVisibility": "visible", "dueRule": null, "assignment": null, "guestConfig": null, "fields": [ { "key": "customer", "type": "text", "label": "Customer", "help": null, "required": true, "defaultVisibility": "visible", "options": null, "formatConfig": { "type": "none" }, "validation": null, "defaultValue": null, "onFocusPopulate": null, "formula": null, "inKickoff": true, "kickoffPosition": 0, "readOnly": false }, { "key": "order_code", "type": "text", "label": "Order code", "help": null, "required": false, "defaultVisibility": "visible", "options": null, "formatConfig": { "type": "custom", "regex": "^[A-Z]{2}-\\d{4}$", "errorMessage": "Use AB-1234." }, "validation": null, "defaultValue": null, "onFocusPopulate": null, "formula": null, "inKickoff": false, "kickoffPosition": null, "readOnly": false }, { "key": "memo", "type": "textarea", "label": "Memo", "help": null, "required": false, "defaultVisibility": "visible", "options": null, "formatConfig": null, "validation": null, "defaultValue": null, "onFocusPopulate": null, "formula": null, "inKickoff": false, "kickoffPosition": null, "readOnly": false }, { "key": "quantity", "type": "number", "label": "Quantity", "help": null, "required": false, "defaultVisibility": "visible", "options": null, "formatConfig": { "type": "number", "decimals": 0, "thousands": true }, "validation": null, "defaultValue": null, "onFocusPopulate": null, "formula": null, "inKickoff": false, "kickoffPosition": null, "readOnly": false }, { "key": "amount", "type": "number", "label": "Amount", "help": null, "required": false, "defaultVisibility": "visible", "options": null, "formatConfig": { "type": "currency" }, "validation": null, "defaultValue": null, "onFocusPopulate": null, "formula": null, "inKickoff": true, "kickoffPosition": 1, "readOnly": false }, { "key": "fee", "type": "number", "label": "Fee", "help": null, "required": false, "defaultVisibility": "visible", "options": null, "formatConfig": { "max": 500, "type": "currency" }, "validation": null, "defaultValue": null, "onFocusPopulate": null, "formula": null, "inKickoff": false, "kickoffPosition": null, "readOnly": false }, { "key": "rate", "type": "number", "label": "Rate", "help": null, "required": false, "defaultVisibility": "visible", "options": null, "formatConfig": { "type": "percent" }, "validation": null, "defaultValue": null, "onFocusPopulate": null, "formula": null, "inKickoff": false, "kickoffPosition": null, "readOnly": false }, { "key": "rate_text", "type": "text", "label": "Rate (text)", "help": null, "required": false, "defaultVisibility": "visible", "options": null, "formatConfig": { "type": "percent" }, "validation": null, "defaultValue": null, "onFocusPopulate": null, "formula": null, "inKickoff": false, "kickoffPosition": null, "readOnly": false }, { "key": "ship_date", "type": "date", "label": "Ship date", "help": null, "required": false, "defaultVisibility": "visible", "options": null, "formatConfig": { "type": "date" }, "validation": null, "defaultValue": null, "onFocusPopulate": null, "formula": null, "inKickoff": false, "kickoffPosition": null, "readOnly": false }, { "key": "start_time", "type": "time", "label": "Start time", "help": null, "required": false, "defaultVisibility": "visible", "options": null, "formatConfig": { "type": "time", "hour12": true }, "validation": null, "defaultValue": null, "onFocusPopulate": null, "formula": null, "inKickoff": false, "kickoffPosition": null, "readOnly": false }, { "key": "gift_wrap", "type": "dropdown", "label": "Gift wrap", "help": null, "required": false, "defaultVisibility": "visible", "options": { "choices": [ "Yes", "No" ] }, "formatConfig": null, "validation": null, "defaultValue": null, "onFocusPopulate": null, "formula": null, "inKickoff": false, "kickoffPosition": null, "readOnly": false }, { "key": "tags", "type": "multi_choice", "label": "Tags", "help": null, "required": false, "defaultVisibility": "visible", "options": { "choices": [ "A", "B", "C" ] }, "formatConfig": null, "validation": null, "defaultValue": null, "onFocusPopulate": null, "formula": null, "inKickoff": false, "kickoffPosition": null, "readOnly": false }, { "key": "agree", "type": "checkbox", "label": "Agree", "help": null, "required": false, "defaultVisibility": "visible", "options": null, "formatConfig": null, "validation": null, "defaultValue": null, "onFocusPopulate": null, "formula": null, "inKickoff": false, "kickoffPosition": null, "readOnly": false }, { "key": "contact_email", "type": "email", "label": "Contact email", "help": null, "required": false, "defaultVisibility": "visible", "options": null, "formatConfig": { "type": "email" }, "validation": null, "defaultValue": null, "onFocusPopulate": null, "formula": null, "inKickoff": false, "kickoffPosition": null, "readOnly": false }, { "key": "website", "type": "url", "label": "Website", "help": null, "required": false, "defaultVisibility": "visible", "options": null, "formatConfig": { "type": "url" }, "validation": null, "defaultValue": null, "onFocusPopulate": null, "formula": null, "inKickoff": false, "kickoffPosition": null, "readOnly": false }, { "key": "phone", "type": "phone", "label": "Phone", "help": null, "required": false, "defaultVisibility": "visible", "options": null, "formatConfig": { "type": "phone" }, "validation": null, "defaultValue": null, "onFocusPopulate": null, "formula": null, "inKickoff": false, "kickoffPosition": null, "readOnly": false }, { "key": "owner", "type": "members", "label": "Owner", "help": null, "required": false, "defaultVisibility": "visible", "options": null, "formatConfig": null, "validation": null, "defaultValue": null, "onFocusPopulate": null, "formula": null, "inKickoff": false, "kickoffPosition": null, "readOnly": false }, { "key": "steps", "type": "subtasks", "label": "Steps", "help": null, "required": false, "defaultVisibility": "visible", "options": { "items": [ { "key": "photos", "label": "Photos" }, { "key": "invoice", "label": "Invoice" } ], "allRequired": false }, "formatConfig": null, "validation": null, "defaultValue": null, "onFocusPopulate": null, "formula": null, "inKickoff": false, "kickoffPosition": null, "readOnly": false }, { "key": "contract", "type": "file", "label": "Contract", "help": null, "required": false, "defaultVisibility": "visible", "options": { "multiple": true }, "formatConfig": null, "validation": null, "defaultValue": null, "onFocusPopulate": null, "formula": null, "inKickoff": false, "kickoffPosition": null, "readOnly": true }, { "key": "double_amount", "type": "formula", "label": "Double amount", "help": null, "required": false, "defaultVisibility": "visible", "options": null, "formatConfig": { "type": "currency" }, "validation": null, "defaultValue": null, "onFocusPopulate": null, "formula": { "expr": "{{amount}} * 2", "mode": "spreadsheet" }, "inKickoff": false, "kickoffPosition": null, "readOnly": true }, { "key": "source", "type": "hidden", "label": "Source", "help": null, "required": false, "defaultVisibility": "hidden", "options": null, "formatConfig": null, "validation": null, "defaultValue": null, "onFocusPopulate": null, "formula": null, "inKickoff": false, "kickoffPosition": null, "readOnly": false }, { "key": "note", "type": "display_text", "label": "Note", "help": null, "required": false, "defaultVisibility": "visible", "options": null, "formatConfig": null, "validation": null, "defaultValue": null, "onFocusPopulate": null, "formula": null, "inKickoff": false, "kickoffPosition": null, "readOnly": true } ] }, { "key": "review", "title": "Review", "kind": "approval", "isStop": false, "defaultVisibility": "visible", "dueRule": null, "assignment": { "assignees": [ { "kind": "user", "userId": "b956f9b0-baa5-48f8-8004-9a80daa37d8a" } ] }, "guestConfig": null, "fields": [] }, { "key": "stop", "title": "Sign-off", "kind": "standard", "isStop": true, "defaultVisibility": "visible", "dueRule": null, "assignment": null, "guestConfig": null, "fields": [] }, { "key": "wrapup", "title": "Wrap up", "kind": "standard", "isStop": false, "defaultVisibility": "visible", "dueRule": null, "assignment": null, "guestConfig": null, "fields": [] } ] } ], "rules": [] } ``` ## PATCH /v1/workflows/{id} **Update a workflow's name, description, folder, Key Date field or status.** Scope `workflows:write` · checked against the key's workflow / folder allowlist (403 `not_allowed_for_key` outside it) · accepts `Idempotency-Key`. Operation id `updateWorkflow` · [open in the API reference](https://developers.processcart.com/reference#tag/workflows/PATCH/v1/workflows/{id}). Changes workflow-level settings (never the published tree — that is the builder's job). `keyDateFieldKey` must name a date-typed field of the published version (of the draft while the workflow has never been published) and re-derives every live run's Key Date. `status: "archived"` hides the workflow from the Run menu. A key restricted to folders may only move the workflow within those folders. **Path parameters** | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | string (uuid) | yes | | **Request headers** | Name | Type | Required | Description | | --- | --- | --- | --- | | `Idempotency-Key` | string | no | Makes the request safe to retry: a second request with the same key and a byte-identical body (and the same `Expected-Revision`) replays the stored response (`Idempotent-Replayed: true`); a different body with the same key is refused (422 `idempotency_key_reused`) and no second resource is created. Keys are stored per workspace for 24 hours and replayed only to the API key that made the request (or the key one rotation away from it); another API key sending the same value is refused (422 `idempotency_key_reused`), so prefix keys per integration. 409, 429 and 5xx answers are never stored. | **Request body** — `application/json`, [UpdateWorkflowBody](https://developers.processcart.com/reference/schemas#updateworkflowbody) | Property | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | no | | | `description` | string \\| null | no | | | `folderId` | [Uuid](https://developers.processcart.com/reference/schemas#uuid) \\| null | no | | | `keyDateFieldKey` | [FieldKey](https://developers.processcart.com/reference/schemas#fieldkey) \\| null | no | | | `status` | `"active"` \\| `"archived"` | no | | **Responses** | Status | Meaning | Body | | --- | --- | --- | | 200 | OK. | [WorkflowSummary](https://developers.processcart.com/reference/schemas#workflowsummary) | | 401 | No usable credential. `unauthorized` — Authentication required; `invalid_api_key` — Invalid API key; `key_revoked` — API key revoked. | [Problem](https://developers.processcart.com/errors) | | 403 | `insufficient_scope` — Insufficient scope; `not_allowed_for_key` — Not allowed for this key; `read_only_key` — Read-only key. | [Problem](https://developers.processcart.com/errors) | | 404 | `not_found` — Not found. | [Problem](https://developers.processcart.com/errors) | | 405 | The verb is not supported on this path. `method_not_allowed` — Method not allowed. | [Problem](https://developers.processcart.com/errors) | | 409 | `idempotency_in_progress` — Request in progress. | [Problem](https://developers.processcart.com/errors) | | 413 | `payload_too_large` — Payload too large. | [Problem](https://developers.processcart.com/errors) | | 415 | `unsupported_media_type` — Unsupported media type. | [Problem](https://developers.processcart.com/errors) | | 422 | `idempotency_key_reused` — Idempotency key reused; `validation_failed` — Validation failed. | [Problem](https://developers.processcart.com/errors) | | 429 | `rate_limited` — Too many requests. | [Problem](https://developers.processcart.com/errors) | | 500 | `internal_error` — Internal error. | [Problem](https://developers.processcart.com/errors) | | 503 | `unavailable` — Service unavailable. | [Problem](https://developers.processcart.com/errors) | **Errors** (every code this operation can answer; see [Errors](https://developers.processcart.com/errors)) - 401 [`invalid_api_key`](https://developers.processcart.com/errors#invalid_api_key) — Invalid API key - 401 [`key_revoked`](https://developers.processcart.com/errors#key_revoked) — API key revoked - 401 [`unauthorized`](https://developers.processcart.com/errors#unauthorized) — Authentication required - 403 [`insufficient_scope`](https://developers.processcart.com/errors#insufficient_scope) — Insufficient scope - 403 [`not_allowed_for_key`](https://developers.processcart.com/errors#not_allowed_for_key) — Not allowed for this key - 403 [`read_only_key`](https://developers.processcart.com/errors#read_only_key) — Read-only key - 404 [`not_found`](https://developers.processcart.com/errors#not_found) — Not found - 405 [`method_not_allowed`](https://developers.processcart.com/errors#method_not_allowed) — Method not allowed - 409 [`idempotency_in_progress`](https://developers.processcart.com/errors#idempotency_in_progress) — Request in progress - 413 [`payload_too_large`](https://developers.processcart.com/errors#payload_too_large) — Payload too large - 415 [`unsupported_media_type`](https://developers.processcart.com/errors#unsupported_media_type) — Unsupported media type - 422 [`idempotency_key_reused`](https://developers.processcart.com/errors#idempotency_key_reused) — Idempotency key reused - 422 [`validation_failed`](https://developers.processcart.com/errors#validation_failed) — Validation failed - 429 [`rate_limited`](https://developers.processcart.com/errors#rate_limited) — Too many requests - 500 [`internal_error`](https://developers.processcart.com/errors#internal_error) — Internal error - 503 [`unavailable`](https://developers.processcart.com/errors#unavailable) — Service unavailable **Example request** ```json { "name": "Zeta intake (renamed)", "status": "archived", "folderId": "bd17f69c-dc2d-4465-81e5-3893b4b95e60" } ``` **Example response** (200) ```json { "id": "8b8b4bb9-e062-4a5b-ba75-a3564da9e606", "name": "Zeta intake (renamed)", "description": null, "status": "archived", "icon": null, "color": null, "folder": { "id": "bd17f69c-dc2d-4465-81e5-3893b4b95e60", "name": "Sales" }, "keyDateFieldKey": null, "kickoffFormEnabled": false, "publishedVersion": { "id": "2f84d122-4c0f-4be5-910d-43a117e05c4a", "number": 1, "publishedAt": "2026-10-10T17:43:35.785Z" }, "taskCount": 4, "createdAt": "2026-10-10T17:43:35.602Z", "updatedAt": "2026-10-10T17:43:46.881Z" } ``` ## GET /v1/workflows/{id}/schema **Get the JSON Schema for a workflow's run bodies.** Scope `workflows:read` · checked against the key's workflow / folder allowlist (403 `not_allowed_for_key` outside it). Operation id `getWorkflowSchema` · [open in the API reference](https://developers.processcart.com/reference#tag/workflows/GET/v1/workflows/{id}/schema). Two JSON Schema (2020-12) documents generated from the published version: `start` describes the `POST /v1/runs` body (every writable field, with `required` naming the kickoff fields no rule can hide) and `fields` the `PATCH /v1/runs/{id}/fields` body. Each property carries the field's type and format (`x-processcart-type`, `x-processcart-format`), its choices as `enum`, bounds, and the task and section it belongs to. Hidden fields are writable but never shown. Unpublished workflows have no schema (409). **Path parameters** | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | string (uuid) | yes | | **Responses** | Status | Meaning | Body | | --- | --- | --- | | 200 | OK. | [GetWorkflowSchemaResponse](https://developers.processcart.com/reference/schemas#getworkflowschemaresponse) | | 401 | No usable credential. `unauthorized` — Authentication required; `invalid_api_key` — Invalid API key; `key_revoked` — API key revoked. | [Problem](https://developers.processcart.com/errors) | | 403 | `insufficient_scope` — Insufficient scope; `not_allowed_for_key` — Not allowed for this key. | [Problem](https://developers.processcart.com/errors) | | 404 | `not_found` — Not found. | [Problem](https://developers.processcart.com/errors) | | 405 | The verb is not supported on this path. `method_not_allowed` — Method not allowed. | [Problem](https://developers.processcart.com/errors) | | 409 | `workflow_not_published` — Workflow not published. | [Problem](https://developers.processcart.com/errors) | | 422 | `validation_failed` — Validation failed. | [Problem](https://developers.processcart.com/errors) | | 429 | `rate_limited` — Too many requests. | [Problem](https://developers.processcart.com/errors) | | 500 | `internal_error` — Internal error. | [Problem](https://developers.processcart.com/errors) | | 503 | `unavailable` — Service unavailable. | [Problem](https://developers.processcart.com/errors) | **Errors** (every code this operation can answer; see [Errors](https://developers.processcart.com/errors)) - 401 [`invalid_api_key`](https://developers.processcart.com/errors#invalid_api_key) — Invalid API key - 401 [`key_revoked`](https://developers.processcart.com/errors#key_revoked) — API key revoked - 401 [`unauthorized`](https://developers.processcart.com/errors#unauthorized) — Authentication required - 403 [`insufficient_scope`](https://developers.processcart.com/errors#insufficient_scope) — Insufficient scope - 403 [`not_allowed_for_key`](https://developers.processcart.com/errors#not_allowed_for_key) — Not allowed for this key - 404 [`not_found`](https://developers.processcart.com/errors#not_found) — Not found - 405 [`method_not_allowed`](https://developers.processcart.com/errors#method_not_allowed) — Method not allowed - 409 [`workflow_not_published`](https://developers.processcart.com/errors#workflow_not_published) — Workflow not published - 422 [`validation_failed`](https://developers.processcart.com/errors#validation_failed) — Validation failed - 429 [`rate_limited`](https://developers.processcart.com/errors#rate_limited) — Too many requests - 500 [`internal_error`](https://developers.processcart.com/errors#internal_error) — Internal error - 503 [`unavailable`](https://developers.processcart.com/errors#unavailable) — Service unavailable **Example response** (200) ```json { "workflowId": "86504e07-177b-45a5-aad7-843ca07111b9", "versionId": "c32e30ba-e3ff-4df8-88f6-cd5a15bd0e8b", "versionNumber": 1, "start": { "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Start a run", "type": "object", "properties": { "workflowId": { "type": "string", "format": "uuid" }, "name": { "type": "string", "minLength": 1, "maxLength": 300 }, "fields": { "type": "object", "properties": { "customer": { "title": "Customer", "type": "string", "x-processcart-type": "text", "x-processcart-format": "none", "x-task": "details", "x-section": "intake", "description": "null (or an empty string) clears the field." }, "order_code": { "title": "Order code", "type": [ "string", "null" ], "pattern": "^[A-Z]{2}-\\d{4}$", "x-processcart-type": "text", "x-processcart-format": "custom", "x-task": "details", "x-section": "intake", "description": "Use AB-1234. null (or an empty string) clears the field. Not a kickoff field; may still be set at start." }, "memo": { "title": "Memo", "type": [ "string", "null" ], "x-processcart-type": "textarea", "x-task": "details", "x-section": "intake", "description": "null (or an empty string) clears the field. Not a kickoff field; may still be set at start." }, "quantity": { "title": "Quantity", "type": [ "number", "string", "null" ], "x-processcart-type": "number", "x-processcart-format": "number", "x-task": "details", "x-section": "intake", "description": "A JSON number is the canonical value; a string is parsed by the field's format (a comma is a thousands separator). null (or an empty string) clears the field. Not a kickoff field; may still be set at start." }, "amount": { "title": "Amount", "type": [ "number", "string", "null" ], "x-processcart-type": "number", "x-processcart-format": "currency", "x-task": "details", "x-section": "intake", "description": "Whole currency units (1700.5 = $1,700.50). A JSON number is the canonical value; a string is parsed by the field's format (a comma is a thousands separator). null (or an empty string) clears the field." }, "fee": { "title": "Fee", "type": [ "number", "string", "null" ], "maximum": 500, "x-processcart-type": "number", "x-processcart-format": "currency", "x-task": "details", "x-section": "intake", "description": "Whole currency units (1700.5 = $1,700.50). A JSON number is the canonical value; a string is parsed by the field's format (a comma is a thousands separator). null (or an empty string) clears the field. Not a kickoff field; may still be set at start." }, "rate": { "title": "Rate", "type": [ "number", "string", "null" ], "x-processcart-type": "number", "x-processcart-format": "percent", "x-task": "details", "x-section": "intake", "description": "A fraction: 0.125 means 12.5 % (bounds are fractions too). A JSON number is the canonical value; a string is parsed by the field's format (a comma is a thousands separator). null (or an empty string) clears the field. Not a kickoff field; may still be set at start." }, "rate_text": { "title": "Rate (text)", "type": [ "number", "string", "null" ], "x-processcart-type": "text", "x-processcart-format": "percent", "x-task": "details", "x-section": "intake", "description": "A fraction: 0.125 means 12.5 % (bounds are fractions too). A JSON number is the canonical value; a string is parsed by the field's format (a comma is a thousands separator). null (or an empty string) clears the field. Not a kickoff field; may still be set at start." }, "ship_date": { "title": "Ship date", "type": [ "string", "null" ], "format": "date", "pattern": "^\\d{4}-\\d{2}-\\d{2}$", "x-processcart-type": "date", "x-processcart-format": "date", "x-task": "details", "x-section": "intake", "description": "Any common date spelling is accepted and normalized to YYYY-MM-DD. null (or an empty string) clears the field. Not a kickoff field; may still be set at start." }, "start_time": { "title": "Start time", "type": [ "string", "null" ], "pattern": "^\\d{2}:\\d{2}$", "x-processcart-type": "time", "x-processcart-format": "time", "x-task": "details", "x-section": "intake", "description": "\"2:30 PM\" is accepted and normalized to 24-hour HH:mm. null (or an empty string) clears the field. Not a kickoff field; may still be set at start." }, "gift_wrap": { "title": "Gift wrap", "type": [ "string", "null" ], "enum": [ "Yes", "No", null ], "x-processcart-type": "dropdown", "x-task": "details", "x-section": "intake", "description": "null (or an empty string) clears the field. Not a kickoff field; may still be set at start." }, "tags": { "title": "Tags", "type": [ "array", "null" ], "items": { "type": "string", "enum": [ "A", "B", "C" ] }, "uniqueItems": true, "x-processcart-type": "multi_choice", "x-task": "details", "x-section": "intake", "description": "null (or an empty string) clears the field. Not a kickoff field; may still be set at start." }, "agree": { "title": "Agree", "type": "boolean", "x-processcart-type": "checkbox", "x-task": "details", "x-section": "intake", "description": "false clears; a never-set checkbox reads false. Not a kickoff field; may still be set at start." }, "contact_email": { "title": "Contact email", "type": [ "string", "null" ], "format": "email", "x-processcart-type": "email", "x-processcart-format": "email", "x-task": "details", "x-section": "intake", "description": "Lowercased on write. null (or an empty string) clears the field. Not a kickoff field; may still be set at start." }, "website": { "title": "Website", "type": [ "string", "null" ], "format": "uri", "x-processcart-type": "url", "x-processcart-format": "url", "x-task": "details", "x-section": "intake", "description": "http(s) only; normalized by the URL parser (a missing scheme is added). null (or an empty string) clears the field. Not a kickoff field; may still be set at start." }, "phone": { "title": "Phone", "type": [ "string", "null" ], "pattern": "^\\+\\d{8,15}$", "x-processcart-type": "phone", "x-processcart-format": "phone", "x-task": "details", "x-section": "intake", "description": "E.164 (US numbers are normalized from any common spelling). null (or an empty string) clears the field. Not a kickoff field; may still be set at start." }, "owner": { "title": "Owner", "type": [ "array", "null" ], "items": { "type": "string", "format": "uuid" }, "uniqueItems": true, "x-processcart-type": "members", "x-task": "details", "x-section": "intake", "description": "User ids of current members (GET /v1/users). null (or an empty string) clears the field. Not a kickoff field; may still be set at start." }, "steps": { "title": "Steps", "type": [ "array", "null" ], "items": { "type": "string", "enum": [ "photos", "invoice" ] }, "uniqueItems": true, "x-processcart-type": "subtasks", "x-task": "details", "x-section": "intake", "description": "Items: photos = Photos; invoice = Invoice. null (or an empty string) clears the field. Not a kickoff field; may still be set at start." }, "source": { "title": "Source", "type": [ "string", "number", "boolean", "array", "null" ], "items": { "type": "string" }, "readOnly": false, "x-processcart-type": "hidden", "x-task": "details", "x-section": "intake", "description": "A hidden run variable: not shown in the app, not secret. Writable through the API (no formula computes it). null (or an empty string) clears the field. Not a kickoff field; may still be set at start." } }, "additionalProperties": false, "propertyNames": { "pattern": "^[a-z][a-z0-9_]*$", "maxLength": 80 }, "required": [ "customer" ] }, "priority": { "type": "string", "enum": [ "none", "low", "medium", "high", "urgent" ] }, "labels": { "type": "array", "items": { "type": "string" }, "maxItems": 50, "description": "Status-label ids from the org's label set." }, "dueAt": { "type": [ "string", "null" ], "description": "An ISO 8601 instant, or a YYYY-MM-DD date (end of that day in the org's time zone)." } }, "required": [ "workflowId", "name", "fields" ], "additionalProperties": false }, "fields": { "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Set field values", "type": "object", "properties": { "customer": { "title": "Customer", "type": [ "string", "null" ], "x-processcart-type": "text", "x-processcart-format": "none", "x-task": "details", "x-section": "intake", "description": "null (or an empty string) clears the field." }, "order_code": { "title": "Order code", "type": [ "string", "null" ], "pattern": "^[A-Z]{2}-\\d{4}$", "x-processcart-type": "text", "x-processcart-format": "custom", "x-task": "details", "x-section": "intake", "description": "Use AB-1234. null (or an empty string) clears the field." }, "memo": { "title": "Memo", "type": [ "string", "null" ], "x-processcart-type": "textarea", "x-task": "details", "x-section": "intake", "description": "null (or an empty string) clears the field." }, "quantity": { "title": "Quantity", "type": [ "number", "string", "null" ], "x-processcart-type": "number", "x-processcart-format": "number", "x-task": "details", "x-section": "intake", "description": "A JSON number is the canonical value; a string is parsed by the field's format (a comma is a thousands separator). null (or an empty string) clears the field." }, "amount": { "title": "Amount", "type": [ "number", "string", "null" ], "x-processcart-type": "number", "x-processcart-format": "currency", "x-task": "details", "x-section": "intake", "description": "Whole currency units (1700.5 = $1,700.50). A JSON number is the canonical value; a string is parsed by the field's format (a comma is a thousands separator). null (or an empty string) clears the field." }, "fee": { "title": "Fee", "type": [ "number", "string", "null" ], "maximum": 500, "x-processcart-type": "number", "x-processcart-format": "currency", "x-task": "details", "x-section": "intake", "description": "Whole currency units (1700.5 = $1,700.50). A JSON number is the canonical value; a string is parsed by the field's format (a comma is a thousands separator). null (or an empty string) clears the field." }, "rate": { "title": "Rate", "type": [ "number", "string", "null" ], "x-processcart-type": "number", "x-processcart-format": "percent", "x-task": "details", "x-section": "intake", "description": "A fraction: 0.125 means 12.5 % (bounds are fractions too). A JSON number is the canonical value; a string is parsed by the field's format (a comma is a thousands separator). null (or an empty string) clears the field." }, "rate_text": { "title": "Rate (text)", "type": [ "number", "string", "null" ], "x-processcart-type": "text", "x-processcart-format": "percent", "x-task": "details", "x-section": "intake", "description": "A fraction: 0.125 means 12.5 % (bounds are fractions too). A JSON number is the canonical value; a string is parsed by the field's format (a comma is a thousands separator). null (or an empty string) clears the field." }, "ship_date": { "title": "Ship date", "type": [ "string", "null" ], "format": "date", "pattern": "^\\d{4}-\\d{2}-\\d{2}$", "x-processcart-type": "date", "x-processcart-format": "date", "x-task": "details", "x-section": "intake", "description": "Any common date spelling is accepted and normalized to YYYY-MM-DD. null (or an empty string) clears the field." }, "start_time": { "title": "Start time", "type": [ "string", "null" ], "pattern": "^\\d{2}:\\d{2}$", "x-processcart-type": "time", "x-processcart-format": "time", "x-task": "details", "x-section": "intake", "description": "\"2:30 PM\" is accepted and normalized to 24-hour HH:mm. null (or an empty string) clears the field." }, "gift_wrap": { "title": "Gift wrap", "type": [ "string", "null" ], "enum": [ "Yes", "No", null ], "x-processcart-type": "dropdown", "x-task": "details", "x-section": "intake", "description": "null (or an empty string) clears the field." }, "tags": { "title": "Tags", "type": [ "array", "null" ], "items": { "type": "string", "enum": [ "A", "B", "C" ] }, "uniqueItems": true, "x-processcart-type": "multi_choice", "x-task": "details", "x-section": "intake", "description": "null (or an empty string) clears the field." }, "agree": { "title": "Agree", "type": "boolean", "x-processcart-type": "checkbox", "x-task": "details", "x-section": "intake", "description": "false clears; a never-set checkbox reads false." }, "contact_email": { "title": "Contact email", "type": [ "string", "null" ], "format": "email", "x-processcart-type": "email", "x-processcart-format": "email", "x-task": "details", "x-section": "intake", "description": "Lowercased on write. null (or an empty string) clears the field." }, "website": { "title": "Website", "type": [ "string", "null" ], "format": "uri", "x-processcart-type": "url", "x-processcart-format": "url", "x-task": "details", "x-section": "intake", "description": "http(s) only; normalized by the URL parser (a missing scheme is added). null (or an empty string) clears the field." }, "phone": { "title": "Phone", "type": [ "string", "null" ], "pattern": "^\\+\\d{8,15}$", "x-processcart-type": "phone", "x-processcart-format": "phone", "x-task": "details", "x-section": "intake", "description": "E.164 (US numbers are normalized from any common spelling). null (or an empty string) clears the field." }, "owner": { "title": "Owner", "type": [ "array", "null" ], "items": { "type": "string", "format": "uuid" }, "uniqueItems": true, "x-processcart-type": "members", "x-task": "details", "x-section": "intake", "description": "User ids of current members (GET /v1/users). null (or an empty string) clears the field." }, "steps": { "title": "Steps", "type": [ "array", "null" ], "items": { "type": "string", "enum": [ "photos", "invoice" ] }, "uniqueItems": true, "x-processcart-type": "subtasks", "x-task": "details", "x-section": "intake", "description": "Items: photos = Photos; invoice = Invoice. null (or an empty string) clears the field." }, "source": { "title": "Source", "type": [ "string", "number", "boolean", "array", "null" ], "items": { "type": "string" }, "readOnly": false, "x-processcart-type": "hidden", "x-task": "details", "x-section": "intake", "description": "A hidden run variable: not shown in the app, not secret. Writable through the API (no formula computes it). null (or an empty string) clears the field." } }, "additionalProperties": false, "propertyNames": { "pattern": "^[a-z][a-z0-9_]*$", "maxLength": 80 }, "minProperties": 1, "maxProperties": 200 } } ``` ## GET /v1/workflows/{id}/template **Export a workflow as a portable template.** Scope `workflows:read` · checked against the key's workflow / folder allowlist (403 `not_allowed_for_key` outside it). Operation id `getWorkflowTemplate` · [open in the API reference](https://developers.processcart.com/reference#tag/workflows/GET/v1/workflows/{id}/template). The published version as a portable template JSON: sections, tasks, fields, rules, due rules and assignments by group / role name — no ids, no people, no workspace settings. Feed it to `POST /v1/workflows` here or in another workspace, or keep it in version control. `exportedAt` is stamped now; `source` is the workspace slug. **Path parameters** | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | string (uuid) | yes | | **Responses** | Status | Meaning | Body | | --- | --- | --- | | 200 | OK. | [WorkflowTemplate](https://developers.processcart.com/reference/schemas#workflowtemplate) | | 401 | No usable credential. `unauthorized` — Authentication required; `invalid_api_key` — Invalid API key; `key_revoked` — API key revoked. | [Problem](https://developers.processcart.com/errors) | | 403 | `insufficient_scope` — Insufficient scope; `not_allowed_for_key` — Not allowed for this key. | [Problem](https://developers.processcart.com/errors) | | 404 | `not_found` — Not found. | [Problem](https://developers.processcart.com/errors) | | 405 | The verb is not supported on this path. `method_not_allowed` — Method not allowed. | [Problem](https://developers.processcart.com/errors) | | 409 | `workflow_not_published` — Workflow not published. | [Problem](https://developers.processcart.com/errors) | | 422 | `validation_failed` — Validation failed. | [Problem](https://developers.processcart.com/errors) | | 429 | `rate_limited` — Too many requests. | [Problem](https://developers.processcart.com/errors) | | 500 | `internal_error` — Internal error. | [Problem](https://developers.processcart.com/errors) | | 503 | `unavailable` — Service unavailable. | [Problem](https://developers.processcart.com/errors) | **Errors** (every code this operation can answer; see [Errors](https://developers.processcart.com/errors)) - 401 [`invalid_api_key`](https://developers.processcart.com/errors#invalid_api_key) — Invalid API key - 401 [`key_revoked`](https://developers.processcart.com/errors#key_revoked) — API key revoked - 401 [`unauthorized`](https://developers.processcart.com/errors#unauthorized) — Authentication required - 403 [`insufficient_scope`](https://developers.processcart.com/errors#insufficient_scope) — Insufficient scope - 403 [`not_allowed_for_key`](https://developers.processcart.com/errors#not_allowed_for_key) — Not allowed for this key - 404 [`not_found`](https://developers.processcart.com/errors#not_found) — Not found - 405 [`method_not_allowed`](https://developers.processcart.com/errors#method_not_allowed) — Method not allowed - 409 [`workflow_not_published`](https://developers.processcart.com/errors#workflow_not_published) — Workflow not published - 422 [`validation_failed`](https://developers.processcart.com/errors#validation_failed) — Validation failed - 429 [`rate_limited`](https://developers.processcart.com/errors#rate_limited) — Too many requests - 500 [`internal_error`](https://developers.processcart.com/errors#internal_error) — Internal error - 503 [`unavailable`](https://developers.processcart.com/errors#unavailable) — Service unavailable **Example response** (200) ```json { "formatVersion": 1, "engineVersion": 1, "exportedAt": "2026-10-10T17:43:34.949Z", "source": "example-workspace", "workflow": { "name": "Order fulfillment", "keyDateFieldKey": "ship_date", "sections": [ { "key": "intake", "title": "Intake", "defaultVisibility": "visible", "tasks": [ { "key": "details", "title": "Details", "instructions": null, "defaultVisibility": "visible", "isStop": false, "kind": "standard", "fields": [ { "key": "customer", "type": "text", "label": "Customer", "help": null, "required": true, "defaultVisibility": "visible", "options": null, "formatConfig": { "type": "none" }, "validation": null, "defaultValue": null, "onFocusPopulate": null, "formula": null, "content": null, "inKickoff": true, "kickoffPosition": 0 }, { "key": "order_code", "type": "text", "label": "Order code", "help": null, "required": false, "defaultVisibility": "visible", "options": null, "formatConfig": { "type": "custom", "regex": "^[A-Z]{2}-\\d{4}$", "errorMessage": "Use AB-1234." }, "validation": null, "defaultValue": null, "onFocusPopulate": null, "formula": null, "content": null, "inKickoff": false, "kickoffPosition": null }, { "key": "memo", "type": "textarea", "label": "Memo", "help": null, "required": false, "defaultVisibility": "visible", "options": null, "formatConfig": null, "validation": null, "defaultValue": null, "onFocusPopulate": null, "formula": null, "content": null, "inKickoff": false, "kickoffPosition": null }, { "key": "quantity", "type": "number", "label": "Quantity", "help": null, "required": false, "defaultVisibility": "visible", "options": null, "formatConfig": { "type": "number", "decimals": 0, "thousands": true }, "validation": null, "defaultValue": null, "onFocusPopulate": null, "formula": null, "content": null, "inKickoff": false, "kickoffPosition": null }, { "key": "amount", "type": "number", "label": "Amount", "help": null, "required": false, "defaultVisibility": "visible", "options": null, "formatConfig": { "type": "currency" }, "validation": null, "defaultValue": null, "onFocusPopulate": null, "formula": null, "content": null, "inKickoff": true, "kickoffPosition": 1 }, { "key": "fee", "type": "number", "label": "Fee", "help": null, "required": false, "defaultVisibility": "visible", "options": null, "formatConfig": { "max": 500, "type": "currency" }, "validation": null, "defaultValue": null, "onFocusPopulate": null, "formula": null, "content": null, "inKickoff": false, "kickoffPosition": null }, { "key": "rate", "type": "number", "label": "Rate", "help": null, "required": false, "defaultVisibility": "visible", "options": null, "formatConfig": { "type": "percent" }, "validation": null, "defaultValue": null, "onFocusPopulate": null, "formula": null, "content": null, "inKickoff": false, "kickoffPosition": null }, { "key": "rate_text", "type": "text", "label": "Rate (text)", "help": null, "required": false, "defaultVisibility": "visible", "options": null, "formatConfig": { "type": "percent" }, "validation": null, "defaultValue": null, "onFocusPopulate": null, "formula": null, "content": null, "inKickoff": false, "kickoffPosition": null }, { "key": "ship_date", "type": "date", "label": "Ship date", "help": null, "required": false, "defaultVisibility": "visible", "options": null, "formatConfig": { "type": "date" }, "validation": null, "defaultValue": null, "onFocusPopulate": null, "formula": null, "content": null, "inKickoff": false, "kickoffPosition": null }, { "key": "start_time", "type": "time", "label": "Start time", "help": null, "required": false, "defaultVisibility": "visible", "options": null, "formatConfig": { "type": "time", "hour12": true }, "validation": null, "defaultValue": null, "onFocusPopulate": null, "formula": null, "content": null, "inKickoff": false, "kickoffPosition": null }, { "key": "gift_wrap", "type": "dropdown", "label": "Gift wrap", "help": null, "required": false, "defaultVisibility": "visible", "options": { "choices": [ "Yes", "No" ] }, "formatConfig": null, "validation": null, "defaultValue": null, "onFocusPopulate": null, "formula": null, "content": null, "inKickoff": false, "kickoffPosition": null }, { "key": "tags", "type": "multi_choice", "label": "Tags", "help": null, "required": false, "defaultVisibility": "visible", "options": { "choices": [ "A", "B", "C" ] }, "formatConfig": null, "validation": null, "defaultValue": null, "onFocusPopulate": null, "formula": null, "content": null, "inKickoff": false, "kickoffPosition": null }, { "key": "agree", "type": "checkbox", "label": "Agree", "help": null, "required": false, "defaultVisibility": "visible", "options": null, "formatConfig": null, "validation": null, "defaultValue": null, "onFocusPopulate": null, "formula": null, "content": null, "inKickoff": false, "kickoffPosition": null }, { "key": "contact_email", "type": "email", "label": "Contact email", "help": null, "required": false, "defaultVisibility": "visible", "options": null, "formatConfig": { "type": "email" }, "validation": null, "defaultValue": null, "onFocusPopulate": null, "formula": null, "content": null, "inKickoff": false, "kickoffPosition": null }, { "key": "website", "type": "url", "label": "Website", "help": null, "required": false, "defaultVisibility": "visible", "options": null, "formatConfig": { "type": "url" }, "validation": null, "defaultValue": null, "onFocusPopulate": null, "formula": null, "content": null, "inKickoff": false, "kickoffPosition": null }, { "key": "phone", "type": "phone", "label": "Phone", "help": null, "required": false, "defaultVisibility": "visible", "options": null, "formatConfig": { "type": "phone" }, "validation": null, "defaultValue": null, "onFocusPopulate": null, "formula": null, "content": null, "inKickoff": false, "kickoffPosition": null }, { "key": "owner", "type": "members", "label": "Owner", "help": null, "required": false, "defaultVisibility": "visible", "options": null, "formatConfig": null, "validation": null, "defaultValue": null, "onFocusPopulate": null, "formula": null, "content": null, "inKickoff": false, "kickoffPosition": null }, { "key": "steps", "type": "subtasks", "label": "Steps", "help": null, "required": false, "defaultVisibility": "visible", "options": { "items": [ { "key": "photos", "label": "Photos" }, { "key": "invoice", "label": "Invoice" } ], "allRequired": false }, "formatConfig": null, "validation": null, "defaultValue": null, "onFocusPopulate": null, "formula": null, "content": null, "inKickoff": false, "kickoffPosition": null }, { "key": "contract", "type": "file", "label": "Contract", "help": null, "required": false, "defaultVisibility": "visible", "options": { "multiple": true }, "formatConfig": null, "validation": null, "defaultValue": null, "onFocusPopulate": null, "formula": null, "content": null, "inKickoff": false, "kickoffPosition": null }, { "key": "double_amount", "type": "formula", "label": "Double amount", "help": null, "required": false, "defaultVisibility": "visible", "options": null, "formatConfig": { "type": "currency" }, "validation": null, "defaultValue": null, "onFocusPopulate": null, "formula": { "expr": "{{amount}} * 2", "mode": "spreadsheet" }, "content": null, "inKickoff": false, "kickoffPosition": null }, { "key": "source", "type": "hidden", "label": "Source", "help": null, "required": false, "defaultVisibility": "hidden", "options": null, "formatConfig": null, "validation": null, "defaultValue": null, "onFocusPopulate": null, "formula": null, "content": null, "inKickoff": false, "kickoffPosition": null }, { "key": "note", "type": "display_text", "label": "Note", "help": null, "required": false, "defaultVisibility": "visible", "options": null, "formatConfig": null, "validation": null, "defaultValue": null, "onFocusPopulate": null, "formula": null, "content": { "type": "doc", "content": [ { "type": "paragraph", "content": [ { "text": "Review the details.", "type": "text" } ] } ] }, "inKickoff": false, "kickoffPosition": null } ] }, { "key": "review", "title": "Review", "instructions": null, "defaultVisibility": "visible", "isStop": false, "kind": "approval", "approvalConfig": { "summaryFieldKeys": [ "amount" ], "rejectReopensTaskKeys": [ "details" ] }, "fields": [] }, { "key": "stop", "title": "Sign-off", "instructions": null, "defaultVisibility": "visible", "isStop": true, "kind": "standard", "fields": [] }, { "key": "wrapup", "title": "Wrap up", "instructions": null, "defaultVisibility": "visible", "isStop": false, "kind": "standard", "fields": [] } ] } ], "rules": [] } } ``` --- # Runs endpoints > Runs of a workflow — the central resource: every field value (canonical + formatted) and every task's state in one document. Every operation with parameters, body, responses, errors and examples. Source: https://developers.processcart.com/reference/runs Runs of a workflow — the central resource: every field value (canonical + formatted) and every task's state in one document. Base URL `https://app.processcart.com/api/v1`. Every operation below is also in the [OpenAPI document](https://developers.processcart.com/openapi.json) and the [interactive reference](https://developers.processcart.com/reference). | Operation | Method and path | Scope | | --- | --- | --- | | [List runs](#listRuns) | GET /v1/runs | Scope `runs:read` | | [Start a run](#startRun) | POST /v1/runs | Scope `runs:write` | | [Get a run](#getRun) | GET /v1/runs/{id} | Scope `runs:read` | | [Update a run's name, priority, labels, due date or status](#updateRun) | PATCH /v1/runs/{id} | Scope `runs:write` | | [Move a run to Trash](#trashRun) | DELETE /v1/runs/{id} | Scope `runs:write` | | [Restore a run from Trash](#restoreRun) | POST /v1/runs/{id}/restore | Scope `runs:write` | ## GET /v1/runs **List runs.** Scope `runs:read` · the key's allowlist narrows the rows (never a 403 — rows outside it are absent) · paginated (`{ data, nextCursor, hasMore }`). Operation id `listRuns` · [open in the API reference](https://developers.processcart.com/reference#tag/runs/GET/v1/runs). Lists the runs the key may see as FULL Run resources (fields and tasks included), newest first. Filter by workflow, status, priority, labels, assignee (`u:` / `g:`), due window or run name; `updatedSince` turns the list into an incremental sync ordered by `updatedAt` ascending (inclusive — overlap by a minute and dedupe by `(id, revision)`). Trashed runs are hidden unless `includeTrashed=true`. `limit` is capped at 100. **Query parameters** (an unknown parameter is a 422 `validation_failed` with `errors[].code = "unknown_parameter"`) | Name | Type | Required | Description | | --- | --- | --- | --- | | `workflowId` | string (uuid) | no | | | `status` | `"active"` \\| `"completed"` \\| `"on_hold"` \\| `"archived"`[] | no | Comma-separated (or repeat the parameter). | | `priority` | `"none"` \\| `"low"` \\| `"medium"` \\| `"high"` \\| `"urgent"`[] | no | Comma-separated (or repeat the parameter). | | `labels` | string[] | no | Comma-separated (or repeat the parameter). | | `assignee` | string | no | | | `due` | `"overdue"` \\| `"today"` \\| `"week"` \\| `"none"` | no | | | `q` | string | no | | | `qMode` | `"prefix"` \\| `"contains"` | no | | | `updatedSince` | string (date-time) | no | Incremental sync: only items whose `updatedAt` is at or after this instant (inclusive), ordered by `updatedAt` ascending; an explicit conflicting `sort` / `order` is a 422. Overlap by a minute and dedupe: a Run by `(id, revision)`, any other item (workflow, view, webhook, schedule, automation rule) by `(id, updatedAt)` — a higher `revision` or a later `updatedAt` replaces the copy you hold, an equal one is unchanged. | | `includeTrashed` | boolean | no | | | `sort` | `"startedAt"` \\| `"updatedAt"` \\| `"name"` \\| `"dueAt"` \\| `"keyDate"` \\| `"completedAt"` \\| `"priority"` | no | | | `order` | `"asc"` \\| `"desc"` | no | | | `limit` | integer | no | | | `cursor` | string | no | The `nextCursor` of the previous page. Opaque, and bound to the query that produced it — a cursor from a different filter or sort is a 422. | **Responses** | Status | Meaning | Body | | --- | --- | --- | | 200 | OK. | [RunList](https://developers.processcart.com/reference/schemas#runlist) | | 401 | No usable credential. `unauthorized` — Authentication required; `invalid_api_key` — Invalid API key; `key_revoked` — API key revoked. | [Problem](https://developers.processcart.com/errors) | | 403 | `insufficient_scope` — Insufficient scope. | [Problem](https://developers.processcart.com/errors) | | 405 | The verb is not supported on this path. `method_not_allowed` — Method not allowed. | [Problem](https://developers.processcart.com/errors) | | 422 | `validation_failed` — Validation failed. | [Problem](https://developers.processcart.com/errors) | | 429 | `rate_limited` — Too many requests. | [Problem](https://developers.processcart.com/errors) | | 500 | `internal_error` — Internal error. | [Problem](https://developers.processcart.com/errors) | | 503 | `unavailable` — Service unavailable. | [Problem](https://developers.processcart.com/errors) | **Errors** (every code this operation can answer; see [Errors](https://developers.processcart.com/errors)) - 401 [`invalid_api_key`](https://developers.processcart.com/errors#invalid_api_key) — Invalid API key - 401 [`key_revoked`](https://developers.processcart.com/errors#key_revoked) — API key revoked - 401 [`unauthorized`](https://developers.processcart.com/errors#unauthorized) — Authentication required - 403 [`insufficient_scope`](https://developers.processcart.com/errors#insufficient_scope) — Insufficient scope - 405 [`method_not_allowed`](https://developers.processcart.com/errors#method_not_allowed) — Method not allowed - 422 [`validation_failed`](https://developers.processcart.com/errors#validation_failed) — Validation failed - 429 [`rate_limited`](https://developers.processcart.com/errors#rate_limited) — Too many requests - 500 [`internal_error`](https://developers.processcart.com/errors#internal_error) — Internal error - 503 [`unavailable`](https://developers.processcart.com/errors#unavailable) — Service unavailable **Example response** (200) ```json { "data": [ { "id": "8b84a82b-e9d8-4657-aa7c-50452cf48c8d", "name": "Order 1001", "status": "active", "priority": "none", "labels": [], "labelDetails": [], "dueAt": null, "keyDate": null, "revision": 1, "updatedAt": "2026-10-13T01:10:36.904Z", "startedAt": "2026-10-13T01:10:36.815Z", "startedBy": "api", "startedByUserId": null, "schedule": null, "scheduledFor": null, "completedAt": null, "deletedAt": null, "workflow": { "id": "e1d9bfda-9e93-4cce-8518-b4a185a6d4cb", "name": "Order fulfillment" }, "versionId": "6b9a8079-3136-4818-8405-26c69bbac982", "url": "https://app.processcart.com/runs/8b84a82b-e9d8-4657-aa7c-50452cf48c8d", "tasks": [ { "key": "details", "title": "Details", "kind": "standard", "section": "Intake", "state": "todo", "visible": true, "locked": false, "dueAt": null, "assignees": [], "completedAt": null, "completedByUserId": null }, { "key": "review", "title": "Review", "kind": "approval", "section": "Intake", "state": "todo", "visible": true, "locked": false, "dueAt": null, "assignees": [ { "kind": "user", "userId": "e9e90546-8500-4502-a8a3-0f705c013cfd" } ], "completedAt": null, "completedByUserId": null }, { "key": "stop", "title": "Sign-off", "kind": "standard", "section": "Intake", "state": "todo", "visible": true, "locked": false, "dueAt": null, "assignees": [], "completedAt": null, "completedByUserId": null }, { "key": "wrapup", "title": "Wrap up", "kind": "standard", "section": "Intake", "state": "todo", "visible": true, "locked": true, "dueAt": null, "assignees": [], "completedAt": null, "completedByUserId": null } ], "fields": [ { "key": "customer", "label": "Customer", "type": "text", "format": "none", "taskKey": "details", "value": "Grace Hopper", "formatted": "Grace Hopper", "visible": true }, { "key": "order_code", "label": "Order code", "type": "text", "format": "custom", "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "memo", "label": "Memo", "type": "textarea", "format": null, "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "quantity", "label": "Quantity", "type": "number", "format": "number", "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "amount", "label": "Amount", "type": "number", "format": "currency", "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "fee", "label": "Fee", "type": "number", "format": "currency", "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "rate", "label": "Rate", "type": "number", "format": "percent", "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "rate_text", "label": "Rate (text)", "type": "text", "format": "percent", "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "ship_date", "label": "Ship date", "type": "date", "format": "date", "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "start_time", "label": "Start time", "type": "time", "format": "time", "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "gift_wrap", "label": "Gift wrap", "type": "dropdown", "format": null, "taskKey": "details", "value": null, "formatted": "", "visible": true, "choices": [ "Yes", "No" ] }, { "key": "tags", "label": "Tags", "type": "multi_choice", "format": null, "taskKey": "details", "value": null, "formatted": "", "visible": true, "choices": [ "A", "B", "C" ] }, { "key": "agree", "label": "Agree", "type": "checkbox", "format": null, "taskKey": "details", "value": false, "formatted": "No", "visible": true }, { "key": "contact_email", "label": "Contact email", "type": "email", "format": "email", "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "website", "label": "Website", "type": "url", "format": "url", "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "phone", "label": "Phone", "type": "phone", "format": "phone", "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "owner", "label": "Owner", "type": "members", "format": null, "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "steps", "label": "Steps", "type": "subtasks", "format": null, "taskKey": "details", "value": null, "formatted": "", "visible": true, "items": [ { "key": "photos", "label": "Photos" }, { "key": "invoice", "label": "Invoice" } ] }, { "key": "contract", "label": "Contract", "type": "file", "format": null, "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "double_amount", "label": "Double amount", "type": "formula", "format": "currency", "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "source", "label": "Source", "type": "hidden", "format": null, "taskKey": "details", "value": null, "formatted": "", "visible": false } ] }, { "id": "845d4745-4cc1-46fe-9d6c-3cfead74544f", "name": "Order 1002", "status": "active", "priority": "none", "labels": [], "labelDetails": [], "dueAt": null, "keyDate": null, "revision": 7, "updatedAt": "2026-10-13T01:10:35.496Z", "startedAt": "2026-10-13T01:10:02.387Z", "startedBy": "api", "startedByUserId": null, "schedule": null, "scheduledFor": null, "completedAt": null, "deletedAt": null, "workflow": { "id": "e1d9bfda-9e93-4cce-8518-b4a185a6d4cb", "name": "Order fulfillment" }, "versionId": "6b9a8079-3136-4818-8405-26c69bbac982", "url": "https://app.processcart.com/runs/845d4745-4cc1-46fe-9d6c-3cfead74544f", "tasks": [ { "key": "details", "title": "Details", "kind": "standard", "section": "Intake", "state": "todo", "visible": true, "locked": false, "dueAt": null, "assignees": [], "completedAt": null, "completedByUserId": null }, { "key": "review", "title": "Review", "kind": "approval", "section": "Intake", "state": "todo", "visible": true, "locked": false, "dueAt": null, "assignees": [ { "kind": "user", "userId": "e9e90546-8500-4502-a8a3-0f705c013cfd" } ], "completedAt": null, "completedByUserId": null }, { "key": "stop", "title": "Sign-off", "kind": "standard", "section": "Intake", "state": "todo", "visible": true, "locked": false, "dueAt": null, "assignees": [], "completedAt": null, "completedByUserId": null }, { "key": "wrapup", "title": "Wrap up", "kind": "standard", "section": "Intake", "state": "todo", "visible": true, "locked": true, "dueAt": null, "assignees": [], "completedAt": null, "completedByUserId": null } ], "fields": [ { "key": "customer", "label": "Customer", "type": "text", "format": "none", "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "order_code", "label": "Order code", "type": "text", "format": "custom", "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "memo", "label": "Memo", "type": "textarea", "format": null, "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "quantity", "label": "Quantity", "type": "number", "format": "number", "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "amount", "label": "Amount", "type": "number", "format": "currency", "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "fee", "label": "Fee", "type": "number", "format": "currency", "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "rate", "label": "Rate", "type": "number", "format": "percent", "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "rate_text", "label": "Rate (text)", "type": "text", "format": "percent", "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "ship_date", "label": "Ship date", "type": "date", "format": "date", "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "start_time", "label": "Start time", "type": "time", "format": "time", "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "gift_wrap", "label": "Gift wrap", "type": "dropdown", "format": null, "taskKey": "details", "value": null, "formatted": "", "visible": true, "choices": [ "Yes", "No" ] }, { "key": "tags", "label": "Tags", "type": "multi_choice", "format": null, "taskKey": "details", "value": null, "formatted": "", "visible": true, "choices": [ "A", "B", "C" ] }, { "key": "agree", "label": "Agree", "type": "checkbox", "format": null, "taskKey": "details", "value": false, "formatted": "No", "visible": true }, { "key": "contact_email", "label": "Contact email", "type": "email", "format": "email", "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "website", "label": "Website", "type": "url", "format": "url", "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "phone", "label": "Phone", "type": "phone", "format": "phone", "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "owner", "label": "Owner", "type": "members", "format": null, "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "steps", "label": "Steps", "type": "subtasks", "format": null, "taskKey": "details", "value": null, "formatted": "", "visible": true, "items": [ { "key": "photos", "label": "Photos" }, { "key": "invoice", "label": "Invoice" } ] }, { "key": "contract", "label": "Contract", "type": "file", "format": null, "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "double_amount", "label": "Double amount", "type": "formula", "format": "currency", "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "source", "label": "Source", "type": "hidden", "format": null, "taskKey": "details", "value": null, "formatted": "", "visible": false } ] } ], "nextCursor": "eyJ2IjoiMjAyNi0xMC0xM1QwMToxMDowMi4zODcwMDBaIiwiaWQiOiI4NDVkNDc0NS00Y2MxLTQ2ZmUtOWQ2Yy0zY2ZlYWQ3NDU0NGYiLCJoIjoiYjU0MzliODk0MmUxZDhlNiJ9", "hasMore": true } ``` ## POST /v1/runs **Start a run.** Scope `runs:write` · checked against the key's workflow / folder allowlist (403 `not_allowed_for_key` outside it) · accepts `Idempotency-Key`. Operation id `startRun` · [open in the API reference](https://developers.processcart.com/reference#tag/runs/POST/v1/runs). Starts a run of a workflow's published version with a name, optional field values (any writable field; kickoff fields marked required must be present), priority, labels and due date — in one transaction. A visible kickoff field (required or not) whose value breaks its bounds or rules is refused with 422 `validation_failed` (`errors[].code` `invalid_value`), the same gate as the app's Start dialog; other fields are accepted and flagged in `validationError`, as on `PATCH /runs/{id}/fields`. Returns the new Run (`revision: 1`) and an absolute `Location` header. **Request headers** | Name | Type | Required | Description | | --- | --- | --- | --- | | `Idempotency-Key` | string | no | Makes the request safe to retry: a second request with the same key and a byte-identical body (and the same `Expected-Revision`) replays the stored response (`Idempotent-Replayed: true`); a different body with the same key is refused (422 `idempotency_key_reused`) and no second resource is created. Keys are stored per workspace for 24 hours and replayed only to the API key that made the request (or the key one rotation away from it); another API key sending the same value is refused (422 `idempotency_key_reused`), so prefix keys per integration. 409, 429 and 5xx answers are never stored. | **Request body** — `application/json`, [RunStart](https://developers.processcart.com/reference/schemas#runstart) | Property | Type | Required | Description | | --- | --- | --- | --- | | `workflowId` | [Uuid](https://developers.processcart.com/reference/schemas#uuid) | yes | | | `name` | string | yes | | | `fields` | [FieldsWrite](https://developers.processcart.com/reference/schemas#fieldswrite) | no | | | `priority` | [RunPriority](https://developers.processcart.com/reference/schemas#runpriority) | no | | | `labels` | string[] | no | | | `dueAt` | [Instant](https://developers.processcart.com/reference/schemas#instant) \\| [CalendarDate](https://developers.processcart.com/reference/schemas#calendardate) \\| null | no | | **Responses** | Status | Meaning | Body | | --- | --- | --- | | 201 | Created. `Location` names the new resource. | [Run](https://developers.processcart.com/reference/schemas#run) | | 401 | No usable credential. `unauthorized` — Authentication required; `invalid_api_key` — Invalid API key; `key_revoked` — API key revoked. | [Problem](https://developers.processcart.com/errors) | | 403 | `insufficient_scope` — Insufficient scope; `not_allowed_for_key` — Not allowed for this key; `read_only_key` — Read-only key. | [Problem](https://developers.processcart.com/errors) | | 404 | `not_found` — Not found. | [Problem](https://developers.processcart.com/errors) | | 405 | The verb is not supported on this path. `method_not_allowed` — Method not allowed. | [Problem](https://developers.processcart.com/errors) | | 409 | `idempotency_in_progress` — Request in progress; `workflow_not_published` — Workflow not published. | [Problem](https://developers.processcart.com/errors) | | 413 | `payload_too_large` — Payload too large. | [Problem](https://developers.processcart.com/errors) | | 415 | `unsupported_media_type` — Unsupported media type. | [Problem](https://developers.processcart.com/errors) | | 422 | `field_not_writable` — Field is not writable; `idempotency_key_reused` — Idempotency key reused; `required_fields_unsatisfied` — Required fields unsatisfied; `validation_failed` — Validation failed. | [Problem](https://developers.processcart.com/errors) | | 429 | `rate_limited` — Too many requests. | [Problem](https://developers.processcart.com/errors) | | 500 | `internal_error` — Internal error. | [Problem](https://developers.processcart.com/errors) | | 503 | `unavailable` — Service unavailable. | [Problem](https://developers.processcart.com/errors) | **Errors** (every code this operation can answer; see [Errors](https://developers.processcart.com/errors)) - 401 [`invalid_api_key`](https://developers.processcart.com/errors#invalid_api_key) — Invalid API key - 401 [`key_revoked`](https://developers.processcart.com/errors#key_revoked) — API key revoked - 401 [`unauthorized`](https://developers.processcart.com/errors#unauthorized) — Authentication required - 403 [`insufficient_scope`](https://developers.processcart.com/errors#insufficient_scope) — Insufficient scope - 403 [`not_allowed_for_key`](https://developers.processcart.com/errors#not_allowed_for_key) — Not allowed for this key - 403 [`read_only_key`](https://developers.processcart.com/errors#read_only_key) — Read-only key - 404 [`not_found`](https://developers.processcart.com/errors#not_found) — Not found - 405 [`method_not_allowed`](https://developers.processcart.com/errors#method_not_allowed) — Method not allowed - 409 [`idempotency_in_progress`](https://developers.processcart.com/errors#idempotency_in_progress) — Request in progress - 409 [`workflow_not_published`](https://developers.processcart.com/errors#workflow_not_published) — Workflow not published - 413 [`payload_too_large`](https://developers.processcart.com/errors#payload_too_large) — Payload too large - 415 [`unsupported_media_type`](https://developers.processcart.com/errors#unsupported_media_type) — Unsupported media type - 422 [`field_not_writable`](https://developers.processcart.com/errors#field_not_writable) — Field is not writable - 422 [`idempotency_key_reused`](https://developers.processcart.com/errors#idempotency_key_reused) — Idempotency key reused - 422 [`required_fields_unsatisfied`](https://developers.processcart.com/errors#required_fields_unsatisfied) — Required fields unsatisfied - 422 [`validation_failed`](https://developers.processcart.com/errors#validation_failed) — Validation failed - 429 [`rate_limited`](https://developers.processcart.com/errors#rate_limited) — Too many requests - 500 [`internal_error`](https://developers.processcart.com/errors#internal_error) — Internal error - 503 [`unavailable`](https://developers.processcart.com/errors#unavailable) — Service unavailable **Example request** ```json { "workflowId": "e1d9bfda-9e93-4cce-8518-b4a185a6d4cb", "name": "ORD 2026-10-15 Acme Corp", "priority": "urgent", "labels": [ "rush" ], "dueAt": "2026-10-20", "fields": { "customer": "Ada Lovelace", "amount": "$1,700.00", "rate": "5%", "rate_text": 0.05, "quantity": 1250, "fee": 900, "ship_date": "2026-10-15", "start_time": "14:30", "gift_wrap": "Yes", "tags": [ "A", "C" ], "agree": true, "contact_email": "Ada@Example.com", "website": "example.com", "phone": "(415) 555-0100", "owner": [ "e9e90546-8500-4502-a8a3-0f705c013cfd" ], "steps": [ "photos" ], "source": "zapier", "order_code": "AB-1234", "memo": "Two lines\nhere" } } ``` **Example response** (201) ```json { "id": "33ec8c8c-e219-422c-8dda-5c8633d2e368", "name": "ORD 2026-10-15 Acme Corp", "status": "active", "priority": "urgent", "labels": [ "rush" ], "labelDetails": [ { "id": "rush", "name": "Rush", "color": "#dc2626" } ], "dueAt": "2026-10-21T06:59:59.000Z", "keyDate": "2026-10-15", "revision": 1, "updatedAt": "2026-10-13T01:09:29.022Z", "startedAt": "2026-10-13T01:09:28.930Z", "startedBy": "api", "startedByUserId": null, "schedule": null, "scheduledFor": null, "completedAt": null, "deletedAt": null, "workflow": { "id": "e1d9bfda-9e93-4cce-8518-b4a185a6d4cb", "name": "Order fulfillment" }, "versionId": "6b9a8079-3136-4818-8405-26c69bbac982", "url": "https://app.processcart.com/runs/33ec8c8c-e219-422c-8dda-5c8633d2e368", "tasks": [ { "key": "details", "title": "Details", "kind": "standard", "section": "Intake", "state": "todo", "visible": true, "locked": false, "dueAt": null, "assignees": [], "completedAt": null, "completedByUserId": null }, { "key": "review", "title": "Review", "kind": "approval", "section": "Intake", "state": "todo", "visible": true, "locked": false, "dueAt": null, "assignees": [ { "kind": "user", "userId": "e9e90546-8500-4502-a8a3-0f705c013cfd" } ], "completedAt": null, "completedByUserId": null }, { "key": "stop", "title": "Sign-off", "kind": "standard", "section": "Intake", "state": "todo", "visible": true, "locked": false, "dueAt": null, "assignees": [], "completedAt": null, "completedByUserId": null }, { "key": "wrapup", "title": "Wrap up", "kind": "standard", "section": "Intake", "state": "todo", "visible": true, "locked": true, "dueAt": null, "assignees": [], "completedAt": null, "completedByUserId": null } ], "fields": [ { "key": "customer", "label": "Customer", "type": "text", "format": "none", "taskKey": "details", "value": "Ada Lovelace", "formatted": "Ada Lovelace", "visible": true }, { "key": "order_code", "label": "Order code", "type": "text", "format": "custom", "taskKey": "details", "value": "AB-1234", "formatted": "AB-1234", "visible": true }, { "key": "memo", "label": "Memo", "type": "textarea", "format": null, "taskKey": "details", "value": "Two lines\nhere", "formatted": "Two lines\nhere", "visible": true }, { "key": "quantity", "label": "Quantity", "type": "number", "format": "number", "taskKey": "details", "value": 1250, "formatted": "1,250", "visible": true }, { "key": "amount", "label": "Amount", "type": "number", "format": "currency", "taskKey": "details", "value": 1700, "formatted": "$1,700.00", "visible": true }, { "key": "fee", "label": "Fee", "type": "number", "format": "currency", "taskKey": "details", "value": 900, "formatted": "$900.00", "visible": true, "validationError": "Must not exceed $500.00." }, { "key": "rate", "label": "Rate", "type": "number", "format": "percent", "taskKey": "details", "value": 0.05, "formatted": "5%", "visible": true }, { "key": "rate_text", "label": "Rate (text)", "type": "text", "format": "percent", "taskKey": "details", "value": 0.05, "formatted": "5%", "visible": true }, { "key": "ship_date", "label": "Ship date", "type": "date", "format": "date", "taskKey": "details", "value": "2026-10-15", "formatted": "Oct 15, 2026", "visible": true }, { "key": "start_time", "label": "Start time", "type": "time", "format": "time", "taskKey": "details", "value": "14:30", "formatted": "2:30 PM", "visible": true }, { "key": "gift_wrap", "label": "Gift wrap", "type": "dropdown", "format": null, "taskKey": "details", "value": "Yes", "formatted": "Yes", "visible": true, "choices": [ "Yes", "No" ] }, { "key": "tags", "label": "Tags", "type": "multi_choice", "format": null, "taskKey": "details", "value": [ "A", "C" ], "formatted": "A, C", "visible": true, "choices": [ "A", "B", "C" ] }, { "key": "agree", "label": "Agree", "type": "checkbox", "format": null, "taskKey": "details", "value": true, "formatted": "Yes", "visible": true }, { "key": "contact_email", "label": "Contact email", "type": "email", "format": "email", "taskKey": "details", "value": "ada@example.com", "formatted": "ada@example.com", "visible": true }, { "key": "website", "label": "Website", "type": "url", "format": "url", "taskKey": "details", "value": "https://example.com/", "formatted": "https://example.com/", "visible": true }, { "key": "phone", "label": "Phone", "type": "phone", "format": "phone", "taskKey": "details", "value": "+14155550100", "formatted": "(415) 555-0100", "visible": true }, { "key": "owner", "label": "Owner", "type": "members", "format": null, "taskKey": "details", "value": [ "e9e90546-8500-4502-a8a3-0f705c013cfd" ], "formatted": "Fixture Admin", "visible": true }, { "key": "steps", "label": "Steps", "type": "subtasks", "format": null, "taskKey": "details", "value": [ "photos" ], "formatted": "1 of 2: Photos", "visible": true, "items": [ { "key": "photos", "label": "Photos" }, { "key": "invoice", "label": "Invoice" } ] }, { "key": "contract", "label": "Contract", "type": "file", "format": null, "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "double_amount", "label": "Double amount", "type": "formula", "format": "currency", "taskKey": "details", "value": 3400, "formatted": "$3,400.00", "visible": true }, { "key": "source", "label": "Source", "type": "hidden", "format": null, "taskKey": "details", "value": "zapier", "formatted": "zapier", "visible": false } ] } ``` ## GET /v1/runs/{id} **Get a run.** Scope `runs:read` · checked against the key's workflow / folder allowlist (403 `not_allowed_for_key` outside it). Operation id `getRun` · [open in the API reference](https://developers.processcart.com/reference#tag/runs/GET/v1/runs/{id}). Returns one run with every field value (canonical + formatted) and task state, plus its `revision` — send it back as `Expected-Revision` on a write that must not overwrite a newer change. A run in Trash loads with `deletedAt` set. **Path parameters** | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | string (uuid) | yes | | **Responses** | Status | Meaning | Body | | --- | --- | --- | | 200 | OK. | [Run](https://developers.processcart.com/reference/schemas#run) | | 401 | No usable credential. `unauthorized` — Authentication required; `invalid_api_key` — Invalid API key; `key_revoked` — API key revoked. | [Problem](https://developers.processcart.com/errors) | | 403 | `insufficient_scope` — Insufficient scope; `not_allowed_for_key` — Not allowed for this key. | [Problem](https://developers.processcart.com/errors) | | 404 | `not_found` — Not found. | [Problem](https://developers.processcart.com/errors) | | 405 | The verb is not supported on this path. `method_not_allowed` — Method not allowed. | [Problem](https://developers.processcart.com/errors) | | 422 | `validation_failed` — Validation failed. | [Problem](https://developers.processcart.com/errors) | | 429 | `rate_limited` — Too many requests. | [Problem](https://developers.processcart.com/errors) | | 500 | `internal_error` — Internal error. | [Problem](https://developers.processcart.com/errors) | | 503 | `unavailable` — Service unavailable. | [Problem](https://developers.processcart.com/errors) | **Errors** (every code this operation can answer; see [Errors](https://developers.processcart.com/errors)) - 401 [`invalid_api_key`](https://developers.processcart.com/errors#invalid_api_key) — Invalid API key - 401 [`key_revoked`](https://developers.processcart.com/errors#key_revoked) — API key revoked - 401 [`unauthorized`](https://developers.processcart.com/errors#unauthorized) — Authentication required - 403 [`insufficient_scope`](https://developers.processcart.com/errors#insufficient_scope) — Insufficient scope - 403 [`not_allowed_for_key`](https://developers.processcart.com/errors#not_allowed_for_key) — Not allowed for this key - 404 [`not_found`](https://developers.processcart.com/errors#not_found) — Not found - 405 [`method_not_allowed`](https://developers.processcart.com/errors#method_not_allowed) — Method not allowed - 422 [`validation_failed`](https://developers.processcart.com/errors#validation_failed) — Validation failed - 429 [`rate_limited`](https://developers.processcart.com/errors#rate_limited) — Too many requests - 500 [`internal_error`](https://developers.processcart.com/errors#internal_error) — Internal error - 503 [`unavailable`](https://developers.processcart.com/errors#unavailable) — Service unavailable **Example response** (200) ```json { "id": "33ec8c8c-e219-422c-8dda-5c8633d2e368", "name": "ORD 2026-10-15 Acme Corp", "status": "active", "priority": "urgent", "labels": [ "rush" ], "labelDetails": [ { "id": "rush", "name": "Rush", "color": "#dc2626" } ], "dueAt": "2026-10-21T06:59:59.000Z", "keyDate": "2026-10-15", "revision": 1, "updatedAt": "2026-10-13T01:09:29.022Z", "startedAt": "2026-10-13T01:09:28.930Z", "startedBy": "api", "startedByUserId": null, "schedule": null, "scheduledFor": null, "completedAt": null, "deletedAt": null, "workflow": { "id": "e1d9bfda-9e93-4cce-8518-b4a185a6d4cb", "name": "Order fulfillment" }, "versionId": "6b9a8079-3136-4818-8405-26c69bbac982", "url": "https://app.processcart.com/runs/33ec8c8c-e219-422c-8dda-5c8633d2e368", "tasks": [ { "key": "details", "title": "Details", "kind": "standard", "section": "Intake", "state": "todo", "visible": true, "locked": false, "dueAt": null, "assignees": [], "completedAt": null, "completedByUserId": null }, { "key": "review", "title": "Review", "kind": "approval", "section": "Intake", "state": "todo", "visible": true, "locked": false, "dueAt": null, "assignees": [ { "kind": "user", "userId": "e9e90546-8500-4502-a8a3-0f705c013cfd" } ], "completedAt": null, "completedByUserId": null }, { "key": "stop", "title": "Sign-off", "kind": "standard", "section": "Intake", "state": "todo", "visible": true, "locked": false, "dueAt": null, "assignees": [], "completedAt": null, "completedByUserId": null }, { "key": "wrapup", "title": "Wrap up", "kind": "standard", "section": "Intake", "state": "todo", "visible": true, "locked": true, "dueAt": null, "assignees": [], "completedAt": null, "completedByUserId": null } ], "fields": [ { "key": "customer", "label": "Customer", "type": "text", "format": "none", "taskKey": "details", "value": "Ada Lovelace", "formatted": "Ada Lovelace", "visible": true }, { "key": "order_code", "label": "Order code", "type": "text", "format": "custom", "taskKey": "details", "value": "AB-1234", "formatted": "AB-1234", "visible": true }, { "key": "memo", "label": "Memo", "type": "textarea", "format": null, "taskKey": "details", "value": "Two lines\nhere", "formatted": "Two lines\nhere", "visible": true }, { "key": "quantity", "label": "Quantity", "type": "number", "format": "number", "taskKey": "details", "value": 1250, "formatted": "1,250", "visible": true }, { "key": "amount", "label": "Amount", "type": "number", "format": "currency", "taskKey": "details", "value": 1700, "formatted": "$1,700.00", "visible": true }, { "key": "fee", "label": "Fee", "type": "number", "format": "currency", "taskKey": "details", "value": 900, "formatted": "$900.00", "visible": true, "validationError": "Must not exceed $500.00." }, { "key": "rate", "label": "Rate", "type": "number", "format": "percent", "taskKey": "details", "value": 0.05, "formatted": "5%", "visible": true }, { "key": "rate_text", "label": "Rate (text)", "type": "text", "format": "percent", "taskKey": "details", "value": 0.05, "formatted": "5%", "visible": true }, { "key": "ship_date", "label": "Ship date", "type": "date", "format": "date", "taskKey": "details", "value": "2026-10-15", "formatted": "Oct 15, 2026", "visible": true }, { "key": "start_time", "label": "Start time", "type": "time", "format": "time", "taskKey": "details", "value": "14:30", "formatted": "2:30 PM", "visible": true }, { "key": "gift_wrap", "label": "Gift wrap", "type": "dropdown", "format": null, "taskKey": "details", "value": "Yes", "formatted": "Yes", "visible": true, "choices": [ "Yes", "No" ] }, { "key": "tags", "label": "Tags", "type": "multi_choice", "format": null, "taskKey": "details", "value": [ "A", "C" ], "formatted": "A, C", "visible": true, "choices": [ "A", "B", "C" ] }, { "key": "agree", "label": "Agree", "type": "checkbox", "format": null, "taskKey": "details", "value": true, "formatted": "Yes", "visible": true }, { "key": "contact_email", "label": "Contact email", "type": "email", "format": "email", "taskKey": "details", "value": "ada@example.com", "formatted": "ada@example.com", "visible": true }, { "key": "website", "label": "Website", "type": "url", "format": "url", "taskKey": "details", "value": "https://example.com/", "formatted": "https://example.com/", "visible": true }, { "key": "phone", "label": "Phone", "type": "phone", "format": "phone", "taskKey": "details", "value": "+14155550100", "formatted": "(415) 555-0100", "visible": true }, { "key": "owner", "label": "Owner", "type": "members", "format": null, "taskKey": "details", "value": [ "e9e90546-8500-4502-a8a3-0f705c013cfd" ], "formatted": "Fixture Admin", "visible": true }, { "key": "steps", "label": "Steps", "type": "subtasks", "format": null, "taskKey": "details", "value": [ "photos" ], "formatted": "1 of 2: Photos", "visible": true, "items": [ { "key": "photos", "label": "Photos" }, { "key": "invoice", "label": "Invoice" } ] }, { "key": "contract", "label": "Contract", "type": "file", "format": null, "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "double_amount", "label": "Double amount", "type": "formula", "format": "currency", "taskKey": "details", "value": 3400, "formatted": "$3,400.00", "visible": true }, { "key": "source", "label": "Source", "type": "hidden", "format": null, "taskKey": "details", "value": "zapier", "formatted": "zapier", "visible": false } ] } ``` ## PATCH /v1/runs/{id} **Update a run's name, priority, labels, due date or status.** Scope `runs:write` · checked against the key's workflow / folder allowlist (403 `not_allowed_for_key` outside it) · accepts `Idempotency-Key` · accepts `Expected-Revision` (409 `revision_mismatch` when stale). Operation id `updateRun` · [open in the API reference](https://developers.processcart.com/reference#tag/runs/PATCH/v1/runs/{id}). Changes run-level attributes under the run lock. `status` is applied first; an archived run refuses every other change. Label ids are checked against the org's label set. Send `Expected-Revision` to refuse the write when the run changed since you read it. **Path parameters** | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | string (uuid) | yes | | **Request headers** | Name | Type | Required | Description | | --- | --- | --- | --- | | `Idempotency-Key` | string | no | Makes the request safe to retry: a second request with the same key and a byte-identical body (and the same `Expected-Revision`) replays the stored response (`Idempotent-Replayed: true`); a different body with the same key is refused (422 `idempotency_key_reused`) and no second resource is created. Keys are stored per workspace for 24 hours and replayed only to the API key that made the request (or the key one rotation away from it); another API key sending the same value is refused (422 `idempotency_key_reused`), so prefix keys per integration. 409, 429 and 5xx answers are never stored. | | `Expected-Revision` | string | no | Optimistic concurrency: the run's `revision` you last saw (from any Run body) — a bare integer (`7`) or the same in double quotes (`"7"`); `*` or no header = no precondition. Checked INSIDE the run lock — a stale value is refused with 409 `revision_mismatch` whose body names `currentRevision`. A list or any other value is a 422. A successful write answers its new `revision` in the body. The API sends no `ETag` and refuses `If-Match` (422): CDN layers evaluate `If-Match` against the response and turn a successful write into a 412. | **Request body** — `application/json`, [RunPatch](https://developers.processcart.com/reference/schemas#runpatch) | Property | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | no | | | `priority` | [RunPriority](https://developers.processcart.com/reference/schemas#runpriority) | no | | | `labels` | string[] | no | | | `dueAt` | [Instant](https://developers.processcart.com/reference/schemas#instant) \\| [CalendarDate](https://developers.processcart.com/reference/schemas#calendardate) \\| null | no | | | `status` | [RunStatus](https://developers.processcart.com/reference/schemas#runstatus) | no | | **Responses** | Status | Meaning | Body | | --- | --- | --- | | 200 | OK. | [Run](https://developers.processcart.com/reference/schemas#run) | | 401 | No usable credential. `unauthorized` — Authentication required; `invalid_api_key` — Invalid API key; `key_revoked` — API key revoked. | [Problem](https://developers.processcart.com/errors) | | 403 | `insufficient_scope` — Insufficient scope; `not_allowed_for_key` — Not allowed for this key; `read_only_key` — Read-only key. | [Problem](https://developers.processcart.com/errors) | | 404 | `not_found` — Not found. | [Problem](https://developers.processcart.com/errors) | | 405 | The verb is not supported on this path. `method_not_allowed` — Method not allowed. | [Problem](https://developers.processcart.com/errors) | | 409 | `idempotency_in_progress` — Request in progress; `revision_mismatch` — Revision mismatch; `run_archived` — Run is archived; `run_trashed` — Run is in Trash. | [Problem](https://developers.processcart.com/errors) | | 413 | `payload_too_large` — Payload too large. | [Problem](https://developers.processcart.com/errors) | | 415 | `unsupported_media_type` — Unsupported media type. | [Problem](https://developers.processcart.com/errors) | | 422 | `idempotency_key_reused` — Idempotency key reused; `validation_failed` — Validation failed. | [Problem](https://developers.processcart.com/errors) | | 429 | `rate_limited` — Too many requests. | [Problem](https://developers.processcart.com/errors) | | 500 | `internal_error` — Internal error. | [Problem](https://developers.processcart.com/errors) | | 503 | `unavailable` — Service unavailable. | [Problem](https://developers.processcart.com/errors) | **Errors** (every code this operation can answer; see [Errors](https://developers.processcart.com/errors)) - 401 [`invalid_api_key`](https://developers.processcart.com/errors#invalid_api_key) — Invalid API key - 401 [`key_revoked`](https://developers.processcart.com/errors#key_revoked) — API key revoked - 401 [`unauthorized`](https://developers.processcart.com/errors#unauthorized) — Authentication required - 403 [`insufficient_scope`](https://developers.processcart.com/errors#insufficient_scope) — Insufficient scope - 403 [`not_allowed_for_key`](https://developers.processcart.com/errors#not_allowed_for_key) — Not allowed for this key - 403 [`read_only_key`](https://developers.processcart.com/errors#read_only_key) — Read-only key - 404 [`not_found`](https://developers.processcart.com/errors#not_found) — Not found - 405 [`method_not_allowed`](https://developers.processcart.com/errors#method_not_allowed) — Method not allowed - 409 [`idempotency_in_progress`](https://developers.processcart.com/errors#idempotency_in_progress) — Request in progress - 409 [`revision_mismatch`](https://developers.processcart.com/errors#revision_mismatch) — Revision mismatch - 409 [`run_archived`](https://developers.processcart.com/errors#run_archived) — Run is archived - 409 [`run_trashed`](https://developers.processcart.com/errors#run_trashed) — Run is in Trash - 413 [`payload_too_large`](https://developers.processcart.com/errors#payload_too_large) — Payload too large - 415 [`unsupported_media_type`](https://developers.processcart.com/errors#unsupported_media_type) — Unsupported media type - 422 [`idempotency_key_reused`](https://developers.processcart.com/errors#idempotency_key_reused) — Idempotency key reused - 422 [`validation_failed`](https://developers.processcart.com/errors#validation_failed) — Validation failed - 429 [`rate_limited`](https://developers.processcart.com/errors#rate_limited) — Too many requests - 500 [`internal_error`](https://developers.processcart.com/errors#internal_error) — Internal error - 503 [`unavailable`](https://developers.processcart.com/errors#unavailable) — Service unavailable **Example request** ```json { "name": "ORD 2026-10-15 Acme Corp (renamed)", "priority": "high", "labels": [ "waiting" ], "dueAt": "2026-11-01T17:00:00Z" } ``` **Example response** (200) ```json { "id": "33ec8c8c-e219-422c-8dda-5c8633d2e368", "name": "ORD 2026-10-15 Acme Corp (renamed)", "status": "active", "priority": "high", "labels": [ "waiting" ], "labelDetails": [ { "id": "waiting", "name": "Waiting", "color": "#f59e0b" } ], "dueAt": "2026-11-01T17:00:00.000Z", "keyDate": "2026-10-15", "revision": 2, "updatedAt": "2026-10-13T01:09:37.189Z", "startedAt": "2026-10-13T01:09:28.930Z", "startedBy": "api", "startedByUserId": null, "schedule": null, "scheduledFor": null, "completedAt": null, "deletedAt": null, "workflow": { "id": "e1d9bfda-9e93-4cce-8518-b4a185a6d4cb", "name": "Order fulfillment" }, "versionId": "6b9a8079-3136-4818-8405-26c69bbac982", "url": "https://app.processcart.com/runs/33ec8c8c-e219-422c-8dda-5c8633d2e368", "tasks": [ { "key": "details", "title": "Details", "kind": "standard", "section": "Intake", "state": "todo", "visible": true, "locked": false, "dueAt": null, "assignees": [], "completedAt": null, "completedByUserId": null }, { "key": "review", "title": "Review", "kind": "approval", "section": "Intake", "state": "todo", "visible": true, "locked": false, "dueAt": null, "assignees": [ { "kind": "user", "userId": "e9e90546-8500-4502-a8a3-0f705c013cfd" } ], "completedAt": null, "completedByUserId": null }, { "key": "stop", "title": "Sign-off", "kind": "standard", "section": "Intake", "state": "todo", "visible": true, "locked": false, "dueAt": null, "assignees": [], "completedAt": null, "completedByUserId": null }, { "key": "wrapup", "title": "Wrap up", "kind": "standard", "section": "Intake", "state": "todo", "visible": true, "locked": true, "dueAt": null, "assignees": [], "completedAt": null, "completedByUserId": null } ], "fields": [ { "key": "customer", "label": "Customer", "type": "text", "format": "none", "taskKey": "details", "value": "Ada Lovelace", "formatted": "Ada Lovelace", "visible": true }, { "key": "order_code", "label": "Order code", "type": "text", "format": "custom", "taskKey": "details", "value": "AB-1234", "formatted": "AB-1234", "visible": true }, { "key": "memo", "label": "Memo", "type": "textarea", "format": null, "taskKey": "details", "value": "Two lines\nhere", "formatted": "Two lines\nhere", "visible": true }, { "key": "quantity", "label": "Quantity", "type": "number", "format": "number", "taskKey": "details", "value": 1250, "formatted": "1,250", "visible": true }, { "key": "amount", "label": "Amount", "type": "number", "format": "currency", "taskKey": "details", "value": 1700, "formatted": "$1,700.00", "visible": true }, { "key": "fee", "label": "Fee", "type": "number", "format": "currency", "taskKey": "details", "value": 900, "formatted": "$900.00", "visible": true, "validationError": "Must not exceed $500.00." }, { "key": "rate", "label": "Rate", "type": "number", "format": "percent", "taskKey": "details", "value": 0.05, "formatted": "5%", "visible": true }, { "key": "rate_text", "label": "Rate (text)", "type": "text", "format": "percent", "taskKey": "details", "value": 0.05, "formatted": "5%", "visible": true }, { "key": "ship_date", "label": "Ship date", "type": "date", "format": "date", "taskKey": "details", "value": "2026-10-15", "formatted": "Oct 15, 2026", "visible": true }, { "key": "start_time", "label": "Start time", "type": "time", "format": "time", "taskKey": "details", "value": "14:30", "formatted": "2:30 PM", "visible": true }, { "key": "gift_wrap", "label": "Gift wrap", "type": "dropdown", "format": null, "taskKey": "details", "value": "Yes", "formatted": "Yes", "visible": true, "choices": [ "Yes", "No" ] }, { "key": "tags", "label": "Tags", "type": "multi_choice", "format": null, "taskKey": "details", "value": [ "A", "C" ], "formatted": "A, C", "visible": true, "choices": [ "A", "B", "C" ] }, { "key": "agree", "label": "Agree", "type": "checkbox", "format": null, "taskKey": "details", "value": true, "formatted": "Yes", "visible": true }, { "key": "contact_email", "label": "Contact email", "type": "email", "format": "email", "taskKey": "details", "value": "ada@example.com", "formatted": "ada@example.com", "visible": true }, { "key": "website", "label": "Website", "type": "url", "format": "url", "taskKey": "details", "value": "https://example.com/", "formatted": "https://example.com/", "visible": true }, { "key": "phone", "label": "Phone", "type": "phone", "format": "phone", "taskKey": "details", "value": "+14155550100", "formatted": "(415) 555-0100", "visible": true }, { "key": "owner", "label": "Owner", "type": "members", "format": null, "taskKey": "details", "value": [ "e9e90546-8500-4502-a8a3-0f705c013cfd" ], "formatted": "Fixture Admin", "visible": true }, { "key": "steps", "label": "Steps", "type": "subtasks", "format": null, "taskKey": "details", "value": [ "photos" ], "formatted": "1 of 2: Photos", "visible": true, "items": [ { "key": "photos", "label": "Photos" }, { "key": "invoice", "label": "Invoice" } ] }, { "key": "contract", "label": "Contract", "type": "file", "format": null, "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "double_amount", "label": "Double amount", "type": "formula", "format": "currency", "taskKey": "details", "value": 3400, "formatted": "$3,400.00", "visible": true }, { "key": "source", "label": "Source", "type": "hidden", "format": null, "taskKey": "details", "value": "zapier", "formatted": "zapier", "visible": false } ] } ``` ## DELETE /v1/runs/{id} **Move a run to Trash.** Scope `runs:write` · checked against the key's workflow / folder allowlist (403 `not_allowed_for_key` outside it) · accepts `Idempotency-Key` · accepts `Expected-Revision` (409 `revision_mismatch` when stale). Operation id `trashRun` · [open in the API reference](https://developers.processcart.com/reference#tag/runs/DELETE/v1/runs/{id}). Soft-deletes the run (recoverable from Trash for 30 days). Returns the Run with `deletedAt` set; a run already in Trash answers 200 unchanged. **Path parameters** | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | string (uuid) | yes | | **Request headers** | Name | Type | Required | Description | | --- | --- | --- | --- | | `Idempotency-Key` | string | no | Makes the request safe to retry: a second request with the same key and a byte-identical body (and the same `Expected-Revision`) replays the stored response (`Idempotent-Replayed: true`); a different body with the same key is refused (422 `idempotency_key_reused`) and no second resource is created. Keys are stored per workspace for 24 hours and replayed only to the API key that made the request (or the key one rotation away from it); another API key sending the same value is refused (422 `idempotency_key_reused`), so prefix keys per integration. 409, 429 and 5xx answers are never stored. | | `Expected-Revision` | string | no | Optimistic concurrency: the run's `revision` you last saw (from any Run body) — a bare integer (`7`) or the same in double quotes (`"7"`); `*` or no header = no precondition. Checked INSIDE the run lock — a stale value is refused with 409 `revision_mismatch` whose body names `currentRevision`. A list or any other value is a 422. A successful write answers its new `revision` in the body. The API sends no `ETag` and refuses `If-Match` (422): CDN layers evaluate `If-Match` against the response and turn a successful write into a 412. | **Responses** | Status | Meaning | Body | | --- | --- | --- | | 200 | OK. | [Run](https://developers.processcart.com/reference/schemas#run) | | 401 | No usable credential. `unauthorized` — Authentication required; `invalid_api_key` — Invalid API key; `key_revoked` — API key revoked. | [Problem](https://developers.processcart.com/errors) | | 403 | `insufficient_scope` — Insufficient scope; `not_allowed_for_key` — Not allowed for this key; `read_only_key` — Read-only key. | [Problem](https://developers.processcart.com/errors) | | 404 | `not_found` — Not found. | [Problem](https://developers.processcart.com/errors) | | 405 | The verb is not supported on this path. `method_not_allowed` — Method not allowed. | [Problem](https://developers.processcart.com/errors) | | 409 | `idempotency_in_progress` — Request in progress; `revision_mismatch` — Revision mismatch. | [Problem](https://developers.processcart.com/errors) | | 422 | `idempotency_key_reused` — Idempotency key reused; `validation_failed` — Validation failed. | [Problem](https://developers.processcart.com/errors) | | 429 | `rate_limited` — Too many requests. | [Problem](https://developers.processcart.com/errors) | | 500 | `internal_error` — Internal error. | [Problem](https://developers.processcart.com/errors) | | 503 | `unavailable` — Service unavailable. | [Problem](https://developers.processcart.com/errors) | **Errors** (every code this operation can answer; see [Errors](https://developers.processcart.com/errors)) - 401 [`invalid_api_key`](https://developers.processcart.com/errors#invalid_api_key) — Invalid API key - 401 [`key_revoked`](https://developers.processcart.com/errors#key_revoked) — API key revoked - 401 [`unauthorized`](https://developers.processcart.com/errors#unauthorized) — Authentication required - 403 [`insufficient_scope`](https://developers.processcart.com/errors#insufficient_scope) — Insufficient scope - 403 [`not_allowed_for_key`](https://developers.processcart.com/errors#not_allowed_for_key) — Not allowed for this key - 403 [`read_only_key`](https://developers.processcart.com/errors#read_only_key) — Read-only key - 404 [`not_found`](https://developers.processcart.com/errors#not_found) — Not found - 405 [`method_not_allowed`](https://developers.processcart.com/errors#method_not_allowed) — Method not allowed - 409 [`idempotency_in_progress`](https://developers.processcart.com/errors#idempotency_in_progress) — Request in progress - 409 [`revision_mismatch`](https://developers.processcart.com/errors#revision_mismatch) — Revision mismatch - 422 [`idempotency_key_reused`](https://developers.processcart.com/errors#idempotency_key_reused) — Idempotency key reused - 422 [`validation_failed`](https://developers.processcart.com/errors#validation_failed) — Validation failed - 429 [`rate_limited`](https://developers.processcart.com/errors#rate_limited) — Too many requests - 500 [`internal_error`](https://developers.processcart.com/errors#internal_error) — Internal error - 503 [`unavailable`](https://developers.processcart.com/errors#unavailable) — Service unavailable **Example response** (200) ```json { "id": "845d4745-4cc1-46fe-9d6c-3cfead74544f", "name": "Order 1002", "status": "active", "priority": "none", "labels": [], "labelDetails": [], "dueAt": null, "keyDate": null, "revision": 4, "updatedAt": "2026-10-13T01:10:28.661Z", "startedAt": "2026-10-13T01:10:02.387Z", "startedBy": "api", "startedByUserId": null, "schedule": null, "scheduledFor": null, "completedAt": null, "deletedAt": "2026-10-13T01:10:28.586Z", "workflow": { "id": "e1d9bfda-9e93-4cce-8518-b4a185a6d4cb", "name": "Order fulfillment" }, "versionId": "6b9a8079-3136-4818-8405-26c69bbac982", "url": "https://app.processcart.com/runs/845d4745-4cc1-46fe-9d6c-3cfead74544f", "tasks": [ { "key": "details", "title": "Details", "kind": "standard", "section": "Intake", "state": "todo", "visible": true, "locked": false, "dueAt": null, "assignees": [], "completedAt": null, "completedByUserId": null }, { "key": "review", "title": "Review", "kind": "approval", "section": "Intake", "state": "todo", "visible": true, "locked": false, "dueAt": null, "assignees": [ { "kind": "user", "userId": "e9e90546-8500-4502-a8a3-0f705c013cfd" } ], "completedAt": null, "completedByUserId": null }, { "key": "stop", "title": "Sign-off", "kind": "standard", "section": "Intake", "state": "todo", "visible": true, "locked": false, "dueAt": null, "assignees": [], "completedAt": null, "completedByUserId": null }, { "key": "wrapup", "title": "Wrap up", "kind": "standard", "section": "Intake", "state": "todo", "visible": true, "locked": true, "dueAt": null, "assignees": [], "completedAt": null, "completedByUserId": null } ], "fields": [ { "key": "customer", "label": "Customer", "type": "text", "format": "none", "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "order_code", "label": "Order code", "type": "text", "format": "custom", "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "memo", "label": "Memo", "type": "textarea", "format": null, "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "quantity", "label": "Quantity", "type": "number", "format": "number", "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "amount", "label": "Amount", "type": "number", "format": "currency", "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "fee", "label": "Fee", "type": "number", "format": "currency", "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "rate", "label": "Rate", "type": "number", "format": "percent", "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "rate_text", "label": "Rate (text)", "type": "text", "format": "percent", "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "ship_date", "label": "Ship date", "type": "date", "format": "date", "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "start_time", "label": "Start time", "type": "time", "format": "time", "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "gift_wrap", "label": "Gift wrap", "type": "dropdown", "format": null, "taskKey": "details", "value": null, "formatted": "", "visible": true, "choices": [ "Yes", "No" ] }, { "key": "tags", "label": "Tags", "type": "multi_choice", "format": null, "taskKey": "details", "value": null, "formatted": "", "visible": true, "choices": [ "A", "B", "C" ] }, { "key": "agree", "label": "Agree", "type": "checkbox", "format": null, "taskKey": "details", "value": false, "formatted": "No", "visible": true }, { "key": "contact_email", "label": "Contact email", "type": "email", "format": "email", "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "website", "label": "Website", "type": "url", "format": "url", "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "phone", "label": "Phone", "type": "phone", "format": "phone", "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "owner", "label": "Owner", "type": "members", "format": null, "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "steps", "label": "Steps", "type": "subtasks", "format": null, "taskKey": "details", "value": null, "formatted": "", "visible": true, "items": [ { "key": "photos", "label": "Photos" }, { "key": "invoice", "label": "Invoice" } ] }, { "key": "contract", "label": "Contract", "type": "file", "format": null, "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "double_amount", "label": "Double amount", "type": "formula", "format": "currency", "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "source", "label": "Source", "type": "hidden", "format": null, "taskKey": "details", "value": null, "formatted": "", "visible": false } ] } ``` ## POST /v1/runs/{id}/restore **Restore a run from Trash.** Scope `runs:write` · checked against the key's workflow / folder allowlist (403 `not_allowed_for_key` outside it) · accepts `Idempotency-Key` · accepts `Expected-Revision` (409 `revision_mismatch` when stale). Operation id `restoreRun` · [open in the API reference](https://developers.processcart.com/reference#tag/runs/POST/v1/runs/{id}/restore). Brings a trashed run back. A run that is not in Trash answers 200 unchanged; a run whose workflow is itself in Trash is refused (restore the workflow first). `Expected-Revision` is honored as a compare-and-set. **Path parameters** | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | string (uuid) | yes | | **Request headers** | Name | Type | Required | Description | | --- | --- | --- | --- | | `Idempotency-Key` | string | no | Makes the request safe to retry: a second request with the same key and a byte-identical body (and the same `Expected-Revision`) replays the stored response (`Idempotent-Replayed: true`); a different body with the same key is refused (422 `idempotency_key_reused`) and no second resource is created. Keys are stored per workspace for 24 hours and replayed only to the API key that made the request (or the key one rotation away from it); another API key sending the same value is refused (422 `idempotency_key_reused`), so prefix keys per integration. 409, 429 and 5xx answers are never stored. | | `Expected-Revision` | string | no | Optimistic concurrency: the run's `revision` you last saw (from any Run body) — a bare integer (`7`) or the same in double quotes (`"7"`); `*` or no header = no precondition. Checked INSIDE the run lock — a stale value is refused with 409 `revision_mismatch` whose body names `currentRevision`. A list or any other value is a 422. A successful write answers its new `revision` in the body. The API sends no `ETag` and refuses `If-Match` (422): CDN layers evaluate `If-Match` against the response and turn a successful write into a 412. | **Responses** | Status | Meaning | Body | | --- | --- | --- | | 200 | OK. | [Run](https://developers.processcart.com/reference/schemas#run) | | 401 | No usable credential. `unauthorized` — Authentication required; `invalid_api_key` — Invalid API key; `key_revoked` — API key revoked. | [Problem](https://developers.processcart.com/errors) | | 403 | `insufficient_scope` — Insufficient scope; `not_allowed_for_key` — Not allowed for this key; `read_only_key` — Read-only key. | [Problem](https://developers.processcart.com/errors) | | 404 | `not_found` — Not found. | [Problem](https://developers.processcart.com/errors) | | 405 | The verb is not supported on this path. `method_not_allowed` — Method not allowed. | [Problem](https://developers.processcart.com/errors) | | 409 | `idempotency_in_progress` — Request in progress; `revision_mismatch` — Revision mismatch; `workflow_trashed` — Workflow is in Trash. | [Problem](https://developers.processcart.com/errors) | | 422 | `idempotency_key_reused` — Idempotency key reused; `validation_failed` — Validation failed. | [Problem](https://developers.processcart.com/errors) | | 429 | `rate_limited` — Too many requests. | [Problem](https://developers.processcart.com/errors) | | 500 | `internal_error` — Internal error. | [Problem](https://developers.processcart.com/errors) | | 503 | `unavailable` — Service unavailable. | [Problem](https://developers.processcart.com/errors) | **Errors** (every code this operation can answer; see [Errors](https://developers.processcart.com/errors)) - 401 [`invalid_api_key`](https://developers.processcart.com/errors#invalid_api_key) — Invalid API key - 401 [`key_revoked`](https://developers.processcart.com/errors#key_revoked) — API key revoked - 401 [`unauthorized`](https://developers.processcart.com/errors#unauthorized) — Authentication required - 403 [`insufficient_scope`](https://developers.processcart.com/errors#insufficient_scope) — Insufficient scope - 403 [`not_allowed_for_key`](https://developers.processcart.com/errors#not_allowed_for_key) — Not allowed for this key - 403 [`read_only_key`](https://developers.processcart.com/errors#read_only_key) — Read-only key - 404 [`not_found`](https://developers.processcart.com/errors#not_found) — Not found - 405 [`method_not_allowed`](https://developers.processcart.com/errors#method_not_allowed) — Method not allowed - 409 [`idempotency_in_progress`](https://developers.processcart.com/errors#idempotency_in_progress) — Request in progress - 409 [`revision_mismatch`](https://developers.processcart.com/errors#revision_mismatch) — Revision mismatch - 409 [`workflow_trashed`](https://developers.processcart.com/errors#workflow_trashed) — Workflow is in Trash - 422 [`idempotency_key_reused`](https://developers.processcart.com/errors#idempotency_key_reused) — Idempotency key reused - 422 [`validation_failed`](https://developers.processcart.com/errors#validation_failed) — Validation failed - 429 [`rate_limited`](https://developers.processcart.com/errors#rate_limited) — Too many requests - 500 [`internal_error`](https://developers.processcart.com/errors#internal_error) — Internal error - 503 [`unavailable`](https://developers.processcart.com/errors#unavailable) — Service unavailable **Example response** (200) ```json { "id": "845d4745-4cc1-46fe-9d6c-3cfead74544f", "name": "Order 1002", "status": "active", "priority": "none", "labels": [], "labelDetails": [], "dueAt": null, "keyDate": null, "revision": 5, "updatedAt": "2026-10-13T01:10:33.053Z", "startedAt": "2026-10-13T01:10:02.387Z", "startedBy": "api", "startedByUserId": null, "schedule": null, "scheduledFor": null, "completedAt": null, "deletedAt": null, "workflow": { "id": "e1d9bfda-9e93-4cce-8518-b4a185a6d4cb", "name": "Order fulfillment" }, "versionId": "6b9a8079-3136-4818-8405-26c69bbac982", "url": "https://app.processcart.com/runs/845d4745-4cc1-46fe-9d6c-3cfead74544f", "tasks": [ { "key": "details", "title": "Details", "kind": "standard", "section": "Intake", "state": "todo", "visible": true, "locked": false, "dueAt": null, "assignees": [], "completedAt": null, "completedByUserId": null }, { "key": "review", "title": "Review", "kind": "approval", "section": "Intake", "state": "todo", "visible": true, "locked": false, "dueAt": null, "assignees": [ { "kind": "user", "userId": "e9e90546-8500-4502-a8a3-0f705c013cfd" } ], "completedAt": null, "completedByUserId": null }, { "key": "stop", "title": "Sign-off", "kind": "standard", "section": "Intake", "state": "todo", "visible": true, "locked": false, "dueAt": null, "assignees": [], "completedAt": null, "completedByUserId": null }, { "key": "wrapup", "title": "Wrap up", "kind": "standard", "section": "Intake", "state": "todo", "visible": true, "locked": true, "dueAt": null, "assignees": [], "completedAt": null, "completedByUserId": null } ], "fields": [ { "key": "customer", "label": "Customer", "type": "text", "format": "none", "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "order_code", "label": "Order code", "type": "text", "format": "custom", "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "memo", "label": "Memo", "type": "textarea", "format": null, "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "quantity", "label": "Quantity", "type": "number", "format": "number", "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "amount", "label": "Amount", "type": "number", "format": "currency", "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "fee", "label": "Fee", "type": "number", "format": "currency", "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "rate", "label": "Rate", "type": "number", "format": "percent", "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "rate_text", "label": "Rate (text)", "type": "text", "format": "percent", "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "ship_date", "label": "Ship date", "type": "date", "format": "date", "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "start_time", "label": "Start time", "type": "time", "format": "time", "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "gift_wrap", "label": "Gift wrap", "type": "dropdown", "format": null, "taskKey": "details", "value": null, "formatted": "", "visible": true, "choices": [ "Yes", "No" ] }, { "key": "tags", "label": "Tags", "type": "multi_choice", "format": null, "taskKey": "details", "value": null, "formatted": "", "visible": true, "choices": [ "A", "B", "C" ] }, { "key": "agree", "label": "Agree", "type": "checkbox", "format": null, "taskKey": "details", "value": false, "formatted": "No", "visible": true }, { "key": "contact_email", "label": "Contact email", "type": "email", "format": "email", "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "website", "label": "Website", "type": "url", "format": "url", "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "phone", "label": "Phone", "type": "phone", "format": "phone", "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "owner", "label": "Owner", "type": "members", "format": null, "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "steps", "label": "Steps", "type": "subtasks", "format": null, "taskKey": "details", "value": null, "formatted": "", "visible": true, "items": [ { "key": "photos", "label": "Photos" }, { "key": "invoice", "label": "Invoice" } ] }, { "key": "contract", "label": "Contract", "type": "file", "format": null, "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "double_amount", "label": "Double amount", "type": "formula", "format": "currency", "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "source", "label": "Source", "type": "hidden", "format": null, "taskKey": "details", "value": null, "formatted": "", "visible": false } ] } ``` --- # Tasks endpoints > Complete, reopen, approve and reject a run's tasks under the engine's gates. Every operation with parameters, body, responses, errors and examples. Source: https://developers.processcart.com/reference/tasks Complete, reopen, approve and reject a run's tasks under the engine's gates. Base URL `https://app.processcart.com/api/v1`. Every operation below is also in the [OpenAPI document](https://developers.processcart.com/openapi.json) and the [interactive reference](https://developers.processcart.com/reference). | Operation | Method and path | Scope | | --- | --- | --- | | [List a run's tasks](#listRunTasks) | GET /v1/runs/{id}/tasks | Scope `runs:read` | | [Complete a task](#completeTask) | POST /v1/runs/{id}/tasks/{key}/complete | Scope `runs:write` | | [Reopen a completed task](#reopenTask) | POST /v1/runs/{id}/tasks/{key}/reopen | Scope `runs:write` | | [Approve an approval task](#approveTask) | POST /v1/runs/{id}/tasks/{key}/approve | Scope `approvals:decide` | | [Reject an approval task](#rejectTask) | POST /v1/runs/{id}/tasks/{key}/reject | Scope `approvals:decide` | ## GET /v1/runs/{id}/tasks **List a run's tasks.** Scope `runs:read` · checked against the key's workflow / folder allowlist (403 `not_allowed_for_key` outside it). Operation id `listRunTasks` · [open in the API reference](https://developers.processcart.com/reference#tag/tasks/GET/v1/runs/{id}/tasks). The run's tasks in checklist order with state, visibility, lock, due date and assignees — the same `tasks[]` the Run carries, as a bounded list (`nextCursor` is null). It takes no query parameters (an unknown one is a 422). **Path parameters** | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | string (uuid) | yes | | **Responses** | Status | Meaning | Body | | --- | --- | --- | | 200 | OK. | [RunTaskList](https://developers.processcart.com/reference/schemas#runtasklist) | | 401 | No usable credential. `unauthorized` — Authentication required; `invalid_api_key` — Invalid API key; `key_revoked` — API key revoked. | [Problem](https://developers.processcart.com/errors) | | 403 | `insufficient_scope` — Insufficient scope; `not_allowed_for_key` — Not allowed for this key. | [Problem](https://developers.processcart.com/errors) | | 404 | `not_found` — Not found. | [Problem](https://developers.processcart.com/errors) | | 405 | The verb is not supported on this path. `method_not_allowed` — Method not allowed. | [Problem](https://developers.processcart.com/errors) | | 422 | `validation_failed` — Validation failed. | [Problem](https://developers.processcart.com/errors) | | 429 | `rate_limited` — Too many requests. | [Problem](https://developers.processcart.com/errors) | | 500 | `internal_error` — Internal error. | [Problem](https://developers.processcart.com/errors) | | 503 | `unavailable` — Service unavailable. | [Problem](https://developers.processcart.com/errors) | **Errors** (every code this operation can answer; see [Errors](https://developers.processcart.com/errors)) - 401 [`invalid_api_key`](https://developers.processcart.com/errors#invalid_api_key) — Invalid API key - 401 [`key_revoked`](https://developers.processcart.com/errors#key_revoked) — API key revoked - 401 [`unauthorized`](https://developers.processcart.com/errors#unauthorized) — Authentication required - 403 [`insufficient_scope`](https://developers.processcart.com/errors#insufficient_scope) — Insufficient scope - 403 [`not_allowed_for_key`](https://developers.processcart.com/errors#not_allowed_for_key) — Not allowed for this key - 404 [`not_found`](https://developers.processcart.com/errors#not_found) — Not found - 405 [`method_not_allowed`](https://developers.processcart.com/errors#method_not_allowed) — Method not allowed - 422 [`validation_failed`](https://developers.processcart.com/errors#validation_failed) — Validation failed - 429 [`rate_limited`](https://developers.processcart.com/errors#rate_limited) — Too many requests - 500 [`internal_error`](https://developers.processcart.com/errors#internal_error) — Internal error - 503 [`unavailable`](https://developers.processcart.com/errors#unavailable) — Service unavailable **Example response** (200) ```json { "data": [ { "key": "details", "title": "Details", "kind": "standard", "section": "Intake", "state": "todo", "visible": true, "locked": false, "dueAt": null, "assignees": [], "completedAt": null, "completedByUserId": null }, { "key": "review", "title": "Review", "kind": "approval", "section": "Intake", "state": "todo", "visible": true, "locked": false, "dueAt": null, "assignees": [ { "kind": "user", "userId": "e9e90546-8500-4502-a8a3-0f705c013cfd" } ], "completedAt": null, "completedByUserId": null }, { "key": "stop", "title": "Sign-off", "kind": "standard", "section": "Intake", "state": "todo", "visible": true, "locked": false, "dueAt": null, "assignees": [], "completedAt": null, "completedByUserId": null }, { "key": "wrapup", "title": "Wrap up", "kind": "standard", "section": "Intake", "state": "todo", "visible": true, "locked": true, "dueAt": null, "assignees": [], "completedAt": null, "completedByUserId": null } ], "nextCursor": null, "hasMore": false } ``` ## POST /v1/runs/{id}/tasks/{key}/complete **Complete a task.** Scope `runs:write` · checked against the key's workflow / folder allowlist (403 `not_allowed_for_key` outside it) · accepts `Idempotency-Key` · accepts `Expected-Revision` (409 `revision_mismatch` when stale). Operation id `completeTask` · [open in the API reference](https://developers.processcart.com/reference#tag/tasks/POST/v1/runs/{id}/tasks/{key}/complete). Marks a standard task done, after the engine's gates: the task must be visible and unlocked and its required fields satisfied (every offender is listed in `errors[]`). Approval tasks are decided through approve / reject instead; an already-completed task answers 200 unchanged. **Path parameters** | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | string (uuid) | yes | | | `key` | string | yes | | **Request headers** | Name | Type | Required | Description | | --- | --- | --- | --- | | `Idempotency-Key` | string | no | Makes the request safe to retry: a second request with the same key and a byte-identical body (and the same `Expected-Revision`) replays the stored response (`Idempotent-Replayed: true`); a different body with the same key is refused (422 `idempotency_key_reused`) and no second resource is created. Keys are stored per workspace for 24 hours and replayed only to the API key that made the request (or the key one rotation away from it); another API key sending the same value is refused (422 `idempotency_key_reused`), so prefix keys per integration. 409, 429 and 5xx answers are never stored. | | `Expected-Revision` | string | no | Optimistic concurrency: the run's `revision` you last saw (from any Run body) — a bare integer (`7`) or the same in double quotes (`"7"`); `*` or no header = no precondition. Checked INSIDE the run lock — a stale value is refused with 409 `revision_mismatch` whose body names `currentRevision`. A list or any other value is a 422. A successful write answers its new `revision` in the body. The API sends no `ETag` and refuses `If-Match` (422): CDN layers evaluate `If-Match` against the response and turn a successful write into a 412. | **Responses** | Status | Meaning | Body | | --- | --- | --- | | 200 | OK. | [Run](https://developers.processcart.com/reference/schemas#run) | | 401 | No usable credential. `unauthorized` — Authentication required; `invalid_api_key` — Invalid API key; `key_revoked` — API key revoked. | [Problem](https://developers.processcart.com/errors) | | 403 | `insufficient_scope` — Insufficient scope; `not_allowed_for_key` — Not allowed for this key; `read_only_key` — Read-only key. | [Problem](https://developers.processcart.com/errors) | | 404 | `not_found` — Not found. | [Problem](https://developers.processcart.com/errors) | | 405 | The verb is not supported on this path. `method_not_allowed` — Method not allowed. | [Problem](https://developers.processcart.com/errors) | | 409 | `idempotency_in_progress` — Request in progress; `revision_mismatch` — Revision mismatch; `run_archived` — Run is archived; `run_trashed` — Run is in Trash; `task_hidden` — Task is hidden; `task_locked` — Task is locked; `task_not_configured` — Task not configured; `wrong_task_kind` — Wrong task kind. | [Problem](https://developers.processcart.com/errors) | | 422 | `idempotency_key_reused` — Idempotency key reused; `required_fields_unsatisfied` — Required fields unsatisfied; `validation_failed` — Validation failed. | [Problem](https://developers.processcart.com/errors) | | 429 | `rate_limited` — Too many requests. | [Problem](https://developers.processcart.com/errors) | | 500 | `internal_error` — Internal error. | [Problem](https://developers.processcart.com/errors) | | 503 | `unavailable` — Service unavailable. | [Problem](https://developers.processcart.com/errors) | **Errors** (every code this operation can answer; see [Errors](https://developers.processcart.com/errors)) - 401 [`invalid_api_key`](https://developers.processcart.com/errors#invalid_api_key) — Invalid API key - 401 [`key_revoked`](https://developers.processcart.com/errors#key_revoked) — API key revoked - 401 [`unauthorized`](https://developers.processcart.com/errors#unauthorized) — Authentication required - 403 [`insufficient_scope`](https://developers.processcart.com/errors#insufficient_scope) — Insufficient scope - 403 [`not_allowed_for_key`](https://developers.processcart.com/errors#not_allowed_for_key) — Not allowed for this key - 403 [`read_only_key`](https://developers.processcart.com/errors#read_only_key) — Read-only key - 404 [`not_found`](https://developers.processcart.com/errors#not_found) — Not found - 405 [`method_not_allowed`](https://developers.processcart.com/errors#method_not_allowed) — Method not allowed - 409 [`idempotency_in_progress`](https://developers.processcart.com/errors#idempotency_in_progress) — Request in progress - 409 [`revision_mismatch`](https://developers.processcart.com/errors#revision_mismatch) — Revision mismatch - 409 [`run_archived`](https://developers.processcart.com/errors#run_archived) — Run is archived - 409 [`run_trashed`](https://developers.processcart.com/errors#run_trashed) — Run is in Trash - 409 [`task_hidden`](https://developers.processcart.com/errors#task_hidden) — Task is hidden - 409 [`task_locked`](https://developers.processcart.com/errors#task_locked) — Task is locked - 409 [`task_not_configured`](https://developers.processcart.com/errors#task_not_configured) — Task not configured - 409 [`wrong_task_kind`](https://developers.processcart.com/errors#wrong_task_kind) — Wrong task kind - 422 [`idempotency_key_reused`](https://developers.processcart.com/errors#idempotency_key_reused) — Idempotency key reused - 422 [`required_fields_unsatisfied`](https://developers.processcart.com/errors#required_fields_unsatisfied) — Required fields unsatisfied - 422 [`validation_failed`](https://developers.processcart.com/errors#validation_failed) — Validation failed - 429 [`rate_limited`](https://developers.processcart.com/errors#rate_limited) — Too many requests - 500 [`internal_error`](https://developers.processcart.com/errors#internal_error) — Internal error - 503 [`unavailable`](https://developers.processcart.com/errors#unavailable) — Service unavailable **Example response** (200) ```json { "id": "33ec8c8c-e219-422c-8dda-5c8633d2e368", "name": "ORD 2026-10-15 Acme Corp", "status": "active", "priority": "high", "labels": [ "waiting" ], "labelDetails": [ { "id": "waiting", "name": "Waiting", "color": "#f59e0b" } ], "dueAt": "2026-11-01T17:00:00.000Z", "keyDate": "2026-10-15", "revision": 9, "updatedAt": "2026-10-13T01:09:58.990Z", "startedAt": "2026-10-13T01:09:28.930Z", "startedBy": "api", "startedByUserId": null, "schedule": null, "scheduledFor": null, "completedAt": null, "deletedAt": null, "workflow": { "id": "e1d9bfda-9e93-4cce-8518-b4a185a6d4cb", "name": "Order fulfillment" }, "versionId": "6b9a8079-3136-4818-8405-26c69bbac982", "url": "https://app.processcart.com/runs/33ec8c8c-e219-422c-8dda-5c8633d2e368", "tasks": [ { "key": "details", "title": "Details", "kind": "standard", "section": "Intake", "state": "done", "visible": true, "locked": false, "dueAt": null, "assignees": [], "completedAt": "2026-10-13T01:09:58.900Z", "completedByUserId": null }, { "key": "review", "title": "Review", "kind": "approval", "section": "Intake", "state": "todo", "visible": true, "locked": false, "dueAt": null, "assignees": [ { "kind": "user", "userId": "e9e90546-8500-4502-a8a3-0f705c013cfd" } ], "completedAt": null, "completedByUserId": null }, { "key": "stop", "title": "Sign-off", "kind": "standard", "section": "Intake", "state": "todo", "visible": true, "locked": false, "dueAt": null, "assignees": [], "completedAt": null, "completedByUserId": null }, { "key": "wrapup", "title": "Wrap up", "kind": "standard", "section": "Intake", "state": "todo", "visible": true, "locked": true, "dueAt": null, "assignees": [], "completedAt": null, "completedByUserId": null } ], "fields": [ { "key": "customer", "label": "Customer", "type": "text", "format": "none", "taskKey": "details", "value": "Ada Lovelace", "formatted": "Ada Lovelace", "visible": true }, { "key": "order_code", "label": "Order code", "type": "text", "format": "custom", "taskKey": "details", "value": "AB-1234", "formatted": "AB-1234", "visible": true }, { "key": "memo", "label": "Memo", "type": "textarea", "format": null, "taskKey": "details", "value": "Updated by the API", "formatted": "Updated by the API", "visible": true }, { "key": "quantity", "label": "Quantity", "type": "number", "format": "number", "taskKey": "details", "value": 1250, "formatted": "1,250", "visible": true }, { "key": "amount", "label": "Amount", "type": "number", "format": "currency", "taskKey": "details", "value": 1850, "formatted": "$1,850.00", "visible": true }, { "key": "fee", "label": "Fee", "type": "number", "format": "currency", "taskKey": "details", "value": 400, "formatted": "$400.00", "visible": true }, { "key": "rate", "label": "Rate", "type": "number", "format": "percent", "taskKey": "details", "value": 0.075, "formatted": "7.5%", "visible": true }, { "key": "rate_text", "label": "Rate (text)", "type": "text", "format": "percent", "taskKey": "details", "value": 0.075, "formatted": "7.5%", "visible": true }, { "key": "ship_date", "label": "Ship date", "type": "date", "format": "date", "taskKey": "details", "value": "2026-10-15", "formatted": "Oct 15, 2026", "visible": true }, { "key": "start_time", "label": "Start time", "type": "time", "format": "time", "taskKey": "details", "value": "14:30", "formatted": "2:30 PM", "visible": true }, { "key": "gift_wrap", "label": "Gift wrap", "type": "dropdown", "format": null, "taskKey": "details", "value": "Yes", "formatted": "Yes", "visible": true, "choices": [ "Yes", "No" ] }, { "key": "tags", "label": "Tags", "type": "multi_choice", "format": null, "taskKey": "details", "value": null, "formatted": "", "visible": true, "choices": [ "A", "B", "C" ] }, { "key": "agree", "label": "Agree", "type": "checkbox", "format": null, "taskKey": "details", "value": false, "formatted": "No", "visible": true }, { "key": "contact_email", "label": "Contact email", "type": "email", "format": "email", "taskKey": "details", "value": "ada@example.com", "formatted": "ada@example.com", "visible": true }, { "key": "website", "label": "Website", "type": "url", "format": "url", "taskKey": "details", "value": "https://example.com/", "formatted": "https://example.com/", "visible": true }, { "key": "phone", "label": "Phone", "type": "phone", "format": "phone", "taskKey": "details", "value": "+14155550100", "formatted": "(415) 555-0100", "visible": true }, { "key": "owner", "label": "Owner", "type": "members", "format": null, "taskKey": "details", "value": [ "61465009-1ab5-4eb6-b4f3-3aae0718d1fb" ], "formatted": "Fixture Member", "visible": true }, { "key": "steps", "label": "Steps", "type": "subtasks", "format": null, "taskKey": "details", "value": [ "photos" ], "formatted": "1 of 2: Photos", "visible": true, "items": [ { "key": "photos", "label": "Photos" }, { "key": "invoice", "label": "Invoice" } ] }, { "key": "contract", "label": "Contract", "type": "file", "format": null, "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "double_amount", "label": "Double amount", "type": "formula", "format": "currency", "taskKey": "details", "value": 3700, "formatted": "$3,700.00", "visible": true }, { "key": "source", "label": "Source", "type": "hidden", "format": null, "taskKey": "details", "value": 42, "formatted": "42", "visible": false } ] } ``` ## POST /v1/runs/{id}/tasks/{key}/reopen **Reopen a completed task.** Scope `runs:write` · checked against the key's workflow / folder allowlist (403 `not_allowed_for_key` outside it) · accepts `Idempotency-Key` · accepts `Expected-Revision` (409 `revision_mismatch` when stale). Operation id `reopenTask` · [open in the API reference](https://developers.processcart.com/reference#tag/tasks/POST/v1/runs/{id}/tasks/{key}/reopen). Puts a done task back to to-do (a completed run becomes active again). Reopening an approval needs the `approvals:decide` scope. A task that is not done answers 200 unchanged. A hidden or locked task can still be reopened — only complete, approve and reject gate on visibility and locks. **Path parameters** | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | string (uuid) | yes | | | `key` | string | yes | | **Request headers** | Name | Type | Required | Description | | --- | --- | --- | --- | | `Idempotency-Key` | string | no | Makes the request safe to retry: a second request with the same key and a byte-identical body (and the same `Expected-Revision`) replays the stored response (`Idempotent-Replayed: true`); a different body with the same key is refused (422 `idempotency_key_reused`) and no second resource is created. Keys are stored per workspace for 24 hours and replayed only to the API key that made the request (or the key one rotation away from it); another API key sending the same value is refused (422 `idempotency_key_reused`), so prefix keys per integration. 409, 429 and 5xx answers are never stored. | | `Expected-Revision` | string | no | Optimistic concurrency: the run's `revision` you last saw (from any Run body) — a bare integer (`7`) or the same in double quotes (`"7"`); `*` or no header = no precondition. Checked INSIDE the run lock — a stale value is refused with 409 `revision_mismatch` whose body names `currentRevision`. A list or any other value is a 422. A successful write answers its new `revision` in the body. The API sends no `ETag` and refuses `If-Match` (422): CDN layers evaluate `If-Match` against the response and turn a successful write into a 412. | **Responses** | Status | Meaning | Body | | --- | --- | --- | | 200 | OK. | [Run](https://developers.processcart.com/reference/schemas#run) | | 401 | No usable credential. `unauthorized` — Authentication required; `invalid_api_key` — Invalid API key; `key_revoked` — API key revoked. | [Problem](https://developers.processcart.com/errors) | | 403 | `approver_only` — Approver only; `insufficient_scope` — Insufficient scope; `not_allowed_for_key` — Not allowed for this key; `read_only_key` — Read-only key. | [Problem](https://developers.processcart.com/errors) | | 404 | `not_found` — Not found. | [Problem](https://developers.processcart.com/errors) | | 405 | The verb is not supported on this path. `method_not_allowed` — Method not allowed. | [Problem](https://developers.processcart.com/errors) | | 409 | `idempotency_in_progress` — Request in progress; `revision_mismatch` — Revision mismatch; `run_archived` — Run is archived; `run_trashed` — Run is in Trash. | [Problem](https://developers.processcart.com/errors) | | 422 | `idempotency_key_reused` — Idempotency key reused; `validation_failed` — Validation failed. | [Problem](https://developers.processcart.com/errors) | | 429 | `rate_limited` — Too many requests. | [Problem](https://developers.processcart.com/errors) | | 500 | `internal_error` — Internal error. | [Problem](https://developers.processcart.com/errors) | | 503 | `unavailable` — Service unavailable. | [Problem](https://developers.processcart.com/errors) | **Errors** (every code this operation can answer; see [Errors](https://developers.processcart.com/errors)) - 401 [`invalid_api_key`](https://developers.processcart.com/errors#invalid_api_key) — Invalid API key - 401 [`key_revoked`](https://developers.processcart.com/errors#key_revoked) — API key revoked - 401 [`unauthorized`](https://developers.processcart.com/errors#unauthorized) — Authentication required - 403 [`approver_only`](https://developers.processcart.com/errors#approver_only) — Approver only - 403 [`insufficient_scope`](https://developers.processcart.com/errors#insufficient_scope) — Insufficient scope - 403 [`not_allowed_for_key`](https://developers.processcart.com/errors#not_allowed_for_key) — Not allowed for this key - 403 [`read_only_key`](https://developers.processcart.com/errors#read_only_key) — Read-only key - 404 [`not_found`](https://developers.processcart.com/errors#not_found) — Not found - 405 [`method_not_allowed`](https://developers.processcart.com/errors#method_not_allowed) — Method not allowed - 409 [`idempotency_in_progress`](https://developers.processcart.com/errors#idempotency_in_progress) — Request in progress - 409 [`revision_mismatch`](https://developers.processcart.com/errors#revision_mismatch) — Revision mismatch - 409 [`run_archived`](https://developers.processcart.com/errors#run_archived) — Run is archived - 409 [`run_trashed`](https://developers.processcart.com/errors#run_trashed) — Run is in Trash - 422 [`idempotency_key_reused`](https://developers.processcart.com/errors#idempotency_key_reused) — Idempotency key reused - 422 [`validation_failed`](https://developers.processcart.com/errors#validation_failed) — Validation failed - 429 [`rate_limited`](https://developers.processcart.com/errors#rate_limited) — Too many requests - 500 [`internal_error`](https://developers.processcart.com/errors#internal_error) — Internal error - 503 [`unavailable`](https://developers.processcart.com/errors#unavailable) — Service unavailable **Example response** (200) ```json { "id": "33ec8c8c-e219-422c-8dda-5c8633d2e368", "name": "ORD 2026-10-15 Acme Corp", "status": "active", "priority": "high", "labels": [ "waiting" ], "labelDetails": [ { "id": "waiting", "name": "Waiting", "color": "#f59e0b" } ], "dueAt": "2026-11-01T17:00:00.000Z", "keyDate": "2026-10-15", "revision": 13, "updatedAt": "2026-10-13T01:10:15.207Z", "startedAt": "2026-10-13T01:09:28.930Z", "startedBy": "api", "startedByUserId": null, "schedule": null, "scheduledFor": null, "completedAt": null, "deletedAt": null, "workflow": { "id": "e1d9bfda-9e93-4cce-8518-b4a185a6d4cb", "name": "Order fulfillment" }, "versionId": "6b9a8079-3136-4818-8405-26c69bbac982", "url": "https://app.processcart.com/runs/33ec8c8c-e219-422c-8dda-5c8633d2e368", "tasks": [ { "key": "details", "title": "Details", "kind": "standard", "section": "Intake", "state": "done", "visible": true, "locked": false, "dueAt": null, "assignees": [], "completedAt": "2026-10-13T01:10:10.107Z", "completedByUserId": null }, { "key": "review", "title": "Review", "kind": "approval", "section": "Intake", "state": "todo", "visible": true, "locked": false, "dueAt": null, "assignees": [ { "kind": "user", "userId": "e9e90546-8500-4502-a8a3-0f705c013cfd" } ], "completedAt": null, "completedByUserId": null }, { "key": "stop", "title": "Sign-off", "kind": "standard", "section": "Intake", "state": "todo", "visible": true, "locked": false, "dueAt": null, "assignees": [], "completedAt": null, "completedByUserId": null }, { "key": "wrapup", "title": "Wrap up", "kind": "standard", "section": "Intake", "state": "todo", "visible": true, "locked": true, "dueAt": null, "assignees": [], "completedAt": null, "completedByUserId": null } ], "fields": [ { "key": "customer", "label": "Customer", "type": "text", "format": "none", "taskKey": "details", "value": "Ada Lovelace", "formatted": "Ada Lovelace", "visible": true }, { "key": "order_code", "label": "Order code", "type": "text", "format": "custom", "taskKey": "details", "value": "AB-1234", "formatted": "AB-1234", "visible": true }, { "key": "memo", "label": "Memo", "type": "textarea", "format": null, "taskKey": "details", "value": "Updated by the API", "formatted": "Updated by the API", "visible": true }, { "key": "quantity", "label": "Quantity", "type": "number", "format": "number", "taskKey": "details", "value": 1250, "formatted": "1,250", "visible": true }, { "key": "amount", "label": "Amount", "type": "number", "format": "currency", "taskKey": "details", "value": 1850, "formatted": "$1,850.00", "visible": true }, { "key": "fee", "label": "Fee", "type": "number", "format": "currency", "taskKey": "details", "value": 400, "formatted": "$400.00", "visible": true }, { "key": "rate", "label": "Rate", "type": "number", "format": "percent", "taskKey": "details", "value": 0.075, "formatted": "7.5%", "visible": true }, { "key": "rate_text", "label": "Rate (text)", "type": "text", "format": "percent", "taskKey": "details", "value": 0.075, "formatted": "7.5%", "visible": true }, { "key": "ship_date", "label": "Ship date", "type": "date", "format": "date", "taskKey": "details", "value": "2026-10-15", "formatted": "Oct 15, 2026", "visible": true }, { "key": "start_time", "label": "Start time", "type": "time", "format": "time", "taskKey": "details", "value": "14:30", "formatted": "2:30 PM", "visible": true }, { "key": "gift_wrap", "label": "Gift wrap", "type": "dropdown", "format": null, "taskKey": "details", "value": "Yes", "formatted": "Yes", "visible": true, "choices": [ "Yes", "No" ] }, { "key": "tags", "label": "Tags", "type": "multi_choice", "format": null, "taskKey": "details", "value": null, "formatted": "", "visible": true, "choices": [ "A", "B", "C" ] }, { "key": "agree", "label": "Agree", "type": "checkbox", "format": null, "taskKey": "details", "value": false, "formatted": "No", "visible": true }, { "key": "contact_email", "label": "Contact email", "type": "email", "format": "email", "taskKey": "details", "value": "ada@example.com", "formatted": "ada@example.com", "visible": true }, { "key": "website", "label": "Website", "type": "url", "format": "url", "taskKey": "details", "value": "https://example.com/", "formatted": "https://example.com/", "visible": true }, { "key": "phone", "label": "Phone", "type": "phone", "format": "phone", "taskKey": "details", "value": "+14155550100", "formatted": "(415) 555-0100", "visible": true }, { "key": "owner", "label": "Owner", "type": "members", "format": null, "taskKey": "details", "value": [ "61465009-1ab5-4eb6-b4f3-3aae0718d1fb" ], "formatted": "Fixture Member", "visible": true }, { "key": "steps", "label": "Steps", "type": "subtasks", "format": null, "taskKey": "details", "value": [ "photos" ], "formatted": "1 of 2: Photos", "visible": true, "items": [ { "key": "photos", "label": "Photos" }, { "key": "invoice", "label": "Invoice" } ] }, { "key": "contract", "label": "Contract", "type": "file", "format": null, "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "double_amount", "label": "Double amount", "type": "formula", "format": "currency", "taskKey": "details", "value": 3700, "formatted": "$3,700.00", "visible": true }, { "key": "source", "label": "Source", "type": "hidden", "format": null, "taskKey": "details", "value": 42, "formatted": "42", "visible": false } ] } ``` ## POST /v1/runs/{id}/tasks/{key}/approve **Approve an approval task.** Scope `approvals:decide` · checked against the key's workflow / folder allowlist (403 `not_allowed_for_key` outside it) · accepts `Idempotency-Key` · accepts `Expected-Revision` (409 `revision_mismatch` when stale). Operation id `approveTask` · [open in the API reference](https://developers.processcart.com/reference#tag/tasks/POST/v1/runs/{id}/tasks/{key}/approve). Records an approval with an optional comment; the body may be left out, or sent as `{}`, when there is nothing to add. Needs the `approvals:decide` scope — the key then decides with Admin authority. An approval already approved is refused until it is reopened. **Path parameters** | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | string (uuid) | yes | | | `key` | string | yes | | **Request headers** | Name | Type | Required | Description | | --- | --- | --- | --- | | `Idempotency-Key` | string | no | Makes the request safe to retry: a second request with the same key and a byte-identical body (and the same `Expected-Revision`) replays the stored response (`Idempotent-Replayed: true`); a different body with the same key is refused (422 `idempotency_key_reused`) and no second resource is created. Keys are stored per workspace for 24 hours and replayed only to the API key that made the request (or the key one rotation away from it); another API key sending the same value is refused (422 `idempotency_key_reused`), so prefix keys per integration. 409, 429 and 5xx answers are never stored. | | `Expected-Revision` | string | no | Optimistic concurrency: the run's `revision` you last saw (from any Run body) — a bare integer (`7`) or the same in double quotes (`"7"`); `*` or no header = no precondition. Checked INSIDE the run lock — a stale value is refused with 409 `revision_mismatch` whose body names `currentRevision`. A list or any other value is a 422. A successful write answers its new `revision` in the body. The API sends no `ETag` and refuses `If-Match` (422): CDN layers evaluate `If-Match` against the response and turn a successful write into a 412. | **Request body** — `application/json`, [TaskDecision](https://developers.processcart.com/reference/schemas#taskdecision) | Property | Type | Required | Description | | --- | --- | --- | --- | | `comment` | string | no | | **Responses** | Status | Meaning | Body | | --- | --- | --- | | 200 | OK. | [Run](https://developers.processcart.com/reference/schemas#run) | | 401 | No usable credential. `unauthorized` — Authentication required; `invalid_api_key` — Invalid API key; `key_revoked` — API key revoked. | [Problem](https://developers.processcart.com/errors) | | 403 | `approver_only` — Approver only; `insufficient_scope` — Insufficient scope; `not_allowed_for_key` — Not allowed for this key; `read_only_key` — Read-only key. | [Problem](https://developers.processcart.com/errors) | | 404 | `not_found` — Not found. | [Problem](https://developers.processcart.com/errors) | | 405 | The verb is not supported on this path. `method_not_allowed` — Method not allowed. | [Problem](https://developers.processcart.com/errors) | | 409 | `idempotency_in_progress` — Request in progress; `revision_mismatch` — Revision mismatch; `run_archived` — Run is archived; `run_trashed` — Run is in Trash; `task_completed` — Task already completed; `task_hidden` — Task is hidden; `task_locked` — Task is locked; `wrong_task_kind` — Wrong task kind. | [Problem](https://developers.processcart.com/errors) | | 413 | `payload_too_large` — Payload too large. | [Problem](https://developers.processcart.com/errors) | | 415 | `unsupported_media_type` — Unsupported media type. | [Problem](https://developers.processcart.com/errors) | | 422 | `idempotency_key_reused` — Idempotency key reused; `required_fields_unsatisfied` — Required fields unsatisfied; `validation_failed` — Validation failed. | [Problem](https://developers.processcart.com/errors) | | 429 | `rate_limited` — Too many requests. | [Problem](https://developers.processcart.com/errors) | | 500 | `internal_error` — Internal error. | [Problem](https://developers.processcart.com/errors) | | 503 | `unavailable` — Service unavailable. | [Problem](https://developers.processcart.com/errors) | **Errors** (every code this operation can answer; see [Errors](https://developers.processcart.com/errors)) - 401 [`invalid_api_key`](https://developers.processcart.com/errors#invalid_api_key) — Invalid API key - 401 [`key_revoked`](https://developers.processcart.com/errors#key_revoked) — API key revoked - 401 [`unauthorized`](https://developers.processcart.com/errors#unauthorized) — Authentication required - 403 [`approver_only`](https://developers.processcart.com/errors#approver_only) — Approver only - 403 [`insufficient_scope`](https://developers.processcart.com/errors#insufficient_scope) — Insufficient scope - 403 [`not_allowed_for_key`](https://developers.processcart.com/errors#not_allowed_for_key) — Not allowed for this key - 403 [`read_only_key`](https://developers.processcart.com/errors#read_only_key) — Read-only key - 404 [`not_found`](https://developers.processcart.com/errors#not_found) — Not found - 405 [`method_not_allowed`](https://developers.processcart.com/errors#method_not_allowed) — Method not allowed - 409 [`idempotency_in_progress`](https://developers.processcart.com/errors#idempotency_in_progress) — Request in progress - 409 [`revision_mismatch`](https://developers.processcart.com/errors#revision_mismatch) — Revision mismatch - 409 [`run_archived`](https://developers.processcart.com/errors#run_archived) — Run is archived - 409 [`run_trashed`](https://developers.processcart.com/errors#run_trashed) — Run is in Trash - 409 [`task_completed`](https://developers.processcart.com/errors#task_completed) — Task already completed - 409 [`task_hidden`](https://developers.processcart.com/errors#task_hidden) — Task is hidden - 409 [`task_locked`](https://developers.processcart.com/errors#task_locked) — Task is locked - 409 [`wrong_task_kind`](https://developers.processcart.com/errors#wrong_task_kind) — Wrong task kind - 413 [`payload_too_large`](https://developers.processcart.com/errors#payload_too_large) — Payload too large - 415 [`unsupported_media_type`](https://developers.processcart.com/errors#unsupported_media_type) — Unsupported media type - 422 [`idempotency_key_reused`](https://developers.processcart.com/errors#idempotency_key_reused) — Idempotency key reused - 422 [`required_fields_unsatisfied`](https://developers.processcart.com/errors#required_fields_unsatisfied) — Required fields unsatisfied - 422 [`validation_failed`](https://developers.processcart.com/errors#validation_failed) — Validation failed - 429 [`rate_limited`](https://developers.processcart.com/errors#rate_limited) — Too many requests - 500 [`internal_error`](https://developers.processcart.com/errors#internal_error) — Internal error - 503 [`unavailable`](https://developers.processcart.com/errors#unavailable) — Service unavailable **Example request** ```json { "comment": "Looks good." } ``` **Example response** (200) ```json { "id": "33ec8c8c-e219-422c-8dda-5c8633d2e368", "name": "ORD 2026-10-15 Acme Corp", "status": "active", "priority": "high", "labels": [ "waiting" ], "labelDetails": [ { "id": "waiting", "name": "Waiting", "color": "#f59e0b" } ], "dueAt": "2026-11-01T17:00:00.000Z", "keyDate": "2026-10-15", "revision": 14, "updatedAt": "2026-10-13T01:10:17.100Z", "startedAt": "2026-10-13T01:09:28.930Z", "startedBy": "api", "startedByUserId": null, "schedule": null, "scheduledFor": null, "completedAt": null, "deletedAt": null, "workflow": { "id": "e1d9bfda-9e93-4cce-8518-b4a185a6d4cb", "name": "Order fulfillment" }, "versionId": "6b9a8079-3136-4818-8405-26c69bbac982", "url": "https://app.processcart.com/runs/33ec8c8c-e219-422c-8dda-5c8633d2e368", "tasks": [ { "key": "details", "title": "Details", "kind": "standard", "section": "Intake", "state": "done", "visible": true, "locked": false, "dueAt": null, "assignees": [], "completedAt": "2026-10-13T01:10:10.107Z", "completedByUserId": null }, { "key": "review", "title": "Review", "kind": "approval", "section": "Intake", "state": "done", "visible": true, "locked": false, "dueAt": null, "assignees": [ { "kind": "user", "userId": "e9e90546-8500-4502-a8a3-0f705c013cfd" } ], "completedAt": "2026-10-13T01:10:17.009Z", "completedByUserId": null }, { "key": "stop", "title": "Sign-off", "kind": "standard", "section": "Intake", "state": "todo", "visible": true, "locked": false, "dueAt": null, "assignees": [], "completedAt": null, "completedByUserId": null }, { "key": "wrapup", "title": "Wrap up", "kind": "standard", "section": "Intake", "state": "todo", "visible": true, "locked": true, "dueAt": null, "assignees": [], "completedAt": null, "completedByUserId": null } ], "fields": [ { "key": "customer", "label": "Customer", "type": "text", "format": "none", "taskKey": "details", "value": "Ada Lovelace", "formatted": "Ada Lovelace", "visible": true }, { "key": "order_code", "label": "Order code", "type": "text", "format": "custom", "taskKey": "details", "value": "AB-1234", "formatted": "AB-1234", "visible": true }, { "key": "memo", "label": "Memo", "type": "textarea", "format": null, "taskKey": "details", "value": "Updated by the API", "formatted": "Updated by the API", "visible": true }, { "key": "quantity", "label": "Quantity", "type": "number", "format": "number", "taskKey": "details", "value": 1250, "formatted": "1,250", "visible": true }, { "key": "amount", "label": "Amount", "type": "number", "format": "currency", "taskKey": "details", "value": 1850, "formatted": "$1,850.00", "visible": true }, { "key": "fee", "label": "Fee", "type": "number", "format": "currency", "taskKey": "details", "value": 400, "formatted": "$400.00", "visible": true }, { "key": "rate", "label": "Rate", "type": "number", "format": "percent", "taskKey": "details", "value": 0.075, "formatted": "7.5%", "visible": true }, { "key": "rate_text", "label": "Rate (text)", "type": "text", "format": "percent", "taskKey": "details", "value": 0.075, "formatted": "7.5%", "visible": true }, { "key": "ship_date", "label": "Ship date", "type": "date", "format": "date", "taskKey": "details", "value": "2026-10-15", "formatted": "Oct 15, 2026", "visible": true }, { "key": "start_time", "label": "Start time", "type": "time", "format": "time", "taskKey": "details", "value": "14:30", "formatted": "2:30 PM", "visible": true }, { "key": "gift_wrap", "label": "Gift wrap", "type": "dropdown", "format": null, "taskKey": "details", "value": "Yes", "formatted": "Yes", "visible": true, "choices": [ "Yes", "No" ] }, { "key": "tags", "label": "Tags", "type": "multi_choice", "format": null, "taskKey": "details", "value": null, "formatted": "", "visible": true, "choices": [ "A", "B", "C" ] }, { "key": "agree", "label": "Agree", "type": "checkbox", "format": null, "taskKey": "details", "value": false, "formatted": "No", "visible": true }, { "key": "contact_email", "label": "Contact email", "type": "email", "format": "email", "taskKey": "details", "value": "ada@example.com", "formatted": "ada@example.com", "visible": true }, { "key": "website", "label": "Website", "type": "url", "format": "url", "taskKey": "details", "value": "https://example.com/", "formatted": "https://example.com/", "visible": true }, { "key": "phone", "label": "Phone", "type": "phone", "format": "phone", "taskKey": "details", "value": "+14155550100", "formatted": "(415) 555-0100", "visible": true }, { "key": "owner", "label": "Owner", "type": "members", "format": null, "taskKey": "details", "value": [ "61465009-1ab5-4eb6-b4f3-3aae0718d1fb" ], "formatted": "Fixture Member", "visible": true }, { "key": "steps", "label": "Steps", "type": "subtasks", "format": null, "taskKey": "details", "value": [ "photos" ], "formatted": "1 of 2: Photos", "visible": true, "items": [ { "key": "photos", "label": "Photos" }, { "key": "invoice", "label": "Invoice" } ] }, { "key": "contract", "label": "Contract", "type": "file", "format": null, "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "double_amount", "label": "Double amount", "type": "formula", "format": "currency", "taskKey": "details", "value": 3700, "formatted": "$3,700.00", "visible": true }, { "key": "source", "label": "Source", "type": "hidden", "format": null, "taskKey": "details", "value": 42, "formatted": "42", "visible": false } ] } ``` ## POST /v1/runs/{id}/tasks/{key}/reject **Reject an approval task.** Scope `approvals:decide` · checked against the key's workflow / folder allowlist (403 `not_allowed_for_key` outside it) · accepts `Idempotency-Key` · accepts `Expected-Revision` (409 `revision_mismatch` when stale). Operation id `rejectTask` · [open in the API reference](https://developers.processcart.com/reference#tag/tasks/POST/v1/runs/{id}/tasks/{key}/reject). Records a rejection — the comment is required — and sends the configured earlier tasks back for redo. Needs the `approvals:decide` scope. A duplicate rejection answers 200 unchanged. An approval already approved is refused until it is reopened. **Path parameters** | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | string (uuid) | yes | | | `key` | string | yes | | **Request headers** | Name | Type | Required | Description | | --- | --- | --- | --- | | `Idempotency-Key` | string | no | Makes the request safe to retry: a second request with the same key and a byte-identical body (and the same `Expected-Revision`) replays the stored response (`Idempotent-Replayed: true`); a different body with the same key is refused (422 `idempotency_key_reused`) and no second resource is created. Keys are stored per workspace for 24 hours and replayed only to the API key that made the request (or the key one rotation away from it); another API key sending the same value is refused (422 `idempotency_key_reused`), so prefix keys per integration. 409, 429 and 5xx answers are never stored. | | `Expected-Revision` | string | no | Optimistic concurrency: the run's `revision` you last saw (from any Run body) — a bare integer (`7`) or the same in double quotes (`"7"`); `*` or no header = no precondition. Checked INSIDE the run lock — a stale value is refused with 409 `revision_mismatch` whose body names `currentRevision`. A list or any other value is a 422. A successful write answers its new `revision` in the body. The API sends no `ETag` and refuses `If-Match` (422): CDN layers evaluate `If-Match` against the response and turn a successful write into a 412. | **Request body** — `application/json`, [TaskDecision](https://developers.processcart.com/reference/schemas#taskdecision) | Property | Type | Required | Description | | --- | --- | --- | --- | | `comment` | string | no | | **Responses** | Status | Meaning | Body | | --- | --- | --- | | 200 | OK. | [Run](https://developers.processcart.com/reference/schemas#run) | | 401 | No usable credential. `unauthorized` — Authentication required; `invalid_api_key` — Invalid API key; `key_revoked` — API key revoked. | [Problem](https://developers.processcart.com/errors) | | 403 | `approver_only` — Approver only; `insufficient_scope` — Insufficient scope; `not_allowed_for_key` — Not allowed for this key; `read_only_key` — Read-only key. | [Problem](https://developers.processcart.com/errors) | | 404 | `not_found` — Not found. | [Problem](https://developers.processcart.com/errors) | | 405 | The verb is not supported on this path. `method_not_allowed` — Method not allowed. | [Problem](https://developers.processcart.com/errors) | | 409 | `idempotency_in_progress` — Request in progress; `revision_mismatch` — Revision mismatch; `run_archived` — Run is archived; `run_trashed` — Run is in Trash; `task_completed` — Task already completed; `task_hidden` — Task is hidden; `task_locked` — Task is locked; `wrong_task_kind` — Wrong task kind. | [Problem](https://developers.processcart.com/errors) | | 413 | `payload_too_large` — Payload too large. | [Problem](https://developers.processcart.com/errors) | | 415 | `unsupported_media_type` — Unsupported media type. | [Problem](https://developers.processcart.com/errors) | | 422 | `idempotency_key_reused` — Idempotency key reused; `validation_failed` — Validation failed. | [Problem](https://developers.processcart.com/errors) | | 429 | `rate_limited` — Too many requests. | [Problem](https://developers.processcart.com/errors) | | 500 | `internal_error` — Internal error. | [Problem](https://developers.processcart.com/errors) | | 503 | `unavailable` — Service unavailable. | [Problem](https://developers.processcart.com/errors) | **Errors** (every code this operation can answer; see [Errors](https://developers.processcart.com/errors)) - 401 [`invalid_api_key`](https://developers.processcart.com/errors#invalid_api_key) — Invalid API key - 401 [`key_revoked`](https://developers.processcart.com/errors#key_revoked) — API key revoked - 401 [`unauthorized`](https://developers.processcart.com/errors#unauthorized) — Authentication required - 403 [`approver_only`](https://developers.processcart.com/errors#approver_only) — Approver only - 403 [`insufficient_scope`](https://developers.processcart.com/errors#insufficient_scope) — Insufficient scope - 403 [`not_allowed_for_key`](https://developers.processcart.com/errors#not_allowed_for_key) — Not allowed for this key - 403 [`read_only_key`](https://developers.processcart.com/errors#read_only_key) — Read-only key - 404 [`not_found`](https://developers.processcart.com/errors#not_found) — Not found - 405 [`method_not_allowed`](https://developers.processcart.com/errors#method_not_allowed) — Method not allowed - 409 [`idempotency_in_progress`](https://developers.processcart.com/errors#idempotency_in_progress) — Request in progress - 409 [`revision_mismatch`](https://developers.processcart.com/errors#revision_mismatch) — Revision mismatch - 409 [`run_archived`](https://developers.processcart.com/errors#run_archived) — Run is archived - 409 [`run_trashed`](https://developers.processcart.com/errors#run_trashed) — Run is in Trash - 409 [`task_completed`](https://developers.processcart.com/errors#task_completed) — Task already completed - 409 [`task_hidden`](https://developers.processcart.com/errors#task_hidden) — Task is hidden - 409 [`task_locked`](https://developers.processcart.com/errors#task_locked) — Task is locked - 409 [`wrong_task_kind`](https://developers.processcart.com/errors#wrong_task_kind) — Wrong task kind - 413 [`payload_too_large`](https://developers.processcart.com/errors#payload_too_large) — Payload too large - 415 [`unsupported_media_type`](https://developers.processcart.com/errors#unsupported_media_type) — Unsupported media type - 422 [`idempotency_key_reused`](https://developers.processcart.com/errors#idempotency_key_reused) — Idempotency key reused - 422 [`validation_failed`](https://developers.processcart.com/errors#validation_failed) — Validation failed - 429 [`rate_limited`](https://developers.processcart.com/errors#rate_limited) — Too many requests - 500 [`internal_error`](https://developers.processcart.com/errors#internal_error) — Internal error - 503 [`unavailable`](https://developers.processcart.com/errors#unavailable) — Service unavailable **Example request** ```json { "comment": "Redo the photos before I sign." } ``` **Example response** (200) ```json { "id": "33ec8c8c-e219-422c-8dda-5c8633d2e368", "name": "ORD 2026-10-15 Acme Corp", "status": "active", "priority": "high", "labels": [ "waiting" ], "labelDetails": [ { "id": "waiting", "name": "Waiting", "color": "#f59e0b" } ], "dueAt": "2026-11-01T17:00:00.000Z", "keyDate": "2026-10-15", "revision": 10, "updatedAt": "2026-10-13T01:10:07.652Z", "startedAt": "2026-10-13T01:09:28.930Z", "startedBy": "api", "startedByUserId": null, "schedule": null, "scheduledFor": null, "completedAt": null, "deletedAt": null, "workflow": { "id": "e1d9bfda-9e93-4cce-8518-b4a185a6d4cb", "name": "Order fulfillment" }, "versionId": "6b9a8079-3136-4818-8405-26c69bbac982", "url": "https://app.processcart.com/runs/33ec8c8c-e219-422c-8dda-5c8633d2e368", "tasks": [ { "key": "details", "title": "Details", "kind": "standard", "section": "Intake", "state": "rejected_redo", "visible": true, "locked": false, "dueAt": null, "assignees": [], "completedAt": null, "completedByUserId": null }, { "key": "review", "title": "Review", "kind": "approval", "section": "Intake", "state": "todo", "visible": true, "locked": false, "dueAt": null, "assignees": [ { "kind": "user", "userId": "e9e90546-8500-4502-a8a3-0f705c013cfd" } ], "completedAt": null, "completedByUserId": null }, { "key": "stop", "title": "Sign-off", "kind": "standard", "section": "Intake", "state": "todo", "visible": true, "locked": false, "dueAt": null, "assignees": [], "completedAt": null, "completedByUserId": null }, { "key": "wrapup", "title": "Wrap up", "kind": "standard", "section": "Intake", "state": "todo", "visible": true, "locked": true, "dueAt": null, "assignees": [], "completedAt": null, "completedByUserId": null } ], "fields": [ { "key": "customer", "label": "Customer", "type": "text", "format": "none", "taskKey": "details", "value": "Ada Lovelace", "formatted": "Ada Lovelace", "visible": true }, { "key": "order_code", "label": "Order code", "type": "text", "format": "custom", "taskKey": "details", "value": "AB-1234", "formatted": "AB-1234", "visible": true }, { "key": "memo", "label": "Memo", "type": "textarea", "format": null, "taskKey": "details", "value": "Updated by the API", "formatted": "Updated by the API", "visible": true }, { "key": "quantity", "label": "Quantity", "type": "number", "format": "number", "taskKey": "details", "value": 1250, "formatted": "1,250", "visible": true }, { "key": "amount", "label": "Amount", "type": "number", "format": "currency", "taskKey": "details", "value": 1850, "formatted": "$1,850.00", "visible": true }, { "key": "fee", "label": "Fee", "type": "number", "format": "currency", "taskKey": "details", "value": 400, "formatted": "$400.00", "visible": true }, { "key": "rate", "label": "Rate", "type": "number", "format": "percent", "taskKey": "details", "value": 0.075, "formatted": "7.5%", "visible": true }, { "key": "rate_text", "label": "Rate (text)", "type": "text", "format": "percent", "taskKey": "details", "value": 0.075, "formatted": "7.5%", "visible": true }, { "key": "ship_date", "label": "Ship date", "type": "date", "format": "date", "taskKey": "details", "value": "2026-10-15", "formatted": "Oct 15, 2026", "visible": true }, { "key": "start_time", "label": "Start time", "type": "time", "format": "time", "taskKey": "details", "value": "14:30", "formatted": "2:30 PM", "visible": true }, { "key": "gift_wrap", "label": "Gift wrap", "type": "dropdown", "format": null, "taskKey": "details", "value": "Yes", "formatted": "Yes", "visible": true, "choices": [ "Yes", "No" ] }, { "key": "tags", "label": "Tags", "type": "multi_choice", "format": null, "taskKey": "details", "value": null, "formatted": "", "visible": true, "choices": [ "A", "B", "C" ] }, { "key": "agree", "label": "Agree", "type": "checkbox", "format": null, "taskKey": "details", "value": false, "formatted": "No", "visible": true }, { "key": "contact_email", "label": "Contact email", "type": "email", "format": "email", "taskKey": "details", "value": "ada@example.com", "formatted": "ada@example.com", "visible": true }, { "key": "website", "label": "Website", "type": "url", "format": "url", "taskKey": "details", "value": "https://example.com/", "formatted": "https://example.com/", "visible": true }, { "key": "phone", "label": "Phone", "type": "phone", "format": "phone", "taskKey": "details", "value": "+14155550100", "formatted": "(415) 555-0100", "visible": true }, { "key": "owner", "label": "Owner", "type": "members", "format": null, "taskKey": "details", "value": [ "61465009-1ab5-4eb6-b4f3-3aae0718d1fb" ], "formatted": "Fixture Member", "visible": true }, { "key": "steps", "label": "Steps", "type": "subtasks", "format": null, "taskKey": "details", "value": [ "photos" ], "formatted": "1 of 2: Photos", "visible": true, "items": [ { "key": "photos", "label": "Photos" }, { "key": "invoice", "label": "Invoice" } ] }, { "key": "contract", "label": "Contract", "type": "file", "format": null, "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "double_amount", "label": "Double amount", "type": "formula", "format": "currency", "taskKey": "details", "value": 3700, "formatted": "$3,700.00", "visible": true }, { "key": "source", "label": "Source", "type": "hidden", "format": null, "taskKey": "details", "value": 42, "formatted": "42", "visible": false } ] } ``` --- # Fields endpoints > Set a run's field values in one transaction; a JSON number is the canonical, a string is parsed by the field's format. Every operation with parameters, body, responses, errors and examples. Source: https://developers.processcart.com/reference/fields Set a run's field values in one transaction; a JSON number is the canonical, a string is parsed by the field's format. Base URL `https://app.processcart.com/api/v1`. Every operation below is also in the [OpenAPI document](https://developers.processcart.com/openapi.json) and the [interactive reference](https://developers.processcart.com/reference). | Operation | Method and path | Scope | | --- | --- | --- | | [Set field values](#setRunFields) | PATCH /v1/runs/{id}/fields | Scope `runs:write` | ## PATCH /v1/runs/{id}/fields **Set field values.** Scope `runs:write` · checked against the key's workflow / folder allowlist (403 `not_allowed_for_key` outside it) · accepts `Idempotency-Key` · accepts `Expected-Revision` (409 `revision_mismatch` when stale). Operation id `setRunFields` · [open in the API reference](https://developers.processcart.com/reference#tag/fields/PATCH/v1/runs/{id}/fields). Writes one to two hundred field values in ONE transaction under the run lock (all-or-nothing — one bad key fails the whole request with every problem listed). A JSON number is the canonical value; a string is parsed by the field's format; `null` clears. Formula, file and display-text fields are not writable; fields of a completed task must be reopened first. Out-of-bounds values are accepted and flagged in `validationError`. **Path parameters** | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | string (uuid) | yes | | **Request headers** | Name | Type | Required | Description | | --- | --- | --- | --- | | `Idempotency-Key` | string | no | Makes the request safe to retry: a second request with the same key and a byte-identical body (and the same `Expected-Revision`) replays the stored response (`Idempotent-Replayed: true`); a different body with the same key is refused (422 `idempotency_key_reused`) and no second resource is created. Keys are stored per workspace for 24 hours and replayed only to the API key that made the request (or the key one rotation away from it); another API key sending the same value is refused (422 `idempotency_key_reused`), so prefix keys per integration. 409, 429 and 5xx answers are never stored. | | `Expected-Revision` | string | no | Optimistic concurrency: the run's `revision` you last saw (from any Run body) — a bare integer (`7`) or the same in double quotes (`"7"`); `*` or no header = no precondition. Checked INSIDE the run lock — a stale value is refused with 409 `revision_mismatch` whose body names `currentRevision`. A list or any other value is a 422. A successful write answers its new `revision` in the body. The API sends no `ETag` and refuses `If-Match` (422): CDN layers evaluate `If-Match` against the response and turn a successful write into a 412. | **Request body** — `application/json`, [FieldsWrite](https://developers.processcart.com/reference/schemas#fieldswrite) **Responses** | Status | Meaning | Body | | --- | --- | --- | | 200 | OK. | [Run](https://developers.processcart.com/reference/schemas#run) | | 401 | No usable credential. `unauthorized` — Authentication required; `invalid_api_key` — Invalid API key; `key_revoked` — API key revoked. | [Problem](https://developers.processcart.com/errors) | | 403 | `insufficient_scope` — Insufficient scope; `not_allowed_for_key` — Not allowed for this key; `read_only_key` — Read-only key. | [Problem](https://developers.processcart.com/errors) | | 404 | `not_found` — Not found. | [Problem](https://developers.processcart.com/errors) | | 405 | The verb is not supported on this path. `method_not_allowed` — Method not allowed. | [Problem](https://developers.processcart.com/errors) | | 409 | `idempotency_in_progress` — Request in progress; `revision_mismatch` — Revision mismatch; `run_archived` — Run is archived; `run_trashed` — Run is in Trash; `task_completed` — Task already completed. | [Problem](https://developers.processcart.com/errors) | | 413 | `payload_too_large` — Payload too large. | [Problem](https://developers.processcart.com/errors) | | 415 | `unsupported_media_type` — Unsupported media type. | [Problem](https://developers.processcart.com/errors) | | 422 | `field_not_writable` — Field is not writable; `idempotency_key_reused` — Idempotency key reused; `validation_failed` — Validation failed. | [Problem](https://developers.processcart.com/errors) | | 429 | `rate_limited` — Too many requests. | [Problem](https://developers.processcart.com/errors) | | 500 | `internal_error` — Internal error. | [Problem](https://developers.processcart.com/errors) | | 503 | `unavailable` — Service unavailable. | [Problem](https://developers.processcart.com/errors) | **Errors** (every code this operation can answer; see [Errors](https://developers.processcart.com/errors)) - 401 [`invalid_api_key`](https://developers.processcart.com/errors#invalid_api_key) — Invalid API key - 401 [`key_revoked`](https://developers.processcart.com/errors#key_revoked) — API key revoked - 401 [`unauthorized`](https://developers.processcart.com/errors#unauthorized) — Authentication required - 403 [`insufficient_scope`](https://developers.processcart.com/errors#insufficient_scope) — Insufficient scope - 403 [`not_allowed_for_key`](https://developers.processcart.com/errors#not_allowed_for_key) — Not allowed for this key - 403 [`read_only_key`](https://developers.processcart.com/errors#read_only_key) — Read-only key - 404 [`not_found`](https://developers.processcart.com/errors#not_found) — Not found - 405 [`method_not_allowed`](https://developers.processcart.com/errors#method_not_allowed) — Method not allowed - 409 [`idempotency_in_progress`](https://developers.processcart.com/errors#idempotency_in_progress) — Request in progress - 409 [`revision_mismatch`](https://developers.processcart.com/errors#revision_mismatch) — Revision mismatch - 409 [`run_archived`](https://developers.processcart.com/errors#run_archived) — Run is archived - 409 [`run_trashed`](https://developers.processcart.com/errors#run_trashed) — Run is in Trash - 409 [`task_completed`](https://developers.processcart.com/errors#task_completed) — Task already completed - 413 [`payload_too_large`](https://developers.processcart.com/errors#payload_too_large) — Payload too large - 415 [`unsupported_media_type`](https://developers.processcart.com/errors#unsupported_media_type) — Unsupported media type - 422 [`field_not_writable`](https://developers.processcart.com/errors#field_not_writable) — Field is not writable - 422 [`idempotency_key_reused`](https://developers.processcart.com/errors#idempotency_key_reused) — Idempotency key reused - 422 [`validation_failed`](https://developers.processcart.com/errors#validation_failed) — Validation failed - 429 [`rate_limited`](https://developers.processcart.com/errors#rate_limited) — Too many requests - 500 [`internal_error`](https://developers.processcart.com/errors#internal_error) — Internal error - 503 [`unavailable`](https://developers.processcart.com/errors#unavailable) — Service unavailable **Example request** ```json { "amount": "$1,850.00", "rate": 0.075, "rate_text": "7.5%", "memo": "Updated by the API", "fee": 400, "agree": false, "tags": [], "source": 42, "owner": [ "61465009-1ab5-4eb6-b4f3-3aae0718d1fb" ] } ``` **Example response** (200) ```json { "id": "33ec8c8c-e219-422c-8dda-5c8633d2e368", "name": "ORD 2026-10-15 Acme Corp", "status": "active", "priority": "high", "labels": [ "waiting" ], "labelDetails": [ { "id": "waiting", "name": "Waiting", "color": "#f59e0b" } ], "dueAt": "2026-11-01T17:00:00.000Z", "keyDate": "2026-10-15", "revision": 8, "updatedAt": "2026-10-13T01:09:51.829Z", "startedAt": "2026-10-13T01:09:28.930Z", "startedBy": "api", "startedByUserId": null, "schedule": null, "scheduledFor": null, "completedAt": null, "deletedAt": null, "workflow": { "id": "e1d9bfda-9e93-4cce-8518-b4a185a6d4cb", "name": "Order fulfillment" }, "versionId": "6b9a8079-3136-4818-8405-26c69bbac982", "url": "https://app.processcart.com/runs/33ec8c8c-e219-422c-8dda-5c8633d2e368", "tasks": [ { "key": "details", "title": "Details", "kind": "standard", "section": "Intake", "state": "todo", "visible": true, "locked": false, "dueAt": null, "assignees": [], "completedAt": null, "completedByUserId": null }, { "key": "review", "title": "Review", "kind": "approval", "section": "Intake", "state": "todo", "visible": true, "locked": false, "dueAt": null, "assignees": [ { "kind": "user", "userId": "e9e90546-8500-4502-a8a3-0f705c013cfd" } ], "completedAt": null, "completedByUserId": null }, { "key": "stop", "title": "Sign-off", "kind": "standard", "section": "Intake", "state": "todo", "visible": true, "locked": false, "dueAt": null, "assignees": [], "completedAt": null, "completedByUserId": null }, { "key": "wrapup", "title": "Wrap up", "kind": "standard", "section": "Intake", "state": "todo", "visible": true, "locked": true, "dueAt": null, "assignees": [], "completedAt": null, "completedByUserId": null } ], "fields": [ { "key": "customer", "label": "Customer", "type": "text", "format": "none", "taskKey": "details", "value": "Ada Lovelace", "formatted": "Ada Lovelace", "visible": true }, { "key": "order_code", "label": "Order code", "type": "text", "format": "custom", "taskKey": "details", "value": "AB-1234", "formatted": "AB-1234", "visible": true }, { "key": "memo", "label": "Memo", "type": "textarea", "format": null, "taskKey": "details", "value": "Updated by the API", "formatted": "Updated by the API", "visible": true }, { "key": "quantity", "label": "Quantity", "type": "number", "format": "number", "taskKey": "details", "value": 1250, "formatted": "1,250", "visible": true }, { "key": "amount", "label": "Amount", "type": "number", "format": "currency", "taskKey": "details", "value": 1850, "formatted": "$1,850.00", "visible": true }, { "key": "fee", "label": "Fee", "type": "number", "format": "currency", "taskKey": "details", "value": 400, "formatted": "$400.00", "visible": true }, { "key": "rate", "label": "Rate", "type": "number", "format": "percent", "taskKey": "details", "value": 0.075, "formatted": "7.5%", "visible": true }, { "key": "rate_text", "label": "Rate (text)", "type": "text", "format": "percent", "taskKey": "details", "value": 0.075, "formatted": "7.5%", "visible": true }, { "key": "ship_date", "label": "Ship date", "type": "date", "format": "date", "taskKey": "details", "value": "2026-10-15", "formatted": "Oct 15, 2026", "visible": true }, { "key": "start_time", "label": "Start time", "type": "time", "format": "time", "taskKey": "details", "value": "14:30", "formatted": "2:30 PM", "visible": true }, { "key": "gift_wrap", "label": "Gift wrap", "type": "dropdown", "format": null, "taskKey": "details", "value": "Yes", "formatted": "Yes", "visible": true, "choices": [ "Yes", "No" ] }, { "key": "tags", "label": "Tags", "type": "multi_choice", "format": null, "taskKey": "details", "value": null, "formatted": "", "visible": true, "choices": [ "A", "B", "C" ] }, { "key": "agree", "label": "Agree", "type": "checkbox", "format": null, "taskKey": "details", "value": false, "formatted": "No", "visible": true }, { "key": "contact_email", "label": "Contact email", "type": "email", "format": "email", "taskKey": "details", "value": "ada@example.com", "formatted": "ada@example.com", "visible": true }, { "key": "website", "label": "Website", "type": "url", "format": "url", "taskKey": "details", "value": "https://example.com/", "formatted": "https://example.com/", "visible": true }, { "key": "phone", "label": "Phone", "type": "phone", "format": "phone", "taskKey": "details", "value": "+14155550100", "formatted": "(415) 555-0100", "visible": true }, { "key": "owner", "label": "Owner", "type": "members", "format": null, "taskKey": "details", "value": [ "61465009-1ab5-4eb6-b4f3-3aae0718d1fb" ], "formatted": "Fixture Member", "visible": true }, { "key": "steps", "label": "Steps", "type": "subtasks", "format": null, "taskKey": "details", "value": [ "photos" ], "formatted": "1 of 2: Photos", "visible": true, "items": [ { "key": "photos", "label": "Photos" }, { "key": "invoice", "label": "Invoice" } ] }, { "key": "contract", "label": "Contract", "type": "file", "format": null, "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "double_amount", "label": "Double amount", "type": "formula", "format": "currency", "taskKey": "details", "value": 3700, "formatted": "$3,700.00", "visible": true }, { "key": "source", "label": "Source", "type": "hidden", "format": null, "taskKey": "details", "value": 42, "formatted": "42", "visible": false } ] } ``` --- # Comments endpoints > Task comments on a run. Every operation with parameters, body, responses, errors and examples. Source: https://developers.processcart.com/reference/comments Task comments on a run. Base URL `https://app.processcart.com/api/v1`. Every operation below is also in the [OpenAPI document](https://developers.processcart.com/openapi.json) and the [interactive reference](https://developers.processcart.com/reference). | Operation | Method and path | Scope | | --- | --- | --- | | [List a run's comments](#listRunComments) | GET /v1/runs/{id}/comments | Scope `runs:read` | | [Comment on a task](#createRunComment) | POST /v1/runs/{id}/comments | Scope `runs:write` | | [Get a comment](#getRunComment) | GET /v1/runs/{id}/comments/{commentId} | Scope `runs:read` | ## GET /v1/runs/{id}/comments **List a run's comments.** Scope `runs:read` · checked against the key's workflow / folder allowlist (403 `not_allowed_for_key` outside it) · paginated (`{ data, nextCursor, hasMore }`). Operation id `listRunComments` · [open in the API reference](https://developers.processcart.com/reference#tag/comments/GET/v1/runs/{id}/comments). The run's live comments, oldest first, with the author, the sanitized body, a plain-text rendering and the mentioned member ids. A run in Trash still answers. **Path parameters** | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | string (uuid) | yes | | **Query parameters** (an unknown parameter is a 422 `validation_failed` with `errors[].code = "unknown_parameter"`) | Name | Type | Required | Description | | --- | --- | --- | --- | | `limit` | integer | no | | | `cursor` | string | no | The `nextCursor` of the previous page. Opaque, and bound to the query that produced it — a cursor from a different filter or sort is a 422. | **Responses** | Status | Meaning | Body | | --- | --- | --- | | 200 | OK. | [ListRunCommentsResponse](https://developers.processcart.com/reference/schemas#listruncommentsresponse) | | 401 | No usable credential. `unauthorized` — Authentication required; `invalid_api_key` — Invalid API key; `key_revoked` — API key revoked. | [Problem](https://developers.processcart.com/errors) | | 403 | `insufficient_scope` — Insufficient scope; `not_allowed_for_key` — Not allowed for this key. | [Problem](https://developers.processcart.com/errors) | | 404 | `not_found` — Not found. | [Problem](https://developers.processcart.com/errors) | | 405 | The verb is not supported on this path. `method_not_allowed` — Method not allowed. | [Problem](https://developers.processcart.com/errors) | | 422 | `validation_failed` — Validation failed. | [Problem](https://developers.processcart.com/errors) | | 429 | `rate_limited` — Too many requests. | [Problem](https://developers.processcart.com/errors) | | 500 | `internal_error` — Internal error. | [Problem](https://developers.processcart.com/errors) | | 503 | `unavailable` — Service unavailable. | [Problem](https://developers.processcart.com/errors) | **Errors** (every code this operation can answer; see [Errors](https://developers.processcart.com/errors)) - 401 [`invalid_api_key`](https://developers.processcart.com/errors#invalid_api_key) — Invalid API key - 401 [`key_revoked`](https://developers.processcart.com/errors#key_revoked) — API key revoked - 401 [`unauthorized`](https://developers.processcart.com/errors#unauthorized) — Authentication required - 403 [`insufficient_scope`](https://developers.processcart.com/errors#insufficient_scope) — Insufficient scope - 403 [`not_allowed_for_key`](https://developers.processcart.com/errors#not_allowed_for_key) — Not allowed for this key - 404 [`not_found`](https://developers.processcart.com/errors#not_found) — Not found - 405 [`method_not_allowed`](https://developers.processcart.com/errors#method_not_allowed) — Method not allowed - 422 [`validation_failed`](https://developers.processcart.com/errors#validation_failed) — Validation failed - 429 [`rate_limited`](https://developers.processcart.com/errors#rate_limited) — Too many requests - 500 [`internal_error`](https://developers.processcart.com/errors#internal_error) — Internal error - 503 [`unavailable`](https://developers.processcart.com/errors#unavailable) — Service unavailable **Example response** (200) ```json { "data": [ { "id": "32629d31-996c-48af-8637-261a6aea7eca", "runId": "3245e6a5-3fbe-463a-ad0b-a7fd871957b0", "taskKey": "details", "runTaskId": "7d6b86f6-5f67-4729-870c-ed29c1be3009", "author": { "kind": "api", "keyId": "d422b49f-a3f2-4244-b97e-8d50f25c3036", "name": "collab key" }, "body": { "type": "doc", "content": [ { "type": "paragraph", "content": [ { "text": "Hello from the API", "type": "text" }, { "type": "hardBreak" }, { "text": "second line", "type": "text" } ] } ] }, "text": "Hello from the API\nsecond line", "mentions": [], "createdAt": "2026-10-13T01:09:30.295Z", "editedAt": null, "url": "https://app.processcart.com/runs/3245e6a5-3fbe-463a-ad0b-a7fd871957b0?task=details" }, { "id": "65c213cd-053b-47aa-93ec-c7fd21b06a49", "runId": "3245e6a5-3fbe-463a-ad0b-a7fd871957b0", "taskKey": "details", "runTaskId": "7d6b86f6-5f67-4729-870c-ed29c1be3009", "author": { "kind": "api", "keyId": "d422b49f-a3f2-4244-b97e-8d50f25c3036", "name": "collab key" }, "body": { "type": "doc", "content": [ { "type": "paragraph", "content": [ { "text": "Ping ", "type": "text" }, { "type": "mention", "attrs": { "id": "0515df8b-d0b5-43c5-b535-3b87ec2df4a8", "label": "Fixture Member" } }, { "text": " and ", "type": "text" }, { "text": " cc ", "type": "text" }, { "type": "mention", "attrs": { "id": "f3e7a668-7e78-44f2-bc92-4457a9a54337", "label": "Fixture Admin" } } ] } ] }, "text": "Ping @Fixture Member and cc @Fixture Admin", "mentions": [ "0515df8b-d0b5-43c5-b535-3b87ec2df4a8", "f3e7a668-7e78-44f2-bc92-4457a9a54337" ], "createdAt": "2026-10-13T01:09:32.773Z", "editedAt": null, "url": "https://app.processcart.com/runs/3245e6a5-3fbe-463a-ad0b-a7fd871957b0?task=details" }, { "id": "b70b33e8-8a42-4289-8fa7-564929b05c43", "runId": "3245e6a5-3fbe-463a-ad0b-a7fd871957b0", "taskKey": "details", "runTaskId": "7d6b86f6-5f67-4729-870c-ed29c1be3009", "author": { "kind": "user", "userId": "f3e7a668-7e78-44f2-bc92-4457a9a54337", "name": "Fixture Admin" }, "body": { "type": "doc", "content": [ { "type": "paragraph", "content": [ { "text": "From Ada", "type": "text" } ] } ] }, "text": "From Ada", "mentions": [], "createdAt": "2026-10-13T01:09:34.271Z", "editedAt": null, "url": "https://app.processcart.com/runs/3245e6a5-3fbe-463a-ad0b-a7fd871957b0?task=details" } ], "nextCursor": null, "hasMore": false } ``` ## POST /v1/runs/{id}/comments **Comment on a task.** Scope `runs:write` · checked against the key's workflow / folder allowlist (403 `not_allowed_for_key` outside it) · accepts `Idempotency-Key`. Operation id `createRunComment` · [open in the API reference](https://developers.processcart.com/reference#tag/comments/POST/v1/runs/{id}/comments). Posts a comment on one of the run's tasks as the API key (`author.kind` = `api`). Send plain `text` or a rich `body` — never both. The body is sanitized strictly: unknown node and mark types are dropped, a mention's label is re-derived from the member list, and a mention of a non-member is removed. Mentioned members are notified. Comments never change the run's revision. Refused on an archived or trashed run. **Path parameters** | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | string (uuid) | yes | | **Request headers** | Name | Type | Required | Description | | --- | --- | --- | --- | | `Idempotency-Key` | string | no | Makes the request safe to retry: a second request with the same key and a byte-identical body (and the same `Expected-Revision`) replays the stored response (`Idempotent-Replayed: true`); a different body with the same key is refused (422 `idempotency_key_reused`) and no second resource is created. Keys are stored per workspace for 24 hours and replayed only to the API key that made the request (or the key one rotation away from it); another API key sending the same value is refused (422 `idempotency_key_reused`), so prefix keys per integration. 409, 429 and 5xx answers are never stored. | **Request body** — `application/json`, [CreateRunCommentBody](https://developers.processcart.com/reference/schemas#createruncommentbody) | Property | Type | Required | Description | | --- | --- | --- | --- | | `taskKey` | string | yes | The task to comment on (any task of the run). | | `text` | string | no | Plain text: blank lines separate paragraphs, a single newline is a line break, and `@Name` becomes a mention when it matches exactly one member. | | `body` | object | no | A rich-text document (ProseMirror JSON). Allowed node types: paragraph, text, heading, bulletList, orderedList, listItem, blockquote, codeBlock, hardBreak, horizontalRule, image, table, tableRow, tableCell, tableHeader, mention, mergeTag (attrs.fieldKey must be a valid field key, else dropped), embed (an https video src on YouTube, Loom or Vimeo, else removed); marks: bold, italic, code, link, textStyle, highlight. Anything else is dropped on save. A mention node is `{ type: "mention", attrs: { id: "" } }` — its label is always re-derived from the member list. | **Responses** | Status | Meaning | Body | | --- | --- | --- | | 201 | Created. `Location` names the new resource. | [Comment](https://developers.processcart.com/reference/schemas#comment) | | 401 | No usable credential. `unauthorized` — Authentication required; `invalid_api_key` — Invalid API key; `key_revoked` — API key revoked. | [Problem](https://developers.processcart.com/errors) | | 403 | `insufficient_scope` — Insufficient scope; `not_allowed_for_key` — Not allowed for this key; `read_only_key` — Read-only key. | [Problem](https://developers.processcart.com/errors) | | 404 | `not_found` — Not found. | [Problem](https://developers.processcart.com/errors) | | 405 | The verb is not supported on this path. `method_not_allowed` — Method not allowed. | [Problem](https://developers.processcart.com/errors) | | 409 | `idempotency_in_progress` — Request in progress; `run_archived` — Run is archived; `run_trashed` — Run is in Trash. | [Problem](https://developers.processcart.com/errors) | | 413 | `payload_too_large` — Payload too large. | [Problem](https://developers.processcart.com/errors) | | 415 | `unsupported_media_type` — Unsupported media type. | [Problem](https://developers.processcart.com/errors) | | 422 | `idempotency_key_reused` — Idempotency key reused; `validation_failed` — Validation failed. | [Problem](https://developers.processcart.com/errors) | | 429 | `rate_limited` — Too many requests. | [Problem](https://developers.processcart.com/errors) | | 500 | `internal_error` — Internal error. | [Problem](https://developers.processcart.com/errors) | | 503 | `unavailable` — Service unavailable. | [Problem](https://developers.processcart.com/errors) | **Errors** (every code this operation can answer; see [Errors](https://developers.processcart.com/errors)) - 401 [`invalid_api_key`](https://developers.processcart.com/errors#invalid_api_key) — Invalid API key - 401 [`key_revoked`](https://developers.processcart.com/errors#key_revoked) — API key revoked - 401 [`unauthorized`](https://developers.processcart.com/errors#unauthorized) — Authentication required - 403 [`insufficient_scope`](https://developers.processcart.com/errors#insufficient_scope) — Insufficient scope - 403 [`not_allowed_for_key`](https://developers.processcart.com/errors#not_allowed_for_key) — Not allowed for this key - 403 [`read_only_key`](https://developers.processcart.com/errors#read_only_key) — Read-only key - 404 [`not_found`](https://developers.processcart.com/errors#not_found) — Not found - 405 [`method_not_allowed`](https://developers.processcart.com/errors#method_not_allowed) — Method not allowed - 409 [`idempotency_in_progress`](https://developers.processcart.com/errors#idempotency_in_progress) — Request in progress - 409 [`run_archived`](https://developers.processcart.com/errors#run_archived) — Run is archived - 409 [`run_trashed`](https://developers.processcart.com/errors#run_trashed) — Run is in Trash - 413 [`payload_too_large`](https://developers.processcart.com/errors#payload_too_large) — Payload too large - 415 [`unsupported_media_type`](https://developers.processcart.com/errors#unsupported_media_type) — Unsupported media type - 422 [`idempotency_key_reused`](https://developers.processcart.com/errors#idempotency_key_reused) — Idempotency key reused - 422 [`validation_failed`](https://developers.processcart.com/errors#validation_failed) — Validation failed - 429 [`rate_limited`](https://developers.processcart.com/errors#rate_limited) — Too many requests - 500 [`internal_error`](https://developers.processcart.com/errors#internal_error) — Internal error - 503 [`unavailable`](https://developers.processcart.com/errors#unavailable) — Service unavailable **Example request** ```json { "taskKey": "details", "text": "Hello from the API\nsecond line" } ``` **Example response** (201) ```json { "id": "32629d31-996c-48af-8637-261a6aea7eca", "runId": "3245e6a5-3fbe-463a-ad0b-a7fd871957b0", "taskKey": "details", "runTaskId": "7d6b86f6-5f67-4729-870c-ed29c1be3009", "author": { "kind": "api", "keyId": "d422b49f-a3f2-4244-b97e-8d50f25c3036", "name": "collab key" }, "body": { "type": "doc", "content": [ { "type": "paragraph", "content": [ { "type": "text", "text": "Hello from the API" }, { "type": "hardBreak" }, { "type": "text", "text": "second line" } ] } ] }, "text": "Hello from the API\nsecond line", "mentions": [], "createdAt": "2026-10-13T01:09:30.295Z", "editedAt": null, "url": "https://app.processcart.com/runs/3245e6a5-3fbe-463a-ad0b-a7fd871957b0?task=details" } ``` ## GET /v1/runs/{id}/comments/{commentId} **Get a comment.** Scope `runs:read` · checked against the key's workflow / folder allowlist (403 `not_allowed_for_key` outside it). Operation id `getRunComment` · [open in the API reference](https://developers.processcart.com/reference#tag/comments/GET/v1/runs/{id}/comments/{commentId}). One live comment of the run — the resource a `POST` answered with (its `Location`). A deleted comment is a 404. **Path parameters** | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | string (uuid) | yes | | | `commentId` | string (uuid) | yes | | **Responses** | Status | Meaning | Body | | --- | --- | --- | | 200 | OK. | [Comment](https://developers.processcart.com/reference/schemas#comment) | | 401 | No usable credential. `unauthorized` — Authentication required; `invalid_api_key` — Invalid API key; `key_revoked` — API key revoked. | [Problem](https://developers.processcart.com/errors) | | 403 | `insufficient_scope` — Insufficient scope; `not_allowed_for_key` — Not allowed for this key. | [Problem](https://developers.processcart.com/errors) | | 404 | `not_found` — Not found. | [Problem](https://developers.processcart.com/errors) | | 405 | The verb is not supported on this path. `method_not_allowed` — Method not allowed. | [Problem](https://developers.processcart.com/errors) | | 422 | `validation_failed` — Validation failed. | [Problem](https://developers.processcart.com/errors) | | 429 | `rate_limited` — Too many requests. | [Problem](https://developers.processcart.com/errors) | | 500 | `internal_error` — Internal error. | [Problem](https://developers.processcart.com/errors) | | 503 | `unavailable` — Service unavailable. | [Problem](https://developers.processcart.com/errors) | **Errors** (every code this operation can answer; see [Errors](https://developers.processcart.com/errors)) - 401 [`invalid_api_key`](https://developers.processcart.com/errors#invalid_api_key) — Invalid API key - 401 [`key_revoked`](https://developers.processcart.com/errors#key_revoked) — API key revoked - 401 [`unauthorized`](https://developers.processcart.com/errors#unauthorized) — Authentication required - 403 [`insufficient_scope`](https://developers.processcart.com/errors#insufficient_scope) — Insufficient scope - 403 [`not_allowed_for_key`](https://developers.processcart.com/errors#not_allowed_for_key) — Not allowed for this key - 404 [`not_found`](https://developers.processcart.com/errors#not_found) — Not found - 405 [`method_not_allowed`](https://developers.processcart.com/errors#method_not_allowed) — Method not allowed - 422 [`validation_failed`](https://developers.processcart.com/errors#validation_failed) — Validation failed - 429 [`rate_limited`](https://developers.processcart.com/errors#rate_limited) — Too many requests - 500 [`internal_error`](https://developers.processcart.com/errors#internal_error) — Internal error - 503 [`unavailable`](https://developers.processcart.com/errors#unavailable) — Service unavailable **Example response** (200) ```json { "id": "32629d31-996c-48af-8637-261a6aea7eca", "runId": "3245e6a5-3fbe-463a-ad0b-a7fd871957b0", "taskKey": "details", "runTaskId": "7d6b86f6-5f67-4729-870c-ed29c1be3009", "author": { "kind": "api", "keyId": "d422b49f-a3f2-4244-b97e-8d50f25c3036", "name": "collab key" }, "body": { "type": "doc", "content": [ { "type": "paragraph", "content": [ { "text": "Hello from the API", "type": "text" }, { "type": "hardBreak" }, { "text": "second line", "type": "text" } ] } ] }, "text": "Hello from the API\nsecond line", "mentions": [], "createdAt": "2026-10-13T01:09:30.295Z", "editedAt": null, "url": "https://app.processcart.com/runs/3245e6a5-3fbe-463a-ad0b-a7fd871957b0?task=details" } ``` --- # Files endpoints > Two-phase file upload to a run's File fields, and signed downloads. Every operation with parameters, body, responses, errors and examples. Source: https://developers.processcart.com/reference/files Two-phase file upload to a run's File fields, and signed downloads. Base URL `https://app.processcart.com/api/v1`. Every operation below is also in the [OpenAPI document](https://developers.processcart.com/openapi.json) and the [interactive reference](https://developers.processcart.com/reference). | Operation | Method and path | Scope | | --- | --- | --- | | [Request an upload ticket](#requestRunFile) | POST /v1/runs/{id}/files | Scope `runs:write` | | [Finalize an upload](#finalizeRunFile) | POST /v1/runs/{id}/files/{attachmentId}/finalize | Scope `runs:write` | | [List a run's files](#listRunFiles) | GET /v1/runs/{id}/files | Scope `runs:read` | | [Get a file with a download link](#getRunFile) | GET /v1/runs/{id}/files/{attachmentId} | Scope `runs:read` | | [Remove a file](#deleteRunFile) | DELETE /v1/runs/{id}/files/{attachmentId} | Scope `runs:write` | ## POST /v1/runs/{id}/files **Request an upload ticket.** Scope `runs:write` · checked against the key's workflow / folder allowlist (403 `not_allowed_for_key` outside it) · accepts `Idempotency-Key`. Operation id `requestRunFile` · [open in the API reference](https://developers.processcart.com/reference#tag/files/POST/v1/runs/{id}/files). Step 1 of 3. Names the file, its size and type and the File field it belongs to; answers 200 with a signed `upload.url` to PUT the raw bytes to (two hours, no other credential) and the `finalize` call to make afterwards. Nothing is stored yet — an abandoned ticket leaves no trace on the run. A hidden field, a non-File field or a comment target is refused. An `Idempotency-Key` replay past the ticket's two hours hands back an expired URL: request a new ticket under a new key. **Path parameters** | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | string (uuid) | yes | | **Request headers** | Name | Type | Required | Description | | --- | --- | --- | --- | | `Idempotency-Key` | string | no | Makes the request safe to retry: a second request with the same key and a byte-identical body (and the same `Expected-Revision`) replays the stored response (`Idempotent-Replayed: true`); a different body with the same key is refused (422 `idempotency_key_reused`) and no second resource is created. Keys are stored per workspace for 24 hours and replayed only to the API key that made the request (or the key one rotation away from it); another API key sending the same value is refused (422 `idempotency_key_reused`), so prefix keys per integration. 409, 429 and 5xx answers are never stored. | **Request body** — `application/json`, [RequestRunFileBody](https://developers.processcart.com/reference/schemas#requestrunfilebody) | Property | Type | Required | Description | | --- | --- | --- | --- | | `filename` | string | yes | The display name. Send the SAME string again at finalize. | | `size` | integer | yes | The byte size (at most 26214400 — 25 MB). | | `contentType` | string | yes | The MIME type you will send as the PUT's Content-Type — it becomes the stored type (echoed in `upload.headers`). | | `target` | object | yes | | **Responses** | Status | Meaning | Body | | --- | --- | --- | | 200 | OK. | [UploadTicket](https://developers.processcart.com/reference/schemas#uploadticket) | | 401 | No usable credential. `unauthorized` — Authentication required; `invalid_api_key` — Invalid API key; `key_revoked` — API key revoked. | [Problem](https://developers.processcart.com/errors) | | 403 | `insufficient_scope` — Insufficient scope; `not_allowed_for_key` — Not allowed for this key; `read_only_key` — Read-only key. | [Problem](https://developers.processcart.com/errors) | | 404 | `not_found` — Not found. | [Problem](https://developers.processcart.com/errors) | | 405 | The verb is not supported on this path. `method_not_allowed` — Method not allowed. | [Problem](https://developers.processcart.com/errors) | | 409 | `idempotency_in_progress` — Request in progress; `run_archived` — Run is archived; `run_trashed` — Run is in Trash. | [Problem](https://developers.processcart.com/errors) | | 413 | `payload_too_large` — Payload too large. | [Problem](https://developers.processcart.com/errors) | | 415 | `unsupported_media_type` — Unsupported media type. | [Problem](https://developers.processcart.com/errors) | | 422 | `field_not_writable` — Field is not writable; `idempotency_key_reused` — Idempotency key reused; `validation_failed` — Validation failed. | [Problem](https://developers.processcart.com/errors) | | 429 | `rate_limited` — Too many requests. | [Problem](https://developers.processcart.com/errors) | | 500 | `internal_error` — Internal error. | [Problem](https://developers.processcart.com/errors) | | 503 | `unavailable` — Service unavailable. | [Problem](https://developers.processcart.com/errors) | **Errors** (every code this operation can answer; see [Errors](https://developers.processcart.com/errors)) - 401 [`invalid_api_key`](https://developers.processcart.com/errors#invalid_api_key) — Invalid API key - 401 [`key_revoked`](https://developers.processcart.com/errors#key_revoked) — API key revoked - 401 [`unauthorized`](https://developers.processcart.com/errors#unauthorized) — Authentication required - 403 [`insufficient_scope`](https://developers.processcart.com/errors#insufficient_scope) — Insufficient scope - 403 [`not_allowed_for_key`](https://developers.processcart.com/errors#not_allowed_for_key) — Not allowed for this key - 403 [`read_only_key`](https://developers.processcart.com/errors#read_only_key) — Read-only key - 404 [`not_found`](https://developers.processcart.com/errors#not_found) — Not found - 405 [`method_not_allowed`](https://developers.processcart.com/errors#method_not_allowed) — Method not allowed - 409 [`idempotency_in_progress`](https://developers.processcart.com/errors#idempotency_in_progress) — Request in progress - 409 [`run_archived`](https://developers.processcart.com/errors#run_archived) — Run is archived - 409 [`run_trashed`](https://developers.processcart.com/errors#run_trashed) — Run is in Trash - 413 [`payload_too_large`](https://developers.processcart.com/errors#payload_too_large) — Payload too large - 415 [`unsupported_media_type`](https://developers.processcart.com/errors#unsupported_media_type) — Unsupported media type - 422 [`field_not_writable`](https://developers.processcart.com/errors#field_not_writable) — Field is not writable - 422 [`idempotency_key_reused`](https://developers.processcart.com/errors#idempotency_key_reused) — Idempotency key reused - 422 [`validation_failed`](https://developers.processcart.com/errors#validation_failed) — Validation failed - 429 [`rate_limited`](https://developers.processcart.com/errors#rate_limited) — Too many requests - 500 [`internal_error`](https://developers.processcart.com/errors#internal_error) — Internal error - 503 [`unavailable`](https://developers.processcart.com/errors#unavailable) — Service unavailable **Example request** ```json { "filename": " contract.pdf ", "size": 1234, "contentType": "application/pdf", "target": { "kind": "field", "fieldKey": "contract" } } ``` **Example response** (200) ```json { "attachmentId": "03f9307d-039e-484d-8da4-a460186ede29", "upload": { "method": "PUT", "url": "https://storage.example.com/upload/REDACTED", "headers": { "Content-Type": "application/pdf" }, "expiresAt": "2026-10-13T03:09:40.975Z" }, "finalize": { "method": "POST", "url": "https://app.processcart.com/api/v1/runs/3245e6a5-3fbe-463a-ad0b-a7fd871957b0/files/03f9307d-039e-484d-8da4-a460186ede29/finalize", "body": { "filename": "contract.pdf", "target": { "kind": "field", "fieldKey": "contract" } } } } ``` ## POST /v1/runs/{id}/files/{attachmentId}/finalize **Finalize an upload.** Scope `runs:write` · checked against the key's workflow / folder allowlist (403 `not_allowed_for_key` outside it) · accepts `Idempotency-Key` · accepts `Expected-Revision` (409 `revision_mismatch` when stale). Operation id `finalizeRunFile` · [open in the API reference](https://developers.processcart.com/reference#tag/files/POST/v1/runs/{id}/files/{attachmentId}/finalize). Step 3 of 3, after the PUT. Verifies the stored object (its size and type are read from Storage, never from you), records the attachment and writes it into the File field under the run lock — a single-file field replaces its current file, a multi-file field appends. Answers the attachment beside the updated Run (its new `revision` is `run.revision`); `Expected-Revision` is honored. Before the bytes arrived it answers 409 `upload_incomplete`; a field of a completed task must be reopened first. Finalizing the same ticket twice is a no-op. **Path parameters** | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | string (uuid) | yes | | | `attachmentId` | string (uuid) | yes | | **Request headers** | Name | Type | Required | Description | | --- | --- | --- | --- | | `Idempotency-Key` | string | no | Makes the request safe to retry: a second request with the same key and a byte-identical body (and the same `Expected-Revision`) replays the stored response (`Idempotent-Replayed: true`); a different body with the same key is refused (422 `idempotency_key_reused`) and no second resource is created. Keys are stored per workspace for 24 hours and replayed only to the API key that made the request (or the key one rotation away from it); another API key sending the same value is refused (422 `idempotency_key_reused`), so prefix keys per integration. 409, 429 and 5xx answers are never stored. | | `Expected-Revision` | string | no | Optimistic concurrency: the run's `revision` you last saw (from any Run body) — a bare integer (`7`) or the same in double quotes (`"7"`); `*` or no header = no precondition. Checked INSIDE the run lock — a stale value is refused with 409 `revision_mismatch` whose body names `currentRevision`. A list or any other value is a 422. A successful write answers its new `revision` in the body. The API sends no `ETag` and refuses `If-Match` (422): CDN layers evaluate `If-Match` against the response and turn a successful write into a 412. | **Request body** — `application/json`, [FinalizeRunFileBody](https://developers.processcart.com/reference/schemas#finalizerunfilebody) | Property | Type | Required | Description | | --- | --- | --- | --- | | `filename` | string | yes | | | `target` | object | yes | | **Responses** | Status | Meaning | Body | | --- | --- | --- | | 200 | OK. | [FinalizeRunFileResponse](https://developers.processcart.com/reference/schemas#finalizerunfileresponse) | | 401 | No usable credential. `unauthorized` — Authentication required; `invalid_api_key` — Invalid API key; `key_revoked` — API key revoked. | [Problem](https://developers.processcart.com/errors) | | 403 | `insufficient_scope` — Insufficient scope; `not_allowed_for_key` — Not allowed for this key; `read_only_key` — Read-only key. | [Problem](https://developers.processcart.com/errors) | | 404 | `not_found` — Not found. | [Problem](https://developers.processcart.com/errors) | | 405 | The verb is not supported on this path. `method_not_allowed` — Method not allowed. | [Problem](https://developers.processcart.com/errors) | | 409 | `idempotency_in_progress` — Request in progress; `revision_mismatch` — Revision mismatch; `run_archived` — Run is archived; `run_trashed` — Run is in Trash; `task_completed` — Task already completed; `upload_incomplete` — Upload incomplete. | [Problem](https://developers.processcart.com/errors) | | 413 | `payload_too_large` — Payload too large. | [Problem](https://developers.processcart.com/errors) | | 415 | `unsupported_media_type` — Unsupported media type. | [Problem](https://developers.processcart.com/errors) | | 422 | `field_not_writable` — Field is not writable; `idempotency_key_reused` — Idempotency key reused; `validation_failed` — Validation failed. | [Problem](https://developers.processcart.com/errors) | | 429 | `rate_limited` — Too many requests. | [Problem](https://developers.processcart.com/errors) | | 500 | `internal_error` — Internal error. | [Problem](https://developers.processcart.com/errors) | | 503 | `unavailable` — Service unavailable. | [Problem](https://developers.processcart.com/errors) | **Errors** (every code this operation can answer; see [Errors](https://developers.processcart.com/errors)) - 401 [`invalid_api_key`](https://developers.processcart.com/errors#invalid_api_key) — Invalid API key - 401 [`key_revoked`](https://developers.processcart.com/errors#key_revoked) — API key revoked - 401 [`unauthorized`](https://developers.processcart.com/errors#unauthorized) — Authentication required - 403 [`insufficient_scope`](https://developers.processcart.com/errors#insufficient_scope) — Insufficient scope - 403 [`not_allowed_for_key`](https://developers.processcart.com/errors#not_allowed_for_key) — Not allowed for this key - 403 [`read_only_key`](https://developers.processcart.com/errors#read_only_key) — Read-only key - 404 [`not_found`](https://developers.processcart.com/errors#not_found) — Not found - 405 [`method_not_allowed`](https://developers.processcart.com/errors#method_not_allowed) — Method not allowed - 409 [`idempotency_in_progress`](https://developers.processcart.com/errors#idempotency_in_progress) — Request in progress - 409 [`revision_mismatch`](https://developers.processcart.com/errors#revision_mismatch) — Revision mismatch - 409 [`run_archived`](https://developers.processcart.com/errors#run_archived) — Run is archived - 409 [`run_trashed`](https://developers.processcart.com/errors#run_trashed) — Run is in Trash - 409 [`task_completed`](https://developers.processcart.com/errors#task_completed) — Task already completed - 409 [`upload_incomplete`](https://developers.processcart.com/errors#upload_incomplete) — Upload incomplete - 413 [`payload_too_large`](https://developers.processcart.com/errors#payload_too_large) — Payload too large - 415 [`unsupported_media_type`](https://developers.processcart.com/errors#unsupported_media_type) — Unsupported media type - 422 [`field_not_writable`](https://developers.processcart.com/errors#field_not_writable) — Field is not writable - 422 [`idempotency_key_reused`](https://developers.processcart.com/errors#idempotency_key_reused) — Idempotency key reused - 422 [`validation_failed`](https://developers.processcart.com/errors#validation_failed) — Validation failed - 429 [`rate_limited`](https://developers.processcart.com/errors#rate_limited) — Too many requests - 500 [`internal_error`](https://developers.processcart.com/errors#internal_error) — Internal error - 503 [`unavailable`](https://developers.processcart.com/errors#unavailable) — Service unavailable **Example request** ```json { "filename": "contract.pdf", "target": { "kind": "field", "fieldKey": "contract" } } ``` **Example response** (200) ```json { "attachment": { "id": "03f9307d-039e-484d-8da4-a460186ede29", "runId": "3245e6a5-3fbe-463a-ad0b-a7fd871957b0", "taskKey": "details", "fieldKey": "contract", "commentId": null, "filename": "contract.pdf", "size": 1234, "contentType": "application/pdf", "uploadedByUserId": null, "uploadedBy": { "kind": "api", "keyId": "d422b49f-a3f2-4244-b97e-8d50f25c3036", "name": "collab key" }, "createdAt": "2026-10-13T01:09:44.456Z" }, "run": { "id": "3245e6a5-3fbe-463a-ad0b-a7fd871957b0", "name": "Collab run", "status": "active", "priority": "none", "labels": [], "labelDetails": [], "dueAt": null, "keyDate": null, "revision": 4, "updatedAt": "2026-10-13T01:09:44.456Z", "startedAt": "2026-10-13T01:09:28.312Z", "startedBy": "api", "startedByUserId": null, "schedule": null, "scheduledFor": null, "completedAt": null, "deletedAt": null, "workflow": { "id": "3037d010-8e72-4ec4-b905-8157df322055", "name": "Order fulfillment" }, "versionId": "ed5d9aa2-f318-4f7f-a597-fb0a530f097f", "url": "https://app.processcart.com/runs/3245e6a5-3fbe-463a-ad0b-a7fd871957b0", "tasks": [ { "key": "details", "title": "Details", "kind": "standard", "section": "Intake", "state": "todo", "visible": true, "locked": false, "dueAt": null, "assignees": [], "completedAt": null, "completedByUserId": null }, { "key": "review", "title": "Review", "kind": "approval", "section": "Intake", "state": "todo", "visible": true, "locked": false, "dueAt": null, "assignees": [ { "kind": "user", "userId": "f3e7a668-7e78-44f2-bc92-4457a9a54337" } ], "completedAt": null, "completedByUserId": null }, { "key": "stop", "title": "Sign-off", "kind": "standard", "section": "Intake", "state": "todo", "visible": true, "locked": false, "dueAt": null, "assignees": [], "completedAt": null, "completedByUserId": null }, { "key": "wrapup", "title": "Wrap up", "kind": "standard", "section": "Intake", "state": "todo", "visible": true, "locked": true, "dueAt": null, "assignees": [], "completedAt": null, "completedByUserId": null } ], "fields": [ { "key": "customer", "label": "Customer", "type": "text", "format": "none", "taskKey": "details", "value": "Ada", "formatted": "Ada", "visible": true }, { "key": "order_code", "label": "Order code", "type": "text", "format": "custom", "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "memo", "label": "Memo", "type": "textarea", "format": null, "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "quantity", "label": "Quantity", "type": "number", "format": "number", "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "amount", "label": "Amount", "type": "number", "format": "currency", "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "fee", "label": "Fee", "type": "number", "format": "currency", "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "rate", "label": "Rate", "type": "number", "format": "percent", "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "rate_text", "label": "Rate (text)", "type": "text", "format": "percent", "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "ship_date", "label": "Ship date", "type": "date", "format": "date", "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "start_time", "label": "Start time", "type": "time", "format": "time", "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "gift_wrap", "label": "Gift wrap", "type": "dropdown", "format": null, "taskKey": "details", "value": null, "formatted": "", "visible": true, "choices": [ "Yes", "No" ] }, { "key": "tags", "label": "Tags", "type": "multi_choice", "format": null, "taskKey": "details", "value": null, "formatted": "", "visible": true, "choices": [ "A", "B", "C" ] }, { "key": "agree", "label": "Agree", "type": "checkbox", "format": null, "taskKey": "details", "value": false, "formatted": "No", "visible": true }, { "key": "contact_email", "label": "Contact email", "type": "email", "format": "email", "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "website", "label": "Website", "type": "url", "format": "url", "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "phone", "label": "Phone", "type": "phone", "format": "phone", "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "owner", "label": "Owner", "type": "members", "format": null, "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "steps", "label": "Steps", "type": "subtasks", "format": null, "taskKey": "details", "value": null, "formatted": "", "visible": true, "items": [ { "key": "photos", "label": "Photos" }, { "key": "invoice", "label": "Invoice" } ] }, { "key": "contract", "label": "Contract", "type": "file", "format": null, "taskKey": "details", "value": [ "03f9307d-039e-484d-8da4-a460186ede29" ], "formatted": "contract.pdf", "visible": true }, { "key": "double_amount", "label": "Double amount", "type": "formula", "format": "currency", "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "source", "label": "Source", "type": "hidden", "format": null, "taskKey": "details", "value": null, "formatted": "", "visible": false } ] } } ``` ## GET /v1/runs/{id}/files **List a run's files.** Scope `runs:read` · checked against the key's workflow / folder allowlist (403 `not_allowed_for_key` outside it). Operation id `listRunFiles` · [open in the API reference](https://developers.processcart.com/reference#tag/files/GET/v1/runs/{id}/files). Every attachment on the run (File-field files and comment files), oldest first, without download links — `GET /files/{attachmentId}` mints one. A bounded list: `nextCursor` is null, and it takes no query parameters (an unknown one is a 422). A run in Trash still answers. **Path parameters** | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | string (uuid) | yes | | **Responses** | Status | Meaning | Body | | --- | --- | --- | | 200 | OK. | [ListRunFilesResponse](https://developers.processcart.com/reference/schemas#listrunfilesresponse) | | 401 | No usable credential. `unauthorized` — Authentication required; `invalid_api_key` — Invalid API key; `key_revoked` — API key revoked. | [Problem](https://developers.processcart.com/errors) | | 403 | `insufficient_scope` — Insufficient scope; `not_allowed_for_key` — Not allowed for this key. | [Problem](https://developers.processcart.com/errors) | | 404 | `not_found` — Not found. | [Problem](https://developers.processcart.com/errors) | | 405 | The verb is not supported on this path. `method_not_allowed` — Method not allowed. | [Problem](https://developers.processcart.com/errors) | | 422 | `validation_failed` — Validation failed. | [Problem](https://developers.processcart.com/errors) | | 429 | `rate_limited` — Too many requests. | [Problem](https://developers.processcart.com/errors) | | 500 | `internal_error` — Internal error. | [Problem](https://developers.processcart.com/errors) | | 503 | `unavailable` — Service unavailable. | [Problem](https://developers.processcart.com/errors) | **Errors** (every code this operation can answer; see [Errors](https://developers.processcart.com/errors)) - 401 [`invalid_api_key`](https://developers.processcart.com/errors#invalid_api_key) — Invalid API key - 401 [`key_revoked`](https://developers.processcart.com/errors#key_revoked) — API key revoked - 401 [`unauthorized`](https://developers.processcart.com/errors#unauthorized) — Authentication required - 403 [`insufficient_scope`](https://developers.processcart.com/errors#insufficient_scope) — Insufficient scope - 403 [`not_allowed_for_key`](https://developers.processcart.com/errors#not_allowed_for_key) — Not allowed for this key - 404 [`not_found`](https://developers.processcart.com/errors#not_found) — Not found - 405 [`method_not_allowed`](https://developers.processcart.com/errors#method_not_allowed) — Method not allowed - 422 [`validation_failed`](https://developers.processcart.com/errors#validation_failed) — Validation failed - 429 [`rate_limited`](https://developers.processcart.com/errors#rate_limited) — Too many requests - 500 [`internal_error`](https://developers.processcart.com/errors#internal_error) — Internal error - 503 [`unavailable`](https://developers.processcart.com/errors#unavailable) — Service unavailable **Example response** (200) ```json { "data": [ { "id": "03f9307d-039e-484d-8da4-a460186ede29", "runId": "3245e6a5-3fbe-463a-ad0b-a7fd871957b0", "taskKey": "details", "fieldKey": "contract", "commentId": null, "filename": "contract.pdf", "size": 1234, "contentType": "application/pdf", "uploadedByUserId": null, "uploadedBy": { "kind": "api", "keyId": "d422b49f-a3f2-4244-b97e-8d50f25c3036", "name": "collab key" }, "createdAt": "2026-10-13T01:09:44.456Z" }, { "id": "58032013-39ef-41cf-a23a-9cde04f17fd8", "runId": "3245e6a5-3fbe-463a-ad0b-a7fd871957b0", "taskKey": "details", "fieldKey": null, "commentId": "32629d31-996c-48af-8637-261a6aea7eca", "filename": "note.txt", "size": 3, "contentType": "text/plain", "uploadedByUserId": "f3e7a668-7e78-44f2-bc92-4457a9a54337", "uploadedBy": { "kind": "user", "userId": "f3e7a668-7e78-44f2-bc92-4457a9a54337" }, "createdAt": "2026-10-13T01:09:46.727Z" } ], "nextCursor": null, "hasMore": false } ``` ## GET /v1/runs/{id}/files/{attachmentId} **Get a file with a download link.** Scope `runs:read` · checked against the key's workflow / folder allowlist (403 `not_allowed_for_key` outside it). Operation id `getRunFile` · [open in the API reference](https://developers.processcart.com/reference#tag/files/GET/v1/runs/{id}/files/{attachmentId}). The attachment plus a signed `downloadUrl` (one hour, `Content-Disposition: attachment`). Mint a fresh one per download rather than storing it. A run in Trash still answers. **Path parameters** | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | string (uuid) | yes | | | `attachmentId` | string (uuid) | yes | | **Responses** | Status | Meaning | Body | | --- | --- | --- | | 200 | OK. | [Attachment](https://developers.processcart.com/reference/schemas#attachment) | | 401 | No usable credential. `unauthorized` — Authentication required; `invalid_api_key` — Invalid API key; `key_revoked` — API key revoked. | [Problem](https://developers.processcart.com/errors) | | 403 | `insufficient_scope` — Insufficient scope; `not_allowed_for_key` — Not allowed for this key. | [Problem](https://developers.processcart.com/errors) | | 404 | `not_found` — Not found. | [Problem](https://developers.processcart.com/errors) | | 405 | The verb is not supported on this path. `method_not_allowed` — Method not allowed. | [Problem](https://developers.processcart.com/errors) | | 422 | `validation_failed` — Validation failed. | [Problem](https://developers.processcart.com/errors) | | 429 | `rate_limited` — Too many requests. | [Problem](https://developers.processcart.com/errors) | | 500 | `internal_error` — Internal error. | [Problem](https://developers.processcart.com/errors) | | 503 | `unavailable` — Service unavailable. | [Problem](https://developers.processcart.com/errors) | **Errors** (every code this operation can answer; see [Errors](https://developers.processcart.com/errors)) - 401 [`invalid_api_key`](https://developers.processcart.com/errors#invalid_api_key) — Invalid API key - 401 [`key_revoked`](https://developers.processcart.com/errors#key_revoked) — API key revoked - 401 [`unauthorized`](https://developers.processcart.com/errors#unauthorized) — Authentication required - 403 [`insufficient_scope`](https://developers.processcart.com/errors#insufficient_scope) — Insufficient scope - 403 [`not_allowed_for_key`](https://developers.processcart.com/errors#not_allowed_for_key) — Not allowed for this key - 404 [`not_found`](https://developers.processcart.com/errors#not_found) — Not found - 405 [`method_not_allowed`](https://developers.processcart.com/errors#method_not_allowed) — Method not allowed - 422 [`validation_failed`](https://developers.processcart.com/errors#validation_failed) — Validation failed - 429 [`rate_limited`](https://developers.processcart.com/errors#rate_limited) — Too many requests - 500 [`internal_error`](https://developers.processcart.com/errors#internal_error) — Internal error - 503 [`unavailable`](https://developers.processcart.com/errors#unavailable) — Service unavailable **Example response** (200) ```json { "id": "03f9307d-039e-484d-8da4-a460186ede29", "runId": "3245e6a5-3fbe-463a-ad0b-a7fd871957b0", "taskKey": "details", "fieldKey": "contract", "commentId": null, "filename": "contract.pdf", "size": 1234, "contentType": "application/pdf", "uploadedByUserId": null, "uploadedBy": { "kind": "api", "keyId": "d422b49f-a3f2-4244-b97e-8d50f25c3036", "name": "collab key" }, "createdAt": "2026-10-13T01:09:44.456Z", "downloadUrl": "https://storage.test/storage/v1/object/sign/attachments/5fc61268-81a7-4437-8ef0-05484e4084cf/3245e6a5-3fbe-463a-ad0b-a7fd871957b0/03f9307d-039e-484d-8da4-a460186ede29/contract.pdf?token=dl&download=contract.pdf", "downloadExpiresAt": "2026-10-13T02:09:47.501Z" } ``` ## DELETE /v1/runs/{id}/files/{attachmentId} **Remove a file.** Scope `runs:write` · checked against the key's workflow / folder allowlist (403 `not_allowed_for_key` outside it) · accepts `Idempotency-Key` · accepts `Expected-Revision` (409 `revision_mismatch` when stale). Operation id `deleteRunFile` · [open in the API reference](https://developers.processcart.com/reference#tag/files/DELETE/v1/runs/{id}/files/{attachmentId}). Removes a File-field file: the field's value drops it under the run lock (the task's required-field gate reacts like any other field change) and the stored object is deleted. Answers 204 with no body — read the run for its new `revision` before the next conditional write; `Expected-Revision` is honored. A comment's file cannot be removed through the API (403); a field of a completed task must be reopened first. **Path parameters** | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | string (uuid) | yes | | | `attachmentId` | string (uuid) | yes | | **Request headers** | Name | Type | Required | Description | | --- | --- | --- | --- | | `Idempotency-Key` | string | no | Makes the request safe to retry: a second request with the same key and a byte-identical body (and the same `Expected-Revision`) replays the stored response (`Idempotent-Replayed: true`); a different body with the same key is refused (422 `idempotency_key_reused`) and no second resource is created. Keys are stored per workspace for 24 hours and replayed only to the API key that made the request (or the key one rotation away from it); another API key sending the same value is refused (422 `idempotency_key_reused`), so prefix keys per integration. 409, 429 and 5xx answers are never stored. | | `Expected-Revision` | string | no | Optimistic concurrency: the run's `revision` you last saw (from any Run body) — a bare integer (`7`) or the same in double quotes (`"7"`); `*` or no header = no precondition. Checked INSIDE the run lock — a stale value is refused with 409 `revision_mismatch` whose body names `currentRevision`. A list or any other value is a 422. A successful write answers its new `revision` in the body. The API sends no `ETag` and refuses `If-Match` (422): CDN layers evaluate `If-Match` against the response and turn a successful write into a 412. | **Responses** | Status | Meaning | Body | | --- | --- | --- | | 204 | Done; no body. | — | | 401 | No usable credential. `unauthorized` — Authentication required; `invalid_api_key` — Invalid API key; `key_revoked` — API key revoked. | [Problem](https://developers.processcart.com/errors) | | 403 | `insufficient_scope` — Insufficient scope; `not_allowed_for_key` — Not allowed for this key; `read_only_key` — Read-only key. | [Problem](https://developers.processcart.com/errors) | | 404 | `not_found` — Not found. | [Problem](https://developers.processcart.com/errors) | | 405 | The verb is not supported on this path. `method_not_allowed` — Method not allowed. | [Problem](https://developers.processcart.com/errors) | | 409 | `idempotency_in_progress` — Request in progress; `revision_mismatch` — Revision mismatch; `run_archived` — Run is archived; `run_trashed` — Run is in Trash; `task_completed` — Task already completed. | [Problem](https://developers.processcart.com/errors) | | 422 | `idempotency_key_reused` — Idempotency key reused; `validation_failed` — Validation failed. | [Problem](https://developers.processcart.com/errors) | | 429 | `rate_limited` — Too many requests. | [Problem](https://developers.processcart.com/errors) | | 500 | `internal_error` — Internal error. | [Problem](https://developers.processcart.com/errors) | | 503 | `unavailable` — Service unavailable. | [Problem](https://developers.processcart.com/errors) | **Errors** (every code this operation can answer; see [Errors](https://developers.processcart.com/errors)) - 401 [`invalid_api_key`](https://developers.processcart.com/errors#invalid_api_key) — Invalid API key - 401 [`key_revoked`](https://developers.processcart.com/errors#key_revoked) — API key revoked - 401 [`unauthorized`](https://developers.processcart.com/errors#unauthorized) — Authentication required - 403 [`insufficient_scope`](https://developers.processcart.com/errors#insufficient_scope) — Insufficient scope - 403 [`not_allowed_for_key`](https://developers.processcart.com/errors#not_allowed_for_key) — Not allowed for this key - 403 [`read_only_key`](https://developers.processcart.com/errors#read_only_key) — Read-only key - 404 [`not_found`](https://developers.processcart.com/errors#not_found) — Not found - 405 [`method_not_allowed`](https://developers.processcart.com/errors#method_not_allowed) — Method not allowed - 409 [`idempotency_in_progress`](https://developers.processcart.com/errors#idempotency_in_progress) — Request in progress - 409 [`revision_mismatch`](https://developers.processcart.com/errors#revision_mismatch) — Revision mismatch - 409 [`run_archived`](https://developers.processcart.com/errors#run_archived) — Run is archived - 409 [`run_trashed`](https://developers.processcart.com/errors#run_trashed) — Run is in Trash - 409 [`task_completed`](https://developers.processcart.com/errors#task_completed) — Task already completed - 422 [`idempotency_key_reused`](https://developers.processcart.com/errors#idempotency_key_reused) — Idempotency key reused - 422 [`validation_failed`](https://developers.processcart.com/errors#validation_failed) — Validation failed - 429 [`rate_limited`](https://developers.processcart.com/errors#rate_limited) — Too many requests - 500 [`internal_error`](https://developers.processcart.com/errors#internal_error) — Internal error - 503 [`unavailable`](https://developers.processcart.com/errors#unavailable) — Service unavailable --- # Activity endpoints > A run's append-only activity trail. Every operation with parameters, body, responses, errors and examples. Source: https://developers.processcart.com/reference/activity A run's append-only activity trail. Base URL `https://app.processcart.com/api/v1`. Every operation below is also in the [OpenAPI document](https://developers.processcart.com/openapi.json) and the [interactive reference](https://developers.processcart.com/reference). | Operation | Method and path | Scope | | --- | --- | --- | | [List a run's activity](#listRunActivity) | GET /v1/runs/{id}/activity | Scope `runs:read` | ## GET /v1/runs/{id}/activity **List a run's activity.** Scope `runs:read` · checked against the key's workflow / folder allowlist (403 `not_allowed_for_key` outside it) · paginated (`{ data, nextCursor, hasMore }`). Operation id `listRunActivity` · [open in the API reference](https://developers.processcart.com/reference#tag/activity/GET/v1/runs/{id}/activity). The run's trail — every recorded change with its actor, raw payload and a human description — newest first (`order=asc` to read forward). Filter by `type`, `category` or `since` (strictly after an instant); page with the cursor. Events written in one transaction share a `createdAt`; the cursor orders them by id so none is skipped or repeated across a page boundary. A run in Trash still answers. **Path parameters** | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | string (uuid) | yes | | **Query parameters** (an unknown parameter is a 422 `validation_failed` with `errors[].code = "unknown_parameter"`) | Name | Type | Required | Description | | --- | --- | --- | --- | | `since` | string (date-time) | no | | | `type` | string | no | | | `category` | `"fields"` \\| `"tasks"` \\| `"approvals"` \\| `"files"` \\| `"comments"` \\| `"run"` | no | | | `order` | `"asc"` \\| `"desc"` | no | | | `limit` | integer | no | | | `cursor` | string | no | The `nextCursor` of the previous page. Opaque, and bound to the query that produced it — a cursor from a different filter or sort is a 422. | **Responses** | Status | Meaning | Body | | --- | --- | --- | | 200 | OK. | [ListRunActivityResponse](https://developers.processcart.com/reference/schemas#listrunactivityresponse) | | 401 | No usable credential. `unauthorized` — Authentication required; `invalid_api_key` — Invalid API key; `key_revoked` — API key revoked. | [Problem](https://developers.processcart.com/errors) | | 403 | `insufficient_scope` — Insufficient scope; `not_allowed_for_key` — Not allowed for this key. | [Problem](https://developers.processcart.com/errors) | | 404 | `not_found` — Not found. | [Problem](https://developers.processcart.com/errors) | | 405 | The verb is not supported on this path. `method_not_allowed` — Method not allowed. | [Problem](https://developers.processcart.com/errors) | | 422 | `validation_failed` — Validation failed. | [Problem](https://developers.processcart.com/errors) | | 429 | `rate_limited` — Too many requests. | [Problem](https://developers.processcart.com/errors) | | 500 | `internal_error` — Internal error. | [Problem](https://developers.processcart.com/errors) | | 503 | `unavailable` — Service unavailable. | [Problem](https://developers.processcart.com/errors) | **Errors** (every code this operation can answer; see [Errors](https://developers.processcart.com/errors)) - 401 [`invalid_api_key`](https://developers.processcart.com/errors#invalid_api_key) — Invalid API key - 401 [`key_revoked`](https://developers.processcart.com/errors#key_revoked) — API key revoked - 401 [`unauthorized`](https://developers.processcart.com/errors#unauthorized) — Authentication required - 403 [`insufficient_scope`](https://developers.processcart.com/errors#insufficient_scope) — Insufficient scope - 403 [`not_allowed_for_key`](https://developers.processcart.com/errors#not_allowed_for_key) — Not allowed for this key - 404 [`not_found`](https://developers.processcart.com/errors#not_found) — Not found - 405 [`method_not_allowed`](https://developers.processcart.com/errors#method_not_allowed) — Method not allowed - 422 [`validation_failed`](https://developers.processcart.com/errors#validation_failed) — Validation failed - 429 [`rate_limited`](https://developers.processcart.com/errors#rate_limited) — Too many requests - 500 [`internal_error`](https://developers.processcart.com/errors#internal_error) — Internal error - 503 [`unavailable`](https://developers.processcart.com/errors#unavailable) — Service unavailable **Example response** (200) ```json { "data": [ { "id": "c0638871-0f3e-40c9-8693-a02cb7419418", "runId": "3245e6a5-3fbe-463a-ad0b-a7fd871957b0", "type": "field_changed", "category": "fields", "actor": { "kind": "api", "name": "collab key", "keyId": "d422b49f-a3f2-4244-b97e-8d50f25c3036" }, "taskKey": "details", "payload": { "to": 900, "from": null, "label": "Quantity", "fieldKey": "quantity" }, "description": "API key collab key set Quantity: — → 900", "createdAt": "2026-10-13T01:10:04.624Z" }, { "id": "af048190-cc58-4541-a8cc-23acc1e831f1", "runId": "3245e6a5-3fbe-463a-ad0b-a7fd871957b0", "type": "field_changed", "category": "fields", "actor": { "kind": "api", "name": "collab key", "keyId": "d422b49f-a3f2-4244-b97e-8d50f25c3036" }, "taskKey": "details", "payload": { "to": 1700, "from": null, "label": "Amount", "fieldKey": "amount" }, "description": "API key collab key set Amount: — → $1,700.00", "createdAt": "2026-10-13T01:10:04.624Z" }, { "id": "37edb078-ad7b-42c2-ad4b-d585e4cb2496", "runId": "3245e6a5-3fbe-463a-ad0b-a7fd871957b0", "type": "field_changed", "category": "fields", "actor": { "kind": "api", "name": "collab key", "keyId": "d422b49f-a3f2-4244-b97e-8d50f25c3036" }, "taskKey": "details", "payload": { "to": "Gift note inside", "from": null, "label": "Memo", "fieldKey": "memo" }, "description": "API key collab key set Memo: — → Gift note inside", "createdAt": "2026-10-13T01:10:04.624Z" } ], "nextCursor": null, "hasMore": false } ``` --- # Views endpoints > Shared saved views (filters + sort) and the runs they select. Every operation with parameters, body, responses, errors and examples. Source: https://developers.processcart.com/reference/views Shared saved views (filters + sort) and the runs they select. Base URL `https://app.processcart.com/api/v1`. Every operation below is also in the [OpenAPI document](https://developers.processcart.com/openapi.json) and the [interactive reference](https://developers.processcart.com/reference). | Operation | Method and path | Scope | | --- | --- | --- | | [List shared views](#listViews) | GET /v1/views | Scope `views:read` | | [Create a shared view](#createView) | POST /v1/views | Scope `views:write` | | [Get a shared view](#getView) | GET /v1/views/{id} | Scope `views:read` | | [Update a shared view](#updateView) | PATCH /v1/views/{id} | Scope `views:write` | | [Delete a shared view](#deleteView) | DELETE /v1/views/{id} | Scope `views:write` | | [List the runs a view selects](#listViewRuns) | GET /v1/views/{id}/runs | Scope `runs:read` | ## GET /v1/views **List shared views.** Scope `views:read` · paginated (`{ data, nextCursor, hasMore }`). Operation id `listViews` · [open in the API reference](https://developers.processcart.com/reference#tag/views/GET/v1/views). Every saved view shared with the workspace (`org`) or with groups (`groups`), by name, with the board query each one selects. Private views never appear. `updatedSince` turns the list into an incremental sync ordered by `updatedAt` ascending (inclusive). **Query parameters** (an unknown parameter is a 422 `validation_failed` with `errors[].code = "unknown_parameter"`) | Name | Type | Required | Description | | --- | --- | --- | --- | | `updatedSince` | string (date-time) | no | Incremental sync: only items whose `updatedAt` is at or after this instant (inclusive), ordered by `updatedAt` ascending; an explicit conflicting `sort` / `order` is a 422. Overlap by a minute and dedupe: a Run by `(id, revision)`, any other item (workflow, view, webhook, schedule, automation rule) by `(id, updatedAt)` — a higher `revision` or a later `updatedAt` replaces the copy you hold, an equal one is unchanged. | | `limit` | integer | no | | | `cursor` | string | no | The `nextCursor` of the previous page. Opaque, and bound to the query that produced it — a cursor from a different filter or sort is a 422. | **Responses** | Status | Meaning | Body | | --- | --- | --- | | 200 | OK. | [ListViewsResponse](https://developers.processcart.com/reference/schemas#listviewsresponse) | | 401 | No usable credential. `unauthorized` — Authentication required; `invalid_api_key` — Invalid API key; `key_revoked` — API key revoked. | [Problem](https://developers.processcart.com/errors) | | 403 | `insufficient_scope` — Insufficient scope. | [Problem](https://developers.processcart.com/errors) | | 405 | The verb is not supported on this path. `method_not_allowed` — Method not allowed. | [Problem](https://developers.processcart.com/errors) | | 422 | `validation_failed` — Validation failed. | [Problem](https://developers.processcart.com/errors) | | 429 | `rate_limited` — Too many requests. | [Problem](https://developers.processcart.com/errors) | | 500 | `internal_error` — Internal error. | [Problem](https://developers.processcart.com/errors) | | 503 | `unavailable` — Service unavailable. | [Problem](https://developers.processcart.com/errors) | **Errors** (every code this operation can answer; see [Errors](https://developers.processcart.com/errors)) - 401 [`invalid_api_key`](https://developers.processcart.com/errors#invalid_api_key) — Invalid API key - 401 [`key_revoked`](https://developers.processcart.com/errors#key_revoked) — API key revoked - 401 [`unauthorized`](https://developers.processcart.com/errors#unauthorized) — Authentication required - 403 [`insufficient_scope`](https://developers.processcart.com/errors#insufficient_scope) — Insufficient scope - 405 [`method_not_allowed`](https://developers.processcart.com/errors#method_not_allowed) — Method not allowed - 422 [`validation_failed`](https://developers.processcart.com/errors#validation_failed) — Validation failed - 429 [`rate_limited`](https://developers.processcart.com/errors#rate_limited) — Too many requests - 500 [`internal_error`](https://developers.processcart.com/errors#internal_error) — Internal error - 503 [`unavailable`](https://developers.processcart.com/errors#unavailable) — Service unavailable **Example response** (200) ```json { "data": [ { "id": "d9d20dae-2010-4211-8b78-d724b96d3125", "name": "Alpha board", "query": { "workflowId": "165fe8d7-471f-4748-b8ab-a6ee3478cd7d", "status": [], "priority": [], "labels": [], "assignee": null, "due": null, "q": "", "qMode": "prefix", "sort": { "key": "started", "dir": "desc" }, "columns": [ "status", "priority", "progress", "key_date", "due", "assignees", "started" ] }, "visibility": "org", "groupIds": [], "owner": { "userId": "fde8fad1-9f03-4acb-bafe-91b9a9583a60", "name": "Fixture Admin" }, "createdAt": "2026-10-13T00:39:38.376Z", "updatedAt": "2026-10-13T00:39:38.376Z" }, { "id": "11478b4b-5c42-4bb8-829e-65d4c8493a3a", "name": "Reviewers' queue", "query": { "workflowId": null, "status": [ "active", "on_hold" ], "priority": [], "labels": [], "assignee": { "kind": "group", "id": "46139a8e-9621-4f2c-98f3-8975bb8d2589" }, "due": null, "q": "", "qMode": "prefix", "sort": { "key": "started", "dir": "desc" }, "columns": [ "status", "priority", "progress", "key_date", "due", "assignees", "started" ] }, "visibility": "groups", "groupIds": [ "46139a8e-9621-4f2c-98f3-8975bb8d2589" ], "owner": { "userId": "4f9b94a3-9d38-479d-a0f3-9c8f01dcff92", "name": "Fixture Member" }, "createdAt": "2026-10-13T00:39:38.462Z", "updatedAt": "2026-10-13T00:39:38.462Z" } ], "nextCursor": null, "hasMore": false } ``` ## POST /v1/views **Create a shared view.** Scope `views:write` · accepts `Idempotency-Key`. Operation id `createView` · [open in the API reference](https://developers.processcart.com/reference#tag/views/POST/v1/views). Saves a board query as a view shared with everyone (default) or with specific groups. Send only the query members you set — the rest take the board's defaults, and the response carries the complete query. The view is owned by the member who created the API key, who must still be a Builder or Admin of the workspace (else 403 — mint a key as a current Admin). Each owner keeps at most 30 views. **Request headers** | Name | Type | Required | Description | | --- | --- | --- | --- | | `Idempotency-Key` | string | no | Makes the request safe to retry: a second request with the same key and a byte-identical body (and the same `Expected-Revision`) replays the stored response (`Idempotent-Replayed: true`); a different body with the same key is refused (422 `idempotency_key_reused`) and no second resource is created. Keys are stored per workspace for 24 hours and replayed only to the API key that made the request (or the key one rotation away from it); another API key sending the same value is refused (422 `idempotency_key_reused`), so prefix keys per integration. 409, 429 and 5xx answers are never stored. | **Request body** — `application/json`, [CreateViewBody](https://developers.processcart.com/reference/schemas#createviewbody) | Property | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | yes | | | `query` | object | yes | The board query the view selects. Send only the members you set; the rest take the board's defaults: `{"workflowId":null,"status":["active","on_hold"],"priority":[],"labels":[],"assignee":null,"due":null,"q":"","qMode":"prefix","sort":{"key":"started","dir":"desc"},"columns":["status","priority","progress","key_date","due","assignees","started"]}`. `sort.key` without `dir` takes that key's natural direction (`asc` for `name`, `key_date` and `due`, else `desc`). An unknown member is a 422. | | `visibility` | `"org"` \\| `"groups"` | no | | | `groupIds` | [Uuid](https://developers.processcart.com/reference/schemas#uuid)[] | no | | **Responses** | Status | Meaning | Body | | --- | --- | --- | | 201 | Created. `Location` names the new resource. | [View](https://developers.processcart.com/reference/schemas#view) | | 401 | No usable credential. `unauthorized` — Authentication required; `invalid_api_key` — Invalid API key; `key_revoked` — API key revoked. | [Problem](https://developers.processcart.com/errors) | | 403 | `insufficient_scope` — Insufficient scope; `not_allowed_for_key` — Not allowed for this key; `read_only_key` — Read-only key. | [Problem](https://developers.processcart.com/errors) | | 405 | The verb is not supported on this path. `method_not_allowed` — Method not allowed. | [Problem](https://developers.processcart.com/errors) | | 409 | `idempotency_in_progress` — Request in progress. | [Problem](https://developers.processcart.com/errors) | | 413 | `payload_too_large` — Payload too large. | [Problem](https://developers.processcart.com/errors) | | 415 | `unsupported_media_type` — Unsupported media type. | [Problem](https://developers.processcart.com/errors) | | 422 | `idempotency_key_reused` — Idempotency key reused; `validation_failed` — Validation failed. | [Problem](https://developers.processcart.com/errors) | | 429 | `rate_limited` — Too many requests. | [Problem](https://developers.processcart.com/errors) | | 500 | `internal_error` — Internal error. | [Problem](https://developers.processcart.com/errors) | | 503 | `unavailable` — Service unavailable. | [Problem](https://developers.processcart.com/errors) | **Errors** (every code this operation can answer; see [Errors](https://developers.processcart.com/errors)) - 401 [`invalid_api_key`](https://developers.processcart.com/errors#invalid_api_key) — Invalid API key - 401 [`key_revoked`](https://developers.processcart.com/errors#key_revoked) — API key revoked - 401 [`unauthorized`](https://developers.processcart.com/errors#unauthorized) — Authentication required - 403 [`insufficient_scope`](https://developers.processcart.com/errors#insufficient_scope) — Insufficient scope - 403 [`not_allowed_for_key`](https://developers.processcart.com/errors#not_allowed_for_key) — Not allowed for this key - 403 [`read_only_key`](https://developers.processcart.com/errors#read_only_key) — Read-only key - 405 [`method_not_allowed`](https://developers.processcart.com/errors#method_not_allowed) — Method not allowed - 409 [`idempotency_in_progress`](https://developers.processcart.com/errors#idempotency_in_progress) — Request in progress - 413 [`payload_too_large`](https://developers.processcart.com/errors#payload_too_large) — Payload too large - 415 [`unsupported_media_type`](https://developers.processcart.com/errors#unsupported_media_type) — Unsupported media type - 422 [`idempotency_key_reused`](https://developers.processcart.com/errors#idempotency_key_reused) — Idempotency key reused - 422 [`validation_failed`](https://developers.processcart.com/errors#validation_failed) — Validation failed - 429 [`rate_limited`](https://developers.processcart.com/errors#rate_limited) — Too many requests - 500 [`internal_error`](https://developers.processcart.com/errors#internal_error) — Internal error - 503 [`unavailable`](https://developers.processcart.com/errors#unavailable) — Service unavailable **Example request** ```json { "name": "Active by name", "query": { "workflowId": "165fe8d7-471f-4748-b8ab-a6ee3478cd7d", "status": [ "active" ], "sort": { "key": "name" } } } ``` **Example response** (201) ```json { "id": "6885f52f-03a9-464e-b9bb-95b359a3a57b", "name": "Active by name", "query": { "workflowId": "165fe8d7-471f-4748-b8ab-a6ee3478cd7d", "status": [ "active" ], "priority": [], "labels": [], "assignee": null, "due": null, "q": "", "qMode": "prefix", "sort": { "key": "name", "dir": "asc" }, "columns": [ "status", "priority", "progress", "key_date", "due", "assignees", "started" ] }, "visibility": "org", "groupIds": [], "owner": { "userId": "fde8fad1-9f03-4acb-bafe-91b9a9583a60", "name": "Fixture Admin" }, "createdAt": "2026-10-13T00:39:39.520Z", "updatedAt": "2026-10-13T00:39:39.520Z" } ``` ## GET /v1/views/{id} **Get a shared view.** Scope `views:read`. Operation id `getView` · [open in the API reference](https://developers.processcart.com/reference#tag/views/GET/v1/views/{id}). One shared view with its query. A private view — even one owned by the member who created the key — is a 404. **Path parameters** | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | string (uuid) | yes | | **Responses** | Status | Meaning | Body | | --- | --- | --- | | 200 | OK. | [View](https://developers.processcart.com/reference/schemas#view) | | 401 | No usable credential. `unauthorized` — Authentication required; `invalid_api_key` — Invalid API key; `key_revoked` — API key revoked. | [Problem](https://developers.processcart.com/errors) | | 403 | `insufficient_scope` — Insufficient scope. | [Problem](https://developers.processcart.com/errors) | | 404 | `not_found` — Not found. | [Problem](https://developers.processcart.com/errors) | | 405 | The verb is not supported on this path. `method_not_allowed` — Method not allowed. | [Problem](https://developers.processcart.com/errors) | | 422 | `validation_failed` — Validation failed. | [Problem](https://developers.processcart.com/errors) | | 429 | `rate_limited` — Too many requests. | [Problem](https://developers.processcart.com/errors) | | 500 | `internal_error` — Internal error. | [Problem](https://developers.processcart.com/errors) | | 503 | `unavailable` — Service unavailable. | [Problem](https://developers.processcart.com/errors) | **Errors** (every code this operation can answer; see [Errors](https://developers.processcart.com/errors)) - 401 [`invalid_api_key`](https://developers.processcart.com/errors#invalid_api_key) — Invalid API key - 401 [`key_revoked`](https://developers.processcart.com/errors#key_revoked) — API key revoked - 401 [`unauthorized`](https://developers.processcart.com/errors#unauthorized) — Authentication required - 403 [`insufficient_scope`](https://developers.processcart.com/errors#insufficient_scope) — Insufficient scope - 404 [`not_found`](https://developers.processcart.com/errors#not_found) — Not found - 405 [`method_not_allowed`](https://developers.processcart.com/errors#method_not_allowed) — Method not allowed - 422 [`validation_failed`](https://developers.processcart.com/errors#validation_failed) — Validation failed - 429 [`rate_limited`](https://developers.processcart.com/errors#rate_limited) — Too many requests - 500 [`internal_error`](https://developers.processcart.com/errors#internal_error) — Internal error - 503 [`unavailable`](https://developers.processcart.com/errors#unavailable) — Service unavailable **Example response** (200) ```json { "id": "d9d20dae-2010-4211-8b78-d724b96d3125", "name": "Alpha board", "query": { "workflowId": "165fe8d7-471f-4748-b8ab-a6ee3478cd7d", "status": [], "priority": [], "labels": [], "assignee": null, "due": null, "q": "", "qMode": "prefix", "sort": { "key": "started", "dir": "desc" }, "columns": [ "status", "priority", "progress", "key_date", "due", "assignees", "started" ] }, "visibility": "org", "groupIds": [], "owner": { "userId": "fde8fad1-9f03-4acb-bafe-91b9a9583a60", "name": "Fixture Admin" }, "createdAt": "2026-10-13T00:39:38.376Z", "updatedAt": "2026-10-13T00:39:38.376Z" } ``` ## PATCH /v1/views/{id} **Update a shared view.** Scope `views:write` · accepts `Idempotency-Key`. Operation id `updateView` · [open in the API reference](https://developers.processcart.com/reference#tag/views/PATCH/v1/views/{id}). Renames a view, changes its query or changes who it is shared with. A `query` changes only the members it sends; the rest keep the view's stored values. Only a view owned by the member who created the key can be changed through the API, and that member must still be a Builder or Admin of the workspace (403 `not_allowed_for_key` otherwise). Switching to `org` drops the group list; `groups` needs at least one group of the workspace. **Path parameters** | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | string (uuid) | yes | | **Request headers** | Name | Type | Required | Description | | --- | --- | --- | --- | | `Idempotency-Key` | string | no | Makes the request safe to retry: a second request with the same key and a byte-identical body (and the same `Expected-Revision`) replays the stored response (`Idempotent-Replayed: true`); a different body with the same key is refused (422 `idempotency_key_reused`) and no second resource is created. Keys are stored per workspace for 24 hours and replayed only to the API key that made the request (or the key one rotation away from it); another API key sending the same value is refused (422 `idempotency_key_reused`), so prefix keys per integration. 409, 429 and 5xx answers are never stored. | **Request body** — `application/json`, [UpdateViewBody](https://developers.processcart.com/reference/schemas#updateviewbody) | Property | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | no | | | `query` | object | no | Changes to the view's board query. Send only the members you change; the rest keep the view's stored values (`null` clears `workflowId`, `assignee` or `due`). `sort.key` without `dir` takes that key's natural direction. An unknown member is a 422. | | `visibility` | `"org"` \\| `"groups"` | no | | | `groupIds` | [Uuid](https://developers.processcart.com/reference/schemas#uuid)[] | no | | **Responses** | Status | Meaning | Body | | --- | --- | --- | | 200 | OK. | [View](https://developers.processcart.com/reference/schemas#view) | | 401 | No usable credential. `unauthorized` — Authentication required; `invalid_api_key` — Invalid API key; `key_revoked` — API key revoked. | [Problem](https://developers.processcart.com/errors) | | 403 | `insufficient_scope` — Insufficient scope; `not_allowed_for_key` — Not allowed for this key; `read_only_key` — Read-only key. | [Problem](https://developers.processcart.com/errors) | | 404 | `not_found` — Not found. | [Problem](https://developers.processcart.com/errors) | | 405 | The verb is not supported on this path. `method_not_allowed` — Method not allowed. | [Problem](https://developers.processcart.com/errors) | | 409 | `idempotency_in_progress` — Request in progress. | [Problem](https://developers.processcart.com/errors) | | 413 | `payload_too_large` — Payload too large. | [Problem](https://developers.processcart.com/errors) | | 415 | `unsupported_media_type` — Unsupported media type. | [Problem](https://developers.processcart.com/errors) | | 422 | `idempotency_key_reused` — Idempotency key reused; `validation_failed` — Validation failed. | [Problem](https://developers.processcart.com/errors) | | 429 | `rate_limited` — Too many requests. | [Problem](https://developers.processcart.com/errors) | | 500 | `internal_error` — Internal error. | [Problem](https://developers.processcart.com/errors) | | 503 | `unavailable` — Service unavailable. | [Problem](https://developers.processcart.com/errors) | **Errors** (every code this operation can answer; see [Errors](https://developers.processcart.com/errors)) - 401 [`invalid_api_key`](https://developers.processcart.com/errors#invalid_api_key) — Invalid API key - 401 [`key_revoked`](https://developers.processcart.com/errors#key_revoked) — API key revoked - 401 [`unauthorized`](https://developers.processcart.com/errors#unauthorized) — Authentication required - 403 [`insufficient_scope`](https://developers.processcart.com/errors#insufficient_scope) — Insufficient scope - 403 [`not_allowed_for_key`](https://developers.processcart.com/errors#not_allowed_for_key) — Not allowed for this key - 403 [`read_only_key`](https://developers.processcart.com/errors#read_only_key) — Read-only key - 404 [`not_found`](https://developers.processcart.com/errors#not_found) — Not found - 405 [`method_not_allowed`](https://developers.processcart.com/errors#method_not_allowed) — Method not allowed - 409 [`idempotency_in_progress`](https://developers.processcart.com/errors#idempotency_in_progress) — Request in progress - 413 [`payload_too_large`](https://developers.processcart.com/errors#payload_too_large) — Payload too large - 415 [`unsupported_media_type`](https://developers.processcart.com/errors#unsupported_media_type) — Unsupported media type - 422 [`idempotency_key_reused`](https://developers.processcart.com/errors#idempotency_key_reused) — Idempotency key reused - 422 [`validation_failed`](https://developers.processcart.com/errors#validation_failed) — Validation failed - 429 [`rate_limited`](https://developers.processcart.com/errors#rate_limited) — Too many requests - 500 [`internal_error`](https://developers.processcart.com/errors#internal_error) — Internal error - 503 [`unavailable`](https://developers.processcart.com/errors#unavailable) — Service unavailable **Example request** ```json { "name": "Alpha board (renamed)" } ``` **Example response** (200) ```json { "id": "d9d20dae-2010-4211-8b78-d724b96d3125", "name": "Alpha board (renamed)", "query": { "workflowId": "165fe8d7-471f-4748-b8ab-a6ee3478cd7d", "status": [], "priority": [], "labels": [], "assignee": null, "due": null, "q": "", "qMode": "prefix", "sort": { "key": "started", "dir": "desc" }, "columns": [ "status", "priority", "progress", "key_date", "due", "assignees", "started" ] }, "visibility": "groups", "groupIds": [ "46139a8e-9621-4f2c-98f3-8975bb8d2589" ], "owner": { "userId": "fde8fad1-9f03-4acb-bafe-91b9a9583a60", "name": "Fixture Admin" }, "createdAt": "2026-10-13T00:39:38.376Z", "updatedAt": "2026-10-13T00:39:41.545Z" } ``` ## DELETE /v1/views/{id} **Delete a shared view.** Scope `views:write` · accepts `Idempotency-Key`. Operation id `deleteView` · [open in the API reference](https://developers.processcart.com/reference#tag/views/DELETE/v1/views/{id}). Removes any shared view of the workspace, whoever owns it (the same power an Admin has in the app); the deletion is recorded in the audit log under the key's name. A private view is a 404. **Path parameters** | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | string (uuid) | yes | | **Request headers** | Name | Type | Required | Description | | --- | --- | --- | --- | | `Idempotency-Key` | string | no | Makes the request safe to retry: a second request with the same key and a byte-identical body (and the same `Expected-Revision`) replays the stored response (`Idempotent-Replayed: true`); a different body with the same key is refused (422 `idempotency_key_reused`) and no second resource is created. Keys are stored per workspace for 24 hours and replayed only to the API key that made the request (or the key one rotation away from it); another API key sending the same value is refused (422 `idempotency_key_reused`), so prefix keys per integration. 409, 429 and 5xx answers are never stored. | **Responses** | Status | Meaning | Body | | --- | --- | --- | | 204 | Done; no body. | — | | 401 | No usable credential. `unauthorized` — Authentication required; `invalid_api_key` — Invalid API key; `key_revoked` — API key revoked. | [Problem](https://developers.processcart.com/errors) | | 403 | `insufficient_scope` — Insufficient scope; `read_only_key` — Read-only key. | [Problem](https://developers.processcart.com/errors) | | 404 | `not_found` — Not found. | [Problem](https://developers.processcart.com/errors) | | 405 | The verb is not supported on this path. `method_not_allowed` — Method not allowed. | [Problem](https://developers.processcart.com/errors) | | 409 | `idempotency_in_progress` — Request in progress. | [Problem](https://developers.processcart.com/errors) | | 422 | `idempotency_key_reused` — Idempotency key reused; `validation_failed` — Validation failed. | [Problem](https://developers.processcart.com/errors) | | 429 | `rate_limited` — Too many requests. | [Problem](https://developers.processcart.com/errors) | | 500 | `internal_error` — Internal error. | [Problem](https://developers.processcart.com/errors) | | 503 | `unavailable` — Service unavailable. | [Problem](https://developers.processcart.com/errors) | **Errors** (every code this operation can answer; see [Errors](https://developers.processcart.com/errors)) - 401 [`invalid_api_key`](https://developers.processcart.com/errors#invalid_api_key) — Invalid API key - 401 [`key_revoked`](https://developers.processcart.com/errors#key_revoked) — API key revoked - 401 [`unauthorized`](https://developers.processcart.com/errors#unauthorized) — Authentication required - 403 [`insufficient_scope`](https://developers.processcart.com/errors#insufficient_scope) — Insufficient scope - 403 [`read_only_key`](https://developers.processcart.com/errors#read_only_key) — Read-only key - 404 [`not_found`](https://developers.processcart.com/errors#not_found) — Not found - 405 [`method_not_allowed`](https://developers.processcart.com/errors#method_not_allowed) — Method not allowed - 409 [`idempotency_in_progress`](https://developers.processcart.com/errors#idempotency_in_progress) — Request in progress - 422 [`idempotency_key_reused`](https://developers.processcart.com/errors#idempotency_key_reused) — Idempotency key reused - 422 [`validation_failed`](https://developers.processcart.com/errors#validation_failed) — Validation failed - 429 [`rate_limited`](https://developers.processcart.com/errors#rate_limited) — Too many requests - 500 [`internal_error`](https://developers.processcart.com/errors#internal_error) — Internal error - 503 [`unavailable`](https://developers.processcart.com/errors#unavailable) — Service unavailable ## GET /v1/views/{id}/runs **List the runs a view selects.** Scope `runs:read` · the key's allowlist narrows the rows (never a 403 — rows outside it are absent) · paginated (`{ data, nextCursor, hasMore }`). Operation id `listViewRuns` · [open in the API reference](https://developers.processcart.com/reference#tag/views/GET/v1/views/{id}/runs). Applies the view's stored query — filters and sort, exactly as the board does — and returns FULL Run resources, paged like `GET /v1/runs` and limited to the key's allowlist. A view that filters by the viewer (`assignee: me`) cannot be resolved for a key: call `GET /v1/runs?assignee=u:` instead (422). A view sorted by progress is a 422 too (that sort is computed in the app). `updatedSince` orders by `updatedAt` ascending instead of the view's sort. **Path parameters** | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | string (uuid) | yes | | **Query parameters** (an unknown parameter is a 422 `validation_failed` with `errors[].code = "unknown_parameter"`) | Name | Type | Required | Description | | --- | --- | --- | --- | | `updatedSince` | string (date-time) | no | Incremental sync: only items whose `updatedAt` is at or after this instant (inclusive), ordered by `updatedAt` ascending; an explicit conflicting `sort` / `order` is a 422. Overlap by a minute and dedupe: a Run by `(id, revision)`, any other item (workflow, view, webhook, schedule, automation rule) by `(id, updatedAt)` — a higher `revision` or a later `updatedAt` replaces the copy you hold, an equal one is unchanged. | | `limit` | integer | no | | | `cursor` | string | no | The `nextCursor` of the previous page. Opaque, and bound to the query that produced it — a cursor from a different filter or sort is a 422. | **Responses** | Status | Meaning | Body | | --- | --- | --- | | 200 | OK. | [ListViewRunsResponse](https://developers.processcart.com/reference/schemas#listviewrunsresponse) | | 401 | No usable credential. `unauthorized` — Authentication required; `invalid_api_key` — Invalid API key; `key_revoked` — API key revoked. | [Problem](https://developers.processcart.com/errors) | | 403 | `insufficient_scope` — Insufficient scope. | [Problem](https://developers.processcart.com/errors) | | 404 | `not_found` — Not found. | [Problem](https://developers.processcart.com/errors) | | 405 | The verb is not supported on this path. `method_not_allowed` — Method not allowed. | [Problem](https://developers.processcart.com/errors) | | 422 | `validation_failed` — Validation failed. | [Problem](https://developers.processcart.com/errors) | | 429 | `rate_limited` — Too many requests. | [Problem](https://developers.processcart.com/errors) | | 500 | `internal_error` — Internal error. | [Problem](https://developers.processcart.com/errors) | | 503 | `unavailable` — Service unavailable. | [Problem](https://developers.processcart.com/errors) | **Errors** (every code this operation can answer; see [Errors](https://developers.processcart.com/errors)) - 401 [`invalid_api_key`](https://developers.processcart.com/errors#invalid_api_key) — Invalid API key - 401 [`key_revoked`](https://developers.processcart.com/errors#key_revoked) — API key revoked - 401 [`unauthorized`](https://developers.processcart.com/errors#unauthorized) — Authentication required - 403 [`insufficient_scope`](https://developers.processcart.com/errors#insufficient_scope) — Insufficient scope - 404 [`not_found`](https://developers.processcart.com/errors#not_found) — Not found - 405 [`method_not_allowed`](https://developers.processcart.com/errors#method_not_allowed) — Method not allowed - 422 [`validation_failed`](https://developers.processcart.com/errors#validation_failed) — Validation failed - 429 [`rate_limited`](https://developers.processcart.com/errors#rate_limited) — Too many requests - 500 [`internal_error`](https://developers.processcart.com/errors#internal_error) — Internal error - 503 [`unavailable`](https://developers.processcart.com/errors#unavailable) — Service unavailable **Example response** (200) ```json { "data": [ { "id": "c4f32706-55c2-4524-8193-8ca38969a675", "name": "View run one", "status": "active", "priority": "none", "labels": [], "labelDetails": [], "dueAt": null, "keyDate": null, "revision": 1, "updatedAt": "2026-10-13T00:39:49.561Z", "startedAt": "2026-10-13T00:39:49.462Z", "startedBy": "api", "startedByUserId": null, "schedule": null, "scheduledFor": null, "completedAt": null, "deletedAt": null, "workflow": { "id": "165fe8d7-471f-4748-b8ab-a6ee3478cd7d", "name": "Order fulfillment" }, "versionId": "36e87c9b-845a-4294-942e-809c38fa6137", "url": "https://app.processcart.com/runs/c4f32706-55c2-4524-8193-8ca38969a675", "tasks": [ { "key": "details", "title": "Details", "kind": "standard", "section": "Intake", "state": "todo", "visible": true, "locked": false, "dueAt": null, "assignees": [], "completedAt": null, "completedByUserId": null }, { "key": "review", "title": "Review", "kind": "approval", "section": "Intake", "state": "todo", "visible": true, "locked": false, "dueAt": null, "assignees": [ { "kind": "user", "userId": "fde8fad1-9f03-4acb-bafe-91b9a9583a60" } ], "completedAt": null, "completedByUserId": null }, { "key": "stop", "title": "Sign-off", "kind": "standard", "section": "Intake", "state": "todo", "visible": true, "locked": false, "dueAt": null, "assignees": [], "completedAt": null, "completedByUserId": null }, { "key": "wrapup", "title": "Wrap up", "kind": "standard", "section": "Intake", "state": "todo", "visible": true, "locked": true, "dueAt": null, "assignees": [], "completedAt": null, "completedByUserId": null } ], "fields": [ { "key": "customer", "label": "Customer", "type": "text", "format": "none", "taskKey": "details", "value": "Ann", "formatted": "Ann", "visible": true }, { "key": "order_code", "label": "Order code", "type": "text", "format": "custom", "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "memo", "label": "Memo", "type": "textarea", "format": null, "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "quantity", "label": "Quantity", "type": "number", "format": "number", "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "amount", "label": "Amount", "type": "number", "format": "currency", "taskKey": "details", "value": 1700, "formatted": "$1,700.00", "visible": true }, { "key": "fee", "label": "Fee", "type": "number", "format": "currency", "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "rate", "label": "Rate", "type": "number", "format": "percent", "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "rate_text", "label": "Rate (text)", "type": "text", "format": "percent", "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "ship_date", "label": "Ship date", "type": "date", "format": "date", "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "start_time", "label": "Start time", "type": "time", "format": "time", "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "gift_wrap", "label": "Gift wrap", "type": "dropdown", "format": null, "taskKey": "details", "value": null, "formatted": "", "visible": true, "choices": [ "Yes", "No" ] }, { "key": "tags", "label": "Tags", "type": "multi_choice", "format": null, "taskKey": "details", "value": null, "formatted": "", "visible": true, "choices": [ "A", "B", "C" ] }, { "key": "agree", "label": "Agree", "type": "checkbox", "format": null, "taskKey": "details", "value": false, "formatted": "No", "visible": true }, { "key": "contact_email", "label": "Contact email", "type": "email", "format": "email", "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "website", "label": "Website", "type": "url", "format": "url", "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "phone", "label": "Phone", "type": "phone", "format": "phone", "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "owner", "label": "Owner", "type": "members", "format": null, "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "steps", "label": "Steps", "type": "subtasks", "format": null, "taskKey": "details", "value": null, "formatted": "", "visible": true, "items": [ { "key": "photos", "label": "Photos" }, { "key": "invoice", "label": "Invoice" } ] }, { "key": "contract", "label": "Contract", "type": "file", "format": null, "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "double_amount", "label": "Double amount", "type": "formula", "format": "currency", "taskKey": "details", "value": 3400, "formatted": "$3,400.00", "visible": true }, { "key": "source", "label": "Source", "type": "hidden", "format": null, "taskKey": "details", "value": null, "formatted": "", "visible": false } ] } ], "nextCursor": null, "hasMore": false } ``` --- # Webhooks endpoints > Webhook endpoints, their signing secrets and the delivery log. Every operation with parameters, body, responses, errors and examples. Source: https://developers.processcart.com/reference/webhooks Webhook endpoints, their signing secrets and the delivery log. Base URL `https://app.processcart.com/api/v1`. Every operation below is also in the [OpenAPI document](https://developers.processcart.com/openapi.json) and the [interactive reference](https://developers.processcart.com/reference). | Operation | Method and path | Scope | | --- | --- | --- | | [List webhooks](#listWebhooks) | GET /v1/webhooks | Scope `webhooks:manage` | | [Create a webhook](#createWebhook) | POST /v1/webhooks | Scope `webhooks:manage` | | [Get a webhook](#getWebhook) | GET /v1/webhooks/{id} | Scope `webhooks:manage` | | [Update a webhook](#updateWebhook) | PATCH /v1/webhooks/{id} | Scope `webhooks:manage` | | [Delete a webhook](#deleteWebhook) | DELETE /v1/webhooks/{id} | Scope `webhooks:manage` | | [Enable a webhook](#enableWebhook) | POST /v1/webhooks/{id}/enable | Scope `webhooks:manage` | | [Disable a webhook](#disableWebhook) | POST /v1/webhooks/{id}/disable | Scope `webhooks:manage` | | [Rotate a webhook's signing secret](#rotateWebhookSecret) | POST /v1/webhooks/{id}/rotate-secret | Scope `webhooks:manage` | | [Send a test event](#testWebhook) | POST /v1/webhooks/{id}/test | Scope `webhooks:manage` | | [List a webhook's deliveries](#listWebhookDeliveries) | GET /v1/webhooks/{id}/deliveries | Scope `webhooks:manage` | | [Get a delivery](#getWebhookDelivery) | GET /v1/webhooks/{id}/deliveries/{deliveryId} | Scope `webhooks:manage` | | [Send a delivery again](#redeliverWebhookDelivery) | POST /v1/webhooks/{id}/deliveries/{deliveryId}/redeliver | Scope `webhooks:manage` | ## GET /v1/webhooks **List webhooks.** Scope `webhooks:manage` · the key's allowlist narrows the rows (never a 403 — rows outside it are absent) · paginated (`{ data, nextCursor, hasMore }`). Operation id `listWebhooks` · [open in the API reference](https://developers.processcart.com/reference#tag/webhooks/GET/v1/webhooks). Lists the workspace's webhook endpoints, newest first (no secrets). `updatedSince` turns the list into an incremental sync ordered by `updatedAt` ascending (inclusive). Keys restricted to folders cannot use the webhook operations (403 `not_allowed_for_key`); keys restricted to workflows manage only webhooks whose `workflowIds` is a non-empty subset of their allowlist, and see only deliveries about those workflows (plus test events). **Query parameters** (an unknown parameter is a 422 `validation_failed` with `errors[].code = "unknown_parameter"`) | Name | Type | Required | Description | | --- | --- | --- | --- | | `updatedSince` | string (date-time) | no | Incremental sync: only items whose `updatedAt` is at or after this instant (inclusive), ordered by `updatedAt` ascending; an explicit conflicting `sort` / `order` is a 422. Overlap by a minute and dedupe: a Run by `(id, revision)`, any other item (workflow, view, webhook, schedule, automation rule) by `(id, updatedAt)` — a higher `revision` or a later `updatedAt` replaces the copy you hold, an equal one is unchanged. | | `limit` | integer | no | | | `cursor` | string | no | The `nextCursor` of the previous page. Opaque, and bound to the query that produced it — a cursor from a different filter or sort is a 422. | **Responses** | Status | Meaning | Body | | --- | --- | --- | | 200 | OK. | [WebhookList](https://developers.processcart.com/reference/schemas#webhooklist) | | 401 | No usable credential. `unauthorized` — Authentication required; `invalid_api_key` — Invalid API key; `key_revoked` — API key revoked. | [Problem](https://developers.processcart.com/errors) | | 403 | `insufficient_scope` — Insufficient scope; `not_allowed_for_key` — Not allowed for this key. | [Problem](https://developers.processcart.com/errors) | | 405 | The verb is not supported on this path. `method_not_allowed` — Method not allowed. | [Problem](https://developers.processcart.com/errors) | | 422 | `validation_failed` — Validation failed. | [Problem](https://developers.processcart.com/errors) | | 429 | `rate_limited` — Too many requests. | [Problem](https://developers.processcart.com/errors) | | 500 | `internal_error` — Internal error. | [Problem](https://developers.processcart.com/errors) | | 503 | `unavailable` — Service unavailable. | [Problem](https://developers.processcart.com/errors) | **Errors** (every code this operation can answer; see [Errors](https://developers.processcart.com/errors)) - 401 [`invalid_api_key`](https://developers.processcart.com/errors#invalid_api_key) — Invalid API key - 401 [`key_revoked`](https://developers.processcart.com/errors#key_revoked) — API key revoked - 401 [`unauthorized`](https://developers.processcart.com/errors#unauthorized) — Authentication required - 403 [`insufficient_scope`](https://developers.processcart.com/errors#insufficient_scope) — Insufficient scope - 403 [`not_allowed_for_key`](https://developers.processcart.com/errors#not_allowed_for_key) — Not allowed for this key - 405 [`method_not_allowed`](https://developers.processcart.com/errors#method_not_allowed) — Method not allowed - 422 [`validation_failed`](https://developers.processcart.com/errors#validation_failed) — Validation failed - 429 [`rate_limited`](https://developers.processcart.com/errors#rate_limited) — Too many requests - 500 [`internal_error`](https://developers.processcart.com/errors#internal_error) — Internal error - 503 [`unavailable`](https://developers.processcart.com/errors#unavailable) — Service unavailable **Example response** (200) ```json { "data": [ { "id": "2b64ebdd-e0ba-4d01-aca7-914b36d905aa", "url": "https://hooks.example.com/processcart", "description": "Everything", "events": [ "*" ], "workflowIds": null, "filters": {}, "enabled": true, "disabledReason": null, "disabledAt": null, "consecutiveFailures": 0, "lastDeliveryAt": null, "createdAt": "2026-10-11T05:28:25.162Z", "createdBy": null, "updatedAt": "2026-10-11T05:28:25.162Z" } ], "nextCursor": null, "hasMore": false } ``` ## POST /v1/webhooks **Create a webhook.** Scope `webhooks:manage` · accepts `Idempotency-Key`. Operation id `createWebhook` · [open in the API reference](https://developers.processcart.com/reference#tag/webhooks/POST/v1/webhooks). Subscribes an https endpoint to events (`*` = everything but the opt-in types), optionally narrowed to some workflows and, for `field.updated`, to some field keys. The response carries the Standard Webhooks signing secret ONCE — store it; rotate to get a new one. The URL is checked against the SSRF policy (public https only). Keys restricted to folders cannot use the webhook operations (403 `not_allowed_for_key`); keys restricted to workflows manage only webhooks whose `workflowIds` is a non-empty subset of their allowlist, and see only deliveries about those workflows (plus test events). **Request headers** | Name | Type | Required | Description | | --- | --- | --- | --- | | `Idempotency-Key` | string | no | Makes the request safe to retry: a second request with the same key and a byte-identical body (and the same `Expected-Revision`) replays the stored response (`Idempotent-Replayed: true`); a different body with the same key is refused (422 `idempotency_key_reused`) and no second resource is created. Keys are stored per workspace for 24 hours and replayed only to the API key that made the request (or the key one rotation away from it); another API key sending the same value is refused (422 `idempotency_key_reused`), so prefix keys per integration. 409, 429 and 5xx answers are never stored. | **Request body** — `application/json`, [WebhookConfig](https://developers.processcart.com/reference/schemas#webhookconfig) | Property | Type | Required | Description | | --- | --- | --- | --- | | `url` | string | yes | | | `description` | string | no | | | `events` | [EventType](https://developers.processcart.com/reference/schemas#eventtype) \\| `"*"`[] | yes | | | `workflowIds` | [Uuid](https://developers.processcart.com/reference/schemas#uuid)[] \\| null | no | | | `filters` | object | no | | **Responses** | Status | Meaning | Body | | --- | --- | --- | | 201 | Created. `Location` names the new resource. | [WebhookWithSecret](https://developers.processcart.com/reference/schemas#webhookwithsecret) | | 401 | No usable credential. `unauthorized` — Authentication required; `invalid_api_key` — Invalid API key; `key_revoked` — API key revoked. | [Problem](https://developers.processcart.com/errors) | | 403 | `insufficient_scope` — Insufficient scope; `not_allowed_for_key` — Not allowed for this key; `read_only_key` — Read-only key. | [Problem](https://developers.processcart.com/errors) | | 405 | The verb is not supported on this path. `method_not_allowed` — Method not allowed. | [Problem](https://developers.processcart.com/errors) | | 409 | `idempotency_in_progress` — Request in progress. | [Problem](https://developers.processcart.com/errors) | | 413 | `payload_too_large` — Payload too large. | [Problem](https://developers.processcart.com/errors) | | 415 | `unsupported_media_type` — Unsupported media type. | [Problem](https://developers.processcart.com/errors) | | 422 | `idempotency_key_reused` — Idempotency key reused; `validation_failed` — Validation failed. | [Problem](https://developers.processcart.com/errors) | | 429 | `rate_limited` — Too many requests. | [Problem](https://developers.processcart.com/errors) | | 500 | `internal_error` — Internal error. | [Problem](https://developers.processcart.com/errors) | | 503 | `unavailable` — Service unavailable. | [Problem](https://developers.processcart.com/errors) | **Errors** (every code this operation can answer; see [Errors](https://developers.processcart.com/errors)) - 401 [`invalid_api_key`](https://developers.processcart.com/errors#invalid_api_key) — Invalid API key - 401 [`key_revoked`](https://developers.processcart.com/errors#key_revoked) — API key revoked - 401 [`unauthorized`](https://developers.processcart.com/errors#unauthorized) — Authentication required - 403 [`insufficient_scope`](https://developers.processcart.com/errors#insufficient_scope) — Insufficient scope - 403 [`not_allowed_for_key`](https://developers.processcart.com/errors#not_allowed_for_key) — Not allowed for this key - 403 [`read_only_key`](https://developers.processcart.com/errors#read_only_key) — Read-only key - 405 [`method_not_allowed`](https://developers.processcart.com/errors#method_not_allowed) — Method not allowed - 409 [`idempotency_in_progress`](https://developers.processcart.com/errors#idempotency_in_progress) — Request in progress - 413 [`payload_too_large`](https://developers.processcart.com/errors#payload_too_large) — Payload too large - 415 [`unsupported_media_type`](https://developers.processcart.com/errors#unsupported_media_type) — Unsupported media type - 422 [`idempotency_key_reused`](https://developers.processcart.com/errors#idempotency_key_reused) — Idempotency key reused - 422 [`validation_failed`](https://developers.processcart.com/errors#validation_failed) — Validation failed - 429 [`rate_limited`](https://developers.processcart.com/errors#rate_limited) — Too many requests - 500 [`internal_error`](https://developers.processcart.com/errors#internal_error) — Internal error - 503 [`unavailable`](https://developers.processcart.com/errors#unavailable) — Service unavailable **Example request** ```json { "url": "https://hooks.example.com/processcart", "description": "Everything", "events": [ "*" ] } ``` **Example response** (201) ```json { "id": "2b64ebdd-e0ba-4d01-aca7-914b36d905aa", "url": "https://hooks.example.com/processcart", "description": "Everything", "events": [ "*" ], "workflowIds": null, "filters": {}, "enabled": true, "disabledReason": null, "disabledAt": null, "consecutiveFailures": 0, "lastDeliveryAt": null, "createdAt": "2026-10-11T05:28:25.162Z", "createdBy": null, "updatedAt": "2026-10-11T05:28:25.162Z", "secret": "whsec_EXAMPLEEXAMPLEEXAMPLEEXAMPLEEXAMPLEEXAMPLE" } ``` ## GET /v1/webhooks/{id} **Get a webhook.** Scope `webhooks:manage`. Operation id `getWebhook` · [open in the API reference](https://developers.processcart.com/reference#tag/webhooks/GET/v1/webhooks/{id}). Returns one webhook endpoint with its subscription, filters, enabled state, failure counter and last delivery time — never the secret. **Path parameters** | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | string (uuid) | yes | | **Responses** | Status | Meaning | Body | | --- | --- | --- | | 200 | OK. | [Webhook](https://developers.processcart.com/reference/schemas#webhook) | | 401 | No usable credential. `unauthorized` — Authentication required; `invalid_api_key` — Invalid API key; `key_revoked` — API key revoked. | [Problem](https://developers.processcart.com/errors) | | 403 | `insufficient_scope` — Insufficient scope; `not_allowed_for_key` — Not allowed for this key. | [Problem](https://developers.processcart.com/errors) | | 404 | `not_found` — Not found. | [Problem](https://developers.processcart.com/errors) | | 405 | The verb is not supported on this path. `method_not_allowed` — Method not allowed. | [Problem](https://developers.processcart.com/errors) | | 422 | `validation_failed` — Validation failed. | [Problem](https://developers.processcart.com/errors) | | 429 | `rate_limited` — Too many requests. | [Problem](https://developers.processcart.com/errors) | | 500 | `internal_error` — Internal error. | [Problem](https://developers.processcart.com/errors) | | 503 | `unavailable` — Service unavailable. | [Problem](https://developers.processcart.com/errors) | **Errors** (every code this operation can answer; see [Errors](https://developers.processcart.com/errors)) - 401 [`invalid_api_key`](https://developers.processcart.com/errors#invalid_api_key) — Invalid API key - 401 [`key_revoked`](https://developers.processcart.com/errors#key_revoked) — API key revoked - 401 [`unauthorized`](https://developers.processcart.com/errors#unauthorized) — Authentication required - 403 [`insufficient_scope`](https://developers.processcart.com/errors#insufficient_scope) — Insufficient scope - 403 [`not_allowed_for_key`](https://developers.processcart.com/errors#not_allowed_for_key) — Not allowed for this key - 404 [`not_found`](https://developers.processcart.com/errors#not_found) — Not found - 405 [`method_not_allowed`](https://developers.processcart.com/errors#method_not_allowed) — Method not allowed - 422 [`validation_failed`](https://developers.processcart.com/errors#validation_failed) — Validation failed - 429 [`rate_limited`](https://developers.processcart.com/errors#rate_limited) — Too many requests - 500 [`internal_error`](https://developers.processcart.com/errors#internal_error) — Internal error - 503 [`unavailable`](https://developers.processcart.com/errors#unavailable) — Service unavailable **Example response** (200) ```json { "id": "2b64ebdd-e0ba-4d01-aca7-914b36d905aa", "url": "https://hooks.example.com/processcart", "description": "Everything", "events": [ "*" ], "workflowIds": null, "filters": {}, "enabled": true, "disabledReason": null, "disabledAt": null, "consecutiveFailures": 0, "lastDeliveryAt": null, "createdAt": "2026-10-11T05:28:25.162Z", "createdBy": null, "updatedAt": "2026-10-11T05:28:25.162Z" } ``` ## PATCH /v1/webhooks/{id} **Update a webhook.** Scope `webhooks:manage` · accepts `Idempotency-Key`. Operation id `updateWebhook` · [open in the API reference](https://developers.processcart.com/reference#tag/webhooks/PATCH/v1/webhooks/{id}). Changes any subset of the configuration: `url` (re-checked against the SSRF policy), `description`, `events`, `workflowIds` (`null` clears the filter; omit the key to keep it) and `filters`. `enabled` is not part of the configuration — use the enable and disable operations. Audited. **Path parameters** | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | string (uuid) | yes | | **Request headers** | Name | Type | Required | Description | | --- | --- | --- | --- | | `Idempotency-Key` | string | no | Makes the request safe to retry: a second request with the same key and a byte-identical body (and the same `Expected-Revision`) replays the stored response (`Idempotent-Replayed: true`); a different body with the same key is refused (422 `idempotency_key_reused`) and no second resource is created. Keys are stored per workspace for 24 hours and replayed only to the API key that made the request (or the key one rotation away from it); another API key sending the same value is refused (422 `idempotency_key_reused`), so prefix keys per integration. 409, 429 and 5xx answers are never stored. | **Request body** — `application/json`, [WebhookPatch](https://developers.processcart.com/reference/schemas#webhookpatch) | Property | Type | Required | Description | | --- | --- | --- | --- | | `url` | string | no | | | `description` | string | no | | | `events` | [EventType](https://developers.processcart.com/reference/schemas#eventtype) \\| `"*"`[] | no | | | `workflowIds` | [Uuid](https://developers.processcart.com/reference/schemas#uuid)[] \\| null | no | | | `filters` | object | no | | **Responses** | Status | Meaning | Body | | --- | --- | --- | | 200 | OK. | [Webhook](https://developers.processcart.com/reference/schemas#webhook) | | 401 | No usable credential. `unauthorized` — Authentication required; `invalid_api_key` — Invalid API key; `key_revoked` — API key revoked. | [Problem](https://developers.processcart.com/errors) | | 403 | `insufficient_scope` — Insufficient scope; `not_allowed_for_key` — Not allowed for this key; `read_only_key` — Read-only key. | [Problem](https://developers.processcart.com/errors) | | 404 | `not_found` — Not found. | [Problem](https://developers.processcart.com/errors) | | 405 | The verb is not supported on this path. `method_not_allowed` — Method not allowed. | [Problem](https://developers.processcart.com/errors) | | 409 | `idempotency_in_progress` — Request in progress. | [Problem](https://developers.processcart.com/errors) | | 413 | `payload_too_large` — Payload too large. | [Problem](https://developers.processcart.com/errors) | | 415 | `unsupported_media_type` — Unsupported media type. | [Problem](https://developers.processcart.com/errors) | | 422 | `idempotency_key_reused` — Idempotency key reused; `validation_failed` — Validation failed. | [Problem](https://developers.processcart.com/errors) | | 429 | `rate_limited` — Too many requests. | [Problem](https://developers.processcart.com/errors) | | 500 | `internal_error` — Internal error. | [Problem](https://developers.processcart.com/errors) | | 503 | `unavailable` — Service unavailable. | [Problem](https://developers.processcart.com/errors) | **Errors** (every code this operation can answer; see [Errors](https://developers.processcart.com/errors)) - 401 [`invalid_api_key`](https://developers.processcart.com/errors#invalid_api_key) — Invalid API key - 401 [`key_revoked`](https://developers.processcart.com/errors#key_revoked) — API key revoked - 401 [`unauthorized`](https://developers.processcart.com/errors#unauthorized) — Authentication required - 403 [`insufficient_scope`](https://developers.processcart.com/errors#insufficient_scope) — Insufficient scope - 403 [`not_allowed_for_key`](https://developers.processcart.com/errors#not_allowed_for_key) — Not allowed for this key - 403 [`read_only_key`](https://developers.processcart.com/errors#read_only_key) — Read-only key - 404 [`not_found`](https://developers.processcart.com/errors#not_found) — Not found - 405 [`method_not_allowed`](https://developers.processcart.com/errors#method_not_allowed) — Method not allowed - 409 [`idempotency_in_progress`](https://developers.processcart.com/errors#idempotency_in_progress) — Request in progress - 413 [`payload_too_large`](https://developers.processcart.com/errors#payload_too_large) — Payload too large - 415 [`unsupported_media_type`](https://developers.processcart.com/errors#unsupported_media_type) — Unsupported media type - 422 [`idempotency_key_reused`](https://developers.processcart.com/errors#idempotency_key_reused) — Idempotency key reused - 422 [`validation_failed`](https://developers.processcart.com/errors#validation_failed) — Validation failed - 429 [`rate_limited`](https://developers.processcart.com/errors#rate_limited) — Too many requests - 500 [`internal_error`](https://developers.processcart.com/errors#internal_error) — Internal error - 503 [`unavailable`](https://developers.processcart.com/errors#unavailable) — Service unavailable **Example request** ```json { "description": "Renamed", "events": [ "run.created", "run.completed" ] } ``` **Example response** (200) ```json { "id": "2b64ebdd-e0ba-4d01-aca7-914b36d905aa", "url": "https://hooks.example.com/processcart", "description": "Renamed", "events": [ "run.created", "run.completed" ], "workflowIds": null, "filters": {}, "enabled": true, "disabledReason": null, "disabledAt": null, "consecutiveFailures": 0, "lastDeliveryAt": "2026-10-11T05:28:29.806Z", "createdAt": "2026-10-11T05:28:25.162Z", "createdBy": null, "updatedAt": "2026-10-11T05:28:30.342Z" } ``` ## DELETE /v1/webhooks/{id} **Delete a webhook.** Scope `webhooks:manage` · accepts `Idempotency-Key`. Operation id `deleteWebhook` · [open in the API reference](https://developers.processcart.com/reference#tag/webhooks/DELETE/v1/webhooks/{id}). Removes the endpoint with its secret and delivery log. Pending deliveries are dropped; nothing is sent to the endpoint. Audited. **Path parameters** | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | string (uuid) | yes | | **Request headers** | Name | Type | Required | Description | | --- | --- | --- | --- | | `Idempotency-Key` | string | no | Makes the request safe to retry: a second request with the same key and a byte-identical body (and the same `Expected-Revision`) replays the stored response (`Idempotent-Replayed: true`); a different body with the same key is refused (422 `idempotency_key_reused`) and no second resource is created. Keys are stored per workspace for 24 hours and replayed only to the API key that made the request (or the key one rotation away from it); another API key sending the same value is refused (422 `idempotency_key_reused`), so prefix keys per integration. 409, 429 and 5xx answers are never stored. | **Responses** | Status | Meaning | Body | | --- | --- | --- | | 204 | Done; no body. | — | | 401 | No usable credential. `unauthorized` — Authentication required; `invalid_api_key` — Invalid API key; `key_revoked` — API key revoked. | [Problem](https://developers.processcart.com/errors) | | 403 | `insufficient_scope` — Insufficient scope; `not_allowed_for_key` — Not allowed for this key; `read_only_key` — Read-only key. | [Problem](https://developers.processcart.com/errors) | | 404 | `not_found` — Not found. | [Problem](https://developers.processcart.com/errors) | | 405 | The verb is not supported on this path. `method_not_allowed` — Method not allowed. | [Problem](https://developers.processcart.com/errors) | | 409 | `idempotency_in_progress` — Request in progress. | [Problem](https://developers.processcart.com/errors) | | 422 | `idempotency_key_reused` — Idempotency key reused; `validation_failed` — Validation failed. | [Problem](https://developers.processcart.com/errors) | | 429 | `rate_limited` — Too many requests. | [Problem](https://developers.processcart.com/errors) | | 500 | `internal_error` — Internal error. | [Problem](https://developers.processcart.com/errors) | | 503 | `unavailable` — Service unavailable. | [Problem](https://developers.processcart.com/errors) | **Errors** (every code this operation can answer; see [Errors](https://developers.processcart.com/errors)) - 401 [`invalid_api_key`](https://developers.processcart.com/errors#invalid_api_key) — Invalid API key - 401 [`key_revoked`](https://developers.processcart.com/errors#key_revoked) — API key revoked - 401 [`unauthorized`](https://developers.processcart.com/errors#unauthorized) — Authentication required - 403 [`insufficient_scope`](https://developers.processcart.com/errors#insufficient_scope) — Insufficient scope - 403 [`not_allowed_for_key`](https://developers.processcart.com/errors#not_allowed_for_key) — Not allowed for this key - 403 [`read_only_key`](https://developers.processcart.com/errors#read_only_key) — Read-only key - 404 [`not_found`](https://developers.processcart.com/errors#not_found) — Not found - 405 [`method_not_allowed`](https://developers.processcart.com/errors#method_not_allowed) — Method not allowed - 409 [`idempotency_in_progress`](https://developers.processcart.com/errors#idempotency_in_progress) — Request in progress - 422 [`idempotency_key_reused`](https://developers.processcart.com/errors#idempotency_key_reused) — Idempotency key reused - 422 [`validation_failed`](https://developers.processcart.com/errors#validation_failed) — Validation failed - 429 [`rate_limited`](https://developers.processcart.com/errors#rate_limited) — Too many requests - 500 [`internal_error`](https://developers.processcart.com/errors#internal_error) — Internal error - 503 [`unavailable`](https://developers.processcart.com/errors#unavailable) — Service unavailable ## POST /v1/webhooks/{id}/enable **Enable a webhook.** Scope `webhooks:manage` · accepts `Idempotency-Key`. Operation id `enableWebhook` · [open in the API reference](https://developers.processcart.com/reference#tag/webhooks/POST/v1/webhooks/{id}/enable). Turns deliveries back on and resets the consecutive-failure counter — also the way to bring back an endpoint that was disabled automatically (after repeated failures or a 410). Enabling an enabled webhook answers 200 unchanged. **Path parameters** | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | string (uuid) | yes | | **Request headers** | Name | Type | Required | Description | | --- | --- | --- | --- | | `Idempotency-Key` | string | no | Makes the request safe to retry: a second request with the same key and a byte-identical body (and the same `Expected-Revision`) replays the stored response (`Idempotent-Replayed: true`); a different body with the same key is refused (422 `idempotency_key_reused`) and no second resource is created. Keys are stored per workspace for 24 hours and replayed only to the API key that made the request (or the key one rotation away from it); another API key sending the same value is refused (422 `idempotency_key_reused`), so prefix keys per integration. 409, 429 and 5xx answers are never stored. | **Responses** | Status | Meaning | Body | | --- | --- | --- | | 200 | OK. | [Webhook](https://developers.processcart.com/reference/schemas#webhook) | | 401 | No usable credential. `unauthorized` — Authentication required; `invalid_api_key` — Invalid API key; `key_revoked` — API key revoked. | [Problem](https://developers.processcart.com/errors) | | 403 | `insufficient_scope` — Insufficient scope; `not_allowed_for_key` — Not allowed for this key; `read_only_key` — Read-only key. | [Problem](https://developers.processcart.com/errors) | | 404 | `not_found` — Not found. | [Problem](https://developers.processcart.com/errors) | | 405 | The verb is not supported on this path. `method_not_allowed` — Method not allowed. | [Problem](https://developers.processcart.com/errors) | | 409 | `idempotency_in_progress` — Request in progress. | [Problem](https://developers.processcart.com/errors) | | 422 | `idempotency_key_reused` — Idempotency key reused; `validation_failed` — Validation failed. | [Problem](https://developers.processcart.com/errors) | | 429 | `rate_limited` — Too many requests. | [Problem](https://developers.processcart.com/errors) | | 500 | `internal_error` — Internal error. | [Problem](https://developers.processcart.com/errors) | | 503 | `unavailable` — Service unavailable. | [Problem](https://developers.processcart.com/errors) | **Errors** (every code this operation can answer; see [Errors](https://developers.processcart.com/errors)) - 401 [`invalid_api_key`](https://developers.processcart.com/errors#invalid_api_key) — Invalid API key - 401 [`key_revoked`](https://developers.processcart.com/errors#key_revoked) — API key revoked - 401 [`unauthorized`](https://developers.processcart.com/errors#unauthorized) — Authentication required - 403 [`insufficient_scope`](https://developers.processcart.com/errors#insufficient_scope) — Insufficient scope - 403 [`not_allowed_for_key`](https://developers.processcart.com/errors#not_allowed_for_key) — Not allowed for this key - 403 [`read_only_key`](https://developers.processcart.com/errors#read_only_key) — Read-only key - 404 [`not_found`](https://developers.processcart.com/errors#not_found) — Not found - 405 [`method_not_allowed`](https://developers.processcart.com/errors#method_not_allowed) — Method not allowed - 409 [`idempotency_in_progress`](https://developers.processcart.com/errors#idempotency_in_progress) — Request in progress - 422 [`idempotency_key_reused`](https://developers.processcart.com/errors#idempotency_key_reused) — Idempotency key reused - 422 [`validation_failed`](https://developers.processcart.com/errors#validation_failed) — Validation failed - 429 [`rate_limited`](https://developers.processcart.com/errors#rate_limited) — Too many requests - 500 [`internal_error`](https://developers.processcart.com/errors#internal_error) — Internal error - 503 [`unavailable`](https://developers.processcart.com/errors#unavailable) — Service unavailable **Example response** (200) ```json { "id": "2b64ebdd-e0ba-4d01-aca7-914b36d905aa", "url": "https://hooks.example.com/processcart", "description": "Everything", "events": [ "*" ], "workflowIds": null, "filters": {}, "enabled": true, "disabledReason": null, "disabledAt": null, "consecutiveFailures": 0, "lastDeliveryAt": "2026-10-11T05:28:27.885Z", "createdAt": "2026-10-11T05:28:25.162Z", "createdBy": null, "updatedAt": "2026-10-11T05:28:28.995Z" } ``` ## POST /v1/webhooks/{id}/disable **Disable a webhook.** Scope `webhooks:manage` · accepts `Idempotency-Key`. Operation id `disableWebhook` · [open in the API reference](https://developers.processcart.com/reference#tag/webhooks/POST/v1/webhooks/{id}/disable). Stops deliveries (reason `manual`). Events that happen while it is disabled are not queued for it. Disabling a disabled webhook answers 200 unchanged. **Path parameters** | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | string (uuid) | yes | | **Request headers** | Name | Type | Required | Description | | --- | --- | --- | --- | | `Idempotency-Key` | string | no | Makes the request safe to retry: a second request with the same key and a byte-identical body (and the same `Expected-Revision`) replays the stored response (`Idempotent-Replayed: true`); a different body with the same key is refused (422 `idempotency_key_reused`) and no second resource is created. Keys are stored per workspace for 24 hours and replayed only to the API key that made the request (or the key one rotation away from it); another API key sending the same value is refused (422 `idempotency_key_reused`), so prefix keys per integration. 409, 429 and 5xx answers are never stored. | **Responses** | Status | Meaning | Body | | --- | --- | --- | | 200 | OK. | [Webhook](https://developers.processcart.com/reference/schemas#webhook) | | 401 | No usable credential. `unauthorized` — Authentication required; `invalid_api_key` — Invalid API key; `key_revoked` — API key revoked. | [Problem](https://developers.processcart.com/errors) | | 403 | `insufficient_scope` — Insufficient scope; `not_allowed_for_key` — Not allowed for this key; `read_only_key` — Read-only key. | [Problem](https://developers.processcart.com/errors) | | 404 | `not_found` — Not found. | [Problem](https://developers.processcart.com/errors) | | 405 | The verb is not supported on this path. `method_not_allowed` — Method not allowed. | [Problem](https://developers.processcart.com/errors) | | 409 | `idempotency_in_progress` — Request in progress. | [Problem](https://developers.processcart.com/errors) | | 422 | `idempotency_key_reused` — Idempotency key reused; `validation_failed` — Validation failed. | [Problem](https://developers.processcart.com/errors) | | 429 | `rate_limited` — Too many requests. | [Problem](https://developers.processcart.com/errors) | | 500 | `internal_error` — Internal error. | [Problem](https://developers.processcart.com/errors) | | 503 | `unavailable` — Service unavailable. | [Problem](https://developers.processcart.com/errors) | **Errors** (every code this operation can answer; see [Errors](https://developers.processcart.com/errors)) - 401 [`invalid_api_key`](https://developers.processcart.com/errors#invalid_api_key) — Invalid API key - 401 [`key_revoked`](https://developers.processcart.com/errors#key_revoked) — API key revoked - 401 [`unauthorized`](https://developers.processcart.com/errors#unauthorized) — Authentication required - 403 [`insufficient_scope`](https://developers.processcart.com/errors#insufficient_scope) — Insufficient scope - 403 [`not_allowed_for_key`](https://developers.processcart.com/errors#not_allowed_for_key) — Not allowed for this key - 403 [`read_only_key`](https://developers.processcart.com/errors#read_only_key) — Read-only key - 404 [`not_found`](https://developers.processcart.com/errors#not_found) — Not found - 405 [`method_not_allowed`](https://developers.processcart.com/errors#method_not_allowed) — Method not allowed - 409 [`idempotency_in_progress`](https://developers.processcart.com/errors#idempotency_in_progress) — Request in progress - 422 [`idempotency_key_reused`](https://developers.processcart.com/errors#idempotency_key_reused) — Idempotency key reused - 422 [`validation_failed`](https://developers.processcart.com/errors#validation_failed) — Validation failed - 429 [`rate_limited`](https://developers.processcart.com/errors#rate_limited) — Too many requests - 500 [`internal_error`](https://developers.processcart.com/errors#internal_error) — Internal error - 503 [`unavailable`](https://developers.processcart.com/errors#unavailable) — Service unavailable **Example response** (200) ```json { "id": "2b64ebdd-e0ba-4d01-aca7-914b36d905aa", "url": "https://hooks.example.com/processcart", "description": "Everything", "events": [ "*" ], "workflowIds": null, "filters": {}, "enabled": false, "disabledReason": "manual", "disabledAt": "2026-10-11T05:28:28.437Z", "consecutiveFailures": 0, "lastDeliveryAt": "2026-10-11T05:28:27.885Z", "createdAt": "2026-10-11T05:28:25.162Z", "createdBy": null, "updatedAt": "2026-10-11T05:28:28.437Z" } ``` ## POST /v1/webhooks/{id}/rotate-secret **Rotate a webhook's signing secret.** Scope `webhooks:manage` · accepts `Idempotency-Key`. Operation id `rotateWebhookSecret` · [open in the API reference](https://developers.processcart.com/reference#tag/webhooks/POST/v1/webhooks/{id}/rotate-secret). Mints a new signing secret and returns it once. Until `previousExpiresAt` every delivery is signed with both secrets (two entries in `webhook-signature`), so the endpoint can switch without missing a beat. Audited. **Path parameters** | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | string (uuid) | yes | | **Request headers** | Name | Type | Required | Description | | --- | --- | --- | --- | | `Idempotency-Key` | string | no | Makes the request safe to retry: a second request with the same key and a byte-identical body (and the same `Expected-Revision`) replays the stored response (`Idempotent-Replayed: true`); a different body with the same key is refused (422 `idempotency_key_reused`) and no second resource is created. Keys are stored per workspace for 24 hours and replayed only to the API key that made the request (or the key one rotation away from it); another API key sending the same value is refused (422 `idempotency_key_reused`), so prefix keys per integration. 409, 429 and 5xx answers are never stored. | **Responses** | Status | Meaning | Body | | --- | --- | --- | | 200 | OK. | [WebhookSecret](https://developers.processcart.com/reference/schemas#webhooksecret) | | 401 | No usable credential. `unauthorized` — Authentication required; `invalid_api_key` — Invalid API key; `key_revoked` — API key revoked. | [Problem](https://developers.processcart.com/errors) | | 403 | `insufficient_scope` — Insufficient scope; `not_allowed_for_key` — Not allowed for this key; `read_only_key` — Read-only key. | [Problem](https://developers.processcart.com/errors) | | 404 | `not_found` — Not found. | [Problem](https://developers.processcart.com/errors) | | 405 | The verb is not supported on this path. `method_not_allowed` — Method not allowed. | [Problem](https://developers.processcart.com/errors) | | 409 | `idempotency_in_progress` — Request in progress. | [Problem](https://developers.processcart.com/errors) | | 422 | `idempotency_key_reused` — Idempotency key reused; `validation_failed` — Validation failed. | [Problem](https://developers.processcart.com/errors) | | 429 | `rate_limited` — Too many requests. | [Problem](https://developers.processcart.com/errors) | | 500 | `internal_error` — Internal error. | [Problem](https://developers.processcart.com/errors) | | 503 | `unavailable` — Service unavailable. | [Problem](https://developers.processcart.com/errors) | **Errors** (every code this operation can answer; see [Errors](https://developers.processcart.com/errors)) - 401 [`invalid_api_key`](https://developers.processcart.com/errors#invalid_api_key) — Invalid API key - 401 [`key_revoked`](https://developers.processcart.com/errors#key_revoked) — API key revoked - 401 [`unauthorized`](https://developers.processcart.com/errors#unauthorized) — Authentication required - 403 [`insufficient_scope`](https://developers.processcart.com/errors#insufficient_scope) — Insufficient scope - 403 [`not_allowed_for_key`](https://developers.processcart.com/errors#not_allowed_for_key) — Not allowed for this key - 403 [`read_only_key`](https://developers.processcart.com/errors#read_only_key) — Read-only key - 404 [`not_found`](https://developers.processcart.com/errors#not_found) — Not found - 405 [`method_not_allowed`](https://developers.processcart.com/errors#method_not_allowed) — Method not allowed - 409 [`idempotency_in_progress`](https://developers.processcart.com/errors#idempotency_in_progress) — Request in progress - 422 [`idempotency_key_reused`](https://developers.processcart.com/errors#idempotency_key_reused) — Idempotency key reused - 422 [`validation_failed`](https://developers.processcart.com/errors#validation_failed) — Validation failed - 429 [`rate_limited`](https://developers.processcart.com/errors#rate_limited) — Too many requests - 500 [`internal_error`](https://developers.processcart.com/errors#internal_error) — Internal error - 503 [`unavailable`](https://developers.processcart.com/errors#unavailable) — Service unavailable **Example response** (200) ```json { "secret": "whsec_EXAMPLEEXAMPLEEXAMPLEEXAMPLEEXAMPLEEXAMPLE", "previousExpiresAt": "2026-10-12T05:28:29.267Z" } ``` ## POST /v1/webhooks/{id}/test **Send a test event.** Scope `webhooks:manage` · accepts `Idempotency-Key`. Operation id `testWebhook` · [open in the API reference](https://developers.processcart.com/reference#tag/webhooks/POST/v1/webhooks/{id}/test). Queues a `webhook.test` delivery to this endpoint and sends it right away (the response does not wait for the endpoint). Read the delivery to see the outcome. A disabled webhook is refused. **Path parameters** | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | string (uuid) | yes | | **Request headers** | Name | Type | Required | Description | | --- | --- | --- | --- | | `Idempotency-Key` | string | no | Makes the request safe to retry: a second request with the same key and a byte-identical body (and the same `Expected-Revision`) replays the stored response (`Idempotent-Replayed: true`); a different body with the same key is refused (422 `idempotency_key_reused`) and no second resource is created. Keys are stored per workspace for 24 hours and replayed only to the API key that made the request (or the key one rotation away from it); another API key sending the same value is refused (422 `idempotency_key_reused`), so prefix keys per integration. 409, 429 and 5xx answers are never stored. | **Responses** | Status | Meaning | Body | | --- | --- | --- | | 202 | Accepted; the work completes asynchronously. | [WebhookQueuedDelivery](https://developers.processcart.com/reference/schemas#webhookqueueddelivery) | | 401 | No usable credential. `unauthorized` — Authentication required; `invalid_api_key` — Invalid API key; `key_revoked` — API key revoked. | [Problem](https://developers.processcart.com/errors) | | 403 | `insufficient_scope` — Insufficient scope; `not_allowed_for_key` — Not allowed for this key; `read_only_key` — Read-only key. | [Problem](https://developers.processcart.com/errors) | | 404 | `not_found` — Not found. | [Problem](https://developers.processcart.com/errors) | | 405 | The verb is not supported on this path. `method_not_allowed` — Method not allowed. | [Problem](https://developers.processcart.com/errors) | | 409 | `idempotency_in_progress` — Request in progress; `webhook_disabled` — Webhook disabled. | [Problem](https://developers.processcart.com/errors) | | 422 | `idempotency_key_reused` — Idempotency key reused; `validation_failed` — Validation failed. | [Problem](https://developers.processcart.com/errors) | | 429 | `rate_limited` — Too many requests. | [Problem](https://developers.processcart.com/errors) | | 500 | `internal_error` — Internal error. | [Problem](https://developers.processcart.com/errors) | | 503 | `unavailable` — Service unavailable. | [Problem](https://developers.processcart.com/errors) | **Errors** (every code this operation can answer; see [Errors](https://developers.processcart.com/errors)) - 401 [`invalid_api_key`](https://developers.processcart.com/errors#invalid_api_key) — Invalid API key - 401 [`key_revoked`](https://developers.processcart.com/errors#key_revoked) — API key revoked - 401 [`unauthorized`](https://developers.processcart.com/errors#unauthorized) — Authentication required - 403 [`insufficient_scope`](https://developers.processcart.com/errors#insufficient_scope) — Insufficient scope - 403 [`not_allowed_for_key`](https://developers.processcart.com/errors#not_allowed_for_key) — Not allowed for this key - 403 [`read_only_key`](https://developers.processcart.com/errors#read_only_key) — Read-only key - 404 [`not_found`](https://developers.processcart.com/errors#not_found) — Not found - 405 [`method_not_allowed`](https://developers.processcart.com/errors#method_not_allowed) — Method not allowed - 409 [`idempotency_in_progress`](https://developers.processcart.com/errors#idempotency_in_progress) — Request in progress - 409 [`webhook_disabled`](https://developers.processcart.com/errors#webhook_disabled) — Webhook disabled - 422 [`idempotency_key_reused`](https://developers.processcart.com/errors#idempotency_key_reused) — Idempotency key reused - 422 [`validation_failed`](https://developers.processcart.com/errors#validation_failed) — Validation failed - 429 [`rate_limited`](https://developers.processcart.com/errors#rate_limited) — Too many requests - 500 [`internal_error`](https://developers.processcart.com/errors#internal_error) — Internal error - 503 [`unavailable`](https://developers.processcart.com/errors#unavailable) — Service unavailable **Example response** (202) ```json { "delivery": { "id": "eb2383ee-da26-48db-aaa3-42e61dc67272", "webhookId": "2b64ebdd-e0ba-4d01-aca7-914b36d905aa", "attempt": 0 } } ``` ## GET /v1/webhooks/{id}/deliveries **List a webhook's deliveries.** Scope `webhooks:manage` · the key's allowlist narrows the rows (never a 403 — rows outside it are absent) · paginated (`{ data, nextCursor, hasMore }`). Operation id `listWebhookDeliveries` · [open in the API reference](https://developers.processcart.com/reference#tag/webhooks/GET/v1/webhooks/{id}/deliveries). The endpoint's delivery log, newest first, with each delivery's status, attempt count, last response code and next attempt time. Filter by `status`. Deliveries older than the retention window are gone. A key restricted to workflows sees only deliveries about those workflows (and test events). **Path parameters** | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | string (uuid) | yes | | **Query parameters** (an unknown parameter is a 422 `validation_failed` with `errors[].code = "unknown_parameter"`) | Name | Type | Required | Description | | --- | --- | --- | --- | | `status` | `"pending"` \\| `"delivered"` \\| `"failed"` \\| `"exhausted"` | no | | | `limit` | integer | no | | | `cursor` | string | no | The `nextCursor` of the previous page. Opaque, and bound to the query that produced it — a cursor from a different filter or sort is a 422. | **Responses** | Status | Meaning | Body | | --- | --- | --- | | 200 | OK. | [WebhookDeliveryList](https://developers.processcart.com/reference/schemas#webhookdeliverylist) | | 401 | No usable credential. `unauthorized` — Authentication required; `invalid_api_key` — Invalid API key; `key_revoked` — API key revoked. | [Problem](https://developers.processcart.com/errors) | | 403 | `insufficient_scope` — Insufficient scope; `not_allowed_for_key` — Not allowed for this key. | [Problem](https://developers.processcart.com/errors) | | 404 | `not_found` — Not found. | [Problem](https://developers.processcart.com/errors) | | 405 | The verb is not supported on this path. `method_not_allowed` — Method not allowed. | [Problem](https://developers.processcart.com/errors) | | 422 | `validation_failed` — Validation failed. | [Problem](https://developers.processcart.com/errors) | | 429 | `rate_limited` — Too many requests. | [Problem](https://developers.processcart.com/errors) | | 500 | `internal_error` — Internal error. | [Problem](https://developers.processcart.com/errors) | | 503 | `unavailable` — Service unavailable. | [Problem](https://developers.processcart.com/errors) | **Errors** (every code this operation can answer; see [Errors](https://developers.processcart.com/errors)) - 401 [`invalid_api_key`](https://developers.processcart.com/errors#invalid_api_key) — Invalid API key - 401 [`key_revoked`](https://developers.processcart.com/errors#key_revoked) — API key revoked - 401 [`unauthorized`](https://developers.processcart.com/errors#unauthorized) — Authentication required - 403 [`insufficient_scope`](https://developers.processcart.com/errors#insufficient_scope) — Insufficient scope - 403 [`not_allowed_for_key`](https://developers.processcart.com/errors#not_allowed_for_key) — Not allowed for this key - 404 [`not_found`](https://developers.processcart.com/errors#not_found) — Not found - 405 [`method_not_allowed`](https://developers.processcart.com/errors#method_not_allowed) — Method not allowed - 422 [`validation_failed`](https://developers.processcart.com/errors#validation_failed) — Validation failed - 429 [`rate_limited`](https://developers.processcart.com/errors#rate_limited) — Too many requests - 500 [`internal_error`](https://developers.processcart.com/errors#internal_error) — Internal error - 503 [`unavailable`](https://developers.processcart.com/errors#unavailable) — Service unavailable **Example response** (200) ```json { "data": [ { "id": "eb2383ee-da26-48db-aaa3-42e61dc67272", "webhookId": "2b64ebdd-e0ba-4d01-aca7-914b36d905aa", "eventId": "evt_9d68ebed-af3b-42da-aace-77503bb95cdc", "eventType": "webhook.test", "status": "delivered", "attemptCount": 1, "lastStatusCode": 200, "lastError": null, "lastAttemptAt": "2026-10-11T05:28:26.227Z", "nextAttemptAt": null, "deliveredAt": "2026-10-11T05:28:26.485Z", "createdAt": "2026-10-11T05:28:26.084Z" } ], "nextCursor": null, "hasMore": false } ``` ## GET /v1/webhooks/{id}/deliveries/{deliveryId} **Get a delivery.** Scope `webhooks:manage`. Operation id `getWebhookDelivery` · [open in the API reference](https://developers.processcart.com/reference#tag/webhooks/GET/v1/webhooks/{id}/deliveries/{deliveryId}). One delivery with the event envelope that was (or will be) POSTed — useful to inspect a failed delivery or to replay an event by hand. **Path parameters** | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | string (uuid) | yes | | | `deliveryId` | string (uuid) | yes | | **Responses** | Status | Meaning | Body | | --- | --- | --- | | 200 | OK. | [WebhookDeliveryWithPayload](https://developers.processcart.com/reference/schemas#webhookdeliverywithpayload) | | 401 | No usable credential. `unauthorized` — Authentication required; `invalid_api_key` — Invalid API key; `key_revoked` — API key revoked. | [Problem](https://developers.processcart.com/errors) | | 403 | `insufficient_scope` — Insufficient scope; `not_allowed_for_key` — Not allowed for this key. | [Problem](https://developers.processcart.com/errors) | | 404 | `not_found` — Not found. | [Problem](https://developers.processcart.com/errors) | | 405 | The verb is not supported on this path. `method_not_allowed` — Method not allowed. | [Problem](https://developers.processcart.com/errors) | | 422 | `validation_failed` — Validation failed. | [Problem](https://developers.processcart.com/errors) | | 429 | `rate_limited` — Too many requests. | [Problem](https://developers.processcart.com/errors) | | 500 | `internal_error` — Internal error. | [Problem](https://developers.processcart.com/errors) | | 503 | `unavailable` — Service unavailable. | [Problem](https://developers.processcart.com/errors) | **Errors** (every code this operation can answer; see [Errors](https://developers.processcart.com/errors)) - 401 [`invalid_api_key`](https://developers.processcart.com/errors#invalid_api_key) — Invalid API key - 401 [`key_revoked`](https://developers.processcart.com/errors#key_revoked) — API key revoked - 401 [`unauthorized`](https://developers.processcart.com/errors#unauthorized) — Authentication required - 403 [`insufficient_scope`](https://developers.processcart.com/errors#insufficient_scope) — Insufficient scope - 403 [`not_allowed_for_key`](https://developers.processcart.com/errors#not_allowed_for_key) — Not allowed for this key - 404 [`not_found`](https://developers.processcart.com/errors#not_found) — Not found - 405 [`method_not_allowed`](https://developers.processcart.com/errors#method_not_allowed) — Method not allowed - 422 [`validation_failed`](https://developers.processcart.com/errors#validation_failed) — Validation failed - 429 [`rate_limited`](https://developers.processcart.com/errors#rate_limited) — Too many requests - 500 [`internal_error`](https://developers.processcart.com/errors#internal_error) — Internal error - 503 [`unavailable`](https://developers.processcart.com/errors#unavailable) — Service unavailable **Example response** (200) ```json { "id": "eb2383ee-da26-48db-aaa3-42e61dc67272", "webhookId": "2b64ebdd-e0ba-4d01-aca7-914b36d905aa", "eventId": "evt_9d68ebed-af3b-42da-aace-77503bb95cdc", "eventType": "webhook.test", "status": "delivered", "attemptCount": 1, "lastStatusCode": 200, "lastError": null, "lastAttemptAt": "2026-10-11T05:28:26.227Z", "nextAttemptAt": null, "deliveredAt": "2026-10-11T05:28:26.485Z", "createdAt": "2026-10-11T05:28:26.084Z", "payload": { "id": "evt_9d68ebed-af3b-42da-aace-77503bb95cdc", "org": { "id": "83b4ff3e-2634-423e-89ab-f2c5c07ced1f", "slug": "example-workspace" }, "data": { "webhook": { "id": "2b64ebdd-e0ba-4d01-aca7-914b36d905aa" } }, "type": "webhook.test", "actor": { "kind": "api", "name": "Zapier", "keyId": "d5ec9555-1a0c-455a-86e6-c7b18bcabc78" }, "timestamp": "2026-10-11T05:28:26.084Z" } } ``` ## POST /v1/webhooks/{id}/deliveries/{deliveryId}/redeliver **Send a delivery again.** Scope `webhooks:manage` · accepts `Idempotency-Key`. Operation id `redeliverWebhookDelivery` · [open in the API reference](https://developers.processcart.com/reference#tag/webhooks/POST/v1/webhooks/{id}/deliveries/{deliveryId}/redeliver). Queues a finished delivery (delivered, failed or exhausted) again with the same `webhook-id` and sends it right away; the attempt count keeps counting. A delivery still in flight, or a disabled webhook, is refused. Audited. **Path parameters** | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | string (uuid) | yes | | | `deliveryId` | string (uuid) | yes | | **Request headers** | Name | Type | Required | Description | | --- | --- | --- | --- | | `Idempotency-Key` | string | no | Makes the request safe to retry: a second request with the same key and a byte-identical body (and the same `Expected-Revision`) replays the stored response (`Idempotent-Replayed: true`); a different body with the same key is refused (422 `idempotency_key_reused`) and no second resource is created. Keys are stored per workspace for 24 hours and replayed only to the API key that made the request (or the key one rotation away from it); another API key sending the same value is refused (422 `idempotency_key_reused`), so prefix keys per integration. 409, 429 and 5xx answers are never stored. | **Responses** | Status | Meaning | Body | | --- | --- | --- | | 202 | Accepted; the work completes asynchronously. | [WebhookQueuedDelivery](https://developers.processcart.com/reference/schemas#webhookqueueddelivery) | | 401 | No usable credential. `unauthorized` — Authentication required; `invalid_api_key` — Invalid API key; `key_revoked` — API key revoked. | [Problem](https://developers.processcart.com/errors) | | 403 | `insufficient_scope` — Insufficient scope; `not_allowed_for_key` — Not allowed for this key; `read_only_key` — Read-only key. | [Problem](https://developers.processcart.com/errors) | | 404 | `not_found` — Not found. | [Problem](https://developers.processcart.com/errors) | | 405 | The verb is not supported on this path. `method_not_allowed` — Method not allowed. | [Problem](https://developers.processcart.com/errors) | | 409 | `delivery_not_terminal` — Delivery not finished; `idempotency_in_progress` — Request in progress; `webhook_disabled` — Webhook disabled. | [Problem](https://developers.processcart.com/errors) | | 422 | `idempotency_key_reused` — Idempotency key reused; `validation_failed` — Validation failed. | [Problem](https://developers.processcart.com/errors) | | 429 | `rate_limited` — Too many requests. | [Problem](https://developers.processcart.com/errors) | | 500 | `internal_error` — Internal error. | [Problem](https://developers.processcart.com/errors) | | 503 | `unavailable` — Service unavailable. | [Problem](https://developers.processcart.com/errors) | **Errors** (every code this operation can answer; see [Errors](https://developers.processcart.com/errors)) - 401 [`invalid_api_key`](https://developers.processcart.com/errors#invalid_api_key) — Invalid API key - 401 [`key_revoked`](https://developers.processcart.com/errors#key_revoked) — API key revoked - 401 [`unauthorized`](https://developers.processcart.com/errors#unauthorized) — Authentication required - 403 [`insufficient_scope`](https://developers.processcart.com/errors#insufficient_scope) — Insufficient scope - 403 [`not_allowed_for_key`](https://developers.processcart.com/errors#not_allowed_for_key) — Not allowed for this key - 403 [`read_only_key`](https://developers.processcart.com/errors#read_only_key) — Read-only key - 404 [`not_found`](https://developers.processcart.com/errors#not_found) — Not found - 405 [`method_not_allowed`](https://developers.processcart.com/errors#method_not_allowed) — Method not allowed - 409 [`delivery_not_terminal`](https://developers.processcart.com/errors#delivery_not_terminal) — Delivery not finished - 409 [`idempotency_in_progress`](https://developers.processcart.com/errors#idempotency_in_progress) — Request in progress - 409 [`webhook_disabled`](https://developers.processcart.com/errors#webhook_disabled) — Webhook disabled - 422 [`idempotency_key_reused`](https://developers.processcart.com/errors#idempotency_key_reused) — Idempotency key reused - 422 [`validation_failed`](https://developers.processcart.com/errors#validation_failed) — Validation failed - 429 [`rate_limited`](https://developers.processcart.com/errors#rate_limited) — Too many requests - 500 [`internal_error`](https://developers.processcart.com/errors#internal_error) — Internal error - 503 [`unavailable`](https://developers.processcart.com/errors#unavailable) — Service unavailable **Example response** (202) ```json { "delivery": { "id": "eb2383ee-da26-48db-aaa3-42e61dc67272", "webhookId": "2b64ebdd-e0ba-4d01-aca7-914b36d905aa", "attempt": 1 } } ``` --- # Schedules endpoints > Recurrence schedules that start runs of a workflow: create, pause, resume, run now and preview the next occurrences. Every operation with parameters, body, responses, errors and examples. Source: https://developers.processcart.com/reference/schedules Recurrence schedules that start runs of a workflow: create, pause, resume, run now and preview the next occurrences. Schedules carry no `revision`, and no operation here reads `Expected-Revision` (the header, or the SDK's `expectedRevision` option, is ignored): an update applies to the schedule as it is stored, and the last write wins. Run now answers the new run with its `revision`. Base URL `https://app.processcart.com/api/v1`. Every operation below is also in the [OpenAPI document](https://developers.processcart.com/openapi.json) and the [interactive reference](https://developers.processcart.com/reference). | Operation | Method and path | Scope | | --- | --- | --- | | [List schedules](#listSchedules) | GET /v1/schedules | Scope `schedules:manage` | | [Create a schedule](#createSchedule) | POST /v1/schedules | Scope `schedules:manage` | | [Get a schedule](#getSchedule) | GET /v1/schedules/{id} | Scope `schedules:manage` | | [Update a schedule](#updateSchedule) | PATCH /v1/schedules/{id} | Scope `schedules:manage` | | [Delete a schedule](#deleteSchedule) | DELETE /v1/schedules/{id} | Scope `schedules:manage` | | [Pause a schedule](#pauseSchedule) | POST /v1/schedules/{id}/pause | Scope `schedules:manage` | | [Resume a schedule](#resumeSchedule) | POST /v1/schedules/{id}/resume | Scope `schedules:manage` | | [Start a run from a schedule now](#runScheduleNow) | POST /v1/schedules/{id}/run | Scope `schedules:manage` | | [Preview a schedule's next occurrences](#previewSchedule) | GET /v1/schedules/{id}/preview | Scope `schedules:manage` | ## GET /v1/schedules **List schedules.** Scope `schedules:manage` · the key's allowlist narrows the rows (never a 403 — rows outside it are absent) · paginated (`{ data, nextCursor, hasMore }`). Operation id `listSchedules` · [open in the API reference](https://developers.processcart.com/reference#tag/schedules/GET/v1/schedules). Lists the workspace's recurrence schedules, newest first, each with its recurrence, presets, enabled state and next occurrences. Filter by `workflowId` or `enabled`. `updatedSince` turns the list into an incremental sync ordered by `updatedAt` ascending (inclusive). A key restricted to workflows or folders lists only the schedules of workflows inside its allowlist. **Query parameters** (an unknown parameter is a 422 `validation_failed` with `errors[].code = "unknown_parameter"`) | Name | Type | Required | Description | | --- | --- | --- | --- | | `workflowId` | string (uuid) | no | | | `enabled` | boolean | no | | | `updatedSince` | string (date-time) | no | Incremental sync: only items whose `updatedAt` is at or after this instant (inclusive), ordered by `updatedAt` ascending; an explicit conflicting `sort` / `order` is a 422. Overlap by a minute and dedupe: a Run by `(id, revision)`, any other item (workflow, view, webhook, schedule, automation rule) by `(id, updatedAt)` — a higher `revision` or a later `updatedAt` replaces the copy you hold, an equal one is unchanged. | | `limit` | integer | no | | | `cursor` | string | no | The `nextCursor` of the previous page. Opaque, and bound to the query that produced it — a cursor from a different filter or sort is a 422. | **Responses** | Status | Meaning | Body | | --- | --- | --- | | 200 | OK. | [ScheduleList](https://developers.processcart.com/reference/schemas#schedulelist) | | 401 | No usable credential. `unauthorized` — Authentication required; `invalid_api_key` — Invalid API key; `key_revoked` — API key revoked. | [Problem](https://developers.processcart.com/errors) | | 403 | `insufficient_scope` — Insufficient scope. | [Problem](https://developers.processcart.com/errors) | | 405 | The verb is not supported on this path. `method_not_allowed` — Method not allowed. | [Problem](https://developers.processcart.com/errors) | | 422 | `validation_failed` — Validation failed. | [Problem](https://developers.processcart.com/errors) | | 429 | `rate_limited` — Too many requests. | [Problem](https://developers.processcart.com/errors) | | 500 | `internal_error` — Internal error. | [Problem](https://developers.processcart.com/errors) | | 503 | `unavailable` — Service unavailable. | [Problem](https://developers.processcart.com/errors) | **Errors** (every code this operation can answer; see [Errors](https://developers.processcart.com/errors)) - 401 [`invalid_api_key`](https://developers.processcart.com/errors#invalid_api_key) — Invalid API key - 401 [`key_revoked`](https://developers.processcart.com/errors#key_revoked) — API key revoked - 401 [`unauthorized`](https://developers.processcart.com/errors#unauthorized) — Authentication required - 403 [`insufficient_scope`](https://developers.processcart.com/errors#insufficient_scope) — Insufficient scope - 405 [`method_not_allowed`](https://developers.processcart.com/errors#method_not_allowed) — Method not allowed - 422 [`validation_failed`](https://developers.processcart.com/errors#validation_failed) — Validation failed - 429 [`rate_limited`](https://developers.processcart.com/errors#rate_limited) — Too many requests - 500 [`internal_error`](https://developers.processcart.com/errors#internal_error) — Internal error - 503 [`unavailable`](https://developers.processcart.com/errors#unavailable) — Service unavailable **Example response** (200) ```json { "data": [ { "id": "12b83373-b279-48e5-86b1-c88e71c9ed29", "workflowId": "39b74b11-d796-42e5-972f-d2a20b5ce7b1", "workflow": { "id": "39b74b11-d796-42e5-972f-d2a20b5ce7b1", "name": "Order fulfillment" }, "name": "Weekly order review", "description": "Every Monday at 9:00 AM", "recurrence": { "freq": "weekly", "interval": 1, "time": "09:00", "startDate": "2026-11-02", "weekly": { "days": [ "mon" ] } }, "timezone": "America/Los_Angeles", "namePattern": "{{workflow}} — {{date}}", "presetValues": { "amount": 250, "customer": "Acme Corp" }, "assignmentOverrides": { "review": [ { "kind": "user", "userId": "d4a284fd-cdc8-43c1-8933-084a811fc33e" } ], "wrapup": [ { "kind": "group", "groupId": "98257cc7-dce3-4ca7-a310-ecf91b75e201" } ] }, "enabled": true, "disabledReason": null, "lastError": null, "consecutiveFailures": 0, "nextRunAt": "2026-11-02T17:00:00.000Z", "nextOccurrences": [ "2026-11-02T17:00:00.000Z", "2026-11-09T17:00:00.000Z", "2026-11-16T17:00:00.000Z" ], "lastRunAt": null, "latestRun": { "id": "1185793a-e8fc-4470-a359-826794842cef", "name": "Order fulfillment — Oct 10, 2026", "startedAt": "2026-10-11T05:27:53.467Z" }, "presetMismatches": [], "createdBy": null, "createdAt": "2026-10-11T05:27:50.731Z", "updatedAt": "2026-10-11T05:27:52.873Z" } ], "nextCursor": null, "hasMore": false } ``` ## POST /v1/schedules **Create a schedule.** Scope `schedules:manage` · checked against the key's workflow / folder allowlist (403 `not_allowed_for_key` outside it) · accepts `Idempotency-Key`. Operation id `createSchedule` · [open in the API reference](https://developers.processcart.com/reference#tag/schedules/POST/v1/schedules). Creates a schedule that starts runs of one published workflow on a recurrence (daily, weekly, monthly or yearly, at a time on a 5-minute step), with a run-name pattern, preset field values (every required kickoff field needs one; a preset outside its field's bounds is refused with 422, `errors[].code` `invalid_value`) and optional per-task assignees. `timezone` defaults to the workspace's. A recurrence with no future occurrence is refused. Audited. A key restricted to workflows or folders sees only the schedules of workflows inside its allowlist; any other schedule answers 403 `not_allowed_for_key`. **Request headers** | Name | Type | Required | Description | | --- | --- | --- | --- | | `Idempotency-Key` | string | no | Makes the request safe to retry: a second request with the same key and a byte-identical body (and the same `Expected-Revision`) replays the stored response (`Idempotent-Replayed: true`); a different body with the same key is refused (422 `idempotency_key_reused`) and no second resource is created. Keys are stored per workspace for 24 hours and replayed only to the API key that made the request (or the key one rotation away from it); another API key sending the same value is refused (422 `idempotency_key_reused`), so prefix keys per integration. 409, 429 and 5xx answers are never stored. | **Request body** — `application/json`, [ScheduleConfig](https://developers.processcart.com/reference/schemas#scheduleconfig) | Property | Type | Required | Description | | --- | --- | --- | --- | | `workflowId` | [Uuid](https://developers.processcart.com/reference/schemas#uuid) | yes | | | `name` | string | yes | | | `recurrence` | [Recurrence](https://developers.processcart.com/reference/schemas#recurrence) | yes | | | `timezone` | string | no | | | `namePattern` | string | no | | | `presetValues` | object (key → [CanonicalValue](https://developers.processcart.com/reference/schemas#canonicalvalue)) | no | | | `assignmentOverrides` | object (key → [AutomationAssignee](https://developers.processcart.com/reference/schemas#automationassignee)[]) | no | | **Responses** | Status | Meaning | Body | | --- | --- | --- | | 201 | Created. `Location` names the new resource. | [Schedule](https://developers.processcart.com/reference/schemas#schedule) | | 401 | No usable credential. `unauthorized` — Authentication required; `invalid_api_key` — Invalid API key; `key_revoked` — API key revoked. | [Problem](https://developers.processcart.com/errors) | | 403 | `insufficient_scope` — Insufficient scope; `not_allowed_for_key` — Not allowed for this key; `read_only_key` — Read-only key; `workflow_not_runnable` — Workflow not runnable. | [Problem](https://developers.processcart.com/errors) | | 405 | The verb is not supported on this path. `method_not_allowed` — Method not allowed. | [Problem](https://developers.processcart.com/errors) | | 409 | `idempotency_in_progress` — Request in progress; `workflow_not_published` — Workflow not published; `workflow_trashed` — Workflow is in Trash. | [Problem](https://developers.processcart.com/errors) | | 413 | `payload_too_large` — Payload too large. | [Problem](https://developers.processcart.com/errors) | | 415 | `unsupported_media_type` — Unsupported media type. | [Problem](https://developers.processcart.com/errors) | | 422 | `idempotency_key_reused` — Idempotency key reused; `validation_failed` — Validation failed. | [Problem](https://developers.processcart.com/errors) | | 429 | `rate_limited` — Too many requests. | [Problem](https://developers.processcart.com/errors) | | 500 | `internal_error` — Internal error. | [Problem](https://developers.processcart.com/errors) | | 503 | `unavailable` — Service unavailable. | [Problem](https://developers.processcart.com/errors) | **Errors** (every code this operation can answer; see [Errors](https://developers.processcart.com/errors)) - 401 [`invalid_api_key`](https://developers.processcart.com/errors#invalid_api_key) — Invalid API key - 401 [`key_revoked`](https://developers.processcart.com/errors#key_revoked) — API key revoked - 401 [`unauthorized`](https://developers.processcart.com/errors#unauthorized) — Authentication required - 403 [`insufficient_scope`](https://developers.processcart.com/errors#insufficient_scope) — Insufficient scope - 403 [`not_allowed_for_key`](https://developers.processcart.com/errors#not_allowed_for_key) — Not allowed for this key - 403 [`read_only_key`](https://developers.processcart.com/errors#read_only_key) — Read-only key - 403 [`workflow_not_runnable`](https://developers.processcart.com/errors#workflow_not_runnable) — Workflow not runnable - 405 [`method_not_allowed`](https://developers.processcart.com/errors#method_not_allowed) — Method not allowed - 409 [`idempotency_in_progress`](https://developers.processcart.com/errors#idempotency_in_progress) — Request in progress - 409 [`workflow_not_published`](https://developers.processcart.com/errors#workflow_not_published) — Workflow not published - 409 [`workflow_trashed`](https://developers.processcart.com/errors#workflow_trashed) — Workflow is in Trash - 413 [`payload_too_large`](https://developers.processcart.com/errors#payload_too_large) — Payload too large - 415 [`unsupported_media_type`](https://developers.processcart.com/errors#unsupported_media_type) — Unsupported media type - 422 [`idempotency_key_reused`](https://developers.processcart.com/errors#idempotency_key_reused) — Idempotency key reused - 422 [`validation_failed`](https://developers.processcart.com/errors#validation_failed) — Validation failed - 429 [`rate_limited`](https://developers.processcart.com/errors#rate_limited) — Too many requests - 500 [`internal_error`](https://developers.processcart.com/errors#internal_error) — Internal error - 503 [`unavailable`](https://developers.processcart.com/errors#unavailable) — Service unavailable **Example request** ```json { "workflowId": "39b74b11-d796-42e5-972f-d2a20b5ce7b1", "name": "Weekly order review", "recurrence": { "freq": "weekly", "interval": 1, "time": "09:00", "startDate": "2026-11-02", "weekly": { "days": [ "mon" ] } }, "presetValues": { "customer": "Acme Corp", "amount": 250 }, "assignmentOverrides": { "review": [ { "kind": "user", "userId": "d4a284fd-cdc8-43c1-8933-084a811fc33e" } ], "wrapup": [ { "kind": "group", "groupId": "98257cc7-dce3-4ca7-a310-ecf91b75e201" } ] } } ``` **Example response** (201) ```json { "id": "12b83373-b279-48e5-86b1-c88e71c9ed29", "workflowId": "39b74b11-d796-42e5-972f-d2a20b5ce7b1", "workflow": { "id": "39b74b11-d796-42e5-972f-d2a20b5ce7b1", "name": "Order fulfillment" }, "name": "Weekly order review", "description": "Every Monday at 9:00 AM", "recurrence": { "freq": "weekly", "interval": 1, "time": "09:00", "startDate": "2026-11-02", "weekly": { "days": [ "mon" ] } }, "timezone": "America/Los_Angeles", "namePattern": "{{workflow}} — {{date}}", "presetValues": { "amount": 250, "customer": "Acme Corp" }, "assignmentOverrides": { "review": [ { "kind": "user", "userId": "d4a284fd-cdc8-43c1-8933-084a811fc33e" } ], "wrapup": [ { "kind": "group", "groupId": "98257cc7-dce3-4ca7-a310-ecf91b75e201" } ] }, "enabled": true, "disabledReason": null, "lastError": null, "consecutiveFailures": 0, "nextRunAt": "2026-11-02T17:00:00.000Z", "nextOccurrences": [ "2026-11-02T17:00:00.000Z", "2026-11-09T17:00:00.000Z", "2026-11-16T17:00:00.000Z" ], "lastRunAt": null, "latestRun": null, "presetMismatches": [], "createdBy": null, "createdAt": "2026-10-11T05:27:50.731Z", "updatedAt": "2026-10-11T05:27:50.731Z" } ``` ## GET /v1/schedules/{id} **Get a schedule.** Scope `schedules:manage` · checked against the key's workflow / folder allowlist (403 `not_allowed_for_key` outside it). Operation id `getSchedule` · [open in the API reference](https://developers.processcart.com/reference#tag/schedules/GET/v1/schedules/{id}). Returns one schedule with its recurrence in words, presets, assignment overrides, enabled state and pause reason, failure counter, next occurrences, newest run and any presets the published workflow no longer accepts. A key restricted to workflows or folders sees only the schedules of workflows inside its allowlist; any other schedule answers 403 `not_allowed_for_key`. **Path parameters** | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | string (uuid) | yes | | **Responses** | Status | Meaning | Body | | --- | --- | --- | | 200 | OK. | [Schedule](https://developers.processcart.com/reference/schemas#schedule) | | 401 | No usable credential. `unauthorized` — Authentication required; `invalid_api_key` — Invalid API key; `key_revoked` — API key revoked. | [Problem](https://developers.processcart.com/errors) | | 403 | `insufficient_scope` — Insufficient scope; `not_allowed_for_key` — Not allowed for this key. | [Problem](https://developers.processcart.com/errors) | | 404 | `not_found` — Not found. | [Problem](https://developers.processcart.com/errors) | | 405 | The verb is not supported on this path. `method_not_allowed` — Method not allowed. | [Problem](https://developers.processcart.com/errors) | | 422 | `validation_failed` — Validation failed. | [Problem](https://developers.processcart.com/errors) | | 429 | `rate_limited` — Too many requests. | [Problem](https://developers.processcart.com/errors) | | 500 | `internal_error` — Internal error. | [Problem](https://developers.processcart.com/errors) | | 503 | `unavailable` — Service unavailable. | [Problem](https://developers.processcart.com/errors) | **Errors** (every code this operation can answer; see [Errors](https://developers.processcart.com/errors)) - 401 [`invalid_api_key`](https://developers.processcart.com/errors#invalid_api_key) — Invalid API key - 401 [`key_revoked`](https://developers.processcart.com/errors#key_revoked) — API key revoked - 401 [`unauthorized`](https://developers.processcart.com/errors#unauthorized) — Authentication required - 403 [`insufficient_scope`](https://developers.processcart.com/errors#insufficient_scope) — Insufficient scope - 403 [`not_allowed_for_key`](https://developers.processcart.com/errors#not_allowed_for_key) — Not allowed for this key - 404 [`not_found`](https://developers.processcart.com/errors#not_found) — Not found - 405 [`method_not_allowed`](https://developers.processcart.com/errors#method_not_allowed) — Method not allowed - 422 [`validation_failed`](https://developers.processcart.com/errors#validation_failed) — Validation failed - 429 [`rate_limited`](https://developers.processcart.com/errors#rate_limited) — Too many requests - 500 [`internal_error`](https://developers.processcart.com/errors#internal_error) — Internal error - 503 [`unavailable`](https://developers.processcart.com/errors#unavailable) — Service unavailable **Example response** (200) ```json { "id": "12b83373-b279-48e5-86b1-c88e71c9ed29", "workflowId": "39b74b11-d796-42e5-972f-d2a20b5ce7b1", "workflow": { "id": "39b74b11-d796-42e5-972f-d2a20b5ce7b1", "name": "Order fulfillment" }, "name": "Weekly order review", "description": "Every Monday at 9:00 AM", "recurrence": { "freq": "weekly", "interval": 1, "time": "09:00", "startDate": "2026-11-02", "weekly": { "days": [ "mon" ] } }, "timezone": "America/Los_Angeles", "namePattern": "{{workflow}} — {{date}}", "presetValues": { "amount": 250, "customer": "Acme Corp" }, "assignmentOverrides": { "review": [ { "kind": "user", "userId": "d4a284fd-cdc8-43c1-8933-084a811fc33e" } ], "wrapup": [ { "kind": "group", "groupId": "98257cc7-dce3-4ca7-a310-ecf91b75e201" } ] }, "enabled": true, "disabledReason": null, "lastError": null, "consecutiveFailures": 0, "nextRunAt": "2026-11-02T17:00:00.000Z", "nextOccurrences": [ "2026-11-02T17:00:00.000Z", "2026-11-09T17:00:00.000Z", "2026-11-16T17:00:00.000Z" ], "lastRunAt": null, "latestRun": { "id": "1185793a-e8fc-4470-a359-826794842cef", "name": "Order fulfillment — Oct 10, 2026", "startedAt": "2026-10-11T05:27:53.467Z" }, "presetMismatches": [], "createdBy": null, "createdAt": "2026-10-11T05:27:50.731Z", "updatedAt": "2026-10-11T05:27:52.873Z" } ``` ## PATCH /v1/schedules/{id} **Update a schedule.** Scope `schedules:manage` · checked against the key's workflow / folder allowlist (403 `not_allowed_for_key` outside it) · accepts `Idempotency-Key`. Operation id `updateSchedule` · [open in the API reference](https://developers.processcart.com/reference#tag/schedules/PATCH/v1/schedules/{id}). Changes any subset of `name`, `recurrence`, `timezone`, `namePattern`, `presetValues` and `assignmentOverrides` (presets and overrides are checked against the workflow's current published version). A new recurrence or zone recomputes the next run from now. `workflowId` cannot change, and `enabled` is not part of the configuration — use the pause and resume operations (422 `unknown_key` on `enabled`). A preset outside its field's bounds is refused (422 `invalid_value`). Audited. **Path parameters** | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | string (uuid) | yes | | **Request headers** | Name | Type | Required | Description | | --- | --- | --- | --- | | `Idempotency-Key` | string | no | Makes the request safe to retry: a second request with the same key and a byte-identical body (and the same `Expected-Revision`) replays the stored response (`Idempotent-Replayed: true`); a different body with the same key is refused (422 `idempotency_key_reused`) and no second resource is created. Keys are stored per workspace for 24 hours and replayed only to the API key that made the request (or the key one rotation away from it); another API key sending the same value is refused (422 `idempotency_key_reused`), so prefix keys per integration. 409, 429 and 5xx answers are never stored. | **Request body** — `application/json`, [SchedulePatch](https://developers.processcart.com/reference/schemas#schedulepatch) | Property | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | no | | | `recurrence` | [Recurrence](https://developers.processcart.com/reference/schemas#recurrence) | no | | | `timezone` | string | no | | | `namePattern` | string | no | | | `presetValues` | object (key → [CanonicalValue](https://developers.processcart.com/reference/schemas#canonicalvalue)) | no | | | `assignmentOverrides` | object (key → [AutomationAssignee](https://developers.processcart.com/reference/schemas#automationassignee)[]) | no | | **Responses** | Status | Meaning | Body | | --- | --- | --- | | 200 | OK. | [Schedule](https://developers.processcart.com/reference/schemas#schedule) | | 401 | No usable credential. `unauthorized` — Authentication required; `invalid_api_key` — Invalid API key; `key_revoked` — API key revoked. | [Problem](https://developers.processcart.com/errors) | | 403 | `insufficient_scope` — Insufficient scope; `not_allowed_for_key` — Not allowed for this key; `read_only_key` — Read-only key; `workflow_not_runnable` — Workflow not runnable. | [Problem](https://developers.processcart.com/errors) | | 404 | `not_found` — Not found. | [Problem](https://developers.processcart.com/errors) | | 405 | The verb is not supported on this path. `method_not_allowed` — Method not allowed. | [Problem](https://developers.processcart.com/errors) | | 409 | `idempotency_in_progress` — Request in progress; `workflow_not_published` — Workflow not published; `workflow_trashed` — Workflow is in Trash. | [Problem](https://developers.processcart.com/errors) | | 413 | `payload_too_large` — Payload too large. | [Problem](https://developers.processcart.com/errors) | | 415 | `unsupported_media_type` — Unsupported media type. | [Problem](https://developers.processcart.com/errors) | | 422 | `idempotency_key_reused` — Idempotency key reused; `validation_failed` — Validation failed. | [Problem](https://developers.processcart.com/errors) | | 429 | `rate_limited` — Too many requests. | [Problem](https://developers.processcart.com/errors) | | 500 | `internal_error` — Internal error. | [Problem](https://developers.processcart.com/errors) | | 503 | `unavailable` — Service unavailable. | [Problem](https://developers.processcart.com/errors) | **Errors** (every code this operation can answer; see [Errors](https://developers.processcart.com/errors)) - 401 [`invalid_api_key`](https://developers.processcart.com/errors#invalid_api_key) — Invalid API key - 401 [`key_revoked`](https://developers.processcart.com/errors#key_revoked) — API key revoked - 401 [`unauthorized`](https://developers.processcart.com/errors#unauthorized) — Authentication required - 403 [`insufficient_scope`](https://developers.processcart.com/errors#insufficient_scope) — Insufficient scope - 403 [`not_allowed_for_key`](https://developers.processcart.com/errors#not_allowed_for_key) — Not allowed for this key - 403 [`read_only_key`](https://developers.processcart.com/errors#read_only_key) — Read-only key - 403 [`workflow_not_runnable`](https://developers.processcart.com/errors#workflow_not_runnable) — Workflow not runnable - 404 [`not_found`](https://developers.processcart.com/errors#not_found) — Not found - 405 [`method_not_allowed`](https://developers.processcart.com/errors#method_not_allowed) — Method not allowed - 409 [`idempotency_in_progress`](https://developers.processcart.com/errors#idempotency_in_progress) — Request in progress - 409 [`workflow_not_published`](https://developers.processcart.com/errors#workflow_not_published) — Workflow not published - 409 [`workflow_trashed`](https://developers.processcart.com/errors#workflow_trashed) — Workflow is in Trash - 413 [`payload_too_large`](https://developers.processcart.com/errors#payload_too_large) — Payload too large - 415 [`unsupported_media_type`](https://developers.processcart.com/errors#unsupported_media_type) — Unsupported media type - 422 [`idempotency_key_reused`](https://developers.processcart.com/errors#idempotency_key_reused) — Idempotency key reused - 422 [`validation_failed`](https://developers.processcart.com/errors#validation_failed) — Validation failed - 429 [`rate_limited`](https://developers.processcart.com/errors#rate_limited) — Too many requests - 500 [`internal_error`](https://developers.processcart.com/errors#internal_error) — Internal error - 503 [`unavailable`](https://developers.processcart.com/errors#unavailable) — Service unavailable **Example request** ```json { "name": "Weekly order review (Mondays)" } ``` **Example response** (200) ```json { "id": "12b83373-b279-48e5-86b1-c88e71c9ed29", "workflowId": "39b74b11-d796-42e5-972f-d2a20b5ce7b1", "workflow": { "id": "39b74b11-d796-42e5-972f-d2a20b5ce7b1", "name": "Order fulfillment" }, "name": "Weekly order review (Mondays)", "description": "Every Monday at 9:00 AM", "recurrence": { "freq": "weekly", "interval": 1, "time": "09:00", "startDate": "2026-11-02", "weekly": { "days": [ "mon" ] } }, "timezone": "America/Los_Angeles", "namePattern": "{{workflow}} — {{date}}", "presetValues": { "amount": 250, "customer": "Acme Corp" }, "assignmentOverrides": { "review": [ { "kind": "user", "userId": "d4a284fd-cdc8-43c1-8933-084a811fc33e" } ], "wrapup": [ { "kind": "group", "groupId": "98257cc7-dce3-4ca7-a310-ecf91b75e201" } ] }, "enabled": true, "disabledReason": null, "lastError": null, "consecutiveFailures": 0, "nextRunAt": "2026-11-02T17:00:00.000Z", "nextOccurrences": [ "2026-11-02T17:00:00.000Z", "2026-11-09T17:00:00.000Z", "2026-11-16T17:00:00.000Z" ], "lastRunAt": null, "latestRun": { "id": "1185793a-e8fc-4470-a359-826794842cef", "name": "Order fulfillment — Oct 10, 2026", "startedAt": "2026-10-11T05:27:53.467Z" }, "presetMismatches": [], "createdBy": null, "createdAt": "2026-10-11T05:27:50.731Z", "updatedAt": "2026-10-11T05:27:55.743Z" } ``` ## DELETE /v1/schedules/{id} **Delete a schedule.** Scope `schedules:manage` · checked against the key's workflow / folder allowlist (403 `not_allowed_for_key` outside it) · accepts `Idempotency-Key`. Operation id `deleteSchedule` · [open in the API reference](https://developers.processcart.com/reference#tag/schedules/DELETE/v1/schedules/{id}). Deletes the schedule. Runs it already created stay; their `schedule` member reads null afterwards. Audited. **Path parameters** | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | string (uuid) | yes | | **Request headers** | Name | Type | Required | Description | | --- | --- | --- | --- | | `Idempotency-Key` | string | no | Makes the request safe to retry: a second request with the same key and a byte-identical body (and the same `Expected-Revision`) replays the stored response (`Idempotent-Replayed: true`); a different body with the same key is refused (422 `idempotency_key_reused`) and no second resource is created. Keys are stored per workspace for 24 hours and replayed only to the API key that made the request (or the key one rotation away from it); another API key sending the same value is refused (422 `idempotency_key_reused`), so prefix keys per integration. 409, 429 and 5xx answers are never stored. | **Responses** | Status | Meaning | Body | | --- | --- | --- | | 204 | Done; no body. | — | | 401 | No usable credential. `unauthorized` — Authentication required; `invalid_api_key` — Invalid API key; `key_revoked` — API key revoked. | [Problem](https://developers.processcart.com/errors) | | 403 | `insufficient_scope` — Insufficient scope; `not_allowed_for_key` — Not allowed for this key; `read_only_key` — Read-only key. | [Problem](https://developers.processcart.com/errors) | | 404 | `not_found` — Not found. | [Problem](https://developers.processcart.com/errors) | | 405 | The verb is not supported on this path. `method_not_allowed` — Method not allowed. | [Problem](https://developers.processcart.com/errors) | | 409 | `idempotency_in_progress` — Request in progress. | [Problem](https://developers.processcart.com/errors) | | 422 | `idempotency_key_reused` — Idempotency key reused; `validation_failed` — Validation failed. | [Problem](https://developers.processcart.com/errors) | | 429 | `rate_limited` — Too many requests. | [Problem](https://developers.processcart.com/errors) | | 500 | `internal_error` — Internal error. | [Problem](https://developers.processcart.com/errors) | | 503 | `unavailable` — Service unavailable. | [Problem](https://developers.processcart.com/errors) | **Errors** (every code this operation can answer; see [Errors](https://developers.processcart.com/errors)) - 401 [`invalid_api_key`](https://developers.processcart.com/errors#invalid_api_key) — Invalid API key - 401 [`key_revoked`](https://developers.processcart.com/errors#key_revoked) — API key revoked - 401 [`unauthorized`](https://developers.processcart.com/errors#unauthorized) — Authentication required - 403 [`insufficient_scope`](https://developers.processcart.com/errors#insufficient_scope) — Insufficient scope - 403 [`not_allowed_for_key`](https://developers.processcart.com/errors#not_allowed_for_key) — Not allowed for this key - 403 [`read_only_key`](https://developers.processcart.com/errors#read_only_key) — Read-only key - 404 [`not_found`](https://developers.processcart.com/errors#not_found) — Not found - 405 [`method_not_allowed`](https://developers.processcart.com/errors#method_not_allowed) — Method not allowed - 409 [`idempotency_in_progress`](https://developers.processcart.com/errors#idempotency_in_progress) — Request in progress - 422 [`idempotency_key_reused`](https://developers.processcart.com/errors#idempotency_key_reused) — Idempotency key reused - 422 [`validation_failed`](https://developers.processcart.com/errors#validation_failed) — Validation failed - 429 [`rate_limited`](https://developers.processcart.com/errors#rate_limited) — Too many requests - 500 [`internal_error`](https://developers.processcart.com/errors#internal_error) — Internal error - 503 [`unavailable`](https://developers.processcart.com/errors#unavailable) — Service unavailable ## POST /v1/schedules/{id}/pause **Pause a schedule.** Scope `schedules:manage` · checked against the key's workflow / folder allowlist (403 `not_allowed_for_key` outside it) · accepts `Idempotency-Key`. Operation id `pauseSchedule` · [open in the API reference](https://developers.processcart.com/reference#tag/schedules/POST/v1/schedules/{id}/pause). Stops the schedule from creating runs (reason `manual`). Pausing a paused schedule answers 200 unchanged and keeps its reason. Audited. **Path parameters** | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | string (uuid) | yes | | **Request headers** | Name | Type | Required | Description | | --- | --- | --- | --- | | `Idempotency-Key` | string | no | Makes the request safe to retry: a second request with the same key and a byte-identical body (and the same `Expected-Revision`) replays the stored response (`Idempotent-Replayed: true`); a different body with the same key is refused (422 `idempotency_key_reused`) and no second resource is created. Keys are stored per workspace for 24 hours and replayed only to the API key that made the request (or the key one rotation away from it); another API key sending the same value is refused (422 `idempotency_key_reused`), so prefix keys per integration. 409, 429 and 5xx answers are never stored. | **Responses** | Status | Meaning | Body | | --- | --- | --- | | 200 | OK. | [Schedule](https://developers.processcart.com/reference/schemas#schedule) | | 401 | No usable credential. `unauthorized` — Authentication required; `invalid_api_key` — Invalid API key; `key_revoked` — API key revoked. | [Problem](https://developers.processcart.com/errors) | | 403 | `insufficient_scope` — Insufficient scope; `not_allowed_for_key` — Not allowed for this key; `read_only_key` — Read-only key. | [Problem](https://developers.processcart.com/errors) | | 404 | `not_found` — Not found. | [Problem](https://developers.processcart.com/errors) | | 405 | The verb is not supported on this path. `method_not_allowed` — Method not allowed. | [Problem](https://developers.processcart.com/errors) | | 409 | `idempotency_in_progress` — Request in progress. | [Problem](https://developers.processcart.com/errors) | | 422 | `idempotency_key_reused` — Idempotency key reused; `validation_failed` — Validation failed. | [Problem](https://developers.processcart.com/errors) | | 429 | `rate_limited` — Too many requests. | [Problem](https://developers.processcart.com/errors) | | 500 | `internal_error` — Internal error. | [Problem](https://developers.processcart.com/errors) | | 503 | `unavailable` — Service unavailable. | [Problem](https://developers.processcart.com/errors) | **Errors** (every code this operation can answer; see [Errors](https://developers.processcart.com/errors)) - 401 [`invalid_api_key`](https://developers.processcart.com/errors#invalid_api_key) — Invalid API key - 401 [`key_revoked`](https://developers.processcart.com/errors#key_revoked) — API key revoked - 401 [`unauthorized`](https://developers.processcart.com/errors#unauthorized) — Authentication required - 403 [`insufficient_scope`](https://developers.processcart.com/errors#insufficient_scope) — Insufficient scope - 403 [`not_allowed_for_key`](https://developers.processcart.com/errors#not_allowed_for_key) — Not allowed for this key - 403 [`read_only_key`](https://developers.processcart.com/errors#read_only_key) — Read-only key - 404 [`not_found`](https://developers.processcart.com/errors#not_found) — Not found - 405 [`method_not_allowed`](https://developers.processcart.com/errors#method_not_allowed) — Method not allowed - 409 [`idempotency_in_progress`](https://developers.processcart.com/errors#idempotency_in_progress) — Request in progress - 422 [`idempotency_key_reused`](https://developers.processcart.com/errors#idempotency_key_reused) — Idempotency key reused - 422 [`validation_failed`](https://developers.processcart.com/errors#validation_failed) — Validation failed - 429 [`rate_limited`](https://developers.processcart.com/errors#rate_limited) — Too many requests - 500 [`internal_error`](https://developers.processcart.com/errors#internal_error) — Internal error - 503 [`unavailable`](https://developers.processcart.com/errors#unavailable) — Service unavailable **Example response** (200) ```json { "id": "12b83373-b279-48e5-86b1-c88e71c9ed29", "workflowId": "39b74b11-d796-42e5-972f-d2a20b5ce7b1", "workflow": { "id": "39b74b11-d796-42e5-972f-d2a20b5ce7b1", "name": "Order fulfillment" }, "name": "Weekly order review", "description": "Every Monday at 9:00 AM", "recurrence": { "freq": "weekly", "interval": 1, "time": "09:00", "startDate": "2026-11-02", "weekly": { "days": [ "mon" ] } }, "timezone": "America/Los_Angeles", "namePattern": "{{workflow}} — {{date}}", "presetValues": { "amount": 250, "customer": "Acme Corp" }, "assignmentOverrides": { "review": [ { "kind": "user", "userId": "d4a284fd-cdc8-43c1-8933-084a811fc33e" } ], "wrapup": [ { "kind": "group", "groupId": "98257cc7-dce3-4ca7-a310-ecf91b75e201" } ] }, "enabled": false, "disabledReason": "manual", "lastError": null, "consecutiveFailures": 0, "nextRunAt": null, "nextOccurrences": [], "lastRunAt": null, "latestRun": null, "presetMismatches": [], "createdBy": null, "createdAt": "2026-10-11T05:27:50.731Z", "updatedAt": "2026-10-11T05:27:51.759Z" } ``` ## POST /v1/schedules/{id}/resume **Resume a schedule.** Scope `schedules:manage` · checked against the key's workflow / folder allowlist (403 `not_allowed_for_key` outside it) · accepts `Idempotency-Key`. Operation id `resumeSchedule` · [open in the API reference](https://developers.processcart.com/reference#tag/schedules/POST/v1/schedules/{id}/resume). Turns a paused schedule back on. Resuming never catches up: occurrences missed while paused are skipped and the next run is the first occurrence after now. A schedule whose end date has passed is refused with 409 `schedule_ended` (change its recurrence first). A schedule whose workflow is unpublished answers 409 `workflow_not_published`, one in Trash 409 `workflow_trashed`, and an archived one 403 `workflow_not_runnable`. Resuming a running schedule answers 200 unchanged. Audited. **Path parameters** | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | string (uuid) | yes | | **Request headers** | Name | Type | Required | Description | | --- | --- | --- | --- | | `Idempotency-Key` | string | no | Makes the request safe to retry: a second request with the same key and a byte-identical body (and the same `Expected-Revision`) replays the stored response (`Idempotent-Replayed: true`); a different body with the same key is refused (422 `idempotency_key_reused`) and no second resource is created. Keys are stored per workspace for 24 hours and replayed only to the API key that made the request (or the key one rotation away from it); another API key sending the same value is refused (422 `idempotency_key_reused`), so prefix keys per integration. 409, 429 and 5xx answers are never stored. | **Responses** | Status | Meaning | Body | | --- | --- | --- | | 200 | OK. | [Schedule](https://developers.processcart.com/reference/schemas#schedule) | | 401 | No usable credential. `unauthorized` — Authentication required; `invalid_api_key` — Invalid API key; `key_revoked` — API key revoked. | [Problem](https://developers.processcart.com/errors) | | 403 | `insufficient_scope` — Insufficient scope; `not_allowed_for_key` — Not allowed for this key; `read_only_key` — Read-only key; `workflow_not_runnable` — Workflow not runnable. | [Problem](https://developers.processcart.com/errors) | | 404 | `not_found` — Not found. | [Problem](https://developers.processcart.com/errors) | | 405 | The verb is not supported on this path. `method_not_allowed` — Method not allowed. | [Problem](https://developers.processcart.com/errors) | | 409 | `idempotency_in_progress` — Request in progress; `schedule_ended` — Schedule ended; `workflow_not_published` — Workflow not published; `workflow_trashed` — Workflow is in Trash. | [Problem](https://developers.processcart.com/errors) | | 422 | `idempotency_key_reused` — Idempotency key reused; `validation_failed` — Validation failed. | [Problem](https://developers.processcart.com/errors) | | 429 | `rate_limited` — Too many requests. | [Problem](https://developers.processcart.com/errors) | | 500 | `internal_error` — Internal error. | [Problem](https://developers.processcart.com/errors) | | 503 | `unavailable` — Service unavailable. | [Problem](https://developers.processcart.com/errors) | **Errors** (every code this operation can answer; see [Errors](https://developers.processcart.com/errors)) - 401 [`invalid_api_key`](https://developers.processcart.com/errors#invalid_api_key) — Invalid API key - 401 [`key_revoked`](https://developers.processcart.com/errors#key_revoked) — API key revoked - 401 [`unauthorized`](https://developers.processcart.com/errors#unauthorized) — Authentication required - 403 [`insufficient_scope`](https://developers.processcart.com/errors#insufficient_scope) — Insufficient scope - 403 [`not_allowed_for_key`](https://developers.processcart.com/errors#not_allowed_for_key) — Not allowed for this key - 403 [`read_only_key`](https://developers.processcart.com/errors#read_only_key) — Read-only key - 403 [`workflow_not_runnable`](https://developers.processcart.com/errors#workflow_not_runnable) — Workflow not runnable - 404 [`not_found`](https://developers.processcart.com/errors#not_found) — Not found - 405 [`method_not_allowed`](https://developers.processcart.com/errors#method_not_allowed) — Method not allowed - 409 [`idempotency_in_progress`](https://developers.processcart.com/errors#idempotency_in_progress) — Request in progress - 409 [`schedule_ended`](https://developers.processcart.com/errors#schedule_ended) — Schedule ended - 409 [`workflow_not_published`](https://developers.processcart.com/errors#workflow_not_published) — Workflow not published - 409 [`workflow_trashed`](https://developers.processcart.com/errors#workflow_trashed) — Workflow is in Trash - 422 [`idempotency_key_reused`](https://developers.processcart.com/errors#idempotency_key_reused) — Idempotency key reused - 422 [`validation_failed`](https://developers.processcart.com/errors#validation_failed) — Validation failed - 429 [`rate_limited`](https://developers.processcart.com/errors#rate_limited) — Too many requests - 500 [`internal_error`](https://developers.processcart.com/errors#internal_error) — Internal error - 503 [`unavailable`](https://developers.processcart.com/errors#unavailable) — Service unavailable **Example response** (200) ```json { "id": "12b83373-b279-48e5-86b1-c88e71c9ed29", "workflowId": "39b74b11-d796-42e5-972f-d2a20b5ce7b1", "workflow": { "id": "39b74b11-d796-42e5-972f-d2a20b5ce7b1", "name": "Order fulfillment" }, "name": "Weekly order review", "description": "Every Monday at 9:00 AM", "recurrence": { "freq": "weekly", "interval": 1, "time": "09:00", "startDate": "2026-11-02", "weekly": { "days": [ "mon" ] } }, "timezone": "America/Los_Angeles", "namePattern": "{{workflow}} — {{date}}", "presetValues": { "amount": 250, "customer": "Acme Corp" }, "assignmentOverrides": { "review": [ { "kind": "user", "userId": "d4a284fd-cdc8-43c1-8933-084a811fc33e" } ], "wrapup": [ { "kind": "group", "groupId": "98257cc7-dce3-4ca7-a310-ecf91b75e201" } ] }, "enabled": true, "disabledReason": null, "lastError": null, "consecutiveFailures": 0, "nextRunAt": "2026-11-02T17:00:00.000Z", "nextOccurrences": [ "2026-11-02T17:00:00.000Z", "2026-11-09T17:00:00.000Z", "2026-11-16T17:00:00.000Z" ], "lastRunAt": null, "latestRun": null, "presetMismatches": [], "createdBy": null, "createdAt": "2026-10-11T05:27:50.731Z", "updatedAt": "2026-10-11T05:27:52.873Z" } ``` ## POST /v1/schedules/{id}/run **Start a run from a schedule now.** Scope `schedules:manage` · checked against the key's workflow / folder allowlist (403 `not_allowed_for_key` outside it) · accepts `Idempotency-Key`. Operation id `runScheduleNow` · [open in the API reference](https://developers.processcart.com/reference#tag/schedules/POST/v1/schedules/{id}/run). Starts one run right away with the schedule's name pattern, presets and assignment overrides — paused or not, outside the recurrence. Returns the new Run (`startedBy: "schedule"`, `schedule` set, `scheduledFor: null`, `revision: 1`) and an absolute `Location`. Presets the published workflow no longer accepts are skipped; a required kickoff field without a preset is refused with 422 `required_fields_unsatisfied`. Audited. **Path parameters** | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | string (uuid) | yes | | **Request headers** | Name | Type | Required | Description | | --- | --- | --- | --- | | `Idempotency-Key` | string | no | Makes the request safe to retry: a second request with the same key and a byte-identical body (and the same `Expected-Revision`) replays the stored response (`Idempotent-Replayed: true`); a different body with the same key is refused (422 `idempotency_key_reused`) and no second resource is created. Keys are stored per workspace for 24 hours and replayed only to the API key that made the request (or the key one rotation away from it); another API key sending the same value is refused (422 `idempotency_key_reused`), so prefix keys per integration. 409, 429 and 5xx answers are never stored. | **Responses** | Status | Meaning | Body | | --- | --- | --- | | 201 | Created. `Location` names the new resource. | [Run](https://developers.processcart.com/reference/schemas#run) | | 401 | No usable credential. `unauthorized` — Authentication required; `invalid_api_key` — Invalid API key; `key_revoked` — API key revoked. | [Problem](https://developers.processcart.com/errors) | | 403 | `insufficient_scope` — Insufficient scope; `not_allowed_for_key` — Not allowed for this key; `read_only_key` — Read-only key; `workflow_not_runnable` — Workflow not runnable. | [Problem](https://developers.processcart.com/errors) | | 404 | `not_found` — Not found. | [Problem](https://developers.processcart.com/errors) | | 405 | The verb is not supported on this path. `method_not_allowed` — Method not allowed. | [Problem](https://developers.processcart.com/errors) | | 409 | `idempotency_in_progress` — Request in progress; `workflow_not_published` — Workflow not published; `workflow_trashed` — Workflow is in Trash. | [Problem](https://developers.processcart.com/errors) | | 422 | `idempotency_key_reused` — Idempotency key reused; `required_fields_unsatisfied` — Required fields unsatisfied; `validation_failed` — Validation failed. | [Problem](https://developers.processcart.com/errors) | | 429 | `rate_limited` — Too many requests. | [Problem](https://developers.processcart.com/errors) | | 500 | `internal_error` — Internal error. | [Problem](https://developers.processcart.com/errors) | | 503 | `unavailable` — Service unavailable. | [Problem](https://developers.processcart.com/errors) | **Errors** (every code this operation can answer; see [Errors](https://developers.processcart.com/errors)) - 401 [`invalid_api_key`](https://developers.processcart.com/errors#invalid_api_key) — Invalid API key - 401 [`key_revoked`](https://developers.processcart.com/errors#key_revoked) — API key revoked - 401 [`unauthorized`](https://developers.processcart.com/errors#unauthorized) — Authentication required - 403 [`insufficient_scope`](https://developers.processcart.com/errors#insufficient_scope) — Insufficient scope - 403 [`not_allowed_for_key`](https://developers.processcart.com/errors#not_allowed_for_key) — Not allowed for this key - 403 [`read_only_key`](https://developers.processcart.com/errors#read_only_key) — Read-only key - 403 [`workflow_not_runnable`](https://developers.processcart.com/errors#workflow_not_runnable) — Workflow not runnable - 404 [`not_found`](https://developers.processcart.com/errors#not_found) — Not found - 405 [`method_not_allowed`](https://developers.processcart.com/errors#method_not_allowed) — Method not allowed - 409 [`idempotency_in_progress`](https://developers.processcart.com/errors#idempotency_in_progress) — Request in progress - 409 [`workflow_not_published`](https://developers.processcart.com/errors#workflow_not_published) — Workflow not published - 409 [`workflow_trashed`](https://developers.processcart.com/errors#workflow_trashed) — Workflow is in Trash - 422 [`idempotency_key_reused`](https://developers.processcart.com/errors#idempotency_key_reused) — Idempotency key reused - 422 [`required_fields_unsatisfied`](https://developers.processcart.com/errors#required_fields_unsatisfied) — Required fields unsatisfied - 422 [`validation_failed`](https://developers.processcart.com/errors#validation_failed) — Validation failed - 429 [`rate_limited`](https://developers.processcart.com/errors#rate_limited) — Too many requests - 500 [`internal_error`](https://developers.processcart.com/errors#internal_error) — Internal error - 503 [`unavailable`](https://developers.processcart.com/errors#unavailable) — Service unavailable **Example response** (201) ```json { "id": "1185793a-e8fc-4470-a359-826794842cef", "name": "Order fulfillment — Oct 10, 2026", "status": "active", "priority": "none", "labels": [], "labelDetails": [], "dueAt": null, "keyDate": null, "revision": 1, "updatedAt": "2026-10-11T05:27:53.910Z", "startedAt": "2026-10-11T05:27:53.467Z", "startedBy": "schedule", "startedByUserId": null, "schedule": { "id": "12b83373-b279-48e5-86b1-c88e71c9ed29", "name": "Weekly order review" }, "scheduledFor": null, "completedAt": null, "deletedAt": null, "workflow": { "id": "39b74b11-d796-42e5-972f-d2a20b5ce7b1", "name": "Order fulfillment" }, "versionId": "9973fead-8dbf-4719-9a61-9dc7b126a7f4", "url": "https://app.processcart.com/runs/1185793a-e8fc-4470-a359-826794842cef", "tasks": [ { "key": "details", "title": "Details", "kind": "standard", "section": "Intake", "state": "todo", "visible": true, "locked": false, "dueAt": null, "assignees": [], "completedAt": null, "completedByUserId": null }, { "key": "review", "title": "Review", "kind": "approval", "section": "Intake", "state": "todo", "visible": true, "locked": false, "dueAt": null, "assignees": [ { "kind": "user", "userId": "d4a284fd-cdc8-43c1-8933-084a811fc33e" } ], "completedAt": null, "completedByUserId": null }, { "key": "stop", "title": "Sign-off", "kind": "standard", "section": "Intake", "state": "todo", "visible": true, "locked": false, "dueAt": null, "assignees": [], "completedAt": null, "completedByUserId": null }, { "key": "wrapup", "title": "Wrap up", "kind": "standard", "section": "Intake", "state": "todo", "visible": true, "locked": true, "dueAt": null, "assignees": [ { "kind": "group", "groupId": "98257cc7-dce3-4ca7-a310-ecf91b75e201" } ], "completedAt": null, "completedByUserId": null } ], "fields": [ { "key": "customer", "label": "Customer", "type": "text", "format": "none", "taskKey": "details", "value": "Acme Corp", "formatted": "Acme Corp", "visible": true }, { "key": "order_code", "label": "Order code", "type": "text", "format": "custom", "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "memo", "label": "Memo", "type": "textarea", "format": null, "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "quantity", "label": "Quantity", "type": "number", "format": "number", "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "amount", "label": "Amount", "type": "number", "format": "currency", "taskKey": "details", "value": 250, "formatted": "$250.00", "visible": true }, { "key": "fee", "label": "Fee", "type": "number", "format": "currency", "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "rate", "label": "Rate", "type": "number", "format": "percent", "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "rate_text", "label": "Rate (text)", "type": "text", "format": "percent", "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "ship_date", "label": "Ship date", "type": "date", "format": "date", "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "start_time", "label": "Start time", "type": "time", "format": "time", "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "gift_wrap", "label": "Gift wrap", "type": "dropdown", "format": null, "taskKey": "details", "value": null, "formatted": "", "visible": true, "choices": [ "Yes", "No" ] }, { "key": "tags", "label": "Tags", "type": "multi_choice", "format": null, "taskKey": "details", "value": null, "formatted": "", "visible": true, "choices": [ "A", "B", "C" ] }, { "key": "agree", "label": "Agree", "type": "checkbox", "format": null, "taskKey": "details", "value": false, "formatted": "No", "visible": true }, { "key": "contact_email", "label": "Contact email", "type": "email", "format": "email", "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "website", "label": "Website", "type": "url", "format": "url", "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "phone", "label": "Phone", "type": "phone", "format": "phone", "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "owner", "label": "Owner", "type": "members", "format": null, "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "steps", "label": "Steps", "type": "subtasks", "format": null, "taskKey": "details", "value": null, "formatted": "", "visible": true, "items": [ { "key": "photos", "label": "Photos" }, { "key": "invoice", "label": "Invoice" } ] }, { "key": "contract", "label": "Contract", "type": "file", "format": null, "taskKey": "details", "value": null, "formatted": "", "visible": true }, { "key": "double_amount", "label": "Double amount", "type": "formula", "format": "currency", "taskKey": "details", "value": 500, "formatted": "$500.00", "visible": true }, { "key": "source", "label": "Source", "type": "hidden", "format": null, "taskKey": "details", "value": null, "formatted": "", "visible": false } ] } ``` ## GET /v1/schedules/{id}/preview **Preview a schedule's next occurrences.** Scope `schedules:manage` · checked against the key's workflow / folder allowlist (403 `not_allowed_for_key` outside it). Operation id `previewSchedule` · [open in the API reference](https://developers.processcart.com/reference#tag/schedules/GET/v1/schedules/{id}/preview). Returns the next `count` (1–10, default 3) times the schedule creates a run, as UTC instants. For a paused schedule, the occurrences a resume would produce (counted from now); empty once its end date has passed. **Path parameters** | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | string (uuid) | yes | | **Query parameters** (an unknown parameter is a 422 `validation_failed` with `errors[].code = "unknown_parameter"`) | Name | Type | Required | Description | | --- | --- | --- | --- | | `count` | integer | no | | **Responses** | Status | Meaning | Body | | --- | --- | --- | | 200 | OK. | [ScheduleOccurrences](https://developers.processcart.com/reference/schemas#scheduleoccurrences) | | 401 | No usable credential. `unauthorized` — Authentication required; `invalid_api_key` — Invalid API key; `key_revoked` — API key revoked. | [Problem](https://developers.processcart.com/errors) | | 403 | `insufficient_scope` — Insufficient scope; `not_allowed_for_key` — Not allowed for this key. | [Problem](https://developers.processcart.com/errors) | | 404 | `not_found` — Not found. | [Problem](https://developers.processcart.com/errors) | | 405 | The verb is not supported on this path. `method_not_allowed` — Method not allowed. | [Problem](https://developers.processcart.com/errors) | | 422 | `validation_failed` — Validation failed. | [Problem](https://developers.processcart.com/errors) | | 429 | `rate_limited` — Too many requests. | [Problem](https://developers.processcart.com/errors) | | 500 | `internal_error` — Internal error. | [Problem](https://developers.processcart.com/errors) | | 503 | `unavailable` — Service unavailable. | [Problem](https://developers.processcart.com/errors) | **Errors** (every code this operation can answer; see [Errors](https://developers.processcart.com/errors)) - 401 [`invalid_api_key`](https://developers.processcart.com/errors#invalid_api_key) — Invalid API key - 401 [`key_revoked`](https://developers.processcart.com/errors#key_revoked) — API key revoked - 401 [`unauthorized`](https://developers.processcart.com/errors#unauthorized) — Authentication required - 403 [`insufficient_scope`](https://developers.processcart.com/errors#insufficient_scope) — Insufficient scope - 403 [`not_allowed_for_key`](https://developers.processcart.com/errors#not_allowed_for_key) — Not allowed for this key - 404 [`not_found`](https://developers.processcart.com/errors#not_found) — Not found - 405 [`method_not_allowed`](https://developers.processcart.com/errors#method_not_allowed) — Method not allowed - 422 [`validation_failed`](https://developers.processcart.com/errors#validation_failed) — Validation failed - 429 [`rate_limited`](https://developers.processcart.com/errors#rate_limited) — Too many requests - 500 [`internal_error`](https://developers.processcart.com/errors#internal_error) — Internal error - 503 [`unavailable`](https://developers.processcart.com/errors#unavailable) — Service unavailable **Example response** (200) ```json { "occurrences": [ "2026-11-02T17:00:00.000Z", "2026-11-09T17:00:00.000Z", "2026-11-16T17:00:00.000Z" ] } ``` --- # Automation rules endpoints > When-then rules on a workflow's runs (set fields, status, labels, assignees; notify; send to a webhook) and their execution history. Every operation with parameters, body, responses, errors and examples. Source: https://developers.processcart.com/reference/automation-rules When-then rules on a workflow's runs (set fields, status, labels, assignees; notify; send to a webhook) and their execution history. Rules carry no `revision`, and no operation here reads `Expected-Revision` (the header, or the SDK's `expectedRevision` option, is ignored): an update applies to the rule as it is stored, and the last write wins. Base URL `https://app.processcart.com/api/v1`. Every operation below is also in the [OpenAPI document](https://developers.processcart.com/openapi.json) and the [interactive reference](https://developers.processcart.com/reference). | Operation | Method and path | Scope | | --- | --- | --- | | [List automation rules](#listAutomationRules) | GET /v1/automation-rules | Scope `rules:manage` | | [Create an automation rule](#createAutomationRule) | POST /v1/automation-rules | Scope `rules:manage` | | [Get an automation rule](#getAutomationRule) | GET /v1/automation-rules/{id} | Scope `rules:manage` | | [Update an automation rule](#updateAutomationRule) | PATCH /v1/automation-rules/{id} | Scope `rules:manage` | | [Delete an automation rule](#deleteAutomationRule) | DELETE /v1/automation-rules/{id} | Scope `rules:manage` | | [Turn an automation rule on](#enableAutomationRule) | POST /v1/automation-rules/{id}/enable | Scope `rules:manage` | | [Turn an automation rule off](#disableAutomationRule) | POST /v1/automation-rules/{id}/disable | Scope `rules:manage` | | [List a rule's executions](#listAutomationRuleExecutions) | GET /v1/automation-rules/{id}/executions | Scope `rules:manage` | ## GET /v1/automation-rules **List automation rules.** Scope `rules:manage` · the key's allowlist narrows the rows (never a 403 — rows outside it are absent) · paginated (`{ data, nextCursor, hasMore }`). Operation id `listAutomationRules` · [open in the API reference](https://developers.processcart.com/reference#tag/automation-rules/GET/v1/automation-rules). Lists the workspace's automation rules, newest first, each with its trigger, filter, conditions, actions and enabled state. Filter by `workflowId` or `enabled`. `updatedSince` turns the list into an incremental sync ordered by `updatedAt` ascending (inclusive). A key restricted to workflows or folders lists only the rules of workflows inside its allowlist; rules for every workflow (`workflowId: null`) are omitted for it. **Query parameters** (an unknown parameter is a 422 `validation_failed` with `errors[].code = "unknown_parameter"`) | Name | Type | Required | Description | | --- | --- | --- | --- | | `workflowId` | string (uuid) | no | | | `enabled` | boolean | no | | | `updatedSince` | string (date-time) | no | Incremental sync: only items whose `updatedAt` is at or after this instant (inclusive), ordered by `updatedAt` ascending; an explicit conflicting `sort` / `order` is a 422. Overlap by a minute and dedupe: a Run by `(id, revision)`, any other item (workflow, view, webhook, schedule, automation rule) by `(id, updatedAt)` — a higher `revision` or a later `updatedAt` replaces the copy you hold, an equal one is unchanged. | | `limit` | integer | no | | | `cursor` | string | no | The `nextCursor` of the previous page. Opaque, and bound to the query that produced it — a cursor from a different filter or sort is a 422. | **Responses** | Status | Meaning | Body | | --- | --- | --- | | 200 | OK. | [AutomationRuleList](https://developers.processcart.com/reference/schemas#automationrulelist) | | 401 | No usable credential. `unauthorized` — Authentication required; `invalid_api_key` — Invalid API key; `key_revoked` — API key revoked. | [Problem](https://developers.processcart.com/errors) | | 403 | `insufficient_scope` — Insufficient scope. | [Problem](https://developers.processcart.com/errors) | | 405 | The verb is not supported on this path. `method_not_allowed` — Method not allowed. | [Problem](https://developers.processcart.com/errors) | | 422 | `validation_failed` — Validation failed. | [Problem](https://developers.processcart.com/errors) | | 429 | `rate_limited` — Too many requests. | [Problem](https://developers.processcart.com/errors) | | 500 | `internal_error` — Internal error. | [Problem](https://developers.processcart.com/errors) | | 503 | `unavailable` — Service unavailable. | [Problem](https://developers.processcart.com/errors) | **Errors** (every code this operation can answer; see [Errors](https://developers.processcart.com/errors)) - 401 [`invalid_api_key`](https://developers.processcart.com/errors#invalid_api_key) — Invalid API key - 401 [`key_revoked`](https://developers.processcart.com/errors#key_revoked) — API key revoked - 401 [`unauthorized`](https://developers.processcart.com/errors#unauthorized) — Authentication required - 403 [`insufficient_scope`](https://developers.processcart.com/errors#insufficient_scope) — Insufficient scope - 405 [`method_not_allowed`](https://developers.processcart.com/errors#method_not_allowed) — Method not allowed - 422 [`validation_failed`](https://developers.processcart.com/errors#validation_failed) — Validation failed - 429 [`rate_limited`](https://developers.processcart.com/errors#rate_limited) — Too many requests - 500 [`internal_error`](https://developers.processcart.com/errors#internal_error) — Internal error - 503 [`unavailable`](https://developers.processcart.com/errors#unavailable) — Service unavailable **Example response** (200) ```json { "data": [ { "id": "f95bfa64-fea6-421b-9b04-a8d262cf2098", "workflowId": "ba1de1e7-01ae-498d-8686-e7b67809372c", "workflow": { "id": "ba1de1e7-01ae-498d-8686-e7b67809372c", "name": "Order fulfillment" }, "name": "Flag large orders", "triggerEvent": "task.completed", "filter": { "taskKey": "details" }, "conditions": { "combinator": "and", "items": [ { "type": "field", "fieldKey": "amount", "op": "greater", "value": 1000 } ] }, "actions": [ { "type": "add_labels", "labelIds": [ "rush" ] }, { "type": "set_priority", "priority": "high" } ], "enabled": true, "disabledReason": null, "disabledAt": null, "consecutiveFailures": 0, "keyMismatches": [], "createdBy": null, "createdAt": "2026-10-11T05:27:50.402Z", "updatedAt": "2026-10-11T05:27:50.402Z" } ], "nextCursor": null, "hasMore": false } ``` ## POST /v1/automation-rules **Create an automation rule.** Scope `rules:manage` · checked against the key's workflow / folder allowlist (403 `not_allowed_for_key` outside it) · accepts `Idempotency-Key`. Operation id `createAutomationRule` · [open in the API reference](https://developers.processcart.com/reference#tag/automation-rules/POST/v1/automation-rules). Creates a rule, turned on: when `triggerEvent` happens on a run of `workflowId` (optionally narrowed by `filter` and field `conditions`), the `actions` run in order — set a field, set the status or priority, add or remove labels, assign a task, notify people, or send the event to a webhook. Task and field keys are checked against the workflow's published version; members, groups, labels and webhooks against the workspace. A `set_field` value is checked like a field write, and one outside the field's bounds is refused (422, `errors[].code` `invalid_value`). A workflow in Trash answers 409 `workflow_trashed`. `workflowId: null` makes a rule for every workflow (header actions, `notify` and `call_webhook` only), which needs a key without an allowlist. A workspace holds at most 100 rules that are on. Audited. A key restricted to workflows or folders may use only the rules of workflows inside its allowlist; a rule for every workflow (`workflowId: null`), or one of a workflow outside the allowlist, answers 403 `not_allowed_for_key`. **Request headers** | Name | Type | Required | Description | | --- | --- | --- | --- | | `Idempotency-Key` | string | no | Makes the request safe to retry: a second request with the same key and a byte-identical body (and the same `Expected-Revision`) replays the stored response (`Idempotent-Replayed: true`); a different body with the same key is refused (422 `idempotency_key_reused`) and no second resource is created. Keys are stored per workspace for 24 hours and replayed only to the API key that made the request (or the key one rotation away from it); another API key sending the same value is refused (422 `idempotency_key_reused`), so prefix keys per integration. 409, 429 and 5xx answers are never stored. | **Request body** — `application/json`, [AutomationRuleConfig](https://developers.processcart.com/reference/schemas#automationruleconfig) | Property | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | yes | | | `workflowId` | [Uuid](https://developers.processcart.com/reference/schemas#uuid) \\| null | yes | | | `triggerEvent` | `"run.created"` \\| `"run.updated"` \\| `"run.status_changed"` \\| `"run.completed"` \\| `"task.ready"` \\| `"task.assigned"` \\| `"task.completed"` \\| `"task.uncompleted"` \\| … | yes | | | `filter` | [RuleFilter](https://developers.processcart.com/reference/schemas#rulefilter) | no | | | `conditions` | [RuleConditions](https://developers.processcart.com/reference/schemas#ruleconditions) | no | | | `actions` | [RuleAction](https://developers.processcart.com/reference/schemas#ruleaction)[] | yes | | **Responses** | Status | Meaning | Body | | --- | --- | --- | | 201 | Created. `Location` names the new resource. | [AutomationRule](https://developers.processcart.com/reference/schemas#automationrule) | | 401 | No usable credential. `unauthorized` — Authentication required; `invalid_api_key` — Invalid API key; `key_revoked` — API key revoked. | [Problem](https://developers.processcart.com/errors) | | 403 | `insufficient_scope` — Insufficient scope; `not_allowed_for_key` — Not allowed for this key; `read_only_key` — Read-only key. | [Problem](https://developers.processcart.com/errors) | | 405 | The verb is not supported on this path. `method_not_allowed` — Method not allowed. | [Problem](https://developers.processcart.com/errors) | | 409 | `idempotency_in_progress` — Request in progress; `workflow_not_published` — Workflow not published; `workflow_trashed` — Workflow is in Trash. | [Problem](https://developers.processcart.com/errors) | | 413 | `payload_too_large` — Payload too large. | [Problem](https://developers.processcart.com/errors) | | 415 | `unsupported_media_type` — Unsupported media type. | [Problem](https://developers.processcart.com/errors) | | 422 | `idempotency_key_reused` — Idempotency key reused; `validation_failed` — Validation failed. | [Problem](https://developers.processcart.com/errors) | | 429 | `rate_limited` — Too many requests. | [Problem](https://developers.processcart.com/errors) | | 500 | `internal_error` — Internal error. | [Problem](https://developers.processcart.com/errors) | | 503 | `unavailable` — Service unavailable. | [Problem](https://developers.processcart.com/errors) | **Errors** (every code this operation can answer; see [Errors](https://developers.processcart.com/errors)) - 401 [`invalid_api_key`](https://developers.processcart.com/errors#invalid_api_key) — Invalid API key - 401 [`key_revoked`](https://developers.processcart.com/errors#key_revoked) — API key revoked - 401 [`unauthorized`](https://developers.processcart.com/errors#unauthorized) — Authentication required - 403 [`insufficient_scope`](https://developers.processcart.com/errors#insufficient_scope) — Insufficient scope - 403 [`not_allowed_for_key`](https://developers.processcart.com/errors#not_allowed_for_key) — Not allowed for this key - 403 [`read_only_key`](https://developers.processcart.com/errors#read_only_key) — Read-only key - 405 [`method_not_allowed`](https://developers.processcart.com/errors#method_not_allowed) — Method not allowed - 409 [`idempotency_in_progress`](https://developers.processcart.com/errors#idempotency_in_progress) — Request in progress - 409 [`workflow_not_published`](https://developers.processcart.com/errors#workflow_not_published) — Workflow not published - 409 [`workflow_trashed`](https://developers.processcart.com/errors#workflow_trashed) — Workflow is in Trash - 413 [`payload_too_large`](https://developers.processcart.com/errors#payload_too_large) — Payload too large - 415 [`unsupported_media_type`](https://developers.processcart.com/errors#unsupported_media_type) — Unsupported media type - 422 [`idempotency_key_reused`](https://developers.processcart.com/errors#idempotency_key_reused) — Idempotency key reused - 422 [`validation_failed`](https://developers.processcart.com/errors#validation_failed) — Validation failed - 429 [`rate_limited`](https://developers.processcart.com/errors#rate_limited) — Too many requests - 500 [`internal_error`](https://developers.processcart.com/errors#internal_error) — Internal error - 503 [`unavailable`](https://developers.processcart.com/errors#unavailable) — Service unavailable **Example request** ```json { "name": "Flag large orders", "workflowId": "ba1de1e7-01ae-498d-8686-e7b67809372c", "triggerEvent": "task.completed", "filter": { "taskKey": "details" }, "conditions": { "combinator": "and", "items": [ { "type": "field", "fieldKey": "amount", "op": "greater", "value": 1000 } ] }, "actions": [ { "type": "add_labels", "labelIds": [ "rush" ] }, { "type": "set_priority", "priority": "high" } ] } ``` **Example response** (201) ```json { "id": "f95bfa64-fea6-421b-9b04-a8d262cf2098", "workflowId": "ba1de1e7-01ae-498d-8686-e7b67809372c", "workflow": { "id": "ba1de1e7-01ae-498d-8686-e7b67809372c", "name": "Order fulfillment" }, "name": "Flag large orders", "triggerEvent": "task.completed", "filter": { "taskKey": "details" }, "conditions": { "combinator": "and", "items": [ { "type": "field", "fieldKey": "amount", "op": "greater", "value": 1000 } ] }, "actions": [ { "type": "add_labels", "labelIds": [ "rush" ] }, { "type": "set_priority", "priority": "high" } ], "enabled": true, "disabledReason": null, "disabledAt": null, "consecutiveFailures": 0, "keyMismatches": [], "createdBy": null, "createdAt": "2026-10-11T05:27:50.402Z", "updatedAt": "2026-10-11T05:27:50.402Z" } ``` ## GET /v1/automation-rules/{id} **Get an automation rule.** Scope `rules:manage` · checked against the key's workflow / folder allowlist (403 `not_allowed_for_key` outside it). Operation id `getAutomationRule` · [open in the API reference](https://developers.processcart.com/reference#tag/automation-rules/GET/v1/automation-rules/{id}). Returns one rule with its trigger, filter, conditions, actions, enabled state and disable reason, failure counter (the rule turns itself off after 10 failed executions in a row), and any keys the published workflow or the workspace no longer has. A key restricted to workflows or folders may use only the rules of workflows inside its allowlist; a rule for every workflow (`workflowId: null`), or one of a workflow outside the allowlist, answers 403 `not_allowed_for_key`. **Path parameters** | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | string (uuid) | yes | | **Responses** | Status | Meaning | Body | | --- | --- | --- | | 200 | OK. | [AutomationRule](https://developers.processcart.com/reference/schemas#automationrule) | | 401 | No usable credential. `unauthorized` — Authentication required; `invalid_api_key` — Invalid API key; `key_revoked` — API key revoked. | [Problem](https://developers.processcart.com/errors) | | 403 | `insufficient_scope` — Insufficient scope; `not_allowed_for_key` — Not allowed for this key. | [Problem](https://developers.processcart.com/errors) | | 404 | `not_found` — Not found. | [Problem](https://developers.processcart.com/errors) | | 405 | The verb is not supported on this path. `method_not_allowed` — Method not allowed. | [Problem](https://developers.processcart.com/errors) | | 422 | `validation_failed` — Validation failed. | [Problem](https://developers.processcart.com/errors) | | 429 | `rate_limited` — Too many requests. | [Problem](https://developers.processcart.com/errors) | | 500 | `internal_error` — Internal error. | [Problem](https://developers.processcart.com/errors) | | 503 | `unavailable` — Service unavailable. | [Problem](https://developers.processcart.com/errors) | **Errors** (every code this operation can answer; see [Errors](https://developers.processcart.com/errors)) - 401 [`invalid_api_key`](https://developers.processcart.com/errors#invalid_api_key) — Invalid API key - 401 [`key_revoked`](https://developers.processcart.com/errors#key_revoked) — API key revoked - 401 [`unauthorized`](https://developers.processcart.com/errors#unauthorized) — Authentication required - 403 [`insufficient_scope`](https://developers.processcart.com/errors#insufficient_scope) — Insufficient scope - 403 [`not_allowed_for_key`](https://developers.processcart.com/errors#not_allowed_for_key) — Not allowed for this key - 404 [`not_found`](https://developers.processcart.com/errors#not_found) — Not found - 405 [`method_not_allowed`](https://developers.processcart.com/errors#method_not_allowed) — Method not allowed - 422 [`validation_failed`](https://developers.processcart.com/errors#validation_failed) — Validation failed - 429 [`rate_limited`](https://developers.processcart.com/errors#rate_limited) — Too many requests - 500 [`internal_error`](https://developers.processcart.com/errors#internal_error) — Internal error - 503 [`unavailable`](https://developers.processcart.com/errors#unavailable) — Service unavailable **Example response** (200) ```json { "id": "f95bfa64-fea6-421b-9b04-a8d262cf2098", "workflowId": "ba1de1e7-01ae-498d-8686-e7b67809372c", "workflow": { "id": "ba1de1e7-01ae-498d-8686-e7b67809372c", "name": "Order fulfillment" }, "name": "Flag large orders", "triggerEvent": "task.completed", "filter": { "taskKey": "details" }, "conditions": { "combinator": "and", "items": [ { "type": "field", "fieldKey": "amount", "op": "greater", "value": 1000 } ] }, "actions": [ { "type": "add_labels", "labelIds": [ "rush" ] }, { "type": "set_priority", "priority": "high" } ], "enabled": true, "disabledReason": null, "disabledAt": null, "consecutiveFailures": 0, "keyMismatches": [], "createdBy": null, "createdAt": "2026-10-11T05:27:50.402Z", "updatedAt": "2026-10-11T05:27:50.402Z" } ``` ## PATCH /v1/automation-rules/{id} **Update an automation rule.** Scope `rules:manage` · checked against the key's workflow / folder allowlist (403 `not_allowed_for_key` outside it) · accepts `Idempotency-Key`. Operation id `updateAutomationRule` · [open in the API reference](https://developers.processcart.com/reference#tag/automation-rules/PATCH/v1/automation-rules/{id}). Changes any subset of `name`, `workflowId`, `triggerEvent`, `filter`, `conditions` and `actions`; the result is checked as a whole, like a create (a workflow in Trash answers 409 `workflow_trashed`), and the failure counter resets. `enabled` is not part of the configuration — use the enable and disable operations (422 `unknown_key` on `enabled`). Audited. **Path parameters** | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | string (uuid) | yes | | **Request headers** | Name | Type | Required | Description | | --- | --- | --- | --- | | `Idempotency-Key` | string | no | Makes the request safe to retry: a second request with the same key and a byte-identical body (and the same `Expected-Revision`) replays the stored response (`Idempotent-Replayed: true`); a different body with the same key is refused (422 `idempotency_key_reused`) and no second resource is created. Keys are stored per workspace for 24 hours and replayed only to the API key that made the request (or the key one rotation away from it); another API key sending the same value is refused (422 `idempotency_key_reused`), so prefix keys per integration. 409, 429 and 5xx answers are never stored. | **Request body** — `application/json`, [AutomationRulePatch](https://developers.processcart.com/reference/schemas#automationrulepatch) | Property | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | no | | | `workflowId` | [Uuid](https://developers.processcart.com/reference/schemas#uuid) \\| null | no | | | `triggerEvent` | `"run.created"` \\| `"run.updated"` \\| `"run.status_changed"` \\| `"run.completed"` \\| `"task.ready"` \\| `"task.assigned"` \\| `"task.completed"` \\| `"task.uncompleted"` \\| … | no | | | `filter` | [RuleFilter](https://developers.processcart.com/reference/schemas#rulefilter) | no | | | `conditions` | [RuleConditions](https://developers.processcart.com/reference/schemas#ruleconditions) | no | | | `actions` | [RuleAction](https://developers.processcart.com/reference/schemas#ruleaction)[] | no | | **Responses** | Status | Meaning | Body | | --- | --- | --- | | 200 | OK. | [AutomationRule](https://developers.processcart.com/reference/schemas#automationrule) | | 401 | No usable credential. `unauthorized` — Authentication required; `invalid_api_key` — Invalid API key; `key_revoked` — API key revoked. | [Problem](https://developers.processcart.com/errors) | | 403 | `insufficient_scope` — Insufficient scope; `not_allowed_for_key` — Not allowed for this key; `read_only_key` — Read-only key. | [Problem](https://developers.processcart.com/errors) | | 404 | `not_found` — Not found. | [Problem](https://developers.processcart.com/errors) | | 405 | The verb is not supported on this path. `method_not_allowed` — Method not allowed. | [Problem](https://developers.processcart.com/errors) | | 409 | `idempotency_in_progress` — Request in progress; `workflow_not_published` — Workflow not published; `workflow_trashed` — Workflow is in Trash. | [Problem](https://developers.processcart.com/errors) | | 413 | `payload_too_large` — Payload too large. | [Problem](https://developers.processcart.com/errors) | | 415 | `unsupported_media_type` — Unsupported media type. | [Problem](https://developers.processcart.com/errors) | | 422 | `idempotency_key_reused` — Idempotency key reused; `validation_failed` — Validation failed. | [Problem](https://developers.processcart.com/errors) | | 429 | `rate_limited` — Too many requests. | [Problem](https://developers.processcart.com/errors) | | 500 | `internal_error` — Internal error. | [Problem](https://developers.processcart.com/errors) | | 503 | `unavailable` — Service unavailable. | [Problem](https://developers.processcart.com/errors) | **Errors** (every code this operation can answer; see [Errors](https://developers.processcart.com/errors)) - 401 [`invalid_api_key`](https://developers.processcart.com/errors#invalid_api_key) — Invalid API key - 401 [`key_revoked`](https://developers.processcart.com/errors#key_revoked) — API key revoked - 401 [`unauthorized`](https://developers.processcart.com/errors#unauthorized) — Authentication required - 403 [`insufficient_scope`](https://developers.processcart.com/errors#insufficient_scope) — Insufficient scope - 403 [`not_allowed_for_key`](https://developers.processcart.com/errors#not_allowed_for_key) — Not allowed for this key - 403 [`read_only_key`](https://developers.processcart.com/errors#read_only_key) — Read-only key - 404 [`not_found`](https://developers.processcart.com/errors#not_found) — Not found - 405 [`method_not_allowed`](https://developers.processcart.com/errors#method_not_allowed) — Method not allowed - 409 [`idempotency_in_progress`](https://developers.processcart.com/errors#idempotency_in_progress) — Request in progress - 409 [`workflow_not_published`](https://developers.processcart.com/errors#workflow_not_published) — Workflow not published - 409 [`workflow_trashed`](https://developers.processcart.com/errors#workflow_trashed) — Workflow is in Trash - 413 [`payload_too_large`](https://developers.processcart.com/errors#payload_too_large) — Payload too large - 415 [`unsupported_media_type`](https://developers.processcart.com/errors#unsupported_media_type) — Unsupported media type - 422 [`idempotency_key_reused`](https://developers.processcart.com/errors#idempotency_key_reused) — Idempotency key reused - 422 [`validation_failed`](https://developers.processcart.com/errors#validation_failed) — Validation failed - 429 [`rate_limited`](https://developers.processcart.com/errors#rate_limited) — Too many requests - 500 [`internal_error`](https://developers.processcart.com/errors#internal_error) — Internal error - 503 [`unavailable`](https://developers.processcart.com/errors#unavailable) — Service unavailable **Example request** ```json { "name": "Flag large orders (over 1,000)" } ``` **Example response** (200) ```json { "id": "f95bfa64-fea6-421b-9b04-a8d262cf2098", "workflowId": "ba1de1e7-01ae-498d-8686-e7b67809372c", "workflow": { "id": "ba1de1e7-01ae-498d-8686-e7b67809372c", "name": "Order fulfillment" }, "name": "Flag large orders (over 1,000)", "triggerEvent": "task.completed", "filter": { "taskKey": "details" }, "conditions": { "combinator": "and", "items": [ { "type": "field", "fieldKey": "amount", "op": "greater", "value": 1000 } ] }, "actions": [ { "type": "add_labels", "labelIds": [ "rush" ] }, { "type": "set_priority", "priority": "high" } ], "enabled": true, "disabledReason": null, "disabledAt": null, "consecutiveFailures": 0, "keyMismatches": [], "createdBy": null, "createdAt": "2026-10-11T05:27:50.402Z", "updatedAt": "2026-10-11T05:27:57.211Z" } ``` ## DELETE /v1/automation-rules/{id} **Delete an automation rule.** Scope `rules:manage` · checked against the key's workflow / folder allowlist (403 `not_allowed_for_key` outside it) · accepts `Idempotency-Key`. Operation id `deleteAutomationRule` · [open in the API reference](https://developers.processcart.com/reference#tag/automation-rules/DELETE/v1/automation-rules/{id}). Deletes the rule and its execution history. Audited. **Path parameters** | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | string (uuid) | yes | | **Request headers** | Name | Type | Required | Description | | --- | --- | --- | --- | | `Idempotency-Key` | string | no | Makes the request safe to retry: a second request with the same key and a byte-identical body (and the same `Expected-Revision`) replays the stored response (`Idempotent-Replayed: true`); a different body with the same key is refused (422 `idempotency_key_reused`) and no second resource is created. Keys are stored per workspace for 24 hours and replayed only to the API key that made the request (or the key one rotation away from it); another API key sending the same value is refused (422 `idempotency_key_reused`), so prefix keys per integration. 409, 429 and 5xx answers are never stored. | **Responses** | Status | Meaning | Body | | --- | --- | --- | | 204 | Done; no body. | — | | 401 | No usable credential. `unauthorized` — Authentication required; `invalid_api_key` — Invalid API key; `key_revoked` — API key revoked. | [Problem](https://developers.processcart.com/errors) | | 403 | `insufficient_scope` — Insufficient scope; `not_allowed_for_key` — Not allowed for this key; `read_only_key` — Read-only key. | [Problem](https://developers.processcart.com/errors) | | 404 | `not_found` — Not found. | [Problem](https://developers.processcart.com/errors) | | 405 | The verb is not supported on this path. `method_not_allowed` — Method not allowed. | [Problem](https://developers.processcart.com/errors) | | 409 | `idempotency_in_progress` — Request in progress. | [Problem](https://developers.processcart.com/errors) | | 422 | `idempotency_key_reused` — Idempotency key reused; `validation_failed` — Validation failed. | [Problem](https://developers.processcart.com/errors) | | 429 | `rate_limited` — Too many requests. | [Problem](https://developers.processcart.com/errors) | | 500 | `internal_error` — Internal error. | [Problem](https://developers.processcart.com/errors) | | 503 | `unavailable` — Service unavailable. | [Problem](https://developers.processcart.com/errors) | **Errors** (every code this operation can answer; see [Errors](https://developers.processcart.com/errors)) - 401 [`invalid_api_key`](https://developers.processcart.com/errors#invalid_api_key) — Invalid API key - 401 [`key_revoked`](https://developers.processcart.com/errors#key_revoked) — API key revoked - 401 [`unauthorized`](https://developers.processcart.com/errors#unauthorized) — Authentication required - 403 [`insufficient_scope`](https://developers.processcart.com/errors#insufficient_scope) — Insufficient scope - 403 [`not_allowed_for_key`](https://developers.processcart.com/errors#not_allowed_for_key) — Not allowed for this key - 403 [`read_only_key`](https://developers.processcart.com/errors#read_only_key) — Read-only key - 404 [`not_found`](https://developers.processcart.com/errors#not_found) — Not found - 405 [`method_not_allowed`](https://developers.processcart.com/errors#method_not_allowed) — Method not allowed - 409 [`idempotency_in_progress`](https://developers.processcart.com/errors#idempotency_in_progress) — Request in progress - 422 [`idempotency_key_reused`](https://developers.processcart.com/errors#idempotency_key_reused) — Idempotency key reused - 422 [`validation_failed`](https://developers.processcart.com/errors#validation_failed) — Validation failed - 429 [`rate_limited`](https://developers.processcart.com/errors#rate_limited) — Too many requests - 500 [`internal_error`](https://developers.processcart.com/errors#internal_error) — Internal error - 503 [`unavailable`](https://developers.processcart.com/errors#unavailable) — Service unavailable ## POST /v1/automation-rules/{id}/enable **Turn an automation rule on.** Scope `rules:manage` · checked against the key's workflow / folder allowlist (403 `not_allowed_for_key` outside it) · accepts `Idempotency-Key`. Operation id `enableAutomationRule` · [open in the API reference](https://developers.processcart.com/reference#tag/automation-rules/POST/v1/automation-rules/{id}/enable). Turns the rule on and resets its failure counter — also the way back after it turned itself off (after 10 failed executions in a row, or the loop guard). A workspace holds at most 100 rules that are on (422, `errors[].code` `limit_reached`). A rule whose workflow is in Trash is refused with 409 `workflow_trashed`. Turning on a rule that is on answers 200 unchanged. Audited. **Path parameters** | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | string (uuid) | yes | | **Request headers** | Name | Type | Required | Description | | --- | --- | --- | --- | | `Idempotency-Key` | string | no | Makes the request safe to retry: a second request with the same key and a byte-identical body (and the same `Expected-Revision`) replays the stored response (`Idempotent-Replayed: true`); a different body with the same key is refused (422 `idempotency_key_reused`) and no second resource is created. Keys are stored per workspace for 24 hours and replayed only to the API key that made the request (or the key one rotation away from it); another API key sending the same value is refused (422 `idempotency_key_reused`), so prefix keys per integration. 409, 429 and 5xx answers are never stored. | **Responses** | Status | Meaning | Body | | --- | --- | --- | | 200 | OK. | [AutomationRule](https://developers.processcart.com/reference/schemas#automationrule) | | 401 | No usable credential. `unauthorized` — Authentication required; `invalid_api_key` — Invalid API key; `key_revoked` — API key revoked. | [Problem](https://developers.processcart.com/errors) | | 403 | `insufficient_scope` — Insufficient scope; `not_allowed_for_key` — Not allowed for this key; `read_only_key` — Read-only key. | [Problem](https://developers.processcart.com/errors) | | 404 | `not_found` — Not found. | [Problem](https://developers.processcart.com/errors) | | 405 | The verb is not supported on this path. `method_not_allowed` — Method not allowed. | [Problem](https://developers.processcart.com/errors) | | 409 | `idempotency_in_progress` — Request in progress; `workflow_trashed` — Workflow is in Trash. | [Problem](https://developers.processcart.com/errors) | | 422 | `idempotency_key_reused` — Idempotency key reused; `validation_failed` — Validation failed. | [Problem](https://developers.processcart.com/errors) | | 429 | `rate_limited` — Too many requests. | [Problem](https://developers.processcart.com/errors) | | 500 | `internal_error` — Internal error. | [Problem](https://developers.processcart.com/errors) | | 503 | `unavailable` — Service unavailable. | [Problem](https://developers.processcart.com/errors) | **Errors** (every code this operation can answer; see [Errors](https://developers.processcart.com/errors)) - 401 [`invalid_api_key`](https://developers.processcart.com/errors#invalid_api_key) — Invalid API key - 401 [`key_revoked`](https://developers.processcart.com/errors#key_revoked) — API key revoked - 401 [`unauthorized`](https://developers.processcart.com/errors#unauthorized) — Authentication required - 403 [`insufficient_scope`](https://developers.processcart.com/errors#insufficient_scope) — Insufficient scope - 403 [`not_allowed_for_key`](https://developers.processcart.com/errors#not_allowed_for_key) — Not allowed for this key - 403 [`read_only_key`](https://developers.processcart.com/errors#read_only_key) — Read-only key - 404 [`not_found`](https://developers.processcart.com/errors#not_found) — Not found - 405 [`method_not_allowed`](https://developers.processcart.com/errors#method_not_allowed) — Method not allowed - 409 [`idempotency_in_progress`](https://developers.processcart.com/errors#idempotency_in_progress) — Request in progress - 409 [`workflow_trashed`](https://developers.processcart.com/errors#workflow_trashed) — Workflow is in Trash - 422 [`idempotency_key_reused`](https://developers.processcart.com/errors#idempotency_key_reused) — Idempotency key reused - 422 [`validation_failed`](https://developers.processcart.com/errors#validation_failed) — Validation failed - 429 [`rate_limited`](https://developers.processcart.com/errors#rate_limited) — Too many requests - 500 [`internal_error`](https://developers.processcart.com/errors#internal_error) — Internal error - 503 [`unavailable`](https://developers.processcart.com/errors#unavailable) — Service unavailable **Example response** (200) ```json { "id": "f95bfa64-fea6-421b-9b04-a8d262cf2098", "workflowId": "ba1de1e7-01ae-498d-8686-e7b67809372c", "workflow": { "id": "ba1de1e7-01ae-498d-8686-e7b67809372c", "name": "Order fulfillment" }, "name": "Flag large orders (over 1,000)", "triggerEvent": "task.completed", "filter": { "taskKey": "details" }, "conditions": { "combinator": "and", "items": [ { "type": "field", "fieldKey": "amount", "op": "greater", "value": 1000 } ] }, "actions": [ { "type": "add_labels", "labelIds": [ "rush" ] }, { "type": "set_priority", "priority": "high" } ], "enabled": true, "disabledReason": null, "disabledAt": null, "consecutiveFailures": 0, "keyMismatches": [], "createdBy": null, "createdAt": "2026-10-11T05:27:50.402Z", "updatedAt": "2026-10-11T05:27:58.667Z" } ``` ## POST /v1/automation-rules/{id}/disable **Turn an automation rule off.** Scope `rules:manage` · checked against the key's workflow / folder allowlist (403 `not_allowed_for_key` outside it) · accepts `Idempotency-Key`. Operation id `disableAutomationRule` · [open in the API reference](https://developers.processcart.com/reference#tag/automation-rules/POST/v1/automation-rules/{id}/disable). Turns the rule off (reason `manual`); events that happen while it is off are not queued for it. Turning off a rule that is off answers 200 unchanged. Audited. **Path parameters** | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | string (uuid) | yes | | **Request headers** | Name | Type | Required | Description | | --- | --- | --- | --- | | `Idempotency-Key` | string | no | Makes the request safe to retry: a second request with the same key and a byte-identical body (and the same `Expected-Revision`) replays the stored response (`Idempotent-Replayed: true`); a different body with the same key is refused (422 `idempotency_key_reused`) and no second resource is created. Keys are stored per workspace for 24 hours and replayed only to the API key that made the request (or the key one rotation away from it); another API key sending the same value is refused (422 `idempotency_key_reused`), so prefix keys per integration. 409, 429 and 5xx answers are never stored. | **Responses** | Status | Meaning | Body | | --- | --- | --- | | 200 | OK. | [AutomationRule](https://developers.processcart.com/reference/schemas#automationrule) | | 401 | No usable credential. `unauthorized` — Authentication required; `invalid_api_key` — Invalid API key; `key_revoked` — API key revoked. | [Problem](https://developers.processcart.com/errors) | | 403 | `insufficient_scope` — Insufficient scope; `not_allowed_for_key` — Not allowed for this key; `read_only_key` — Read-only key. | [Problem](https://developers.processcart.com/errors) | | 404 | `not_found` — Not found. | [Problem](https://developers.processcart.com/errors) | | 405 | The verb is not supported on this path. `method_not_allowed` — Method not allowed. | [Problem](https://developers.processcart.com/errors) | | 409 | `idempotency_in_progress` — Request in progress. | [Problem](https://developers.processcart.com/errors) | | 422 | `idempotency_key_reused` — Idempotency key reused; `validation_failed` — Validation failed. | [Problem](https://developers.processcart.com/errors) | | 429 | `rate_limited` — Too many requests. | [Problem](https://developers.processcart.com/errors) | | 500 | `internal_error` — Internal error. | [Problem](https://developers.processcart.com/errors) | | 503 | `unavailable` — Service unavailable. | [Problem](https://developers.processcart.com/errors) | **Errors** (every code this operation can answer; see [Errors](https://developers.processcart.com/errors)) - 401 [`invalid_api_key`](https://developers.processcart.com/errors#invalid_api_key) — Invalid API key - 401 [`key_revoked`](https://developers.processcart.com/errors#key_revoked) — API key revoked - 401 [`unauthorized`](https://developers.processcart.com/errors#unauthorized) — Authentication required - 403 [`insufficient_scope`](https://developers.processcart.com/errors#insufficient_scope) — Insufficient scope - 403 [`not_allowed_for_key`](https://developers.processcart.com/errors#not_allowed_for_key) — Not allowed for this key - 403 [`read_only_key`](https://developers.processcart.com/errors#read_only_key) — Read-only key - 404 [`not_found`](https://developers.processcart.com/errors#not_found) — Not found - 405 [`method_not_allowed`](https://developers.processcart.com/errors#method_not_allowed) — Method not allowed - 409 [`idempotency_in_progress`](https://developers.processcart.com/errors#idempotency_in_progress) — Request in progress - 422 [`idempotency_key_reused`](https://developers.processcart.com/errors#idempotency_key_reused) — Idempotency key reused - 422 [`validation_failed`](https://developers.processcart.com/errors#validation_failed) — Validation failed - 429 [`rate_limited`](https://developers.processcart.com/errors#rate_limited) — Too many requests - 500 [`internal_error`](https://developers.processcart.com/errors#internal_error) — Internal error - 503 [`unavailable`](https://developers.processcart.com/errors#unavailable) — Service unavailable **Example response** (200) ```json { "id": "f95bfa64-fea6-421b-9b04-a8d262cf2098", "workflowId": "ba1de1e7-01ae-498d-8686-e7b67809372c", "workflow": { "id": "ba1de1e7-01ae-498d-8686-e7b67809372c", "name": "Order fulfillment" }, "name": "Flag large orders (over 1,000)", "triggerEvent": "task.completed", "filter": { "taskKey": "details" }, "conditions": { "combinator": "and", "items": [ { "type": "field", "fieldKey": "amount", "op": "greater", "value": 1000 } ] }, "actions": [ { "type": "add_labels", "labelIds": [ "rush" ] }, { "type": "set_priority", "priority": "high" } ], "enabled": false, "disabledReason": "manual", "disabledAt": "2026-10-11T05:27:57.496Z", "consecutiveFailures": 0, "keyMismatches": [], "createdBy": null, "createdAt": "2026-10-11T05:27:50.402Z", "updatedAt": "2026-10-11T05:27:57.894Z" } ``` ## GET /v1/automation-rules/{id}/executions **List a rule's executions.** Scope `rules:manage` · checked against the key's workflow / folder allowlist (403 `not_allowed_for_key` outside it) · paginated (`{ data, nextCursor, hasMore }`). Operation id `listAutomationRuleExecutions` · [open in the API reference](https://developers.processcart.com/reference#tag/automation-rules/GET/v1/automation-rules/{id}/executions). The rule's execution history, newest first: one execution per matched event, with its status (`queued`, `running`, `done`, `failed`), each action's outcome (`ok`, `skipped`, `failed`) and the error. Filter by `status`. Executions older than 30 days are gone. **Path parameters** | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | string (uuid) | yes | | **Query parameters** (an unknown parameter is a 422 `validation_failed` with `errors[].code = "unknown_parameter"`) | Name | Type | Required | Description | | --- | --- | --- | --- | | `status` | `"queued"` \\| `"running"` \\| `"done"` \\| `"failed"` | no | | | `limit` | integer | no | | | `cursor` | string | no | The `nextCursor` of the previous page. Opaque, and bound to the query that produced it — a cursor from a different filter or sort is a 422. | **Responses** | Status | Meaning | Body | | --- | --- | --- | | 200 | OK. | [RuleExecutionList](https://developers.processcart.com/reference/schemas#ruleexecutionlist) | | 401 | No usable credential. `unauthorized` — Authentication required; `invalid_api_key` — Invalid API key; `key_revoked` — API key revoked. | [Problem](https://developers.processcart.com/errors) | | 403 | `insufficient_scope` — Insufficient scope; `not_allowed_for_key` — Not allowed for this key. | [Problem](https://developers.processcart.com/errors) | | 404 | `not_found` — Not found. | [Problem](https://developers.processcart.com/errors) | | 405 | The verb is not supported on this path. `method_not_allowed` — Method not allowed. | [Problem](https://developers.processcart.com/errors) | | 422 | `validation_failed` — Validation failed. | [Problem](https://developers.processcart.com/errors) | | 429 | `rate_limited` — Too many requests. | [Problem](https://developers.processcart.com/errors) | | 500 | `internal_error` — Internal error. | [Problem](https://developers.processcart.com/errors) | | 503 | `unavailable` — Service unavailable. | [Problem](https://developers.processcart.com/errors) | **Errors** (every code this operation can answer; see [Errors](https://developers.processcart.com/errors)) - 401 [`invalid_api_key`](https://developers.processcart.com/errors#invalid_api_key) — Invalid API key - 401 [`key_revoked`](https://developers.processcart.com/errors#key_revoked) — API key revoked - 401 [`unauthorized`](https://developers.processcart.com/errors#unauthorized) — Authentication required - 403 [`insufficient_scope`](https://developers.processcart.com/errors#insufficient_scope) — Insufficient scope - 403 [`not_allowed_for_key`](https://developers.processcart.com/errors#not_allowed_for_key) — Not allowed for this key - 404 [`not_found`](https://developers.processcart.com/errors#not_found) — Not found - 405 [`method_not_allowed`](https://developers.processcart.com/errors#method_not_allowed) — Method not allowed - 422 [`validation_failed`](https://developers.processcart.com/errors#validation_failed) — Validation failed - 429 [`rate_limited`](https://developers.processcart.com/errors#rate_limited) — Too many requests - 500 [`internal_error`](https://developers.processcart.com/errors#internal_error) — Internal error - 503 [`unavailable`](https://developers.processcart.com/errors#unavailable) — Service unavailable **Example response** (200) ```json { "data": [ { "id": "716f9d9d-761b-4292-a371-12c836633430", "ruleId": "f95bfa64-fea6-421b-9b04-a8d262cf2098", "runId": "498bf18e-e9c2-421c-aba7-101c8d88b7d0", "run": { "id": "498bf18e-e9c2-421c-aba7-101c8d88b7d0", "name": "Order 1042" }, "eventId": "evt_5e687bca-29e2-4089-bd89-deca38ed741b", "eventType": "task.completed", "status": "done", "result": [ { "ok": true, "action": "add_labels", "detail": "Added labels.", "outcome": "ok" }, { "ok": true, "action": "set_priority", "detail": "Set priority to “High”.", "outcome": "ok" } ], "error": null, "createdAt": "2026-10-11T05:27:52.935Z", "startedAt": "2026-10-11T05:27:53.283Z", "finishedAt": "2026-10-11T05:27:55.033Z" } ], "nextCursor": null, "hasMore": false } ``` --- # Directory endpoints > Members and groups — ids, names, roles; never email addresses. Every operation with parameters, body, responses, errors and examples. Source: https://developers.processcart.com/reference/directory Members and groups — ids, names, roles; never email addresses. Base URL `https://app.processcart.com/api/v1`. Every operation below is also in the [OpenAPI document](https://developers.processcart.com/openapi.json) and the [interactive reference](https://developers.processcart.com/reference). | Operation | Method and path | Scope | | --- | --- | --- | | [List members](#listUsers) | GET /v1/users | Scope `users:read` | | [List groups](#listGroups) | GET /v1/groups | Scope `users:read` | ## GET /v1/users **List members.** Scope `users:read` · paginated (`{ data, nextCursor, hasMore }`). Operation id `listUsers` · [open in the API reference](https://developers.processcart.com/reference#tag/directory/GET/v1/users). The workspace's members in the order they joined: user id, display name, role (`member`, `builder`, `admin`) and avatar. Email addresses are not exposed. Use the ids to read assignees and to filter runs with `assignee=u:`. **Query parameters** (an unknown parameter is a 422 `validation_failed` with `errors[].code = "unknown_parameter"`) | Name | Type | Required | Description | | --- | --- | --- | --- | | `limit` | integer | no | | | `cursor` | string | no | The `nextCursor` of the previous page. Opaque, and bound to the query that produced it — a cursor from a different filter or sort is a 422. | **Responses** | Status | Meaning | Body | | --- | --- | --- | | 200 | OK. | [ListUsersResponse](https://developers.processcart.com/reference/schemas#listusersresponse) | | 401 | No usable credential. `unauthorized` — Authentication required; `invalid_api_key` — Invalid API key; `key_revoked` — API key revoked. | [Problem](https://developers.processcart.com/errors) | | 403 | `insufficient_scope` — Insufficient scope. | [Problem](https://developers.processcart.com/errors) | | 405 | The verb is not supported on this path. `method_not_allowed` — Method not allowed. | [Problem](https://developers.processcart.com/errors) | | 422 | `validation_failed` — Validation failed. | [Problem](https://developers.processcart.com/errors) | | 429 | `rate_limited` — Too many requests. | [Problem](https://developers.processcart.com/errors) | | 500 | `internal_error` — Internal error. | [Problem](https://developers.processcart.com/errors) | | 503 | `unavailable` — Service unavailable. | [Problem](https://developers.processcart.com/errors) | **Errors** (every code this operation can answer; see [Errors](https://developers.processcart.com/errors)) - 401 [`invalid_api_key`](https://developers.processcart.com/errors#invalid_api_key) — Invalid API key - 401 [`key_revoked`](https://developers.processcart.com/errors#key_revoked) — API key revoked - 401 [`unauthorized`](https://developers.processcart.com/errors#unauthorized) — Authentication required - 403 [`insufficient_scope`](https://developers.processcart.com/errors#insufficient_scope) — Insufficient scope - 405 [`method_not_allowed`](https://developers.processcart.com/errors#method_not_allowed) — Method not allowed - 422 [`validation_failed`](https://developers.processcart.com/errors#validation_failed) — Validation failed - 429 [`rate_limited`](https://developers.processcart.com/errors#rate_limited) — Too many requests - 500 [`internal_error`](https://developers.processcart.com/errors#internal_error) — Internal error - 503 [`unavailable`](https://developers.processcart.com/errors#unavailable) — Service unavailable **Example response** (200) ```json { "data": [ { "id": "a5340bfc-1ba6-429e-97b3-ac741f62ffdf", "name": "Fixture Admin", "role": "admin", "avatarUrl": null, "joinedAt": "2026-10-10T17:44:11.041Z" }, { "id": "7a632343-8bfa-4b60-849a-aaad7fcd7b4c", "name": "Fixture Member", "role": "member", "avatarUrl": null, "joinedAt": "2026-10-10T17:44:11.272Z" } ], "nextCursor": null, "hasMore": false } ``` ## GET /v1/groups **List groups.** Scope `users:read` · paginated (`{ data, nextCursor, hasMore }`). Operation id `listGroups` · [open in the API reference](https://developers.processcart.com/reference#tag/directory/GET/v1/groups). The workspace's groups by name, each with the ids of its current members. Groups appear as `{ kind: "group" }` assignees and in `assignee=g:` filters. **Query parameters** (an unknown parameter is a 422 `validation_failed` with `errors[].code = "unknown_parameter"`) | Name | Type | Required | Description | | --- | --- | --- | --- | | `limit` | integer | no | | | `cursor` | string | no | The `nextCursor` of the previous page. Opaque, and bound to the query that produced it — a cursor from a different filter or sort is a 422. | **Responses** | Status | Meaning | Body | | --- | --- | --- | | 200 | OK. | [ListGroupsResponse](https://developers.processcart.com/reference/schemas#listgroupsresponse) | | 401 | No usable credential. `unauthorized` — Authentication required; `invalid_api_key` — Invalid API key; `key_revoked` — API key revoked. | [Problem](https://developers.processcart.com/errors) | | 403 | `insufficient_scope` — Insufficient scope. | [Problem](https://developers.processcart.com/errors) | | 405 | The verb is not supported on this path. `method_not_allowed` — Method not allowed. | [Problem](https://developers.processcart.com/errors) | | 422 | `validation_failed` — Validation failed. | [Problem](https://developers.processcart.com/errors) | | 429 | `rate_limited` — Too many requests. | [Problem](https://developers.processcart.com/errors) | | 500 | `internal_error` — Internal error. | [Problem](https://developers.processcart.com/errors) | | 503 | `unavailable` — Service unavailable. | [Problem](https://developers.processcart.com/errors) | **Errors** (every code this operation can answer; see [Errors](https://developers.processcart.com/errors)) - 401 [`invalid_api_key`](https://developers.processcart.com/errors#invalid_api_key) — Invalid API key - 401 [`key_revoked`](https://developers.processcart.com/errors#key_revoked) — API key revoked - 401 [`unauthorized`](https://developers.processcart.com/errors#unauthorized) — Authentication required - 403 [`insufficient_scope`](https://developers.processcart.com/errors#insufficient_scope) — Insufficient scope - 405 [`method_not_allowed`](https://developers.processcart.com/errors#method_not_allowed) — Method not allowed - 422 [`validation_failed`](https://developers.processcart.com/errors#validation_failed) — Validation failed - 429 [`rate_limited`](https://developers.processcart.com/errors#rate_limited) — Too many requests - 500 [`internal_error`](https://developers.processcart.com/errors#internal_error) — Internal error - 503 [`unavailable`](https://developers.processcart.com/errors#unavailable) — Service unavailable **Example response** (200) ```json { "data": [ { "id": "bca06b45-d08f-4b9a-a769-1ad979a0dfd6", "name": "Reviewers", "memberIds": [ "7a632343-8bfa-4b60-849a-aaad7fcd7b4c" ], "createdAt": "2026-10-10T17:44:11.326Z" } ], "nextCursor": null, "hasMore": false } ``` --- # Exports endpoints > CSV export of a workflow's runs. Every operation with parameters, body, responses, errors and examples. Source: https://developers.processcart.com/reference/exports CSV export of a workflow's runs. Base URL `https://app.processcart.com/api/v1`. Every operation below is also in the [OpenAPI document](https://developers.processcart.com/openapi.json) and the [interactive reference](https://developers.processcart.com/reference). | Operation | Method and path | Scope | | --- | --- | --- | | [Export a workflow's runs as CSV](#exportRunsCsv) | GET /v1/exports/runs.csv | Scope `runs:read` | ## GET /v1/exports/runs.csv **Export a workflow's runs as CSV.** Scope `runs:read` · checked against the key's workflow / folder allowlist (403 `not_allowed_for_key` outside it). Operation id `exportRunsCsv` · [open in the API reference](https://developers.processcart.com/reference#tag/exports/GET/v1/exports/runs.csv). One workflow's runs as a CSV file, newest first, with the same filters as `GET /v1/runs` and the Reports grid's column choice: `cols` picks the run columns, `fields` the field columns (values formatted as the app shows them — currency with its symbol, dates in the workspace's time zone, members by name). UTF-8 with a BOM and CRLF line ends, so it opens cleanly in Excel. The whole file is built before it is sent; a selection above 10,000 rows is refused with a 422 — narrow the filter. **Query parameters** (an unknown parameter is a 422 `validation_failed` with `errors[].code = "unknown_parameter"`) | Name | Type | Required | Description | | --- | --- | --- | --- | | `workflowId` | string (uuid) | yes | | | `status` | `"active"` \\| `"completed"` \\| `"on_hold"` \\| `"archived"`[] | no | Comma-separated (or repeat the parameter). | | `priority` | `"none"` \\| `"low"` \\| `"medium"` \\| `"high"` \\| `"urgent"`[] | no | Comma-separated (or repeat the parameter). | | `labels` | string[] | no | Comma-separated (or repeat the parameter). | | `assignee` | string | no | | | `due` | `"overdue"` \\| `"today"` \\| `"week"` \\| `"none"` | no | | | `q` | string | no | | | `qMode` | `"prefix"` \\| `"contains"` | no | | | `updatedSince` | string (date-time) | no | Incremental sync: only items whose `updatedAt` is at or after this instant (inclusive), ordered by `updatedAt` ascending; an explicit conflicting `sort` / `order` is a 422. Overlap by a minute and dedupe: a Run by `(id, revision)`, any other item (workflow, view, webhook, schedule, automation rule) by `(id, updatedAt)` — a higher `revision` or a later `updatedAt` replaces the copy you hold, an equal one is unchanged. | | `includeTrashed` | boolean | no | | | `cols` | `"status"` \\| `"priority"` \\| `"labels"` \\| `"progress"` \\| `"key_date"` \\| `"due"` \\| `"started"` \\| `"completed"`[] | no | Comma-separated (or repeat the parameter). | | `fields` | string[] | no | Comma-separated (or repeat the parameter). | **Responses** | Status | Meaning | Body | | --- | --- | --- | | 200 | OK. | `text/csv; charset=utf-8` | | 401 | No usable credential. `unauthorized` — Authentication required; `invalid_api_key` — Invalid API key; `key_revoked` — API key revoked. | [Problem](https://developers.processcart.com/errors) | | 403 | `insufficient_scope` — Insufficient scope; `not_allowed_for_key` — Not allowed for this key. | [Problem](https://developers.processcart.com/errors) | | 404 | `not_found` — Not found. | [Problem](https://developers.processcart.com/errors) | | 405 | The verb is not supported on this path. `method_not_allowed` — Method not allowed. | [Problem](https://developers.processcart.com/errors) | | 409 | `workflow_not_published` — Workflow not published. | [Problem](https://developers.processcart.com/errors) | | 422 | `validation_failed` — Validation failed. | [Problem](https://developers.processcart.com/errors) | | 429 | `rate_limited` — Too many requests. | [Problem](https://developers.processcart.com/errors) | | 500 | `internal_error` — Internal error. | [Problem](https://developers.processcart.com/errors) | | 503 | `unavailable` — Service unavailable. | [Problem](https://developers.processcart.com/errors) | **Errors** (every code this operation can answer; see [Errors](https://developers.processcart.com/errors)) - 401 [`invalid_api_key`](https://developers.processcart.com/errors#invalid_api_key) — Invalid API key - 401 [`key_revoked`](https://developers.processcart.com/errors#key_revoked) — API key revoked - 401 [`unauthorized`](https://developers.processcart.com/errors#unauthorized) — Authentication required - 403 [`insufficient_scope`](https://developers.processcart.com/errors#insufficient_scope) — Insufficient scope - 403 [`not_allowed_for_key`](https://developers.processcart.com/errors#not_allowed_for_key) — Not allowed for this key - 404 [`not_found`](https://developers.processcart.com/errors#not_found) — Not found - 405 [`method_not_allowed`](https://developers.processcart.com/errors#method_not_allowed) — Method not allowed - 409 [`workflow_not_published`](https://developers.processcart.com/errors#workflow_not_published) — Workflow not published - 422 [`validation_failed`](https://developers.processcart.com/errors#validation_failed) — Validation failed - 429 [`rate_limited`](https://developers.processcart.com/errors#rate_limited) — Too many requests - 500 [`internal_error`](https://developers.processcart.com/errors#internal_error) — Internal error - 503 [`unavailable`](https://developers.processcart.com/errors#unavailable) — Service unavailable **Example response** (200) ```json "Run name,Status,Started,Completed,Customer,Order code,Memo,Quantity,Amount,Fee\r\nCSV run two,Active,\"Oct 10, 2026\",,Ann,,,,\"$2,500.50\",\r\nCSV run one,Active,\"Oct 10, 2026\",,Ann,,,,\"$1,700.00\",\r\n" ``` --- # Health endpoints > The unauthenticated status probe behind the status page. Every operation with parameters, body, responses, errors and examples. Source: https://developers.processcart.com/reference/health The unauthenticated status probe behind the status page. Base URL `https://app.processcart.com/api/v1`. Every operation below is also in the [OpenAPI document](https://developers.processcart.com/openapi.json) and the [interactive reference](https://developers.processcart.com/reference). | Operation | Method and path | Scope | | --- | --- | --- | | [Check that the API is up](#getHealth) | GET /v1/health | No credential | ## GET /v1/health **Check that the API is up.** No credential. Operation id `getHealth` · [open in the API reference](https://developers.processcart.com/reference#tag/health/GET/v1/health). The status-page probe: `{ "status": "ok" }` with 200 when the database answers, `{ "status": "degraded" }` with 503 otherwise. Unauthenticated, cached per instance for ten seconds, limited per client address, never logged. **Responses** | Status | Meaning | Body | | --- | --- | --- | | 200 | OK. | [Health](https://developers.processcart.com/reference/schemas#health) | | 405 | The verb is not supported on this path. `method_not_allowed` — Method not allowed. | [Problem](https://developers.processcart.com/errors) | | 429 | `rate_limited` — Too many requests. | [Problem](https://developers.processcart.com/errors) | | 500 | `internal_error` — Internal error. | [Problem](https://developers.processcart.com/errors) | | 503 | The database did not answer in time: `{ "status": "degraded" }` (JSON, not a problem). | [Health](https://developers.processcart.com/reference/schemas#health) | **Errors** (every code this operation can answer; see [Errors](https://developers.processcart.com/errors)) - 405 [`method_not_allowed`](https://developers.processcart.com/errors#method_not_allowed) — Method not allowed - 429 [`rate_limited`](https://developers.processcart.com/errors#rate_limited) — Too many requests - 500 [`internal_error`](https://developers.processcart.com/errors#internal_error) — Internal error - 503 [`unavailable`](https://developers.processcart.com/errors#unavailable) — Service unavailable **Example response** (200) ```json { "status": "ok" } ``` --- # Scopes > What each API key scope allows, and the read-only rule. Source: https://developers.processcart.com/reference/scopes A key holds one or more action scopes; an operation names the one it needs (the reference pages and the OpenAPI document carry it as `x-processcart-scope`). `*` is every scope, present and future. | Scope | Label | Allows | | --- | --- | --- | | `runs:read` | Read runs | List and read runs, tasks, field values, comments and activity. | | `runs:write` | Write runs | Start runs, set field values, complete / reopen tasks, comment, upload files, trash and restore. | | `approvals:decide` | Decide approvals | Approve or reject approval tasks (acts with Admin authority on approvals). | | `workflows:read` | Read workflows | List workflows, read their published schema and export the portable template. | | `workflows:write` | Write workflows | Create workflows from a template JSON and change workflow settings (name, description, folder, Key Date field, archive / unarchive). | | `views:read` | Read saved views | List saved views and query the runs board. | | `views:write` | Write saved views | Create saved views and update views owned by the key's creator; delete any shared view in the workspace (Admin power, audited). | | `webhooks:manage` | Manage webhooks | Create, update, test, disable and delete webhooks; read and redeliver deliveries. | | `schedules:manage` | Manage schedules | Create, update, pause, resume, preview and delete recurrence schedules, and start a schedule's run now (the response is the full Run with its field values). | | `rules:manage` | Manage automation rules | Create, update, enable, disable and delete automation rules, and read their execution log. A rule's actions change runs (fields, status, priority, labels, task assignees), notify members and call webhooks. | | `users:read` | Read users & groups | List members and groups (ids, names, roles). | | `*` | Full access | Every scope, present and future. | ## Rules - **Read-only keys** may only make `GET` and `HEAD` requests, whatever scopes they hold — a read-only key with `webhooks:manage` can list webhooks but never change one (403 `read_only_key`). - A missing scope is a 403 `insufficient_scope`; the `detail` names the scope. - `approvals:decide` is never implied by `runs:write`: a key that holds it decides approvals with Admin authority (approve, reject, reopen an approval). - A key may also carry a **workflow or folder allowlist**; see [Authentication & API keys](https://developers.processcart.com/guides/authentication#allowlists). - `/v1/me` needs no particular scope — any valid key may identify itself; `/v1/health` needs no key at all. --- # Events > Every webhook event type, when it fires and what its data carries; the envelope. Source: https://developers.processcart.com/reference/events Every outbound event type, from the catalog the webhooks subscribe to. A subscription lists concrete types and/or `*`; `*` covers everything except the opt-in types marked below. The payload shape of each event is in the OpenAPI document's `webhooks` object (one entry per type) and summarized here. | Event | When | `data` | In `*` | | --- | --- | --- | --- | | `run.created` | A run was started (by a member, the API, a schedule or a public kickoff form). | `run`; `kickoffLinkId` when a public form started it. | yes | | `run.updated` | A run's name, priority, labels or due date changed. | `run` + `changes` keyed by attribute (`name`, `priority`, `labels`, `dueAt`), each `{ from, to }`. | yes | | `run.status_changed` | A run's status changed (active / completed / on_hold / archived), by hand or automatically. | `run` + `changes.status { from, to }`. | yes | | `run.completed` | Every task is done, or the run was marked completed by hand. | `run`. | yes | | `run.trashed` | A run moved to Trash — on its own, or with its workflow. | `run` (the lite header `{ id, name, revision, workflow, deletedAt }` when a workflow trash cascaded) + `withWorkflow`. | yes | | `run.restored` | A run came back from Trash — on its own, or with its workflow. | `run` (lite when a workflow restore cascaded) + `withWorkflow`. | yes | | `task.ready` | A task became actionable: visible, unlocked and not done. | `run` + `task`. | yes | | `task.assigned` | A task's assignees were resolved or changed, or the task became actionable for them. | `run` + `task` + `assignees`. | yes | | `task.completed` | A task was completed. | `run` + `task`. | yes | | `task.uncompleted` | A done task was reopened, by hand or by a rejected approval. | `run` + `task`; `byApproval` names the approval task whose rejection reopened it. | yes | | `field.updated` | A field value changed (opt-in: excluded from `*`; `filters.fieldKeys` narrows it). | `run` + `field` (the resource's field entry: key, label, type, format, value, formatted, …) + `field.previous`. | no (opt-in) | | `approval.requested` | An approval task became actionable for its approvers (again when a decided approval is voided by a reviewed-value change). | `run` + `task`. | yes | | `approval.decided` | An approval was approved or rejected. | `run` + `task` + `approval { id, decision, comment, decidedBy, decidedByEmail, decidedAt }`. | yes | | `comment.created` | A comment was posted on a task. | `run` + `task` + `comment { id, authorId, text, mentions }`. | yes | | `attachment.created` | A file finished uploading to a field or a comment. | `run` + `task` + `attachment { id, filename, size, mime, fieldKey, taskKey, commentId }`. | yes | | `guest.task_viewed` | A guest first opened their task link (opt-in: excluded from `*`). | `run` + `task` + `guest { email, linkId }`. | no (opt-in) | | `guest.task_completed` | A guest submitted their task (fires beside `task.completed`, whose actor is the guest). | `run` + `task` + `guest { email, linkId }`. | yes | | `workflow.published` | A workflow version was published. | `workflow { id, name }` + `version { id, number }`. | yes | | `schedule.run_created` | A recurrence schedule started a run. | `run` + `schedule { id, name }`. | yes | | `schedule.created` | A recurrence schedule was created. | `schedule` — the Schedule resource, as `GET /v1/schedules/{id}` returns it. | yes | | `schedule.updated` | A schedule changed: its settings were edited, it was paused or resumed, or the system paused it. | `schedule` + `change`: `settings`, `paused`, `resumed` or `auto_disabled` (`auto_disabled`: the system paused it, and `schedule.disabledReason` says why). | yes | | `schedule.deleted` | A schedule was deleted. | `schedule` — its last state. | yes | | `rule.created` | An automation rule was created. | `rule` — the AutomationRule resource, as `GET /v1/automation-rules/{id}` returns it. | yes | | `rule.updated` | An automation rule changed: its settings were edited, it was turned on or off, or the system turned it off after repeated failures. | `rule` + `change`: `settings`, `enabled`, `disabled` or `auto_disabled` (`auto_disabled`: the system turned it off, and `rule.disabledReason` says why). | yes | | `rule.deleted` | An automation rule was deleted. | `rule` — its last state. | yes | | `webhook.created` | A webhook endpoint was created. | `webhook` — the Webhook resource, as `GET /v1/webhooks/{id}` returns it (never the signing secret). | yes | | `webhook.updated` | A webhook endpoint changed: its settings were edited, it was enabled or disabled, its secret was rotated, or the system disabled it. | `webhook` + `change`: `settings`, `enabled`, `disabled`, `secret_rotated` or `auto_disabled` (`auto_disabled`: the system disabled it, and `webhook.disabledReason` says why). Never the secret. | yes | | `webhook.deleted` | A webhook endpoint was deleted. | `webhook` — its last state. | yes | | `webhook.test` | A test event sent from Settings or `POST /v1/webhooks/{id}/test`. | `webhook { id }`. | yes | ## Configuration events `schedule.created`, `schedule.updated`, `schedule.deleted`, `rule.created`, `rule.updated`, `rule.deleted`, `webhook.created`, `webhook.updated`, `webhook.deleted` announce the lifecycle of your automations themselves — a webhook, schedule or automation rule created, changed or deleted — so an integration learns by webhook when one is switched off. - `data.webhook` / `data.schedule` / `data.rule` is the resource exactly as `GET` returns it ([Webhook](https://developers.processcart.com/reference/schemas#webhook), [Schedule](https://developers.processcart.com/reference/schemas#schedule), [AutomationRule](https://developers.processcart.com/reference/schemas#automationrule)); a `*.deleted` event carries its last state. No payload ever carries a signing secret or key material. - `*.updated` adds `data.change`: for a webhook `settings`, `enabled`, `disabled`, `secret_rotated`, `auto_disabled`; for a schedule `settings`, `paused`, `resumed`, `auto_disabled`; for a rule `settings`, `enabled`, `disabled`, `auto_disabled`. `auto_disabled` means Process Cart turned it off — the resource's `disabledReason` says why; switching something to the state it is already in announces nothing. - `actor` is the member (`user`), the API key (`api`, with its name), or `system` for an automatic change. - In `*`, subscribable by name like every other type; automation rules never trigger on them. A disabled or deleted webhook is not delivered to, so its own `disabled`, `auto_disabled` and `webhook.deleted` events reach the workspace's other enabled endpoints only. ## Envelope Every delivery is one JSON object: ```json { "id": "evt_7f2c1c2e-0f3a-4f0e-9d6c-2b7a4c9e1f00", "type": "task.completed", "timestamp": "2026-10-07T18:42:11.000Z", "org": { "id": "0f7c4b3e-5a2d-4e1b-8c9f-1a2b3c4d5e6f", "slug": "acme" }, "actor": { "kind": "api", "keyId": "6a1f0c2d-3b4e-4f5a-8b6c-7d8e9f0a1b2c", "name": "Zapier" }, "data": { "run": { "…": "the full Run resource" }, "task": { "…": "the run's task entry" } } } ``` - `id` is also the `webhook-id` header — stable across retries and redeliveries; dedupe on it. - `actor` is who caused the change: `{ kind: "user", userId }` (a member), `{ kind: "guest", email }`, `{ kind: "api", keyId, name }` (an API key), `{ kind: "schedule", scheduleId, name }` (a recurrence schedule started the run), `{ kind: "rule", ruleId, name }` (an automation rule acted) or `{ kind: "system" }` (an automatic change), plus a display `name` for a member when known. - `data.run` is the full [Run](https://developers.processcart.com/reference/schemas#run) resource except on the cascade variants of `run.trashed` / `run.restored` (a workflow moved to or from Trash), which carry the lite header `{ id, name, revision, workflow, deletedAt }`. `workflow.published`, `webhook.test` and the configuration events carry no `data.run` — route on `type` first. - Deliveries are not ordered. Order by `data.run.revision` when it matters, and treat an older revision than the one you hold as stale. See [Webhooks](https://developers.processcart.com/webhooks) for signatures, retries and redelivery. --- # Field typing > How every field type reads (value, formatted, blank) and writes, generated from the typing rule. Source: https://developers.processcart.com/reference/field-typing Every field type, with the JSON shape its `value` takes on read, how a blank reads, what a write accepts and whether `PATCH /v1/runs/{id}/fields` (or `fields` at `POST /v1/runs`) may write it. The table is generated from the same typing rule the API serializes and validates with; [Field values & typing](https://developers.processcart.com/guides/field-values) explains the rules around it. | Field type | Format | `value` (read) | Blank | Write accepts | Example `value` → `formatted` | Writable | | --- | --- | --- | --- | --- | --- | --- | | `text` | `none` | string | null | a string; an empty or whitespace-only string clears | `"88 Birch Court"` → `"88 Birch Court"` | yes | | `text` | `custom` | string matching the field's mask or pattern | null | a string the mask / pattern accepts | `"AB-1234"` → `"AB-1234"` | yes | | `textarea` | — | string (multi-line) | null | a string; whitespace-only clears | `"Keys handed over at 3 pm."` → `"Keys handed over at 3 pm."` | yes | | `number` | `number` | number | null | a JSON number (the canonical) or a string parsed by the format ("1,234.5" — a comma is always a thousands separator) | `1234.5` → `"1,234.5"` | yes | | `number` | `currency` | number in whole currency units | null | a JSON number (the canonical) or a string parsed by the format ("$1,700.50" → 1700.5) | `1700.5` → `"$1,700.50"` | yes | | `number` | `percent` | number — a FRACTION (0.125 means 12.5 %) | null | a JSON number (the canonical) or a string parsed by the format ("12.5%" → 0.125; a bare "12.5" follows the field's entry mode) | `0.125` → `"12.5%"` | yes | | `date` | `date` | string, YYYY-MM-DD (no time zone) | null | a date string in any common form ("2026-06-25", "06/25/2026", "Jun 25 2026") | `"2026-06-25"` → `"Jun 25, 2026"` | yes | | `time` | `time` | string, HH:mm (24-hour, no seconds, no time zone) | null | a time string ("14:30", "2:30 PM") | `"14:30"` → `"2:30 PM"` | yes | | `dropdown` | — | string — one of the field's `choices` | null | one of `choices`, exactly | `"Yes"` → `"Yes"` | yes | | `multi_choice` | — | string[] — labels from `choices`, in the field's order | null | an array of `choices` entries (an unknown entry is a 422, never dropped) | `["A","C"]` → `"A, C"` | yes | | `checkbox` | — | boolean — never null; a never-set checkbox reads false | false | a boolean (false clears) | `true` → `"Yes"` | yes | | `email` | `email` | string, lowercased | null | an email address | `"ada@example.com"` → `"ada@example.com"` | yes | | `url` | `url` | string, normalized by the URL parser (a scheme is added) | null | an http(s) URL | `"https://processcart.com/"` → `"https://processcart.com/"` | yes | | `phone` | `phone` | string, E.164 | null | a US phone number in any common form ("(555) 123-4567") | `"+15551234567"` → `"(555) 123-4567"` | yes | | `members` | — | string[] of member user ids | null | an array of current member user ids (an unknown id is a 422) | `["11111111-1111-4111-8111-111111111111"]` → `"Ada Lovelace"` | yes | | `subtasks` | — | string[] of checked item keys, in the field's order | null | an array of `items[].key` entries | `["photos"]` → `"1 of 2: Photos"` | yes | | `file` | — | string[] of attachment ids | null | not writable here — upload through `POST /v1/runs/{id}/files` | `["22222222-2222-4222-8222-222222222222"]` → `"contract.pdf"` | no | | `formula` | — | number \| string \| boolean \| null (engine output; dates as YYYY-MM-DD, date-times as YYYY-MM-DDTHH:mm:ss in the org's time zone) | null (also for a formula error) | not writable (computed) | `2400` → `"$2,400.00"` | no | | `hidden` | — | whatever canonical shape was written (number \| string \| boolean \| string[]) | null | any canonical value — writable through the API when the field carries no formula (not shown in the app, not secret) | `"zapier"` → `"zapier"` | yes | | `display_text` | — | — (never appears in `fields[]`; it carries no value) | — | not writable | — | no | The effective **format** decides the shape first: a `text` field carrying a currency format reads and writes as a number, exactly like a `number` field would. `GET /v1/workflows/{id}/schema` applies the same rule per field and emits JSON Schema with `x-processcart-type` / `x-processcart-format`. --- # Limits and schedules > Every published limit — rate, sizes, pages, keys, files, webhooks — and the retry schedule. Source: https://developers.processcart.com/reference/limits Every published number in one place, generated from the constants the API enforces. Limits are the same on every plan. | What | Limit | | --- | --- | | Rate limit per key | 600 requests / 60 s | | Rate limit on `/v1/health` | 60 requests / 60 s per client address | | Request body | 1,048,576 bytes (1 MiB) — 413 above it | | Page size (`limit`) | default 50; max 200 (100 on `/v1/runs` and `/v1/views/{id}/runs`); above the max is a 422, never clamped | | Fields per write | 1–200 keys per `PATCH /v1/runs/{id}/fields` / `fields` at start | | Field value | strings ≤ 10,000 characters; arrays ≤ 200 items of ≤ 200 characters; numbers finite | | Run name | 1–300 characters | | Labels per run | 50 ids | | Approval comment | 5,000 characters | | `Idempotency-Key` | 1–255 printable ASCII characters; stored 24 h; responses over 262,144 bytes are not stored (a retry re-executes) | | `X-Request-Id` | echoed when 1–128 printable ASCII characters; else server-minted `req_` | | API key name | 80 characters | | Key rotation grace | 24 h (the previous secret keeps working) | | Allowlist per key | up to 200 workflow ids and 200 folder ids | | File upload | 25 MB per file; the upload URL is valid 2 hours; download URLs 1 hour | | Webhook URL / description | 2048 / 200 characters; https only | | Webhook timeout | 10 s per attempt | | Webhook retries | 1 / 5 / 15 / 30 / 60 minutes after a failure (6 attempts), then exhausted | | Webhook auto-disable | after 5 consecutive exhausted deliveries, or one 410 | | Webhook secret rotation grace | 24 h (both secrets sign) | | Webhook timestamp tolerance | 300 s (reject older or newer deliveries as replays) | | Delivery log retention | 30 days | | Events per subscription | any number of types, or `*` | | Schedules | 200 per workspace; times on 5-minute steps — runs are created at the scheduled time, usually within a minute | | Schedule name / run-name pattern | 120 / 300 characters; the run name it renders is cut to 200 | | Schedule presets / assignment overrides | 50 preset fields; 30 overridden tasks, each with 1–20 assignees | | Schedule start failures | paused (`failed`) after 3 in a row; a database outage is retried and never counts | | Automation rules that are on | 100 per workspace | | Rule name / actions / conditions | 120 characters; up to 10 actions and 10 conditions per rule | | Rule `assign_task` / `notify` | 20 assignees; 20 recipients and a 500-character message | | Rule auto-disable | after 10 consecutive failed executions | | Rule loop guard | 20 executions of one rule on one run in any 60 minutes | | Rule execution log retention | 30 days | ## Webhook retry schedule - First attempt immediately; after a failure, retries at **1 min → 5 min → 15 min → 30 min → 60 min** after the previous attempt — 6 attempts in all over about 1 h 51 min; then the delivery is `exhausted`. - Per-attempt timeout 10 seconds; any 2xx counts as delivered, anything else (or a timeout, or a refused connection) as a failed attempt. - A `410 Gone` disables the endpoint at once (`disabledReason: "gone"`); 5 consecutive exhausted deliveries disable it (`"failures"`). Either way the workspace's Builders and Admins are notified; enable it again from Automations → Webhooks or `POST /v1/webhooks/{id}/enable`. - Deliveries are kept 30 days. --- # Schemas > Every component schema of the OpenAPI document with its properties. Source: https://developers.processcart.com/reference/schemas Every component schema of the [OpenAPI document](https://developers.processcart.com/openapi.json), as a quick lookup: the reference pages link here from their type columns. Objects list their properties; other schemas show their shape. Growable enums (statuses, event types, error codes, field types) are marked open in the document — treat an unknown value as such. ### ActivityActor Type: object ### ActivityItem | Property | Type | Required | Description | | --- | --- | --- | --- | | `id` | [Uuid](https://developers.processcart.com/reference/schemas#uuid) | yes | | | `runId` | [Uuid](https://developers.processcart.com/reference/schemas#uuid) | yes | | | `type` | string | yes | | | `category` | `"fields"` \\| `"tasks"` \\| `"approvals"` \\| `"files"` \\| `"comments"` \\| `"run"` | yes | | | `actor` | [EventActor](https://developers.processcart.com/reference/schemas#eventactor) | yes | | | `taskKey` | string \\| null | yes | | | `payload` | object (key → any) | yes | | | `description` | string | yes | | | `createdAt` | [Instant](https://developers.processcart.com/reference/schemas#instant) | yes | | ### Actor Type: object ### ApiErrorCode Open enum: new values may be added without a version bump — treat an unknown value as such. Type: `"unauthorized"` \| `"invalid_api_key"` \| `"key_revoked"` \| `"insufficient_scope"` \| `"read_only_key"` \| `"not_allowed_for_key"` \| `"rate_limited"` \| `"idempotency_key_reused"` \| … ### ApiScope Type: `"runs:read"` \| `"runs:write"` \| `"approvals:decide"` \| `"workflows:read"` \| `"workflows:write"` \| `"views:read"` \| `"views:write"` \| `"webhooks:manage"` \| … ### Assignment | Property | Type | Required | Description | | --- | --- | --- | --- | | `assignees` | object[] | yes | | ### Attachment | Property | Type | Required | Description | | --- | --- | --- | --- | | `id` | [Uuid](https://developers.processcart.com/reference/schemas#uuid) | yes | | | `runId` | [Uuid](https://developers.processcart.com/reference/schemas#uuid) | yes | | | `taskKey` | string \\| null | yes | | | `fieldKey` | string \\| null | yes | | | `commentId` | [Uuid](https://developers.processcart.com/reference/schemas#uuid) \\| null | yes | | | `filename` | string | yes | | | `size` | integer | yes | | | `contentType` | string | yes | | | `uploadedByUserId` | [Uuid](https://developers.processcart.com/reference/schemas#uuid) \\| null | yes | | | `uploadedBy` | [Actor](https://developers.processcart.com/reference/schemas#actor) | yes | | | `createdAt` | [Instant](https://developers.processcart.com/reference/schemas#instant) | yes | | | `downloadUrl` | string (uri) | no | A signed download link (`Content-Disposition: attachment`), valid one hour. | | `downloadExpiresAt` | [Instant](https://developers.processcart.com/reference/schemas#instant) | no | | ### AutomationAssignee Type: object ### AutomationRule | Property | Type | Required | Description | | --- | --- | --- | --- | | `id` | [Uuid](https://developers.processcart.com/reference/schemas#uuid) | yes | | | `workflowId` | [Uuid](https://developers.processcart.com/reference/schemas#uuid) \\| null | yes | | | `workflow` | object \\| null | yes | | | `name` | string | yes | | | `triggerEvent` | `"run.created"` \\| `"run.updated"` \\| `"run.status_changed"` \\| `"run.completed"` \\| `"task.ready"` \\| `"task.assigned"` \\| `"task.completed"` \\| `"task.uncompleted"` \\| … | yes | | | `filter` | object | yes | | | `conditions` | object | yes | | | `actions` | object[] | yes | | | `enabled` | boolean | yes | | | `disabledReason` | `"manual"` \\| `"failures"` \\| `"loop_guard"` \\| null | yes | | | `disabledAt` | [Instant](https://developers.processcart.com/reference/schemas#instant) \\| null | yes | | | `consecutiveFailures` | integer | yes | | | `keyMismatches` | object[] | yes | | | `createdBy` | [Uuid](https://developers.processcart.com/reference/schemas#uuid) \\| null | yes | | | `createdAt` | [Instant](https://developers.processcart.com/reference/schemas#instant) | yes | | | `updatedAt` | [Instant](https://developers.processcart.com/reference/schemas#instant) | yes | | ### AutomationRuleConfig | Property | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | yes | | | `workflowId` | [Uuid](https://developers.processcart.com/reference/schemas#uuid) \\| null | yes | | | `triggerEvent` | `"run.created"` \\| `"run.updated"` \\| `"run.status_changed"` \\| `"run.completed"` \\| `"task.ready"` \\| `"task.assigned"` \\| `"task.completed"` \\| `"task.uncompleted"` \\| … | yes | | | `filter` | [RuleFilter](https://developers.processcart.com/reference/schemas#rulefilter) | no | | | `conditions` | [RuleConditions](https://developers.processcart.com/reference/schemas#ruleconditions) | no | | | `actions` | [RuleAction](https://developers.processcart.com/reference/schemas#ruleaction)[] | yes | | ### AutomationRuleList | Property | Type | Required | Description | | --- | --- | --- | --- | | `data` | [AutomationRule](https://developers.processcart.com/reference/schemas#automationrule)[] | yes | | | `nextCursor` | string \\| null | yes | | | `hasMore` | boolean | yes | | ### AutomationRulePatch | Property | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | no | | | `workflowId` | [Uuid](https://developers.processcart.com/reference/schemas#uuid) \\| null | no | | | `triggerEvent` | `"run.created"` \\| `"run.updated"` \\| `"run.status_changed"` \\| `"run.completed"` \\| `"task.ready"` \\| `"task.assigned"` \\| `"task.completed"` \\| `"task.uncompleted"` \\| … | no | | | `filter` | [RuleFilter](https://developers.processcart.com/reference/schemas#rulefilter) | no | | | `conditions` | [RuleConditions](https://developers.processcart.com/reference/schemas#ruleconditions) | no | | | `actions` | [RuleAction](https://developers.processcart.com/reference/schemas#ruleaction)[] | no | | ### CalendarDate Type: string ### CanonicalValue Type: string \| number \| boolean \| string[] ### Comment | Property | Type | Required | Description | | --- | --- | --- | --- | | `id` | [Uuid](https://developers.processcart.com/reference/schemas#uuid) | yes | | | `runId` | [Uuid](https://developers.processcart.com/reference/schemas#uuid) | yes | | | `taskKey` | [Key](https://developers.processcart.com/reference/schemas#key) | yes | | | `runTaskId` | [Uuid](https://developers.processcart.com/reference/schemas#uuid) | yes | | | `author` | [Actor](https://developers.processcart.com/reference/schemas#actor) & object | yes | | | `body` | object | yes | A rich-text document (ProseMirror JSON). Allowed node types: paragraph, text, heading, bulletList, orderedList, listItem, blockquote, codeBlock, hardBreak, horizontalRule, image, table, tableRow, tableCell, tableHeader, mention, mergeTag (attrs.fieldKey must be a valid field key, else dropped), embed (an https video src on YouTube, Loom or Vimeo, else removed); marks: bold, italic, code, link, textStyle, highlight. Anything else is dropped on save. A mention node is `{ type: "mention", attrs: { id: "" } }` — its label is always re-derived from the member list. | | `text` | string | yes | | | `mentions` | [Uuid](https://developers.processcart.com/reference/schemas#uuid)[] | yes | | | `createdAt` | [Instant](https://developers.processcart.com/reference/schemas#instant) | yes | | | `editedAt` | [Instant](https://developers.processcart.com/reference/schemas#instant) \\| null | yes | | | `url` | string (uri) | yes | | ### CreateRunCommentBody | Property | Type | Required | Description | | --- | --- | --- | --- | | `taskKey` | string | yes | The task to comment on (any task of the run). | | `text` | string | no | Plain text: blank lines separate paragraphs, a single newline is a line break, and `@Name` becomes a mention when it matches exactly one member. | | `body` | object | no | A rich-text document (ProseMirror JSON). Allowed node types: paragraph, text, heading, bulletList, orderedList, listItem, blockquote, codeBlock, hardBreak, horizontalRule, image, table, tableRow, tableCell, tableHeader, mention, mergeTag (attrs.fieldKey must be a valid field key, else dropped), embed (an https video src on YouTube, Loom or Vimeo, else removed); marks: bold, italic, code, link, textStyle, highlight. Anything else is dropped on save. A mention node is `{ type: "mention", attrs: { id: "" } }` — its label is always re-derived from the member list. | ### CreateViewBody | Property | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | yes | | | `query` | object | yes | The board query the view selects. Send only the members you set; the rest take the board's defaults: `{"workflowId":null,"status":["active","on_hold"],"priority":[],"labels":[],"assignee":null,"due":null,"q":"","qMode":"prefix","sort":{"key":"started","dir":"desc"},"columns":["status","priority","progress","key_date","due","assignees","started"]}`. `sort.key` without `dir` takes that key's natural direction (`asc` for `name`, `key_date` and `due`, else `desc`). An unknown member is a 422. | | `visibility` | `"org"` \\| `"groups"` | no | | | `groupIds` | [Uuid](https://developers.processcart.com/reference/schemas#uuid)[] | no | | ### CreateWorkflowBody | Property | Type | Required | Description | | --- | --- | --- | --- | | `template` | [WorkflowTemplate](https://developers.processcart.com/reference/schemas#workflowtemplate) | yes | | | `name` | string | no | | | `description` | string \\| null | no | | | `folderId` | [Uuid](https://developers.processcart.com/reference/schemas#uuid) \\| null | no | | | `publish` | boolean | no | | ### CreateWorkflowResponse | Property | Type | Required | Description | | --- | --- | --- | --- | | `id` | [Uuid](https://developers.processcart.com/reference/schemas#uuid) | yes | | | `name` | string | yes | | | `description` | string \\| null | yes | | | `status` | `"active"` \\| `"archived"` | yes | | | `icon` | string \\| null | yes | | | `color` | string \\| null | yes | | | `folder` | object \\| null | yes | | | `keyDateFieldKey` | string \\| null | yes | | | `kickoffFormEnabled` | boolean | yes | | | `publishedVersion` | object \\| null | yes | | | `taskCount` | integer | yes | | | `createdAt` | [Instant](https://developers.processcart.com/reference/schemas#instant) | yes | | | `updatedAt` | [Instant](https://developers.processcart.com/reference/schemas#instant) | yes | | | `sections` | [WorkflowSection](https://developers.processcart.com/reference/schemas#workflowsection)[] | yes | | | `rules` | [RuleDef](https://developers.processcart.com/reference/schemas#ruledef)[] | yes | | | `import` | [ImportReport](https://developers.processcart.com/reference/schemas#importreport) | yes | | | `publishWarnings` | string[] | yes | | ### Cursor Type: string ### DueRule Type: object ### EventActor | Property | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | no | | ### EventApproval | Property | Type | Required | Description | | --- | --- | --- | --- | | `id` | [Uuid](https://developers.processcart.com/reference/schemas#uuid) | yes | | | `decision` | `"approved"` \\| `"rejected"` | yes | | | `comment` | string \\| null | yes | | | `decidedBy` | [Uuid](https://developers.processcart.com/reference/schemas#uuid) \\| null | yes | | | `decidedByEmail` | string \\| null | yes | | | `decidedAt` | [Instant](https://developers.processcart.com/reference/schemas#instant) | yes | | ### EventApprovalDecided | Property | Type | Required | Description | | --- | --- | --- | --- | | `run` | [Run](https://developers.processcart.com/reference/schemas#run) | yes | | | `task` | [RunTask](https://developers.processcart.com/reference/schemas#runtask) | yes | | | `approval` | [EventApproval](https://developers.processcart.com/reference/schemas#eventapproval) | yes | | ### EventApprovalRequested | Property | Type | Required | Description | | --- | --- | --- | --- | | `run` | [Run](https://developers.processcart.com/reference/schemas#run) | yes | | | `task` | [RunTask](https://developers.processcart.com/reference/schemas#runtask) | yes | | ### EventAttachment | Property | Type | Required | Description | | --- | --- | --- | --- | | `id` | [Uuid](https://developers.processcart.com/reference/schemas#uuid) | yes | | | `filename` | string | yes | | | `size` | integer | yes | | | `mime` | string | yes | | | `fieldKey` | string \\| null | yes | | | `taskKey` | string | yes | | | `commentId` | [Uuid](https://developers.processcart.com/reference/schemas#uuid) \\| null | yes | | ### EventAttachmentCreated | Property | Type | Required | Description | | --- | --- | --- | --- | | `run` | [Run](https://developers.processcart.com/reference/schemas#run) | yes | | | `task` | [RunTask](https://developers.processcart.com/reference/schemas#runtask) | yes | | | `attachment` | [EventAttachment](https://developers.processcart.com/reference/schemas#eventattachment) | yes | | ### EventComment | Property | Type | Required | Description | | --- | --- | --- | --- | | `id` | [Uuid](https://developers.processcart.com/reference/schemas#uuid) | yes | | | `authorId` | [Uuid](https://developers.processcart.com/reference/schemas#uuid) \\| null | yes | | | `text` | string | yes | | | `mentions` | [Uuid](https://developers.processcart.com/reference/schemas#uuid)[] | yes | | ### EventCommentCreated | Property | Type | Required | Description | | --- | --- | --- | --- | | `run` | [Run](https://developers.processcart.com/reference/schemas#run) | yes | | | `task` | [RunTask](https://developers.processcart.com/reference/schemas#runtask) | yes | | | `comment` | [EventComment](https://developers.processcart.com/reference/schemas#eventcomment) | yes | | ### EventEnvelope | Property | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | yes | | | `type` | [EventType](https://developers.processcart.com/reference/schemas#eventtype) | yes | | | `timestamp` | string | yes | | | `org` | object | yes | | | `actor` | [EventActor](https://developers.processcart.com/reference/schemas#eventactor) | yes | | | `data` | object (key → any) | yes | | ### EventField | Property | Type | Required | Description | | --- | --- | --- | --- | | `key` | [FieldKey](https://developers.processcart.com/reference/schemas#fieldkey) | yes | | | `label` | string | yes | | | `type` | [FieldType](https://developers.processcart.com/reference/schemas#fieldtype) | yes | | | `format` | [FormatType](https://developers.processcart.com/reference/schemas#formattype) \\| null | yes | | | `taskKey` | [Key](https://developers.processcart.com/reference/schemas#key) | yes | | | `value` | [CanonicalValue](https://developers.processcart.com/reference/schemas#canonicalvalue) \\| null | yes | | | `formatted` | string | yes | | | `visible` | boolean | yes | | | `choices` | string[] | no | | | `items` | object[] | no | | | `validationError` | string | no | | | `previous` | [CanonicalValue](https://developers.processcart.com/reference/schemas#canonicalvalue) \\| null | yes | | ### EventFieldUpdated | Property | Type | Required | Description | | --- | --- | --- | --- | | `run` | [Run](https://developers.processcart.com/reference/schemas#run) | yes | | | `field` | [EventField](https://developers.processcart.com/reference/schemas#eventfield) | yes | | ### EventGuest | Property | Type | Required | Description | | --- | --- | --- | --- | | `email` | string | yes | | | `linkId` | [Uuid](https://developers.processcart.com/reference/schemas#uuid) | yes | | ### EventGuestTaskCompleted | Property | Type | Required | Description | | --- | --- | --- | --- | | `run` | [Run](https://developers.processcart.com/reference/schemas#run) | yes | | | `task` | [RunTask](https://developers.processcart.com/reference/schemas#runtask) | yes | | | `guest` | [EventGuest](https://developers.processcart.com/reference/schemas#eventguest) | yes | | ### EventGuestTaskViewed | Property | Type | Required | Description | | --- | --- | --- | --- | | `run` | [Run](https://developers.processcart.com/reference/schemas#run) | yes | | | `task` | [RunTask](https://developers.processcart.com/reference/schemas#runtask) | yes | | | `guest` | [EventGuest](https://developers.processcart.com/reference/schemas#eventguest) | yes | | ### EventRuleCreated | Property | Type | Required | Description | | --- | --- | --- | --- | | `rule` | [AutomationRule](https://developers.processcart.com/reference/schemas#automationrule) | yes | | ### EventRuleDeleted | Property | Type | Required | Description | | --- | --- | --- | --- | | `rule` | [AutomationRule](https://developers.processcart.com/reference/schemas#automationrule) | yes | | ### EventRuleUpdated | Property | Type | Required | Description | | --- | --- | --- | --- | | `rule` | [AutomationRule](https://developers.processcart.com/reference/schemas#automationrule) | yes | | | `change` | `"settings"` \\| `"enabled"` \\| `"disabled"` \\| `"auto_disabled"` | yes | | ### EventRunCompleted | Property | Type | Required | Description | | --- | --- | --- | --- | | `run` | [Run](https://developers.processcart.com/reference/schemas#run) | yes | | ### EventRunCreated | Property | Type | Required | Description | | --- | --- | --- | --- | | `run` | [Run](https://developers.processcart.com/reference/schemas#run) | yes | | | `kickoffLinkId` | [Uuid](https://developers.processcart.com/reference/schemas#uuid) | no | | ### EventRunRestored | Property | Type | Required | Description | | --- | --- | --- | --- | | `run` | [Run](https://developers.processcart.com/reference/schemas#run) \\| [RunLite](https://developers.processcart.com/reference/schemas#runlite) | yes | | | `withWorkflow` | boolean | no | | ### EventRunStatusChanged | Property | Type | Required | Description | | --- | --- | --- | --- | | `run` | [Run](https://developers.processcart.com/reference/schemas#run) | yes | | | `changes` | object | yes | | ### EventRunTrashed | Property | Type | Required | Description | | --- | --- | --- | --- | | `run` | [Run](https://developers.processcart.com/reference/schemas#run) \\| [RunLite](https://developers.processcart.com/reference/schemas#runlite) | yes | | | `withWorkflow` | boolean | no | | ### EventRunUpdated | Property | Type | Required | Description | | --- | --- | --- | --- | | `run` | [Run](https://developers.processcart.com/reference/schemas#run) | yes | | | `changes` | object (key → object) | yes | | ### EventScheduleCreated | Property | Type | Required | Description | | --- | --- | --- | --- | | `schedule` | [Schedule](https://developers.processcart.com/reference/schemas#schedule) | yes | | ### EventScheduleDeleted | Property | Type | Required | Description | | --- | --- | --- | --- | | `schedule` | [Schedule](https://developers.processcart.com/reference/schemas#schedule) | yes | | ### EventScheduleRunCreated | Property | Type | Required | Description | | --- | --- | --- | --- | | `run` | [Run](https://developers.processcart.com/reference/schemas#run) | yes | | | `schedule` | object | yes | | ### EventScheduleUpdated | Property | Type | Required | Description | | --- | --- | --- | --- | | `schedule` | [Schedule](https://developers.processcart.com/reference/schemas#schedule) | yes | | | `change` | `"settings"` \\| `"paused"` \\| `"resumed"` \\| `"auto_disabled"` | yes | | ### EventTaskAssigned | Property | Type | Required | Description | | --- | --- | --- | --- | | `run` | [Run](https://developers.processcart.com/reference/schemas#run) | yes | | | `task` | [RunTask](https://developers.processcart.com/reference/schemas#runtask) | yes | | | `assignees` | [ResolvedAssignee](https://developers.processcart.com/reference/schemas#resolvedassignee)[] | yes | | ### EventTaskCompleted | Property | Type | Required | Description | | --- | --- | --- | --- | | `run` | [Run](https://developers.processcart.com/reference/schemas#run) | yes | | | `task` | [RunTask](https://developers.processcart.com/reference/schemas#runtask) | yes | | ### EventTaskReady | Property | Type | Required | Description | | --- | --- | --- | --- | | `run` | [Run](https://developers.processcart.com/reference/schemas#run) | yes | | | `task` | [RunTask](https://developers.processcart.com/reference/schemas#runtask) | yes | | ### EventTaskUncompleted | Property | Type | Required | Description | | --- | --- | --- | --- | | `run` | [Run](https://developers.processcart.com/reference/schemas#run) | yes | | | `task` | [RunTask](https://developers.processcart.com/reference/schemas#runtask) | yes | | | `byApproval` | [Key](https://developers.processcart.com/reference/schemas#key) | no | | ### EventType Open enum: new values may be added without a version bump — treat an unknown value as such. Type: `"run.created"` \| `"run.updated"` \| `"run.status_changed"` \| `"run.completed"` \| `"run.trashed"` \| `"run.restored"` \| `"task.ready"` \| `"task.assigned"` \| … ### EventWebhookCreated | Property | Type | Required | Description | | --- | --- | --- | --- | | `webhook` | [Webhook](https://developers.processcart.com/reference/schemas#webhook) | yes | | ### EventWebhookDeleted | Property | Type | Required | Description | | --- | --- | --- | --- | | `webhook` | [Webhook](https://developers.processcart.com/reference/schemas#webhook) | yes | | ### EventWebhookTest | Property | Type | Required | Description | | --- | --- | --- | --- | | `webhook` | object | yes | | ### EventWebhookUpdated | Property | Type | Required | Description | | --- | --- | --- | --- | | `webhook` | [Webhook](https://developers.processcart.com/reference/schemas#webhook) | yes | | | `change` | `"settings"` \\| `"enabled"` \\| `"disabled"` \\| `"secret_rotated"` \\| `"auto_disabled"` | yes | | ### EventWorkflowPublished | Property | Type | Required | Description | | --- | --- | --- | --- | | `workflow` | object | yes | | | `version` | object | yes | | ### ExportRunsCsvResponse The CSV text: a header row, then one row per run (UTF-8, BOM, CRLF). Type: string ### FieldKey Type: string ### FieldOptions | Property | Type | Required | Description | | --- | --- | --- | --- | | `choices` | string[] | no | | | `items` | object[] | no | | | `allRequired` | boolean | no | | | `multiple` | boolean | no | | ### FieldsWrite Type: object (key → [FieldWriteValue](https://developers.processcart.com/reference/schemas#fieldwritevalue)) ### FieldType Open enum: new values may be added without a version bump — treat an unknown value as such. Type: `"text"` \| `"textarea"` \| `"number"` \| `"date"` \| `"time"` \| `"dropdown"` \| `"multi_choice"` \| `"checkbox"` \| … ### FieldValidation | Property | Type | Required | Description | | --- | --- | --- | --- | | `expr` | string | yes | | | `message` | string | no | | ### FieldWriteValue Type: string \| number \| boolean \| string[] \| null ### FinalizeRunFileBody | Property | Type | Required | Description | | --- | --- | --- | --- | | `filename` | string | yes | | | `target` | object | yes | | ### FinalizeRunFileResponse | Property | Type | Required | Description | | --- | --- | --- | --- | | `attachment` | [Attachment](https://developers.processcart.com/reference/schemas#attachment) | yes | | | `run` | [Run](https://developers.processcart.com/reference/schemas#run) | yes | | ### FormatConfig Type: object ### FormatType Open enum: new values may be added without a version bump — treat an unknown value as such. Type: `"none"` \| `"number"` \| `"currency"` \| `"percent"` \| `"date"` \| `"time"` \| `"phone"` \| `"email"` \| … ### Formula | Property | Type | Required | Description | | --- | --- | --- | --- | | `mode` | `"spreadsheet"` \\| `"js"` | yes | | | `expr` | string | yes | | ### GetWorkflowSchemaResponse | Property | Type | Required | Description | | --- | --- | --- | --- | | `workflowId` | [Uuid](https://developers.processcart.com/reference/schemas#uuid) | yes | | | `versionId` | [Uuid](https://developers.processcart.com/reference/schemas#uuid) | yes | | | `versionNumber` | integer | yes | | | `start` | object (key → any) | yes | | | `fields` | object (key → any) | yes | | ### Group | Property | Type | Required | Description | | --- | --- | --- | --- | | `id` | [Uuid](https://developers.processcart.com/reference/schemas#uuid) | yes | | | `name` | string | yes | | | `memberIds` | [Uuid](https://developers.processcart.com/reference/schemas#uuid)[] | yes | | | `createdAt` | [Instant](https://developers.processcart.com/reference/schemas#instant) | yes | | ### GuestConfig | Property | Type | Required | Description | | --- | --- | --- | --- | | `contextFieldKeys` | [Key](https://developers.processcart.com/reference/schemas#key)[] | no | | ### Health | Property | Type | Required | Description | | --- | --- | --- | --- | | `status` | `"ok"` \\| `"degraded"` | yes | | ### ImportReport | Property | Type | Required | Description | | --- | --- | --- | --- | | `errors` | string[] | yes | | | `warnings` | string[] | yes | | | `counts` | object | yes | | | `clearedAssignments` | integer | yes | | | `flaggedEmails` | string[] | yes | | ### Instant Type: string (date-time) ### Key Type: string ### LabelDetail | Property | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | yes | | | `name` | string | yes | | | `color` | string | yes | | ### ListGroupsResponse | Property | Type | Required | Description | | --- | --- | --- | --- | | `data` | [Group](https://developers.processcart.com/reference/schemas#group)[] | yes | | | `nextCursor` | string \\| null | yes | | | `hasMore` | boolean | yes | | ### ListRunActivityResponse | Property | Type | Required | Description | | --- | --- | --- | --- | | `data` | [ActivityItem](https://developers.processcart.com/reference/schemas#activityitem)[] | yes | | | `nextCursor` | string \\| null | yes | | | `hasMore` | boolean | yes | | ### ListRunCommentsResponse | Property | Type | Required | Description | | --- | --- | --- | --- | | `data` | [Comment](https://developers.processcart.com/reference/schemas#comment)[] | yes | | | `nextCursor` | string \\| null | yes | | | `hasMore` | boolean | yes | | ### ListRunFilesResponse | Property | Type | Required | Description | | --- | --- | --- | --- | | `data` | [Attachment](https://developers.processcart.com/reference/schemas#attachment)[] | yes | | | `nextCursor` | string \\| null | yes | | | `hasMore` | boolean | yes | | ### ListUsersResponse | Property | Type | Required | Description | | --- | --- | --- | --- | | `data` | [User](https://developers.processcart.com/reference/schemas#user)[] | yes | | | `nextCursor` | string \\| null | yes | | | `hasMore` | boolean | yes | | ### ListViewRunsResponse | Property | Type | Required | Description | | --- | --- | --- | --- | | `data` | [Run](https://developers.processcart.com/reference/schemas#run)[] | yes | | | `nextCursor` | string \\| null | yes | | | `hasMore` | boolean | yes | | ### ListViewsResponse | Property | Type | Required | Description | | --- | --- | --- | --- | | `data` | [View](https://developers.processcart.com/reference/schemas#view)[] | yes | | | `nextCursor` | string \\| null | yes | | | `hasMore` | boolean | yes | | ### ListWorkflowsResponse | Property | Type | Required | Description | | --- | --- | --- | --- | | `data` | [WorkflowSummary](https://developers.processcart.com/reference/schemas#workflowsummary)[] | yes | | | `nextCursor` | string \\| null | yes | | | `hasMore` | boolean | yes | | ### Me | Property | Type | Required | Description | | --- | --- | --- | --- | | `org` | object | yes | | | `key` | object | yes | | | `rateLimit` | object | yes | | ### OnFocusPopulate | Property | Type | Required | Description | | --- | --- | --- | --- | | `expr` | string | yes | | ### Problem | Property | Type | Required | Description | | --- | --- | --- | --- | | `type` | string | yes | | | `title` | string | yes | | | `status` | integer | yes | | | `detail` | string | no | | | `code` | `"unauthorized"` \\| `"invalid_api_key"` \\| `"key_revoked"` \\| `"insufficient_scope"` \\| `"read_only_key"` \\| `"not_allowed_for_key"` \\| `"rate_limited"` \\| `"idempotency_key_reused"` \\| … | yes | Open enum: new values may be added without a version bump — treat an unknown value as such. | | `requestId` | string | yes | | | `errors` | object[] | no | | | `currentRevision` | integer | no | Only on a 409 `revision_mismatch`: the run's current revision. Read the run again, merge, and retry with `Expected-Revision: `. | ### ProblemFieldError | Property | Type | Required | Description | | --- | --- | --- | --- | | `field` | string | yes | | | `code` | string | yes | | | `message` | string | yes | | ### Recurrence | Property | Type | Required | Description | | --- | --- | --- | --- | | `freq` | `"daily"` \\| `"weekly"` \\| `"monthly"` \\| `"yearly"` | yes | | | `interval` | integer | yes | | | `time` | string | yes | | | `startDate` | [CalendarDate](https://developers.processcart.com/reference/schemas#calendardate) | yes | | | `endDate` | [CalendarDate](https://developers.processcart.com/reference/schemas#calendardate) \\| null | no | | | `weekly` | object | no | | | `monthly` | object | no | | | `yearly` | object | no | | ### RequestRunFileBody | Property | Type | Required | Description | | --- | --- | --- | --- | | `filename` | string | yes | The display name. Send the SAME string again at finalize. | | `size` | integer | yes | The byte size (at most 26214400 — 25 MB). | | `contentType` | string | yes | The MIME type you will send as the PUT's Content-Type — it becomes the stored type (echoed in `upload.headers`). | | `target` | object | yes | | ### ResolvedAssignee Type: object ### RichDoc | Property | Type | Required | Description | | --- | --- | --- | --- | | `type` | `"doc"` | yes | | | `content` | [RichNode](https://developers.processcart.com/reference/schemas#richnode)[] | no | | ### RichNode | Property | Type | Required | Description | | --- | --- | --- | --- | | `type` | string | yes | | | `attrs` | object (key → any) | no | | | `content` | [RichNode](https://developers.processcart.com/reference/schemas#richnode)[] | no | | | `marks` | object[] | no | | | `text` | string | no | | ### RuleAction Type: object ### RuleConditions | Property | Type | Required | Description | | --- | --- | --- | --- | | `combinator` | `"and"` \\| `"or"` | yes | | | `items` | object[] | yes | | ### RuleDef | Property | Type | Required | Description | | --- | --- | --- | --- | | `effect` | `"show"` \\| `"hide"` | yes | | | `when` | object | yes | | | `targets` | object[] | yes | | ### RuleExecution | Property | Type | Required | Description | | --- | --- | --- | --- | | `id` | [Uuid](https://developers.processcart.com/reference/schemas#uuid) | yes | | | `ruleId` | [Uuid](https://developers.processcart.com/reference/schemas#uuid) | yes | | | `runId` | [Uuid](https://developers.processcart.com/reference/schemas#uuid) | yes | | | `run` | object \\| null | yes | | | `eventId` | string | yes | | | `eventType` | string | yes | | | `status` | `"queued"` \\| `"running"` \\| `"done"` \\| `"failed"` | yes | | | `result` | object[] | yes | | | `error` | string \\| null | yes | | | `createdAt` | [Instant](https://developers.processcart.com/reference/schemas#instant) | yes | | | `startedAt` | [Instant](https://developers.processcart.com/reference/schemas#instant) \\| null | yes | | | `finishedAt` | [Instant](https://developers.processcart.com/reference/schemas#instant) \\| null | yes | | ### RuleExecutionList | Property | Type | Required | Description | | --- | --- | --- | --- | | `data` | [RuleExecution](https://developers.processcart.com/reference/schemas#ruleexecution)[] | yes | | | `nextCursor` | string \\| null | yes | | | `hasMore` | boolean | yes | | ### RuleFilter | Property | Type | Required | Description | | --- | --- | --- | --- | | `taskKey` | [Key](https://developers.processcart.com/reference/schemas#key) | no | | | `fieldKey` | [FieldKey](https://developers.processcart.com/reference/schemas#fieldkey) | no | | ### Run | Property | Type | Required | Description | | --- | --- | --- | --- | | `id` | [Uuid](https://developers.processcart.com/reference/schemas#uuid) | yes | | | `name` | string | yes | | | `status` | [RunStatus](https://developers.processcart.com/reference/schemas#runstatus) | yes | | | `priority` | [RunPriority](https://developers.processcart.com/reference/schemas#runpriority) | yes | | | `labels` | string[] | yes | | | `labelDetails` | [LabelDetail](https://developers.processcart.com/reference/schemas#labeldetail)[] | yes | | | `dueAt` | [Instant](https://developers.processcart.com/reference/schemas#instant) \\| null | yes | | | `keyDate` | [CalendarDate](https://developers.processcart.com/reference/schemas#calendardate) \\| null | yes | | | `revision` | integer | yes | | | `updatedAt` | [Instant](https://developers.processcart.com/reference/schemas#instant) | yes | | | `startedAt` | [Instant](https://developers.processcart.com/reference/schemas#instant) | yes | | | `startedBy` | [RunStartedSource](https://developers.processcart.com/reference/schemas#runstartedsource) | yes | | | `startedByUserId` | [Uuid](https://developers.processcart.com/reference/schemas#uuid) \\| null | yes | | | `schedule` | object \\| null | yes | | | `scheduledFor` | [Instant](https://developers.processcart.com/reference/schemas#instant) \\| null | yes | | | `completedAt` | [Instant](https://developers.processcart.com/reference/schemas#instant) \\| null | yes | | | `deletedAt` | [Instant](https://developers.processcart.com/reference/schemas#instant) \\| null | yes | | | `workflow` | object | yes | | | `versionId` | [Uuid](https://developers.processcart.com/reference/schemas#uuid) | yes | | | `url` | string (uri) | yes | | | `tasks` | [RunTask](https://developers.processcart.com/reference/schemas#runtask)[] | yes | | | `fields` | [RunField](https://developers.processcart.com/reference/schemas#runfield)[] | yes | | ### RunField | Property | Type | Required | Description | | --- | --- | --- | --- | | `key` | [FieldKey](https://developers.processcart.com/reference/schemas#fieldkey) | yes | | | `label` | string | yes | | | `type` | [FieldType](https://developers.processcart.com/reference/schemas#fieldtype) | yes | | | `format` | [FormatType](https://developers.processcart.com/reference/schemas#formattype) \\| null | yes | | | `taskKey` | [Key](https://developers.processcart.com/reference/schemas#key) | yes | | | `value` | [CanonicalValue](https://developers.processcart.com/reference/schemas#canonicalvalue) \\| null | yes | | | `formatted` | string | yes | | | `visible` | boolean | yes | | | `choices` | string[] | no | | | `items` | object[] | no | | | `validationError` | string | no | | ### RunList | Property | Type | Required | Description | | --- | --- | --- | --- | | `data` | [Run](https://developers.processcart.com/reference/schemas#run)[] | yes | | | `nextCursor` | string \\| null | yes | | | `hasMore` | boolean | yes | | ### RunLite | Property | Type | Required | Description | | --- | --- | --- | --- | | `id` | [Uuid](https://developers.processcart.com/reference/schemas#uuid) | yes | | | `name` | string | yes | | | `revision` | integer | yes | | | `workflow` | object | yes | | | `deletedAt` | [Instant](https://developers.processcart.com/reference/schemas#instant) \\| null | yes | | ### RunPatch | Property | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | no | | | `priority` | [RunPriority](https://developers.processcart.com/reference/schemas#runpriority) | no | | | `labels` | string[] | no | | | `dueAt` | [Instant](https://developers.processcart.com/reference/schemas#instant) \\| [CalendarDate](https://developers.processcart.com/reference/schemas#calendardate) \\| null | no | | | `status` | [RunStatus](https://developers.processcart.com/reference/schemas#runstatus) | no | | ### RunPriority Open enum: new values may be added without a version bump — treat an unknown value as such. Type: `"none"` \| `"low"` \| `"medium"` \| `"high"` \| `"urgent"` ### RunsQuery | Property | Type | Required | Description | | --- | --- | --- | --- | | `workflowId` | string (uuid) \\| null | yes | | | `status` | `"active"` \\| `"completed"` \\| `"on_hold"` \\| `"archived"`[] | yes | | | `priority` | `"none"` \\| `"low"` \\| `"medium"` \\| `"high"` \\| `"urgent"`[] | yes | | | `labels` | string[] | yes | | | `assignee` | object \\| null | yes | | | `due` | `"overdue"` \\| `"today"` \\| `"week"` \\| `"none"` \\| null | yes | | | `q` | string | yes | | | `qMode` | `"prefix"` \\| `"contains"` | no | | | `sort` | object | yes | | | `columns` | `"workflow"` \\| `"status"` \\| `"priority"` \\| `"progress"` \\| `"key_date"` \\| `"due"` \\| `"assignees"` \\| `"started"` \\| …[] | yes | | ### RunStart | Property | Type | Required | Description | | --- | --- | --- | --- | | `workflowId` | [Uuid](https://developers.processcart.com/reference/schemas#uuid) | yes | | | `name` | string | yes | | | `fields` | [FieldsWrite](https://developers.processcart.com/reference/schemas#fieldswrite) | no | | | `priority` | [RunPriority](https://developers.processcart.com/reference/schemas#runpriority) | no | | | `labels` | string[] | no | | | `dueAt` | [Instant](https://developers.processcart.com/reference/schemas#instant) \\| [CalendarDate](https://developers.processcart.com/reference/schemas#calendardate) \\| null | no | | ### RunStartedSource Type: `"manual"` \| `"schedule"` \| `"api"` \| `"kickoff"` ### RunStatus Open enum: new values may be added without a version bump — treat an unknown value as such. Type: `"active"` \| `"completed"` \| `"on_hold"` \| `"archived"` ### RunTask | Property | Type | Required | Description | | --- | --- | --- | --- | | `key` | [Key](https://developers.processcart.com/reference/schemas#key) | yes | | | `title` | string | yes | | | `kind` | [TaskKind](https://developers.processcart.com/reference/schemas#taskkind) | yes | | | `section` | string | yes | | | `state` | [RunTaskState](https://developers.processcart.com/reference/schemas#runtaskstate) | yes | | | `visible` | boolean | yes | | | `locked` | boolean | yes | | | `dueAt` | [Instant](https://developers.processcart.com/reference/schemas#instant) \\| null | yes | | | `assignees` | [ResolvedAssignee](https://developers.processcart.com/reference/schemas#resolvedassignee)[] | yes | | | `completedAt` | [Instant](https://developers.processcart.com/reference/schemas#instant) \\| null | yes | | | `completedByUserId` | [Uuid](https://developers.processcart.com/reference/schemas#uuid) \\| null | yes | | ### RunTaskList | Property | Type | Required | Description | | --- | --- | --- | --- | | `data` | [RunTask](https://developers.processcart.com/reference/schemas#runtask)[] | yes | | | `nextCursor` | string \\| null | yes | | | `hasMore` | boolean | yes | | ### RunTaskState Open enum: new values may be added without a version bump — treat an unknown value as such. Type: `"todo"` \| `"done"` \| `"rejected_redo"` ### Schedule | Property | Type | Required | Description | | --- | --- | --- | --- | | `id` | [Uuid](https://developers.processcart.com/reference/schemas#uuid) | yes | | | `workflowId` | [Uuid](https://developers.processcart.com/reference/schemas#uuid) | yes | | | `workflow` | object | yes | | | `name` | string | yes | | | `description` | string | yes | | | `recurrence` | object | yes | | | `timezone` | string | yes | | | `namePattern` | string | yes | | | `presetValues` | object (key → [CanonicalValue](https://developers.processcart.com/reference/schemas#canonicalvalue)) | yes | | | `assignmentOverrides` | object (key → object[]) | yes | | | `enabled` | boolean | yes | | | `disabledReason` | `"manual"` \\| `"ended"` \\| `"unpublished"` \\| `"failed"` \\| `"workflow_trashed"` \\| `"workflow_archived"` \\| null | yes | | | `lastError` | string \\| null | yes | | | `consecutiveFailures` | integer | yes | | | `nextRunAt` | [Instant](https://developers.processcart.com/reference/schemas#instant) \\| null | yes | | | `nextOccurrences` | [Instant](https://developers.processcart.com/reference/schemas#instant)[] | yes | | | `lastRunAt` | [Instant](https://developers.processcart.com/reference/schemas#instant) \\| null | yes | | | `latestRun` | object \\| null | yes | | | `presetMismatches` | object[] | yes | | | `createdBy` | [Uuid](https://developers.processcart.com/reference/schemas#uuid) \\| null | yes | | | `createdAt` | [Instant](https://developers.processcart.com/reference/schemas#instant) | yes | | | `updatedAt` | [Instant](https://developers.processcart.com/reference/schemas#instant) | yes | | ### ScheduleConfig | Property | Type | Required | Description | | --- | --- | --- | --- | | `workflowId` | [Uuid](https://developers.processcart.com/reference/schemas#uuid) | yes | | | `name` | string | yes | | | `recurrence` | [Recurrence](https://developers.processcart.com/reference/schemas#recurrence) | yes | | | `timezone` | string | no | | | `namePattern` | string | no | | | `presetValues` | object (key → [CanonicalValue](https://developers.processcart.com/reference/schemas#canonicalvalue)) | no | | | `assignmentOverrides` | object (key → [AutomationAssignee](https://developers.processcart.com/reference/schemas#automationassignee)[]) | no | | ### ScheduleList | Property | Type | Required | Description | | --- | --- | --- | --- | | `data` | [Schedule](https://developers.processcart.com/reference/schemas#schedule)[] | yes | | | `nextCursor` | string \\| null | yes | | | `hasMore` | boolean | yes | | ### ScheduleOccurrences | Property | Type | Required | Description | | --- | --- | --- | --- | | `occurrences` | [Instant](https://developers.processcart.com/reference/schemas#instant)[] | yes | | ### SchedulePatch | Property | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | no | | | `recurrence` | [Recurrence](https://developers.processcart.com/reference/schemas#recurrence) | no | | | `timezone` | string | no | | | `namePattern` | string | no | | | `presetValues` | object (key → [CanonicalValue](https://developers.processcart.com/reference/schemas#canonicalvalue)) | no | | | `assignmentOverrides` | object (key → [AutomationAssignee](https://developers.processcart.com/reference/schemas#automationassignee)[]) | no | | ### TaskDecision | Property | Type | Required | Description | | --- | --- | --- | --- | | `comment` | string | no | | ### TaskKind Open enum: new values may be added without a version bump — treat an unknown value as such. Type: `"standard"` \| `"approval"` \| `"email"` ### UpdateViewBody | Property | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | no | | | `query` | object | no | Changes to the view's board query. Send only the members you change; the rest keep the view's stored values (`null` clears `workflowId`, `assignee` or `due`). `sort.key` without `dir` takes that key's natural direction. An unknown member is a 422. | | `visibility` | `"org"` \\| `"groups"` | no | | | `groupIds` | [Uuid](https://developers.processcart.com/reference/schemas#uuid)[] | no | | ### UpdateWorkflowBody | Property | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | no | | | `description` | string \\| null | no | | | `folderId` | [Uuid](https://developers.processcart.com/reference/schemas#uuid) \\| null | no | | | `keyDateFieldKey` | [FieldKey](https://developers.processcart.com/reference/schemas#fieldkey) \\| null | no | | | `status` | `"active"` \\| `"archived"` | no | | ### UploadTicket | Property | Type | Required | Description | | --- | --- | --- | --- | | `attachmentId` | string (uuid) | yes | The id the attachment will have once finalized. | | `upload` | object | yes | | | `finalize` | object | yes | | ### User | Property | Type | Required | Description | | --- | --- | --- | --- | | `id` | [Uuid](https://developers.processcart.com/reference/schemas#uuid) | yes | | | `name` | string \\| null | yes | | | `role` | `"member"` \\| `"builder"` \\| `"admin"` | yes | | | `avatarUrl` | string \\| null | yes | | | `joinedAt` | [Instant](https://developers.processcart.com/reference/schemas#instant) | yes | | ### Uuid Type: string (uuid) ### View | Property | Type | Required | Description | | --- | --- | --- | --- | | `id` | [Uuid](https://developers.processcart.com/reference/schemas#uuid) | yes | | | `name` | string | yes | | | `query` | [RunsQuery](https://developers.processcart.com/reference/schemas#runsquery) | yes | | | `visibility` | `"org"` \\| `"groups"` | yes | | | `groupIds` | [Uuid](https://developers.processcart.com/reference/schemas#uuid)[] | yes | | | `owner` | object \\| null | yes | | | `createdAt` | [Instant](https://developers.processcart.com/reference/schemas#instant) | yes | | | `updatedAt` | [Instant](https://developers.processcart.com/reference/schemas#instant) | yes | | ### Visibility Type: `"visible"` \| `"hidden"` ### Webhook | Property | Type | Required | Description | | --- | --- | --- | --- | | `id` | [Uuid](https://developers.processcart.com/reference/schemas#uuid) | yes | | | `url` | string | yes | | | `description` | string | yes | | | `events` | string[] | yes | | | `workflowIds` | [Uuid](https://developers.processcart.com/reference/schemas#uuid)[] \\| null | yes | | | `filters` | object | yes | | | `enabled` | boolean | yes | | | `disabledReason` | `"failures"` \\| `"gone"` \\| `"manual"` \\| null | yes | | | `disabledAt` | [Instant](https://developers.processcart.com/reference/schemas#instant) \\| null | yes | | | `consecutiveFailures` | integer | yes | | | `lastDeliveryAt` | [Instant](https://developers.processcart.com/reference/schemas#instant) \\| null | yes | | | `createdAt` | [Instant](https://developers.processcart.com/reference/schemas#instant) | yes | | | `createdBy` | [Uuid](https://developers.processcart.com/reference/schemas#uuid) \\| null | yes | | | `updatedAt` | [Instant](https://developers.processcart.com/reference/schemas#instant) | yes | | ### WebhookConfig | Property | Type | Required | Description | | --- | --- | --- | --- | | `url` | string | yes | | | `description` | string | no | | | `events` | [EventType](https://developers.processcart.com/reference/schemas#eventtype) \\| `"*"`[] | yes | | | `workflowIds` | [Uuid](https://developers.processcart.com/reference/schemas#uuid)[] \\| null | no | | | `filters` | object | no | | ### WebhookDelivery | Property | Type | Required | Description | | --- | --- | --- | --- | | `id` | [Uuid](https://developers.processcart.com/reference/schemas#uuid) | yes | | | `webhookId` | [Uuid](https://developers.processcart.com/reference/schemas#uuid) | yes | | | `eventId` | string | yes | | | `eventType` | string | yes | | | `status` | `"pending"` \\| `"delivered"` \\| `"failed"` \\| `"exhausted"` | yes | | | `attemptCount` | integer | yes | | | `lastStatusCode` | integer \\| null | yes | | | `lastError` | string \\| null | yes | | | `lastAttemptAt` | [Instant](https://developers.processcart.com/reference/schemas#instant) \\| null | yes | | | `nextAttemptAt` | [Instant](https://developers.processcart.com/reference/schemas#instant) \\| null | yes | | | `deliveredAt` | [Instant](https://developers.processcart.com/reference/schemas#instant) \\| null | yes | | | `createdAt` | [Instant](https://developers.processcart.com/reference/schemas#instant) | yes | | ### WebhookDeliveryList | Property | Type | Required | Description | | --- | --- | --- | --- | | `data` | [WebhookDelivery](https://developers.processcart.com/reference/schemas#webhookdelivery)[] | yes | | | `nextCursor` | string \\| null | yes | | | `hasMore` | boolean | yes | | ### WebhookDeliveryWithPayload | Property | Type | Required | Description | | --- | --- | --- | --- | | `id` | [Uuid](https://developers.processcart.com/reference/schemas#uuid) | yes | | | `webhookId` | [Uuid](https://developers.processcart.com/reference/schemas#uuid) | yes | | | `eventId` | string | yes | | | `eventType` | string | yes | | | `status` | `"pending"` \\| `"delivered"` \\| `"failed"` \\| `"exhausted"` | yes | | | `attemptCount` | integer | yes | | | `lastStatusCode` | integer \\| null | yes | | | `lastError` | string \\| null | yes | | | `lastAttemptAt` | [Instant](https://developers.processcart.com/reference/schemas#instant) \\| null | yes | | | `nextAttemptAt` | [Instant](https://developers.processcart.com/reference/schemas#instant) \\| null | yes | | | `deliveredAt` | [Instant](https://developers.processcart.com/reference/schemas#instant) \\| null | yes | | | `createdAt` | [Instant](https://developers.processcart.com/reference/schemas#instant) | yes | | | `payload` | [EventEnvelope](https://developers.processcart.com/reference/schemas#eventenvelope) | yes | | ### WebhookList | Property | Type | Required | Description | | --- | --- | --- | --- | | `data` | [Webhook](https://developers.processcart.com/reference/schemas#webhook)[] | yes | | | `nextCursor` | string \\| null | yes | | | `hasMore` | boolean | yes | | ### WebhookPatch | Property | Type | Required | Description | | --- | --- | --- | --- | | `url` | string | no | | | `description` | string | no | | | `events` | [EventType](https://developers.processcart.com/reference/schemas#eventtype) \\| `"*"`[] | no | | | `workflowIds` | [Uuid](https://developers.processcart.com/reference/schemas#uuid)[] \\| null | no | | | `filters` | object | no | | ### WebhookQueuedDelivery | Property | Type | Required | Description | | --- | --- | --- | --- | | `delivery` | object | yes | | ### WebhookSecret | Property | Type | Required | Description | | --- | --- | --- | --- | | `secret` | string | yes | | | `previousExpiresAt` | [Instant](https://developers.processcart.com/reference/schemas#instant) | yes | | ### WebhookWithSecret | Property | Type | Required | Description | | --- | --- | --- | --- | | `id` | [Uuid](https://developers.processcart.com/reference/schemas#uuid) | yes | | | `url` | string | yes | | | `description` | string | yes | | | `events` | string[] | yes | | | `workflowIds` | [Uuid](https://developers.processcart.com/reference/schemas#uuid)[] \\| null | yes | | | `filters` | object | yes | | | `enabled` | boolean | yes | | | `disabledReason` | `"failures"` \\| `"gone"` \\| `"manual"` \\| null | yes | | | `disabledAt` | [Instant](https://developers.processcart.com/reference/schemas#instant) \\| null | yes | | | `consecutiveFailures` | integer | yes | | | `lastDeliveryAt` | [Instant](https://developers.processcart.com/reference/schemas#instant) \\| null | yes | | | `createdAt` | [Instant](https://developers.processcart.com/reference/schemas#instant) | yes | | | `createdBy` | [Uuid](https://developers.processcart.com/reference/schemas#uuid) \\| null | yes | | | `updatedAt` | [Instant](https://developers.processcart.com/reference/schemas#instant) | yes | | | `secret` | string | yes | | ### WorkflowDetail | Property | Type | Required | Description | | --- | --- | --- | --- | | `id` | [Uuid](https://developers.processcart.com/reference/schemas#uuid) | yes | | | `name` | string | yes | | | `description` | string \\| null | yes | | | `status` | `"active"` \\| `"archived"` | yes | | | `icon` | string \\| null | yes | | | `color` | string \\| null | yes | | | `folder` | object \\| null | yes | | | `keyDateFieldKey` | string \\| null | yes | | | `kickoffFormEnabled` | boolean | yes | | | `publishedVersion` | object \\| null | yes | | | `taskCount` | integer | yes | | | `createdAt` | [Instant](https://developers.processcart.com/reference/schemas#instant) | yes | | | `updatedAt` | [Instant](https://developers.processcart.com/reference/schemas#instant) | yes | | | `sections` | [WorkflowSection](https://developers.processcart.com/reference/schemas#workflowsection)[] | yes | | | `rules` | [RuleDef](https://developers.processcart.com/reference/schemas#ruledef)[] | yes | | ### WorkflowField | Property | Type | Required | Description | | --- | --- | --- | --- | | `key` | [FieldKey](https://developers.processcart.com/reference/schemas#fieldkey) | yes | | | `type` | [FieldType](https://developers.processcart.com/reference/schemas#fieldtype) | yes | | | `label` | string | yes | | | `help` | string \\| null | yes | | | `required` | boolean | yes | | | `defaultVisibility` | [Visibility](https://developers.processcart.com/reference/schemas#visibility) | yes | | | `options` | [FieldOptions](https://developers.processcart.com/reference/schemas#fieldoptions) \\| null | yes | | | `formatConfig` | [FormatConfig](https://developers.processcart.com/reference/schemas#formatconfig) \\| null | yes | | | `validation` | [FieldValidation](https://developers.processcart.com/reference/schemas#fieldvalidation) \\| null | yes | | | `defaultValue` | object \\| null | yes | | | `onFocusPopulate` | [OnFocusPopulate](https://developers.processcart.com/reference/schemas#onfocuspopulate) \\| null | yes | | | `formula` | [Formula](https://developers.processcart.com/reference/schemas#formula) \\| null | yes | | | `inKickoff` | boolean | yes | | | `kickoffPosition` | integer \\| null | yes | | | `readOnly` | boolean | yes | | ### WorkflowSection | Property | Type | Required | Description | | --- | --- | --- | --- | | `key` | [Key](https://developers.processcart.com/reference/schemas#key) | yes | | | `title` | string | yes | | | `defaultVisibility` | [Visibility](https://developers.processcart.com/reference/schemas#visibility) | yes | | | `tasks` | [WorkflowTask](https://developers.processcart.com/reference/schemas#workflowtask)[] | yes | | ### WorkflowSummary | Property | Type | Required | Description | | --- | --- | --- | --- | | `id` | [Uuid](https://developers.processcart.com/reference/schemas#uuid) | yes | | | `name` | string | yes | | | `description` | string \\| null | yes | | | `status` | `"active"` \\| `"archived"` | yes | | | `icon` | string \\| null | yes | | | `color` | string \\| null | yes | | | `folder` | object \\| null | yes | | | `keyDateFieldKey` | string \\| null | yes | | | `kickoffFormEnabled` | boolean | yes | | | `publishedVersion` | object \\| null | yes | | | `taskCount` | integer | yes | | | `createdAt` | [Instant](https://developers.processcart.com/reference/schemas#instant) | yes | | | `updatedAt` | [Instant](https://developers.processcart.com/reference/schemas#instant) | yes | | ### WorkflowTask | Property | Type | Required | Description | | --- | --- | --- | --- | | `key` | [Key](https://developers.processcart.com/reference/schemas#key) | yes | | | `title` | string | yes | | | `kind` | [TaskKind](https://developers.processcart.com/reference/schemas#taskkind) | yes | | | `isStop` | boolean | yes | | | `defaultVisibility` | [Visibility](https://developers.processcart.com/reference/schemas#visibility) | yes | | | `dueRule` | [DueRule](https://developers.processcart.com/reference/schemas#duerule) \\| null | yes | | | `assignment` | [Assignment](https://developers.processcart.com/reference/schemas#assignment) \\| null | yes | | | `guestConfig` | [GuestConfig](https://developers.processcart.com/reference/schemas#guestconfig) \\| null | yes | | | `fields` | [WorkflowField](https://developers.processcart.com/reference/schemas#workflowfield)[] | yes | | ### WorkflowTemplate | Property | Type | Required | Description | | --- | --- | --- | --- | | `formatVersion` | integer | yes | | | `engineVersion` | integer | yes | | | `exportedAt` | string (date-time) \\| null | no | | | `source` | string \\| null | no | | | `workflow` | object | yes | | --- # OpenAPI > The OpenAPI 3.1 document — JSON and YAML, generated from the same schemas the API validates with — and how to use it for code generation. Source: https://developers.processcart.com/guides/openapi The whole API is described by one OpenAPI 3.1 document, generated from the request and response schemas the server validates with, so it cannot drift from the code: - **JSON:** [`/openapi.json`](https://developers.processcart.com/openapi.json) - **YAML:** [`/openapi.yaml`](https://developers.processcart.com/openapi.yaml) Both are served live; the same document is browsable in the [interactive reference](https://developers.processcart.com/reference). ## What is in it - Every operation with its `operationId`, parameters, request body, every response status it can answer and the exact error `code` list per status — the same list the contract tests hold the routes to. - Every component schema (`Run`, `RunField`, `Problem`, …), with growable enums marked `x-processcart-open-enum: true`. - The security schemes (`bearerAuth`, `apiKeyHeader`), the request headers `Idempotency-Key` and `Expected-Revision`, and the response headers: `X-Request-Id`, `RateLimit-*`, `Retry-After`, `Idempotent-Replayed`, `Location`, `Deprecation`, `Sunset`. - Worked request and response examples captured from real responses by the contract tests, redacted. - The **`webhooks` object**: one entry per event type with the Standard Webhooks headers (`webhook-id`, `webhook-timestamp`, `webhook-signature`, `x-processcart-event`), the envelope and the per-event `data` schema, and the retry schedule. - Vendor extensions: `x-processcart-scope` (the scope an operation needs), `x-processcart-allowlist`, `x-processcart-paginated`, `x-processcart-idempotency-key`, and on `info` the rate limit and the webhook retry minutes. `servers` is `https://app.processcart.com/api` and every path starts with `/v1`. ## Generating a client The document validates against the OpenAPI 3.1 meta-schema and works with the usual generators — `openapi-typescript`, `openapi-generator`, `kiota`, Postman and Insomnia imports. Keep three conventions in mind: - **Problem responses** are `application/problem+json`; the `Problem` schema is shared by every error status, narrowed per operation to the codes it can answer. - **Field values** are typed per field at runtime (`GET /v1/workflows/{id}/schema`), not in the static document: `RunField.value` is the union of every canonical shape. Generate the per-workflow schema when you need exact types. - **Open enums**: generate them as strings (or add an "unknown" arm) so a new status or event type does not break deserialization. The official TypeScript SDK, [`@processcart/sdk`](https://developers.processcart.com/guides/sdk), is typed from this document (its types and operations table are generated from it; the methods are written against them): one typed method per operation, plus cursor pagination, idempotency keys, retries and webhook signature verification. --- # Changelog > Every change to the API, dated — what shipped, what it covers, what is coming. Source: https://developers.processcart.com/changelog Changes are listed newest first. Additive changes ship inside `/v1`; a breaking change would arrive as `/v2` with the overlap described in [Versioning & deprecation](https://developers.processcart.com/guides/versioning). **Entries dated before the public launch describe pre-release builds:** until the launch date is announced here, a breaking change may still ship inside `/v1` with a changelog entry and no `/v2` — the 2026-10-12 concurrency header is the one such change — and the versioning policy applies in full from launch. ## 2026-10-13 — MCP server `@processcart/mcp`, a Model Context Protocol server for Claude Desktop, Claude Code, Cursor and other MCP clients, is on npm: `npx -y @processcart/mcp` with your API key in `PROCESSCART_API_KEY` ([MCP server](https://developers.processcart.com/guides/mcp)). No API change. - **Fourteen task-shaped tools:** `whoami`, `list_workflows`, `get_workflow_schema`, `start_run`, `find_runs`, `get_run`, `set_fields`, `complete_task`, `reopen_task`, `decide_approval`, `add_comment`, `get_activity`, `update_run` and `list_people`. Each answers Markdown for the assistant plus JSON as `structuredContent`: a run answer is the whole run with its `revision` (the API's Run resource), `find_runs` carries one summary per run (no tasks or fields), and `list_people` and `whoami` reshape theirs too ([MCP server](https://developers.processcart.com/guides/mcp#tools)). - **Scoped to the key:** the server reads `GET /v1/me` at startup and offers only the tools the key's scopes allow; a read-only key, or `--read-only`, gets the read tools only. - **Safe by construction:** no admin tools and no tool that deletes a run, workflow or comment; `set_fields`, `update_run`, `reopen_task` and `decide_approval` (which can overwrite, clear or send work back) carry the MCP `destructiveHint`; the server tells the assistant that run content is data, never instructions; a plain-`http` base URL is accepted only for a server on the same machine; every write carries an `Idempotency-Key`; the write tools take `expectedRevision` (sent as `Expected-Revision`); errors come back as tool results with every field problem, `currentRevision` on a `409` and the wait on a `429`. - **Registry:** the package ships its MCP Registry metadata as `com.processcart/mcp`; the listing is pending. ## 2026-10-12 — Configuration events, Expected-Revision, partial view queries Additive inside `/v1`, except for the concurrency header (the first item), which replaces `If-Match` and `ETag` — a breaking change made before the public launch, so it ships inside `/v1` without a `/v2` (see the note above). - **`Expected-Revision` replaces `If-Match`; the API sends no `ETag`.** Every Run body carries `revision`, the one version field (a new run starts at `1`; a finalized upload answers `run.revision`). The writes that change an existing run — `PATCH /v1/runs/{id}`, `PATCH /v1/runs/{id}/fields`, the four task actions, trash and restore, finalizing and removing a file — take `Expected-Revision: `: a bare integer (`7`) or the same quoted (`"7"`); `*` or no header means no precondition; anything else (a list, a weak `W/"7"`, text, a negative or fractional number) is a 422 `validation_failed` with `errors[].field: "Expected-Revision"`. A stale value is refused with 409 `revision_mismatch`, whose problem body names the run's current revision in the new extension member `currentRevision`. No response carries an `ETag` header — reads and `201`s included. A request that carries `If-Match` is refused with 422 `validation_failed` (`errors[].field: "If-Match"`) on every authenticated operation; some hosting layers answer it with `412 Precondition Failed` first — both mean "send `Expected-Revision` instead". `Expected-Revision` takes `If-Match`'s place in the `Idempotency-Key` request fingerprint. Why: CDN layers evaluate `If-Match` against the response and turn a successful write into a 412 ([Concurrency](https://developers.processcart.com/guides/concurrency)). This supersedes the `ETag` / `If-Match` wording of the earlier entries below. - **Nine configuration events:** `webhook.created` / `.updated` / `.deleted`, `schedule.created` / `.updated` / `.deleted`, `rule.created` / `.updated` / `.deleted`. `data.webhook`, `data.schedule` or `data.rule` is the resource as `GET` returns it (never a secret; a `*.deleted` event carries the last state), and every `*.updated` names what happened in `data.change` — `settings`, `enabled` / `disabled` or `paused` / `resumed`, `secret_rotated` (webhooks), or `auto_disabled` when Process Cart switched it off (a webhook after a `410` or repeated failures, a rule after repeated failures, a schedule that ended, could not start its runs, or whose workflow was moved to Trash, archived or unpublished). They are included in `*` and never trigger an automation rule ([Configuration events](https://developers.processcart.com/webhooks#configuration-events)). - **`POST /v1/views` takes a partial `query`:** omitted members take the board's defaults, `sort.key` without `dir` takes the key's natural direction, and the 201 carries the complete query. On `PATCH /v1/views/{id}`, a `query` changes only the members it sends (`null` clears `workflowId`, `assignee` or `due`). An unknown member of `query` or `query.sort` is now a 422 `unrecognized_keys` instead of being dropped ([Views & exports](https://developers.processcart.com/guides/views-exports)). - **`GET /v1/me` lists the workspace's status labels** in `org.labels` (`[{ id, name, color }]`, empty when there are none) — the ids `labels` and the label filters take ([Authentication](https://developers.processcart.com/guides/authentication)). - **`finalize.url`** in an upload ticket is now an absolute URL, like every `Location`. On a host without a configured app URL, `Run.url`, a comment's `url` and a webhook's `data.run.url` now follow the request's origin rather than a local address. - **Idempotency:** answers that read the run or its setup at that moment are no longer stored for replay — 422 `required_fields_unsatisfied` and `field_not_writable`, 403 `approver_only`, `workflow_not_runnable` and `not_allowed_for_key` — so a retry with the same `Idempotency-Key` re-executes once the state is fixed ([Idempotency](https://developers.processcart.com/guides/idempotency)). - **Field errors:** text that a number, currency or percent field cannot parse is `errors[].code` `unparseable` (it was `not_finite`); `not_finite` now means only a non-finite JSON number. - **Read-only keys:** the 403 for a write now names the methods such a key may use (`GET`, `HEAD`). - **Docs:** the RateLimit headers are described as they are sent — on every response to an authenticated request; a 401, a 405 or an unknown-path 404 carries none ([Rate limits](https://developers.processcart.com/guides/rate-limits)). - **TypeScript SDK:** the `ifMatch` option is now `expectedRevision` (a number, sent as `Expected-Revision`); `$meta.etag` and the `etagOf` helper are gone; `$meta.revision` is set on every response that returns a run (from the body), and `ProcessCartError.currentRevision` comes from the 409's body — so a write chains from the last one: `expectedRevision: updated.revision` ([TypeScript SDK](https://developers.processcart.com/guides/sdk)). ## 2026-10-12 — Schedules and automation rules Two new resources, both additive inside `/v1` ([Schedules](https://developers.processcart.com/guides/schedules) · [Automation rules](https://developers.processcart.com/guides/automation-rules)). - **Schedules** (scope `schedules:manage`): `GET` / `POST /v1/schedules`, `GET` / `PATCH` / `DELETE /v1/schedules/{id}`, `POST /v1/schedules/{id}/pause`, `POST /v1/schedules/{id}/resume`, `POST /v1/schedules/{id}/run` (start a run now) and `GET /v1/schedules/{id}/preview` (the next occurrences). - **Automation rules** (scope `rules:manage`): `GET` / `POST /v1/automation-rules`, `GET` / `PATCH` / `DELETE /v1/automation-rules/{id}`, `POST /v1/automation-rules/{id}/enable`, `POST /v1/automation-rules/{id}/disable` and `GET /v1/automation-rules/{id}/executions`. - **The Run resource** gains `schedule` (`{ id, name }` of the schedule that created the run, or `null`) and `scheduledFor` (the occurrence it was created for; `null` for every other run, including a schedule's run now). Runs a schedule creates have `startedBy: "schedule"`. - **Actors:** activity entries and event payloads can name a schedule (`{ "kind": "schedule", "scheduleId", "name" }`) or an automation rule (`{ "kind": "rule", "ruleId", "name" }`). - **Errors:** the new code `schedule_ended` (409); the `errors[].code` `unknown_webhook`; `limit_reached` now also covers schedules and rules that are on per workspace; `unknown_key` on `enabled` in a schedule or rule body points at pause / resume and enable / disable, as on webhooks. - **OpenAPI:** the request body of `POST /v1/runs/{id}/tasks/{key}/approve` is now marked optional, as the endpoint always treated it (send none, or `{}`); generated clients make the argument optional. ## 2026-10-09 — TypeScript SDK The official TypeScript client covers every operation with a typed method, plus cursor pagination, automatic idempotency keys, safe retries, `If-Match` helpers and Standard Webhooks signature verification, with request and response types generated from the OpenAPI document. Install it from npm as `@processcart/sdk` ([TypeScript SDK](https://developers.processcart.com/guides/sdk)). ## 2026-10-08 — v1, first complete build (pre-release) The first public version of the Process Cart API. **Covered** - **Workflows:** list, read (summary + the published version's sections, tasks, fields and rules), per-workflow JSON Schema for starting runs and writing fields, the portable template (export), create from a template (import, published by default), update name / description / folder / key-date field / status. - **Runs:** list with filters and incremental sync, start (with field values, priority, labels and due date in one transaction), read with `ETag`, update the header, move to Trash and restore, the task list. - **Tasks:** complete, reopen, approve and reject under the engine's gates. - **Field values:** set 1–200 fields in one all-or-nothing transaction; canonical + formatted on read. - **Comments and activity:** list and post comments (text or rich document); the activity trail with cursor tailing. - **Files:** two-phase upload to File fields, list, download URLs, remove. - **Saved views:** list and read shared views, run a view's query, create, update and delete. - **Webhooks:** create (secret shown once), read, update, enable, disable, rotate the secret, test, delete; the delivery log and redelivery. - **Directory:** members (without email addresses) and groups. - **Exports:** CSV of a workflow's runs. - **Conventions:** API keys with scopes, read-only keys and allowlists; 600 requests per minute per key; RFC 9457 errors; `Idempotency-Key`; `ETag` / `If-Match`; `{ data, nextCursor, hasMore }` pagination; `updatedSince`; Standard Webhooks signatures with retries. - **Docs:** this site, the OpenAPI 3.1 document, `llms.txt` / `llms-full.txt` and a `.md` twin of every page. **Coming** - Recurrence schedules and automation rules — since shipped ([2026-10-12 — Schedules and automation rules](#2026-10-12-schedules-and-automation-rules)). - Editing and deleting comments through the API (available in the app today). --- # Status & uptime > The public status page shows live status, incident history and 90-day uptime for the web app, the API and scheduled jobs & webhooks. Source: https://developers.processcart.com/status The public status page is **[status.processcart.com](https://status.processcart.com)**. It shows live status, the incident history and 90-day uptime for each component: | Component | What it covers | How it is checked | | --- | --- | --- | | **Web app** | `app.processcart.com` — the member app, guest pages and kickoff forms | An HTTPS check of the app, continuously | | **API** | `/api/v1` | `GET /api/v1/health` — the unauthenticated probe that reports `ok` (200) or `degraded` (503) | | **Scheduled jobs & webhooks** | The background workers that deliver webhooks, send email and run the due-date scanner | A heartbeat the due-date scanner sends on every run | An incident is opened when a monitor fails and closed with a note when it is resolved; the history stays on the page. ## Reading an incident from your side - A failing `GET /v1/health` (503 `degraded`) means the database did not answer in time. Until it recovers, a request that needs it answers 503 `unavailable` with `Retry-After` when the connection is refused or dropped — or, when the database stalls rather than refusing, may take long enough to time out. Retry with backoff; a 503 is never stored for idempotent replay, so retrying with the same `Idempotency-Key` re-executes. - Webhook deliveries that fail during an incident on your side are retried on the published schedule ([Webhooks](https://developers.processcart.com/webhooks)); an incident on our side delays dispatch but loses no delivery — the outbox is durable. - Every response carries `X-Request-Id`; quote it when reporting a problem. --- # MCP server > Connect Claude, Cursor and other MCP clients to Process Cart with the @processcart/mcp server — find workflows, start runs, set fields, complete tasks and read activity with your API key. Source: https://developers.processcart.com/guides/mcp `@processcart/mcp` is a [Model Context Protocol](https://modelcontextprotocol.io) server for Process Cart. Add it to Claude Desktop, Claude Code, Cursor or any other MCP client, give it an API key, and the assistant can find a workflow, start a run, fill in its fields, complete its tasks, decide approvals, comment and read what happened — the day-to-day work of a run, in plain language ("start an Order fulfillment run for Order 1042 and set the customer email to ada@example.com"). It runs on your machine over stdio and calls the same `/api/v1` this site documents, with the same key, scopes and limits. It is built on the [TypeScript SDK](https://developers.processcart.com/guides/sdk). ## Install ```bash npx -y @processcart/mcp ``` You do not run that by hand: your MCP client starts the server with it, as configured below. It needs Node.js 22.12 or later, and an API key — an Admin creates one under **Settings → API** ([Authentication & API keys](https://developers.processcart.com/guides/authentication)). A key acts on its whole workspace, so give the assistant a key of its own, with only the scopes it needs, and try it on a [sandbox](https://developers.processcart.com/guides/sandbox) key first. ## Configure your client ### Claude Desktop Open **Settings → Developer → Edit Config** and add the server to `claude_desktop_config.json`, then restart Claude Desktop: ```json { "mcpServers": { "processcart": { "command": "npx", "args": ["-y", "@processcart/mcp"], "env": { "PROCESSCART_API_KEY": "pc_live_…" } } } } ``` On Windows, if Claude Desktop cannot start `npx`, use `"command": "cmd"` with `"args": ["/c", "npx", "-y", "@processcart/mcp"]`. ### Claude Code ```bash claude mcp add processcart -e PROCESSCART_API_KEY=pc_live_… -- npx -y @processcart/mcp ``` Add `--scope user` to make it available in every project. To share it with your team through the project's checked-in `.mcp.json`, leave the key out of the file: Claude Code expands `${VAR}` in `.mcp.json`, so register the variable's name, in single quotes so your shell does not expand it, and let each teammate export their own `PROCESSCART_API_KEY`: ```bash claude mcp add processcart --scope project -e 'PROCESSCART_API_KEY=${PROCESSCART_API_KEY}' -- npx -y @processcart/mcp ``` ### Cursor Add the server to `~/.cursor/mcp.json` (every project) or `.cursor/mcp.json` (one project): ```json { "mcpServers": { "processcart": { "command": "npx", "args": ["-y", "@processcart/mcp"], "env": { "PROCESSCART_API_KEY": "pc_live_…" } } } } ``` Any other client that runs a local stdio server takes the same command, arguments and environment. ## Environment variables and flags | Setting | What it does | | ----------------------- | -------------------------------------------------------------------------------------------------------------- | | `PROCESSCART_API_KEY` | Your API key, `pc_live_…` or `pc_test_…`. Required. | | `PROCESSCART_BASE_URL` | The API base URL. Default `https://app.processcart.com/api`; change it only to reach a proxy or a test server. | | `PROCESSCART_MCP_DEBUG` | Set to `1` to log each tool call — its name, outcome and duration — to stderr. Never the key or field values. | | `--api-key ` | The key as a flag. Prefer the environment variable: other processes on the machine can see a command line. | | `--base-url ` | The base URL as a flag. | | `--read-only` | Offer the read tools only, whatever the key allows. | | `--help`, `--version` | Print the usage or the version and exit. | The base URL must use `https`. Plain `http` is accepted only for a server on your own machine (`localhost`, `127.x.x.x` or `::1`), so the key never crosses a network in cleartext; any other `http` URL stops the server with a usage error. The server writes nothing to stdout except the MCP protocol itself; its diagnostics go to stderr, which your client keeps in its MCP log. They never carry the key: anything shaped like one is cut to its prefix (`pc_live_…`), including a key passed by mistake as a bare argument or in a mistyped flag. ## Tools Fourteen tools, each shaped like a task rather than an endpoint. Every answer comes back twice: as Markdown the assistant reads, with every key and id it needs for the next step, and as JSON (`structuredContent`). A run answer is always the whole run — every task, grouped by section with its key, and every field as "Label (`key`): value" — with its current `revision`, so the assistant never needs a second call to see the result of a write; its JSON is the API's Run resource. Long runs are trimmed in the Markdown ("… and 40 more fields"); the JSON keeps everything. Three tools reshape their JSON: `find_runs` carries one summary per run (id, name, status, priority, labels, due date, key date, timestamps, revision, workflow, link, task progress and the open tasks' keys and titles — no tasks or fields), `list_people` carries `{ users, groups, complete }`, and `whoami` carries the API's `/v1/me` answer plus a `server` member (its mode, base URL and tools). | Tool | What it does | Scope | | --------------------- | -------------------------------------------------------------------------------------------------------------------- | ------------------ | | `whoami` | The workspace, the key (name, scopes, read-only), the rate limit, the status labels and the tools on offer | any key | | `list_workflows` | The workflows the key can see, with their ids, published version and task count; `q` searches names and descriptions | `workflows:read` | | `get_workflow_schema` | A workflow's fields by key — type, task, allowed values, bounds — and which ones the start form requires | `workflows:read` | | `start_run` | Start a run of a published workflow, with field values, priority, labels and a due date | `runs:write` | | `find_runs` | Search runs by name, workflow, status, priority, labels, assignee or due window, one page at a time | `runs:read` | | `get_run` | One run in full: tasks, fields, revision and app link; required fields still blank are flagged (see below) | `runs:read` | | `set_fields` | Set one or more field values by key, all or nothing | `runs:write` | | `complete_task` | Complete a task by key; a refusal names every required field still blank | `runs:write` | | `reopen_task` | Put a completed task back to to-do | `runs:write` | | `decide_approval` | Approve or reject an approval task (a rejection needs a comment) | `approvals:decide` | | `add_comment` | Comment on a task; `@Name` mentions notify members | `runs:write` | | `get_activity` | A run's trail, newest first, with its latest comments | `runs:read` | | `update_run` | Change a run's name, status, priority, labels or due date | `runs:write` | | `list_people` | Members and groups with the ids `find_runs` filters by | `users:read` | A key with full access (`*`) gets all fourteen. `get_run` flags the required fields that are still blank only when the key also has `workflows:read` and the run is on the workflow's published version; otherwise it says so, and `complete_task` names any blank required field when it refuses. ## Scopes and read-only keys When it starts, the server calls `GET /v1/me` and offers only the tools the key can use: each read tool needs its scope, `decide_approval` needs `approvals:decide`, and every other write needs `runs:write`. A [read-only key](https://developers.processcart.com/guides/authentication) — or the `--read-only` flag — gets the read tools only, so the assistant cannot change anything. A key limited to some workflows or folders keeps that limit: the API applies the [allowlist](https://developers.processcart.com/guides/authentication#allowlists) on every call. If the server cannot read the key at startup (a typo, a revoked key, no network — it waits at most 10 seconds, once), it starts with the read tools only and says why on stderr; `whoami` then answers with the error. Fix the key and restart the server to get the full list. ## Safety - **No admin tools, and nothing deletes.** No tool deletes a run, workflow or comment; `set_fields` and `update_run` can overwrite or clear values; `reopen_task` and a rejection send work back. Those four tools carry the MCP `destructiveHint`, so a client that confirms destructive calls asks you first. Webhooks, schedules, automation rules, saved views, workflow edits and imports, files, CSV exports and Trash stay in the app and the [REST API](https://developers.processcart.com/reference) — an assistant reaches only the day-to-day work of runs. - **Content is data.** Run names, field values, task titles, comments and activity are written by people, guests included. The server tells the assistant to treat them as data, never as instructions, and to act only on your request; every name and value stays on the line it is rendered on, so none can pose as a heading or a new instruction. - **Cancellation reaches the API.** When your client cancels a call, the request in flight is aborted rather than left to finish in the background. - **Safe retries.** Every write carries an `Idempotency-Key` ([Idempotency](https://developers.processcart.com/guides/idempotency)), so a retried request is applied once. `start_run` and `add_comment` create something new on each call; the tool descriptions tell the assistant not to repeat them after a success. - **No lost updates.** Every run answer carries `revision`. The write tools take `expectedRevision`, sent as the `Expected-Revision` header ([Concurrency](https://developers.processcart.com/guides/concurrency)): if someone changed the run in between, the write is refused with `409 revision_mismatch`, and the answer names the current revision so the assistant reads the run again before retrying. - **Errors are answers.** A refusal comes back as a tool result with the error code, every field problem, the current revision on a conflict and the wait on a `429` ([Errors](https://developers.processcart.com/errors)) — never as a crash the assistant cannot read. - **Your key stays local.** The server runs on your machine and sends the key only to the API. It never logs the key or your field values. ## MCP Registry The package carries its MCP Registry metadata (`server.json`, under the name `com.processcart/mcp`); the registry listing is pending. Until it appears, install it from npm as shown above. ## Hosted endpoint A hosted MCP endpoint that signs in with OAuth instead of an API key — no local install — is planned. Watch the [changelog](https://developers.processcart.com/changelog).