Automation JSON import format


Published Sep 21, 2026

A complete, AI-oriented reference for authoring version 1 Automation JSON bundles.

Automations JSON import AI workflows Reference

Automation JSON import format

This is the canonical version 1 interchange format for exporting and importing Crewvana Automations. It is intentionally explicit enough for an AI agent to author a workflow without reading application source. Imports create safe drafts; they never publish or execute a workflow.

Envelope and schema version

Every file is one bundle. A single-Automation export still uses the automations array with one item.

  • format must be exactly crewvana.automation.
  • schema_version must be the integer 1.
  • automations must contain 1 to 100 Automation objects.
  • Unknown envelope fields and unsupported versions are rejected.
  • Encode the file as UTF-8 JSON and use a .json filename.
{
    "format": "crewvana.automation",
    "schema_version": 1,
    "automations": [
        {
            "key": "example_automation",
            "name": "Example Automation",
            "settings": {
                "delay_seconds": 0,
                "folder": null
            },
            "nodes": [
                {
                    "key": "trigger_contact",
                    "type": "trigger",
                    "name": "lead_created",
                    "parameters": {}
                }
            ],
            "connections": [
                {
                    "from": null,
                    "to": "trigger_contact",
                    "branch": "trigger",
                    "order": 1
                }
            ]
        }
    ]
}

Automation object

Each Automation has these fields:

Field Required Meaning
key Yes A document-local key matching ^[A-Za-z][A-Za-z0-9_-]{0,63}$. It is used by references inside this bundle and is never a database ID.
name Yes Requested Automation name, 1 to 255 characters.
settings Yes An object containing optional delay_seconds (0 to 31,536,000) and folder (a name or null).
nodes Yes Up to 500 trigger/action nodes.
connections Yes Up to 500 explicit placement records.

The importer keeps a requested name when it is unused, using a case-insensitive collision check. On a collision it tries <name> (Imported), then <name> (Imported 2), increasing the suffix until the name is unused. References using automation_key bind by document key before this rename occurs.

Nodes and parameters

A node contains key, type, name, optional display_name, and parameters. The key is local to its Automation. type is trigger or action. name is the stable identifier from the node catalog. parameters is always an object, including for nodes with no parameters.

  • A draft may contain zero or one registered trigger. Add one documented trigger before publishing a workflow; multiple triggers are rejected.
  • Use only registered node identifiers. Class names and translated labels are not identifiers.
  • Unknown parameters and unknown node-specific settings are rejected.
  • display_name is an optional editor label. It does not alter runtime behavior.
  • Database IDs, runtime logs, enrollments, waits, history, publication state, and tenant IDs do not belong in this format.

The current catalog contains 38 registered nodes. Compatibility-only nodes remain importable for round trips but are hidden from the new-node selector.

Connections, ordering, and branches

Each connection has from, to, branch, and order:

  • Every root node uses from: null. A root trigger uses branch: "trigger"; every root action uses branch: "actions".
  • A branch child uses the branching action key in from and its exact branch token in branch.
  • order is 1-based and contiguous within a path. The root trigger and root actions are separate paths because they use the distinct trigger and actions branch tokens.
  • Each node must appear as to exactly once. A node cannot have multiple parents.
  • Branch parents must be registered branching actions, and branch tokens are case-sensitive.
  • When present, the single trigger must be a root at order 1 and cannot be the source of child connections.
  • A branching action must be the final action in its path. Put later work inside one of its branches.
  • A wait timeout connection is valid only when that wait node has timeout_enabled set to true.
  • The graph is an ordered tree of paths, not a general DAG: no cycles, joins, convergence, dangling endpoints, or cross-Automation node edges.
  • No node may be deeper than 25 nodes from its root.
  • A branch path may be empty. Execution ends when the selected empty branch is reached.

The importer derives database parent IDs and sort orders from these records. Never include database node IDs.

Dependency references

Named tenant references resolve only inside the importing tenant. Unless create_if_missing is explicitly allowed, resolution must find exactly one eligible record; zero matches and ambiguous matches are errors. Matching never falls back to a numeric database ID. A Facebook form uses its external form id rather than a tenant database ID; that id must be present in exactly one active Facebook integration for the target tenant.

