> Portal Navigation:
> 
> - Append `.md` to any URL under `https://dev.wix.com/docs/` to get its markdown version.
> - Pages are either content pages (article or reference text) or menu pages (a list of links to child pages).
> - To get a menu page, truncate any URL to a parent path and append `.md` (e.g. `https://dev.wix.com/docs/sdk.md`, `https://dev.wix.com/docs/sdk/core-modules.md`).
> - Top-level index of all portals: https://dev.wix.com/docs/llms.txt
> - Full concatenated docs: https://dev.wix.com/docs/llms-full.txt

## Resource: Automations Validation and Persistence

## Article: Automations Validation and Persistence

## Article Link: https://dev.wix.com/docs/api-reference/business-management/automations/skills/automations-validation-and-persistence.md

## Article Content:

> **Stage scope:** this stage covers only what [Build and Manage Wix Automations](https://dev.wix.com/docs/api-reference/business-management/automations/skills/build-and-manage-wix-automations.md) lists; do not perform anything marked "Later stages only", and follow the instruction attached to each guide marked "not yet published". "Stage", "Later stages only" and "not yet published" describe how this skill is being rolled out; never mention them to the user. Explain a limit in product terms: what can't be done through the API here and where the user can do it instead (for example, in the automations builder).

# Validation and Verification

How to prove an automation is correct, saved, and editable in the builder before you tell the
user it exists. This file owns the create / update / activate procedures. API shapes: [Automations API Catalog](https://dev.wix.com/docs/api-reference/business-management/automations/skills/automations-api-catalog.md).

**TL;DR**

- Climb the ladder in order: **§4 renderability checklist → Validate Automation → Create/Update →
  Get Automation read-back → (optional, authorized) Test Automation.**
- **§4 gates Create, configuration edits and activation.** `VALID` alone does not prove the
  builder can draw, edit and re-save the automation. Status-only deactivation follows §3 and
  must not be blocked by unrelated configuration errors.
- Create and Update do not validate; an unvalidated or unsaved automation is **not created**.
- Test Automation executes actions for real — only with explicit user authorization (§5).

## 1. Local checks and semantic review

Run the §4 checklist on the complete automation object before **every** Validate call. Items
tagged **[code]** are deterministic — a small script beats reasoning (ids, graph, namespaces,
`var()` inventory, field limits). The rest need you to read schemas.

Semantic review (you, not code) — required for every action before acceptance; full rules in
[Automations Action Configuration](https://dev.wix.com/docs/api-reference/business-management/automations/skills/automations-action-configuration.md) §6:

- **Side-effect contract:** the action does what the user's verb says to the affected entity —
  retrieval is not assignment; a message about a change is not the change.
- **Recipient lineage** (email, chat, SMS, push): prove the recipient path — the trigger contact and
  an upstream-created contact differ even when both are `contactId`; copy never proves who receives
  it; for emails (later stages only — email is outside stage 3) the audience fields decide (§3 of Automations Email Actions (not yet published; email configuration is outside stage 3)).
- **No fabricated content:** every user-specific value (recipient, subject, entity, amount) comes
  from the user, the site, or the payload.

## 2. Validate Automation

Request: `{automation: <full object>, validationSettings?}`. Validate the complete candidate
you intend to save; an unsaved object doesn't need an id. For creation, set `origin: "USER"`,
a real `name`, and `configuration.status: "INACTIVE"`. For updates, preserve the fetched
`origin`, `settings` and status unless a status change was requested. For activation, validate
the candidate with `configuration.status: "ACTIVE"`. Never rewrite an installed automation's
origin or deactivate a live automation merely to validate it.

`validationSettings.actionIds` validates just the listed actions — useful while configuring node
by node. `skipProviderValidations: true` skips app-specific checks — acceptable for intermediate
checks only. **The final gate is always a full validation with no settings.**

Persist the exact validated candidate, without retyping component identities. Immediately before Create/Update, compare trigger `appId`/`triggerKey` and each action `appId`/`actionKey` with their catalog values and the validated object. For creation, configuration edits or activation, any candidate change requires another full Validate; a success on the old object does not cover a changed ID. Status-only deactivation still follows §3. Compare those identities again on read-back before claiming the requested automation was saved.

Response: `status` is `VALID`, `VALID_WITH_WARNINGS` (only `errorSeverity: WARNING`), or
`INVALID` (at least one `CRITICAL`). Target `VALID`. Accept `VALID_WITH_WARNINGS` only when you
understand each warning and tell the user about it.

**What the builder does with it.** When the user opens the automation, the builder runs the same
full Validate and pins **every** returned error to its node (trigger or action) — mapping errors
included. A `CRITICAL` shows as a node error and **blocks Activate / Publish** until fixed; a
`WARNING` shows as a node warning. Mapping errors show on the step until the user opens it, then
its form replaces them with field-level marks; other configuration errors show as error/warning
text; a provider error shows as "title: message". So an automation you save with errors opens
broken.

Each `triggerValidationErrors[]` / `actionValidationErrors[]` item has `errorType`
(`CONFIGURATION_ERROR` | `PROVIDER_ERROR`), `errorSeverity`, and one of:

- `configurationError{errorType, filterFieldKey | fieldKey}` — a platform check (tables below);
- `providerConfigurationError{title, message, ctaLabel, ctaUrl}` — the owning app's own check
  (`validateConfiguration`; often a missing connection or entity).
  Action errors also carry `actionId`, `appId`, `actionKey`.

### Error types → fix

Trigger (`configurationError.errorType`):

- `NOT_FOUND`, `INVALID_TRIGGER_KEY` — not in this site's catalog → re-resolve; never guess keys.
- `APP_NOT_INSTALLED`, `MODERATION_MISMATCH` — not available on this site → tell the user; don't
  substitute silently.
- `DEPRECATED` — being retired → prefer its replacement, or tell the user.
- `INVALID_FILTER_FIELD_KEY` — not a filter the trigger defines → use only its `filters[]`.
- `INVALID_FILTER_EXPRESSION` → rebuild with the filter shapes in [Automations Trigger Configuration](https://dev.wix.com/docs/api-reference/business-management/automations/skills/automations-trigger-configuration.md) §4.
- `MISSING_REQUIRED_FILTER` → add it (ask the user for its value if needed).

Action (`configurationError.errorType`, with `fieldKey` — fix the field it names; never delete a
field to silence an error):

- `NOT_FOUND`, `INVALID_ACTION_KEY`, `APP_NOT_INSTALLED`, `MODERATION_MISMATCH` — not available
  on this site → re-resolve; pick another action or tell the user.
- `DEPRECATED` — the builder shows "Action will be removed soon" → use its replacement
  (§2 of Automations Feasibility and Planning (topic guide not yet published; consult the [Automations API reference](https://dev.wix.com/docs/api-reference/business-management/automations.md))).
- `INVALID_MAPPING` → remove keys not in the effective schema; re-fetch the dynamic input schema.
- `MAPPING_TYPE_MISMATCH` → convert (`toString`, `toNumber`), map another field, or build the
  object shape (e.g. MONEY).
- `MAPPING_MISSING_REQUIRED_FIELD` → map it from the payload or ask the user; never a dummy value.
- `MAPPING_SCHEMA_MISMATCH` (enum, uuid, email format…) → a legal enum value / real id.
- `MAPPING_VARIABLE_MISSING_FROM_SCHEMA` → fix the path against the aggregated schema, or reorder
  so the source is an ancestor.
- `SAMPLE_CODE_RUN_FAILED` → fix the code step (guard missing fields — [Automations Conditions](https://dev.wix.com/docs/api-reference/business-management/automations/skills/automations-conditions.md) §5).
- `POST_ACTION_NOT_FOUND` → a successor id doesn't exist; fix the graph.

Create/Update can also fail with `ON_BEFORE_SAVE_ACTION_EXCEPTION`: the owning app's save hook (e.g. for Send an email) rejected the step. Treat it like a provider error — read the message, fix the mapping or tell the user; don't retry the same write blindly.
`PROVIDER_ERROR` usually needs something only the user can do (connect an account, pick a
resource, finish setup). Report its `title`/`message` and the `ctaUrl`, and don't loop on it.

### Fix loop

1. Group errors by class (type + node). Fix all independent errors in one pass.
2. Re-run the §4 checklist, then re-validate.
3. At most 2–3 attempts per class. If an error comes back unchanged, re-read the reference that
   owns that construct rather than trying variations.
4. Still failing → stop. Tell the user exactly which node and why, and what you need from them.
   Don't save an INVALID configuration or activate it. Status-only deactivation is still
   allowed under §3; stopping future runs does not require repairing the automation first.

## 3. Persistence, read-back and honest reporting

### Create

1. §4 checklist passes and the final full Validate is `VALID` (or understood warnings).
2. Create Automation with `origin: "USER"`, `configuration.status: "INACTIVE"`, no `settings`.
   Capture `id`. Send Create as its own call, after a separate full Validate call has returned
   `VALID` (or understood warnings) — never in the same script as that Validate or as discovery.
   Send it once per candidate: if a Create's outcome is unknown, Query Automations before
   resending it (Reporting rules below). A Create that was never sent is not an attempt.
3. **Read back** with Get Automation on the same site. Confirm the trigger, the root id, and the
   set of action ids/types match what you sent (string literals come back URI-encoded, as sent).
   If you used an override schema, Get with `fields: ["OVERRIDE_SCHEMA"]` ([Automations API Catalog](https://dev.wix.com/docs/api-reference/business-management/automations/skills/automations-api-catalog.md) §2).
   Run the Get as a separate call once Create has returned (not batched into the same script),
   and keep its response body as returned: the returned `automation` is the evidence, so don't
   replace it with a summary you assemble from selected fields.
4. Hand off, leading with the outcome. Make the first line of your answer the saved state, for
   example: "Saved **<name>** as inactive (ID `<automationId>`). Click **Activate** in the
   builder to turn it on: `https://manage.wix.com/dashboard/<metaSiteId>/triggers/edit/<automationId>`".
   That first line MUST name the automation, its status and its ID together; don't move the ID
   to a later line, because a reader may see only the first line.
   Then say what it does, and ask the user to review it in the builder. If the user asked to
   turn it on, activate it first (step 5) and make the first line the active state instead:
   "Saved **<name>** and turned it on (ID `<automationId>`): <edit link>".
5. Activate only on request — see "Activation status, activate, deactivate" below.

### Update

1. Get Automation with `fields: ["OVERRIDE_SCHEMA"]` so a complete-object update preserves
   trigger/action override schemas. Check `origin` and `settings` locks
   (§ Update restrictions below) before planning any change.
2. Change only what was asked, on the current object. Keep ids of untouched nodes.
3. §4 checklist + full Validate on the merged object.
4. If the automation is `ACTIVE`, the update goes live immediately — say so and get confirmation
   first (consent rules: § Update restrictions below).
5. Update with the whole merged automation, the `revision` you read, and `origin` + `settings`
   exactly as fetched (else `INVALID_ORIGIN_TYPE`, even with a field mask — [Automations API Catalog](https://dev.wix.com/docs/api-reference/business-management/automations/skills/automations-api-catalog.md) §2).
   On a revision conflict, re-Get, re-apply, re-validate.
6. Capture the returned automation's `id` — the first override of a preinstalled automation
   can have a new id. Read back that id and compare the **content** of every node you changed (and that untouched node ids
   survived). A bumped `revision` with the old content means the update didn't apply — re-send
   with `fieldMask: configuration,name` and verify again.
7. If the change makes the automation's `name` or a step's `displayName` inaccurate (e.g. "…1
   day…" after the wait became 2 hours; "Add VIP label" now adding another label), offer new
   names; rename only with their OK.

### What happens when the user opens it in the builder

- The builder edits a **hidden draft copy** and autosaves the user's changes into it. The
  published object (what Get returns) changes only when they click **Activate** (inactive
  automation) or **Publish Changes** (active one) — both publish, and Activate also turns it on.
- On load it re-resolves every trigger/action from the site catalog ("not found" on a miss), runs
  Validate (§2) plus its own checks (required filters, empty filters, the create-contact rule in
  §4), and pins the results to nodes.
- On load/save it silently rewrites some shapes: RATE_LIMIT `{{1440}} MINUTES` → `{{24}} HOURS`;
  a webhook trigger filter that is missing or has another id is rebuilt from `webhookId`; unreachable actions and root ids that
  point nowhere are **deleted**; an empty name becomes "Untitled …". The §4 checklist avoids all of
  these, so the user's first save doesn't change your automation.
- Before you Update an automation the user has been editing in the builder, ask them to publish
  or discard there and close the tab, then Get — else their publish can overwrite your change.

### Activation status, activate, deactivate

Read or change status only as [Automations Activation Status](https://dev.wix.com/docs/api-reference/business-management/automations/skills/automations-activation-status.md) describes; this guide adds no status
procedure of its own. Deactivation does not require repairing the configuration.

### Reporting rules

- Report separate counts: **attempted**, **acknowledged**, **verified** by read-back, **active**.
  Only verified automations "exist".
- An id without a successful read-back is **unverified** — say so; don't blindly retry the create
  (Query Automations first to see whether it landed).
- A validated-but-unsaved object is a proposal, not an automation.
- Never describe runtime behavior ("it will send…") as verified — only validated.

## 4. Builder-renderability checklist — the single pre-save gate

Use this gate before Create, configuration edits and activation, not status-only deactivation.
Creation restrictions ("never add", conservative feature support) do not require rewriting
existing builder-supported nodes during an unrelated update. Preserve those nodes and their
metadata; use the owning reference's preservation rules and full Validate. A failing item is a
blocker, not a warning: some make the canvas spin forever or crash, others are silently
rewritten or deleted on the user's first save. Each owning reference has a detailed checklist —
this list is the union; run those too for the node types you used.

**Graph and ids** — [Automations Graph and Data Model](https://dev.wix.com/docs/api-reference/business-management/automations/skills/automations-graph-and-data-model.md)

1. [code] Exactly one `rootActionIds` entry, and it exists in `actions`. RATE_LIMIT, if present, is
   that root and the only one.
2. [code] Single-parent tree: every successor list (`postActionIds`, `truePostActionIds`,
   `falsePostActionIds`, SPLIT `postActionId`) has ≤ 1 id and points to an existing action; every
   non-root action has exactly one parent and is reachable from the root; no cycles; no MERGE /
   `mergeActionId`. Converging branches = duplicated downstream steps.
3. [code] `actions[k].id === k`; ids are fresh uuid v4, unique across actions, SPLIT path ids and
   the trigger key, and never collide with `<id>true`, `<id>false`, `<id>-end`.
4. [code] Each action has a known `type`, only its own type's `*Info`, `[]` (not omitted) for an
   ending successor list, and a builder-format `namespace`. Namespaces are unique except that
   SET_VARIABLES steps share `setVariable`; their output keys must be unique across steps.
   For every action type, `skipActionExpression` is `"{{true}}"` or absent.

**Trigger** — [Automations Trigger Configuration](https://dev.wix.com/docs/api-reference/business-management/automations/skills/automations-trigger-configuration.md) §7

5. `appId`+`triggerKey` came from Resolve Triggers on this site.
6. Filters: catalog filter `id`, verbatim `fieldKey`, Shape A/B expression whose literal value array
   holds the form its filter type allows ([Automations Trigger Configuration](https://dev.wix.com/docs/api-reference/business-management/automations/skills/automations-trigger-configuration.md) §4.1: quoted entity ids, quoted static
   values, or exactly one bare number/boolean); **no filter with an empty `filterExpression`**; ≤ 5 filters; every required
   filter present, plus required follow-ups of every set parent. Webhook trigger: exactly its
   `webhookId` shape.
7. Scheduled / future-date configuration and `scheduledEventOffset` match
   [Automations Schemas and Scheduling](https://dev.wix.com/docs/api-reference/business-management/automations/skills/automations-schemas-and-scheduling.md).

**Actions** — [Automations Action Configuration](https://dev.wix.com/docs/api-reference/business-management/automations/skills/automations-action-configuration.md), Automations Entity and Provider Configuration (topic guide not yet published; consult the [Automations API reference](https://dev.wix.com/docs/api-reference/business-management/automations.md)), §2 of Automations Feasibility and Planning (topic guide not yet published; consult the [Automations API reference](https://dev.wix.com/docs/api-reference/business-management/automations.md))

8. Every APP_DEFINED `appId`+`actionKey` came from Resolve Actions on this site; no unsupported
   action added — including no new code-variable step (the public Create refuses it).
   **Later stages only — email is outside stage 3:** New email steps have a provider-generated mapping used once and persisted before content edits; no fabricated opaque-widget mapping; unrelated existing ones unchanged
   (except the documented site-owner audience replacement).
9. `inputMapping`: only schema keys (an existing email/widget step's mapping passes through unchanged), types and
   enums match, every required field mapped (optional ones per [Automations Action Configuration](https://dev.wix.com/docs/api-reference/business-management/automations/skills/automations-action-configuration.md) §5.2), entity-selector fields hold ids, formulas
   only in `dynamicValuesOptions.enabled` fields.
10. No "Create a contact" action (`contacts-create_contact`) when the trigger payload already has
    an `identityType: "contact"` field — the builder marks it "can't create a contact" and blocks
    activation.

**Expressions and conditions** — [Automations Mapping Expressions](https://dev.wix.com/docs/api-reference/business-management/automations/skills/automations-mapping-expressions.md) §9, [Automations Conditions](https://dev.wix.com/docs/api-reference/business-management/automations/skills/automations-conditions.md) §6

11. [code] Every `var()` path exists in that node's aggregated schema
    — trigger + ancestor outputs only — with a compatible type.
12. Builder-renderable functions only; `;` separators; double-quoted, URI-encoded string literals;
    number/boolean fields hold exactly one expression of that type.
13. CONDITION: exactly one expression group with `AND`/`OR` and 1–20 panel-renderable expressions;
    not both branches empty; otherwise a CODE_CONDITION.

**Special steps** — [Automations Delays Variables and Branches](https://dev.wix.com/docs/api-reference/business-management/automations/skills/automations-delays-variables-and-branches.md)

14. DELAY: exactly one mode — relative (`offsetExpression` a `{{N}}` literal + unit) or absolute
    (`dueDateExpression`). RATE_LIMIT: `{{1}}`, a trigger identifier allowed by
    [Automations Delays Variables and Branches](https://dev.wix.com/docs/api-reference/business-management/automations/skills/automations-delays-variables-and-branches.md) §2 (preserve existing supported member/user/UUID identifiers), and a
    duration (`{{24}} HOURS`, never `{{1440}} MINUTES`) unless the user explicitly wants
    "only ever once" (no duration).
    SET_VARIABLES: matching keys, every property has a `title`, keys unique across all variable
    steps; preserve existing image-variable metadata per [Automations Delays Variables and Branches](https://dev.wix.com/docs/api-reference/business-management/automations/skills/automations-delays-variables-and-branches.md) §4.
    SPLIT: 2–10 paths.

**Object and limits**

15. [code] Text limits: `name` 1–100 chars (API allows 500; the builder's name field holds 100),
    `description` ≤ 2000, action `displayName` 1–60, `namespace` ≤ 100, SPLIT path `name` ≤ 100.
    Expression limits: condition expression ≤ 5000 chars (a longer one fails the builder's save),
    code ≤ 1000, filter expression ≤ 5000, `skipActionExpression`/delay/rate-limit expressions ≤ 1000.
    Step count: ≤ 45 actions of any type — at 45 the builder blocks adding or duplicating steps.
    Count before duplicating downstream steps into each branch.
16. On create: `origin: "USER"`, no `settings` (never `hidden` or `readonly`). On update:
    `origin` and `settings` sent back exactly as fetched; status unchanged unless requested.
17. Full Validate Automation returns no `CRITICAL` errors, and you can explain every `WARNING`.

## 5. Test Automation — only with explicit authorization

Test Automation runs the automation **for real**: emails and messages are sent to real
recipients, contacts/labels/orders change, webhooks fire. It skips delays and works on inactive
automations. It is not a dry run.

- Ask first, naming the concrete side effects; "go ahead and build it" is not authorization.
- Build `payload` to match the trigger payload schema, with test data the user approves (e.g.
  their own contact), never real customers.
- The response is an `activationId`: report "started" — not completed or succeeded — and tell
  the user to check the run's results in the automation's activity view in the Wix dashboard.
  Do not rerun the test to confirm it ran.


## Update restrictions

Before any update: Get Automation, read `origin`, `settings`, `configuration.status`, `revision`.

Locks apply only to `origin` `APPLICATION` / `PREINSTALLED` (a `USER` automation has none):

- **Fully read-only** — `settings.readonly === true`: nothing can change; every action counts as
  read-only and permanent.
- **Trigger locked** — `settings.triggerSettings` absent, or
  `.disableConfigurationModification === true`: no trigger or filter changes. These are internal
  fields public responses may omit, so for these origins **assume the trigger is locked**.
- **Read-only actions** — id ∈ `settings.actionSettings.readonlyActionIds`: can't edit them.
- **Permanent actions** — id ∈ `settings.actionSettings.permanentActionIds`: can't delete them or
  change their `skipActionExpression`.
- **Last action** — always: can't delete the last remaining action.
- **No new action nodes** — always for these origins: only new CONDITION and DELAY nodes may be
  added — no new app actions, variables or splits. `actionSettings.disableConditionAddition` /
  `disableDelayAddition` (internal, may be missing) forbid those too; if an Update or the builder
  rejects a new condition/delay, report the lock instead of retrying.
- **No status change** — `settings.disableStatusChange`: can't activate/deactivate.
- **No delete** — `settings.disableDelete`: can't delete the automation.
- Send `settings` back exactly as fetched; never edit it to lift a lock.

Explain what's locked and why (installed by Wix or an app), say what IS editable, never work around a lock.

Every update:

- **Live edits**: if `status` is `ACTIVE`, Update Automation changes the running automation immediately. Tell the user and confirm before writing. (Or offer: deactivate, edit, re-activate.) If the request already contains that consent ("it's live, change it anyway"), don't ask again — record in your answer that it was a live edit made on their stated consent.
- **Procedure** (builder edits published or discarded first → Get → merge → Validate → Update with the current `revision`, `origin`, `settings` → read-back; revision conflict → re-Get, re-merge, re-validate): §3 of this guide.
- Touch only what was asked; keep untouched nodes, ids, namespaces and SPLIT paths exactly as they are. A new step's namespace number = 1 + the largest number among the steps that REMAIN ([Automations Graph and Data Model](https://dev.wix.com/docs/api-reference/business-management/automations/skills/automations-graph-and-data-model.md) §3): numbers of removed steps are not reserved, so removing the highest-numbered step frees its number for the next new step. Re-point or remove every `var()` that read a removed step before reusing its namespace. The single-parent tree rules still apply to any restructuring.
- Re-verify every entity and field the change touches — they may have been renamed or deleted since creation.
- **Later stages only — email is outside stage 3:** **Email content of an existing step** (subject, preview text, body text — "add the phone number to the email") → edit it in place with Get / Set Email Content (§3 of Automations Email Actions (not yet published; email configuration is outside stage 3)), after the user's OK: it goes live immediately. Recipient or design changes → the user does them in the email editor (site-owner audience: the one allowed edit). Do not initialize or recreate an existing step for content-only changes. Adding a distinct new email uses Generate Action Input Mapping, subject to the origin's new-node restrictions.
- **Deleting a node needs the user's explicit OK**: name the nodes you'll remove (recreating an email creates a different content resource, not restoration) and get a yes before the Update. Then connect its parent to its child and fix or ask about any downstream `var()` that read its outputs.