Filters
Internal beta — not released yet
The Automation API is an internal beta and is not released yet. Its endpoints and payloads are not final and may change or be withdrawn at any time without notice.
A filter decides whether an automation runs: a recursive and/or tree of conditions the triggering record must
match. An automation's filter is null when it has none (it always runs). The same tree shape is reused for a
conditional action's condition and a for_loop's break/continue conditions — see
actions.
The same tree is used everywhere conditions appear
This public tree (operator: "and" | "or", rows) is used for an automation's filter, a control-flow action's
condition / break_condition / continue_condition, and a condition embedded inside an action's config (e.g. a
collect_app_records / filter_record_collection match_condition) — all with the same lower-case operator + rows
shape.
The tree: groups and conditions
A group combines its rows with and (match all) or or (match any); rows are nested groups or leaf conditions.
The root group has no id; nested nodes carry one.
| Field | Type | On | Description |
|---|---|---|---|
id | string | nested nodes | Opaque node ID. Optional on input (server-minted when omitted); always present on read. |
operator | string | group | and or or. |
rows | array | group | Nested groups and/or conditions. |
subject | object | condition | A field reference — the value being tested. |
operator | string | condition | One of the operators below. |
value | object | condition | The tagged comparison value (below). Absent for value-less operators. |
code | array | condition | A raw code predicate — a code dynamic value. A script condition carries code instead of subject/operator/value. |
{
"operator": "and",
"rows": [
{
"subject": { "kind": "variable", "source": "field", "field_id": 512, "field_type": "single_category", "previous": false, "collection": false, "triggering": false },
"operator": "is_any_of",
"value": { "type": "ids", "ids": [9001] }
},
{
"subject": { "kind": "variable", "source": "field", "field_id": 513, "field_type": "number", "previous": false, "collection": false, "triggering": false },
"operator": "greater_or_equal_than",
"value": { "type": "number", "number": ["1000"] }
}
]
}
Operators
There are 22 operators. Each has a value_arity — none (no value, e.g. is_empty), single, or multiple.
The operators valid for a given field type differ; the authoritative per-field-type set is returned by
GET /v1/automation/meta/filter?field_type=….
| Operator | Arity | Operator | Arity | |
|---|---|---|---|---|
is | single | has_any_of | multiple | |
is_not | single | has_all_of | multiple | |
contains | single | has_none_of | multiple | |
does_not_contain | single | is_exactly | multiple | |
starts_with | single | is_any_of | multiple | |
ends_with | single | is_none_of | multiple | |
greater_than | single | is_empty | none | |
greater_or_equal_than | single | is_not_empty | none | |
less_than | single | is_completed | none | |
less_or_equal_than | single | is_incomplete | none | |
is_before | single | |||
is_after | single |
The value union
The condition value is tagged by type so you can read it without the per-field rules:
type | Payload | Meaning |
|---|---|---|
text | text: a template dynamic value | Text / template comparison. |
number | number: a code dynamic value | Numeric comparison as a code expression. |
ids | ids: array of number or reference | Option / status / relation / user ID set. |
date | date: { base, operator: "plus"\|"minus", unit: "hour"\|"day"\|"week"\|"month", amount } | Relative date. base is required — omitting it currently makes the condition silently never-match (intended to become a 400). |
boolean | boolean | Boolean comparison — arises from a webhook-payload-property subject. Round-trips on write. |
entity_type | entity_type: string | Comment/reply entity-type comparison. Round-trips on write. |
On write, the type tag must match the field
Reading a value is tag-driven, but writing one is field-driven. On create/update the server picks the value
channel from the subject field's type — number / unique-id → number; date fields → date; category / status /
relation / user → ids; text → text — and reads only that tag. A value whose type doesn't match the field's
channel is silently dropped: the condition is stored with an empty comparison and no 400. The valid tag for
a field type is not served by meta/filter (which returns operators only),
so match the tag to the field yourself.
An empty comparison is a valid definition, so validate stays green —
but at run time a filter value that composes to empty is refused with a protective error rather than matching every
record. See Troubleshooting → Filter values.
v1 limitations
- A subject must have a boolean-expression leaf. On create/update, a structured condition's
subjectmay be a field reference, most record-metadata references (app_record_id,created_at,last_modified_at,created_by,last_modified_by), a trigger-output reference (e.g.webhook_payload_property) or a prior action's file collection — all of these are writable and round-trip. Value-only tokens that have no filter leaf (meta_typerecord_id/record_url/revision/all_comments, the rawwebhook_payload) andglobal/customreferences are rejected with a400on write. - Change-tracking conditions are read-only. A "has changed" style condition has no public operator, so it reads as
an operator-less
{ id, subject }and cannot be re-submitted verbatim.
A verbatim GET → PUT still fails only for an automation that carries a change-tracking condition — strip those
first. Ordinary field / metadata / trigger / action-file subjects (including the boolean / entity_type value kinds)
round-trip.
This is a known v1 limitation, not a bug.