Resource JSON shape Resolution rule
Pipeline {"name":"Sales"} Exact Pipeline name; ambiguous duplicate names fail.
Pipeline status {"name":"New Lead"} Exact status name inside the separately supplied Pipeline.
Contact tag {"name":"VIP","create_if_missing":false} Exact tag name. Creation is allowed only when explicitly supported and true.
Form {"name":"Website Contact Form"} Exact tenant Form name; null means any form where documented.
Facebook form {"id":"1234567890","name":"Website Leads"} External form ID in the tenant’s single active Facebook integration; name is descriptive.
Team {"name":"Sales"} Exact Team name; duplicates fail.
User {"email":"alex@example.com","name":"Alex Johnson"} Unique tenant email; name is descriptive.
Communication template {"name":"Welcome","type":"email"} or {"custom":true} Exact reusable template name/type, or explicit custom content.
SMS channel {"endpoint":"+18015550100","label":"Main SMS"} Active functional tenant sending endpoint; label is descriptive.
Incoming webhook {"name":"Lead Intake","create_if_missing":false} Exact Webhook name, or explicit creation where allowed.
Automation {"automation_key":"follow_up"} or {"name":"Existing Follow-up"} Same-bundle document key or one exact existing tenant name.

An Automation reference using automation_key targets another Automation in the same file. An Automation reference using name targets one existing tenant Automation. Prefer automation_key whenever the target is in the bundle.

create_if_missing is explicit. It is supported only where the node reference documents it; omission means false. The importer never silently creates other dependencies.

Conditions

A conditions value is a non-empty array of at most 50 objects. Every condition in the array uses AND semantics.

Operator family Required object keys Meaning
has_tag, not_has_tag operator, value value is the exact Contact Tag name. Omit field.
empty, not_empty operator, field Tests whether the selected field has a value. Omit value.
equals, not_equals, contains, not_contains operator, field, value Requires a nonblank string or finite number. Use empty/not_empty for blank values. Pipeline/status fields accept these operators with an exact human-readable name.
greater_than, greater_than_or_equal, less_than, less_than_or_equal operator, field, value Requires a finite numeric value and cannot be used with Pipeline or Pipeline Status fields.

Allowed field tokens are: email, phone, first_name, last_name, full_name, source, city, state, pipeline, pipeline_id, pipeline_status, pipeline_status_id, tenant.activated, tenant.activation_step, tenant.plan_id, tenant.plan_name, tenant.plan_free_trial_days, tenant.subscription_status, tenant.subscription_billing_cycle, tenant.subscription_amount, tenant.subscription_trial_days_left, tenant.subscription_trial_end_date, tenant.subscription_has_card_on_file, tenant.subscription_card_brand, tenant.subscription_card_last_four, and tenant.subscription_next_billing_at.

For pipeline, pipeline_id, pipeline_status, and pipeline_status_id, value is still the exact human-readable Pipeline or Pipeline Status name in the target tenant—not a numeric ID. The name must resolve uniquely.

Examples:

[
    {
        "operator": "has_tag",
        "value": "VIP"
    },
    {
        "operator": "not_empty",
        "field": "email"
    },
    {
        "operator": "equals",
        "field": "state",
        "value": "CO"
    }
]

Variables, JSON, and escaping

Templated setting strings and webhook payload string values can use double-brace placeholders such as {{contact.first_name}}. Keep the braces inside the JSON string. Escape JSON quotes and backslashes normally.

  • Common groups are contact, service_job, team, user, form, facebook_form, date, search, and platform-only tenant values.
  • Incoming webhook payload fields use dotted paths such as {{webhook.customer.email}}.
  • Variables are replaced at runtime, so the required runtime context still has to exist.
  • References are resolved during import and cannot contain variables.
  • A send_webhook payload is a JSON object, not text containing encoded JSON.
  • Null, booleans, numbers, arrays, and objects should remain typed JSON values unless a node article explicitly calls for a string.
  • Integer-valued JSON numbers must stay in the lossless range −9007199254740991 through 9007199254740991. Use a documented string field when a larger identifier is required.

Import safety and naming

