TL;DR
rootActionIds has EXACTLY one id; every connection field holds AT MOST one id; no joins, no cycles, no orphans. Parallelism only via SPLIT; "both branches continue" = duplicate the downstream steps per branch (the builder caps an automation at 45 actions — count before duplicating).actions[k].id === k, a fresh uuid v4, a unique namespace (except variable steps, which share setVariable), and ONLY its own type's *Info object.var("...")) only its node's aggregated payload schema: trigger payload + identity enrichment + outputs of its ANCESTOR steps.| Field | Rules |
|---|---|
id, revision | Read-only. revision MUST be sent back on Update Automation (§6). |
name | Required, 1–100 chars (API allows 500; the builder holds 100); the business outcome ("Send welcome email to new subscribers"). |
description | Optional, ≤2000 chars. |
origin | Required on create, immutable. USER for anything you create. APPLICATION/PREINSTALLED carry update locks (Automations Validation and Persistence — included update restrictions). |
settings | Locks for APPLICATION/PREINSTALLED (readonly, actionSettings, …). Never set it on create; on Update send it back exactly as fetched. |
configuration.status | Required, ACTIVE | INACTIVE. Create INACTIVE; activate only on request. |
configuration.trigger | {appId, triggerKey, filters[], scheduledEventOffset?, overrideSchema?, automationConfigMapping?} — Automations Trigger Configuration. |
configuration.rootActionIds | Exactly one action id. |
configuration.actions | Map actionId → Action. |
Action (common fields):
id — uuid v4, equal to its map key.type — APP_DEFINED, CONDITION, CODE_CONDITION, DELAY, RATE_LIMIT, SET_VARIABLES, SPLIT (the last two are not yet in the public API docs, but the builder supports them — Automations Delays Variables and Branches). NEVER UNKNOWN_ACTION_TYPE, NEVER MERGE.namespace — 1–100 chars, unique except the shared setVariable namespace (§3). Outputs of this step appear under it.displayName — optional but always set it: 1–60 chars, sentence case, purpose-specific (Automations Delays Variables and Branches §7). Absent → the canvas shows the catalog name.skipActionExpression — absent (runs) or exactly "{{true}}" (skipped), the only value the builder writes. A skipped step adds NO outputs downstream (§4).*Info matching type: appDefinedInfo, conditionInfo, codeConditionInfo, delayInfo, rateLimitInfo, setVariablesInfo, splitInfo.rootActionIds = [oneId], and no action may point at the root. The builder draws only rootActionIds[0]; extra roots are saved but invisible.appDefinedInfo.postActionIds, delayInfo.postActionIds, rateLimitInfo.postActionIds, setVariablesInfo.postActionIds, conditionInfo/codeConditionInfo.truePostActionIds / falsePostActionIds each hold 0 or 1 id (API maxItems 1).[], never omit the field.MERGE type and conditionInfo.mergeActionId, but the canvas cannot draw them (MERGE nodes never finish loading). Never create either; when you read an automation that has them, preserve them untouched and don't add more.appId + triggerKey exist in the site's trigger catalog (a missing trigger can leave every node loading).actions[k].id === k for every entry.triggerKey string (it is the trigger node's id), and the builder's synthetic ids <id>true, <id>false, <id>-end, <id>true-end. Duplicates crash the layout. Generate every id as a fresh uuid v4 in code; never reuse ids from these references.*Info. The canvas ignores type when finding children and takes the first present of appDefinedInfo → delayInfo → rateLimitInfo → setVariablesInfo → condition — even an empty leftover appDefinedInfo: {postActionIds: []} on a CONDITION hides both branches.appId + actionKey match the site's action catalog, else the node shows "Action not available".setVariable, with fresh variable keys and updated downstream references). A Send an email in a duplicated stage is added by the user in each branch — never copy an existing step's messageId / templateId / uniqueRuleId: both steps would share one email, so editing one changes both and deleting one can delete the shared email. Never point both branches at the same action. Never use a "gate" where the TRUE branch does its side-action and stops while the timeline should continue.Add a condition/delay ONLY for explicit flow control ("if X, do a DIFFERENT step Y", "after 2 days"). Audience/copy framing is CONTENT, not a condition:
Use the builder's own convention (it derives new indexes from the trailing number):
| Type | Namespace |
|---|---|
| APP_DEFINED | <actionKey>-<N> (e.g. triggered-emails-3) |
| CONDITION and CODE_CONDITION | CONDITION-<N> |
| DELAY / SPLIT / RATE_LIMIT | <TYPE>-<N> (e.g. DELAY-2, SPLIT-5, RATE_LIMIT-1) |
| SET_VARIABLES (and existing code-variable steps) | always the literal setVariable (Automations Delays Variables and Branches §4) |
N = 1 + the largest number after the last - among all the automation's namespaces (one counter shared across every step type — e.g. DELAY-1 then createTask-2). A namespace with no trailing number (setVariable) doesn't count: if it is the only one, the next N is 1. A CODE_CONDITION keeps CONDITION-<N>. The builder never rewrites an existing namespace — on update keep every existing one as is. Variable and existing code-variable steps intentionally share setVariable; their distinct property keys are merged. For other output-producing actions, never reuse a namespace — the later output overwrites the earlier one.
var() can read at a nodeThis section owns the rule. Build one JSON Schema object per node, in code, from the root down its own branch, from public schema APIs (calls: Automations API Catalog §3–§4).
payloadDataSchema (custom/webhook triggers: trigger.overrideSchema instead, Automations Schemas and Scheduling §3). If the trigger implements dynamic schema, call Get Trigger Dynamic Schema with the SELECTED (saved) filter options and MERGE its properties over the static ones (Automations Trigger Configuration §5); before filters are chosen, dynamic fields are UNKNOWN, not absent. Trigger fields are un-prefixed: var("orderId").identityType: "contact" or "member", a root-level object is added, keyed by that field's namespace annotation when it has one, else its (deprecated) name annotation, else the identity type (contact / member). Shape: Get Identities Schema (identitiesSchema.properties.contact / .member). Both stay valid: var("contactId") and var("contact.name.first"); also e.g. var("contact.email"), var("member.profile.nickname"). A field annotated name: "post_author_member" (some Blog / File Share triggers) is enriched as var("post_author_member.profile.nickname") — two member ids on one trigger give two objects; use the key the field declares (member.* does not exist there). Other identity types (e.g. visitor) add no object; webhook/custom triggers (override schema) get none. The builder does not enrich identity fields of action outputs — don't read contact.* that only an action output would provide.properties[<namespace>]: catalog outputSchema, with overrideOutputSchema.properties merged over it, and — for provider-controlled dynamic outputs — Get Action Dynamic Output Schema (called with that step's final inputMapping) merged over that. For wix_automations-llm_call, declare user-defined result fields directly in overrideOutputSchema; do not call dynamic output discovery for those fields (Automations Action Configuration §4). Example ancestor path: var("triggered-emails-3.messageId").outputSchema) of non-skipped ancestor SET_VARIABLES / code-variable (namespace: "setVariable") steps, merged under setVariable: var("setVariable.<key>") (Automations Delays Variables and Branches §4).Scope rules:
skipActionExpression: "{{true}}") contributes nothing; never skip a step whose outputs a later step reads.inputMapping → its provider-controlled dynamic output schema (if any; user-defined wix_automations-llm_call fields come from overrideOutputSchema without a dynamic-output call) → add it to its children's schema.var() path MUST exist in this node's aggregated schema with a compatible type — never reason from a raw trigger or action schema alone, and never invent a field. Copy keys from the schema (titles/labels are not keys); arrays cannot be traversed by path (Automations Mapping Expressions §5).var("contact.locale") into the email's dynamic content; no separate "language" field is needed.Syntax of expressions and the allowed function list: Automations Mapping Expressions.
The simplest shape: one trigger and a chain of APP_DEFINED actions linked by postActionIds.
The trigger and action identities below are the Contacts "new contact created" trigger and the
Tasks "create task" action; confirm them, and each action's input keys, against the site's
catalog before use. Generate fresh uuid v4 action ids.
Why it's valid: one parentless root; each action has at most one successor and one parent; ids
equal their keys; namespaces are unique per §3; inputMapping keys come from the input schema;
every var() path is in that node's aggregated schema; status is INACTIVE.
All ids are placeholders; generate real uuid v4s. Resolve appId/triggerKey/actionKey/input keys from the catalogs.
Why it's valid: one parentless root; ≤1 id per connection; one parent each; ids = keys, unique; own *Info only; namespaces per §3; one renderable condition group (Automations Conditions); inputMapping keys from the input schema (Automations Action Configuration); every var() path in that node's aggregated schema.
Create INACTIVE (your "draft"); activate only on request. Create procedure and read-back: Automations Validation and Persistence §3; calls: Automations API Catalog §2.
Update: Get → change only what was asked, keep every other node byte-for-byte → Validate → Update with the full merged object + revision, origin and settings as fetched; on an ACTIVE automation the change is live — confirm first. Locks: Automations Validation and Persistence — included update restrictions.
Script the graph/id/namespace/var() items of Automations Validation and Persistence §4 (items 1–4 and 11 — the rules of §2–§4 above: one parentless root, RATE_LIMIT only as that root, ≤1 id per connection, one parent each, no cycles/orphans/MERGE/mergeActionId/UNKNOWN_ACTION_TYPE, keys = ids, globally unique uuid v4s incl. SPLIT path ids, triggerKey and <id>true/false/-end, own *Info only, §3 namespaces unique except setVariable, every var() in THAT node's aggregated schema). Also check that only user-requested conditions/delays exist; then Validate Automation must return no trigger or action errors.
Last updated: 2 November 2026