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.
Every field in a Run (and in every webhook payload) is one entry of fields[]:
{ "key": "amount", "label": "Amount", "type": "number", "format": "currency", "taskKey": "details",
"value": 1700.5, "formatted": "$1,700.50", "visible": true }valueis the canonical value — use it for math, comparisons and filters.formattedis the display text exactly as the app shows it (en-US) — use it in documents and messages. It is""when the field is blank.typeis the field type from the builder;formatis the field's effective format — the axis that decides the JSON shape ofvalue. Atextfield with a currency format reads and writes as a number, like anumberfield would.
Rules
- Blank is
null. A field with no value readsvalue: null, formatted: "". A checkbox is the exception: it never readsnull— a never-set checkbox readsfalse. - Numbers are JSON numbers. A currency
valueis a number in whole currency units (1700.5reads"$1,700.50"), never cents and never a string. A percentvalueis a fraction:0.125means 12.5 % and reads"12.5%". A plain number is the number. - Dates are
YYYY-MM-DDwith no time zone; times areHH: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'sformattedfollows 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 as1700.5;"Jun 25 2026"as"2026-06-25";"ADA@Example.com"as"ada@example.com".
Writing values
PATCH /v1/runs/{id}/fields takes { "<fieldKey>": 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 (
falseclears it). - An array of strings writes a multi-choice (every entry must be one of
choices), subtasks (every key one ofitems), or members field (every id a current member — see Directory); an unknown entry is a 422, never silently dropped. nullor""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). A write to one is a 422field_not_writablewhoseerrors[]names the reason (computed,fileordisplay_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'svalidationErrornames the problem, and completing the task refuses until it is fixed (422required_fields_unsatisfied). A dropdown value outsidechoicesand an unparseable string are refused outright. At start, a visible kickoff field that breaks its bounds or rules is refused instead (422,errors[].codeinvalid_value— see 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).
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). 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:/ → "https:/ |
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 / |
["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 carries it on its own page).