The complete bundle is validated before persistence and created transactionally. Every imported Automation receives fresh database IDs and starts inactive and unpublished with no copied runtime records. Review it in the editor and publish it explicitly before it can run.

Imports do not copy executions, enrollments, logs, waits, history, published snapshots, owners, tenant IDs, webhook history samples, or audit timestamps. A failed import leaves the database unchanged.

Validation errors and limits

Validation errors identify a JSON path, for example $.automations[0].nodes[2].parameters.pipeline.name. Fix every reported path and submit the complete file again.

The published JSON Schema enforces the closed envelope, all 38 registered type/name pairs, exact parameter and settings keys, value types, enums, reference shapes, and condition object variants. The importer then performs semantic checks that JSON Schema cannot fully express, including graph topology and ordering, branch-parent compatibility, dependency resolution, cross-Automation references, and tenant-specific runtime constraints.

  • A bundle supports at most 100 Automations, 500 nodes per Automation, 500 connections per Automation, 5000 total nodes, and 5000 total connections.
  • Duplicate Automation keys, duplicate node keys, missing endpoints, duplicate path orders, cycles, excessive depth, invalid branches, and branching actions followed by siblings are rejected.
  • Multiple triggers, mismatched node type/name pairs, non-importable nodes, invalid parameter types, and unknown fields are rejected.
  • Missing or ambiguous tenant dependencies are rejected instead of being guessed.
  • The HTTP upload limit is 2 MiB. JSON parser depth and validation-error limits also apply before persistence.

Forward compatibility

Authors must emit only schema version 1. Importers reject versions they do not understand rather than guessing. Do not add extension fields: version 1 objects use closed property sets. Re-export an Automation after a future schema migration to obtain that version’s canonical representation.

Complete examples

These examples use the same envelope accepted by import. They intentionally avoid tenant dependencies, so their JSON contracts validate in any tenant; replace the example webhook destinations before using them.

Basic workflow

A new-contact trigger posts typed contact data to an external webhook.

{
    "format": "crewvana.automation",
    "schema_version": 1,
    "automations": [
        {
            "key": "new_contact_webhook",
            "name": "New Contact Webhook",
            "settings": {
                "delay_seconds": 0,
                "folder": "Imported Examples"
            },
            "nodes": [
                {
                    "key": "trigger_contact",
                    "type": "trigger",
                    "name": "lead_created",
                    "parameters": {}
                },
                {
                    "key": "post_contact",
                    "type": "action",
                    "name": "send_webhook",
                    "parameters": {
                        "url": "https://example.com/hooks/new-contact",
                        "payload": {
                            "event": "contact_created",
                            "contact_id": "{{contact.id}}",
                            "source": "{{contact.source}}"
                        }
                    }
                }
            ],
            "connections": [
                {
                    "from": null,
                    "to": "trigger_contact",
                    "branch": "trigger",
                    "order": 1
                },
                {
                    "from": null,
                    "to": "post_contact",
                    "branch": "actions",
                    "order": 1
                }
            ]
        }
    ]
}

If / Else workflow

The condition action posts to different webhook destinations based on the contact source.

{
    "format": "crewvana.automation",
    "schema_version": 1,
    "automations": [
        {
            "key": "contact_source_routing",
            "name": "Contact Source Routing",
            "settings": {
                "delay_seconds": 0,
                "folder": "Imported Examples"
            },
            "nodes": [
                {
                    "key": "trigger_contact",
                    "type": "trigger",
                    "name": "lead_created",
                    "parameters": {}
                },
                {
                    "key": "check_source",
                    "type": "action",
                    "name": "if_else",
                    "parameters": {
                        "settings": {
                            "conditions": [
                                {
                                    "operator": "equals",
                                    "field": "source",
                                    "value": "Website"
                                }
                            ]
                        }
                    }
                },
                {
                    "key": "website_webhook",
                    "type": "action",
                    "name": "send_webhook",
                    "parameters": {
                        "url": "https://example.com/hooks/website-contact",
                        "payload": {
                            "route": "website",
                            "contact_id": "{{contact.id}}"
                        }
                    }
                },
                {
                    "key": "other_source_webhook",
                    "type": "action",
                    "name": "send_webhook",
                    "parameters": {
                        "url": "https://example.com/hooks/other-contact",
                        "payload": {
                            "route": "other",
                            "contact_id": "{{contact.id}}"
                        }
                    }
                }
            ],
            "connections": [
                {
                    "from": null,
                    "to": "trigger_contact",
                    "branch": "trigger",
                    "order": 1
                },
                {
                    "from": null,
                    "to": "check_source",
                    "branch": "actions",
                    "order": 1
                },
                {
                    "from": "check_source",
                    "to": "website_webhook",
                    "branch": "if",
                    "order": 1
                },
                {
                    "from": "check_source",
                    "to": "other_source_webhook",
                    "branch": "else",
                    "order": 1
                }
            ]
        }
    ]
}

