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.

View this page as Markdown

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 }
  • 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 { "<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 (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); 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). 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).
  • 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://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 carries it on its own page).