calculated
A calculated field refuses every write with "is read only" and every filter with "cannot be used in a filter", yet it sorts and summarizes fine, and the formula is exposed as calculated_function.
One card per data_type, in all. Each was probed on a real
field of that type: how it reads, every value accepted on write, every value that clears it, the
complete operator list the API returns when sent a bogus one, and the traps.
A calculated field refuses every write with "is read only" and every filter with "cannot be used in a filter", yet it sorts and summarizes fine, and the formula is exposed as calculated_function.
A checkbox is two-state, never null - an untouched row already reads false, null is unwritable and unfilterable, and the only relations are is/is_not, so fill rate reads 100% on every checkbox.
Task.color holds the token pipeline_step rather than a colour: read step.Step.color in the same dotted call and keep a client default. A real value is decimal r,g,b, never hex.
A date is the string "YYYY-MM-DD" and nothing else: any timestamp 400s on write and as a filter value. Every negating operator (is_not, not_in, not_in_last) also matches rows that are null.
Stored and read as UTC YYYY-MM-DDTHH:MM:SSZ: a written offset is silently normalised, a zoneless string is taken as UTC, and a date-only filter value means midnight UTC, not the whole day.
A duration is a bare integer of minutes and the unit is on the site: GET /preferences gives hours_per_day and duration_units. A Float truncates toward zero at 200, so round before writing.
An entity link is a {type,id} hash under relationships, cleared only by null. Enforce valid_types yourself: it binds on a few fields, is ignored on most, and nothing in the schema marks which.
An entity_type field is a bare schema-name string in attributes, validated on write against 290 built-in type names but not against the site's enabled ones, and filtered only by is/is_not/in/not_in.
A float reads back as a JSON string rounded to 6 decimals and rejects Integer on both write and filter: send 1.0 or "1.0", never 1; 0.0 and null stay distinct, and 1e-9 silently becomes 0.0.
Only the upload dance sets an image: every value but null 400s, and clearing it also clears filmstrip_image. The value is a presigned URL re-signed per read, so store the row id, never the string.
jsonb filters, where serializable cannot: is, is_not, contains, not_contains, values always hashes. Note.meta stores what you send but is create-only, so nothing written there is ever editable.
A list is one bare string in attributes; a write outside valid_values 400s and is case-sensitive, while filters are case-insensitive and only is/is_not/in/not_in exist.
A bare list replaces the whole link set, but {"multi_entity_update_mode": "add"|"remove"|"set", "value": [...]} adds and removes in place; the field never reads null and null 400s.
A number is a signed 32-bit integer: floats 400, 2**31 is "integer out of range", and 0 is not null, yet is_not and not_in match null rows while greater_than and less_than do not.
A password field reads as a constant seven-asterisk mask on every row, including through a dotted path; it cannot be filtered, sort is accepted and ignored, and it must never be written.
A percent is a bare integer on a 0-100 scale (50% is 50, and 0.5 is rejected as Float), but nothing is clamped, so -1, 1000 and 2**31-1 all store at HTTP 200.
A pivot_column is a web-UI task rollup with no REST implementation - it reads null on every row, and write, filter, sort and _summarize each fail with a different error.
No operator works on a serializable field: every filter 400s as unfilterable. Task.splits answers a well-formed array of hashes with 200 while storing null, so REST cannot write it.
REST does not enforce hidden_values: a project-hidden status writes and reads back fine, so every client must subtract it itself. Only valid_values is enforced.
A summary field is a live rollup: refused on write even where editable=true, unfilterable, unsortable, and null on every custom one here, so re-run the query /schema exposes to select on it.
A text field has no empty string: writing "" stores null, so is "" and is None are one filter; matching is case-insensitive, whitespace is stripped, and a non-string 400s.
A timecode stores milliseconds as a signed 32-bit integer. No schema or preference names its frame rate, but a _summarize group_name renders HH:MM:SS:FF and the rate solves out of that.
The value is a presigned link re-minted on every read and expiring on X-Amz-Expires, so persist the Attachment id and re-read. No filter relation exists at all, and sort is a 200 no-op.
A uuid field is server-generated and rejects every write with "is read only", so it cannot hold your key; it filters on is/is_not/in/not_in only, and a malformed value 400s.