Wait workflow

The wait pauses for an SMS reply, then follows either the continued or timeout branch.

{
    "format": "crewvana.automation",
    "schema_version": 1,
    "automations": [
        {
            "key": "reply_follow_up",
            "name": "Reply Follow-up",
            "settings": {
                "delay_seconds": 0,
                "folder": "Imported Examples"
            },
            "nodes": [
                {
                    "key": "trigger_contact",
                    "type": "trigger",
                    "name": "lead_created",
                    "parameters": {}
                },
                {
                    "key": "wait_for_reply",
                    "type": "action",
                    "name": "wait",
                    "parameters": {
                        "settings": {
                            "wait_mode": "reply",
                            "reply_channel": "sms",
                            "wait_until_business_hours": true,
                            "resume_timezone_mode": "recipient",
                            "resume_day_mon": true,
                            "resume_day_tue": true,
                            "resume_day_wed": true,
                            "resume_day_thu": true,
                            "resume_day_fri": true,
                            "resume_start_time": "09:00",
                            "resume_end_time": "17:00",
                            "timeout_enabled": true,
                            "timeout_number": 2,
                            "timeout_unit": "days"
                        }
                    }
                },
                {
                    "key": "reply_received",
                    "type": "action",
                    "name": "send_webhook",
                    "parameters": {
                        "url": "https://example.com/hooks/reply-received",
                        "payload": {
                            "outcome": "continued",
                            "contact_id": "{{contact.id}}"
                        }
                    }
                },
                {
                    "key": "reply_timeout",
                    "type": "action",
                    "name": "send_webhook",
                    "parameters": {
                        "url": "https://example.com/hooks/reply-timeout",
                        "payload": {
                            "outcome": "timeout",
                            "contact_id": "{{contact.id}}"
                        }
                    }
                }
            ],
            "connections": [
                {
                    "from": null,
                    "to": "trigger_contact",
                    "branch": "trigger",
                    "order": 1
                },
                {
                    "from": null,
                    "to": "wait_for_reply",
                    "branch": "actions",
                    "order": 1
                },
                {
                    "from": "wait_for_reply",
                    "to": "reply_received",
                    "branch": "continued",
                    "order": 1
                },
                {
                    "from": "wait_for_reply",
                    "to": "reply_timeout",
                    "branch": "timeout",
                    "order": 1
                }
            ]
        }
    ]
}

AI author checklist

Before returning a generated file:

  1. Emit one envelope with the exact format and schema_version values.
  2. Give every Automation and node a unique, readable document key; never invent database IDs.
  3. Select one registered trigger for an executable workflow and only documented actions. A deliberately incomplete draft may omit the trigger.
  4. Build every parameters object from that node’s article and remove unknown keys.
  5. Use portable dependency references and verify that names/emails/endpoints will resolve uniquely in the target tenant.
  6. Add exactly one incoming connection per node with contiguous 1-based order; root triggers use branch trigger and root actions use branch actions.
  7. Make every branching action the last node in its path and use only its documented branch tokens.
  8. Check for cycles, joins, dangling keys, accidental self-enrollment, and unreachable actions.
  9. Keep webhook payloads as objects and preserve JSON value types.
  10. Expect an inactive, unpublished draft; never claim that import publishes or executes it.

Node catalog

Search

More guidance related to this article.

Need more help?

If this article doesn’t solve it, send us a message and we’ll help directly.

Contact Support