Publish Gate
Saving a draft accepts anything: work in progress is allowed, with errors and all. Publishing is the gate. When you press Publish, the flow is validated twice, first in the editor and then by the engine that will run it, and only what would provably fail on a real conversation stops the publish. Everything else is a warning you can read, fix or knowingly ignore.
How the check runs
- The editor validates locally. Every node, connection and variable is checked in the browser. Nodes with problems are highlighted on the canvas and the error panel lists them. If at least one error is found, the publish stops right there: the toast shows the first error and how many more there are. Warnings alone never stop it.
- The engine re-validates. If the local check passes, the draft is saved and the publish request goes to the engine, which runs the same graph through the code path a live conversation uses. If it finds blocking errors it refuses the publish, the flow stays a draft, and the errors come back to the editor: each one is painted on its node the same way local errors are, and the toast shows the first one.
- Only engine errors travel back. The engine also produces warnings, but they are not shown in the editor and never block a publish.
- An outage never blocks. If the engine cannot run the check (for example, it cannot resolve a Jump To target because a dependency is down), it does not blame your flow: the report gets an
engine_check_unavailablewarning and the publish goes through on the structural checks alone.
If you build flows through the API instead of the editor, the same report is available without publishing through the flow validation endpoint, both for a graph sent in the request and for a stored flow. Each finding carries the rule, the message, the suggestion and, when it applies, the node. A refused publish returns that same list of errors.
What blocks: engine rules
These are the rules the engine applies. Each one describes a flow that cannot run, or that would silently lose a node or a result.
| Rule | What it means | Fix |
|---|---|---|
flow_too_large | More than 500 nodes or 2000 connections. | Split the flow and chain the parts with Jump To. |
duplicate_node_id | Two nodes share an id; one of them silently disappears at runtime. | Delete one and create it again so it gets its own id. |
node_missing_id, node_missing_type | A node has no id or no type. | Only happens with hand-built graphs; give every node both. |
node_type_missing_prefix | A node type without the conversational- prefix. The canvas sticky note (notes) is the one exception. | Use a node type from the palette. |
missing_start_node, multiple_start_nodes | No Start node, or more than one. With several, the order of the array decides where the conversation begins. | Leave exactly one Start. |
edge_source_not_found, edge_target_not_found, edge_missing_source, edge_missing_target | A connection points at a node that is not in this flow. | Remove the connection or reconnect it. |
edge_missing_source_handle | A connection with no output handle on its source node. | Reconnect it from an output of the node. |
extract_information_invalid_targets | A field to extract that the extraction service refuses on every run: an unsupported type, a duplicate name, a dict or list[dict] field without named subfields, too many subfields. | Fix the field in the node panel. |
extract_information_no_input | The node has no source: no file variable, no text variable, or a URL source with an empty URL. | Pick the source variable or write the URL. |
extract_information_result_discarded | No result variable and no package to store into, so the run only spends OCR. | Pick the result variable. With One result per file the package option is ignored, so the variable is the only output. |
invalid_variable | A declared variable with a type outside string, number, date, boolean, object, array, attachment. | Change the variable type in the variables panel. |
solver_missing_node_field, solver_missing_edge_field, solver_unknown_reference, solver_null_field, solver_error | The engine could not build the graph: a node or connection is missing a field it reads, a field is null, or a connection references something that does not exist. | The message names the field or the reference; fix it and publish again. |
START_CONNECTED | The Start node is connected to nothing. | Connect Start to the first node. |
TARGET_EXISTS | A connection whose destination node does not exist. | Remove or reconnect it. |
NO_DUPLICATE_NODES | Duplicate node ids, reported by the rule checker as well. | Same fix as duplicate_node_id. |
Of the rule checks the engine runs, only those last three are fatal. The rest of that family (a dangling error handle, an interactive node over the provider's option limit, an unrecognized node type) describe flows that are untidy but alive, and plenty of them are live in production today, so they travel as warnings.
What blocks: editor rules
The editor blocks more than the engine does. Its rules mirror what a node needs in order to do its job, and they are what you see highlighted on the canvas before the request is ever sent.
Structure
| Rule | Detail |
|---|---|
| One Start with an output | Exactly one Start node, and it must have at least one outgoing connection. |
| Every node has an input | Every node needs an incoming connection, except Start, Global Keywords, Inactivity follow-up and sticky notes. |
| Conditional with two branches | A Conditional needs two outgoing connections, one from true and one from false. The error handle does not count as a branch. |
| Every output connected | Every dynamic output handle that is not an error handle must be connected: interactive options, keywords, login results, AI router routes, response handlers, custom formula outputs. An unconnected error handle is a warning (see below). |
| Connection to a handle that no longer exists | A connection whose output handle was removed from the node (for example, an option you deleted). Delete the connection. |
| Labels present and unique | Every option, keyword or handle needs a label, and labels cannot repeat within the same node. |
| More than 10 interactive options | WhatsApp cannot send an interactive message with more than 10 options. Above 3 is a warning, above 10 an error. |
| Cycle with nothing waiting for the customer | A loop of nodes where none of them stops to wait for the customer would run forever. Add a node that asks something, or break the loop. |
An answer with nowhere to go
An input node whose answer is not stored anywhere runs, asks, and throws the answer away.
| Node | Rule |
|---|---|
| Ask Date | Needs a variable. The message on the canvas is explicit: the date cannot be stored on an entity. |
| Ask File | Needs a variable, an entity destination, or both. An entity destination is complete when the entity and its key are chosen; for Address the key is replaced by the variable holding the address id, and that variable is what must be set. |
| Ask File, variable type | With Multiple files on, the variable must be an array; off, an attachment. The engine rejects the mismatch. |
| Ask File, limit | When Multiple files is on and a maximum is set, it must be a whole number greater than zero. Empty means no limit. |
Nodes that read a file
Every node that reads a file from a variable is checked against what writes that variable. A node that needs one file breaks when it receives a list.
| Consumers | Accepts | Blocked when |
|---|---|---|
| OCR Verification front and back image, Face Verification selfie and id, Create Asset image from a variable, a Customer Update row of type attachment | One file (attachment; string is tolerated for older flows) | The variable is declared array, or is written as a list by an Ask File with Multiple files, an Extract Information with One result per file, or an attachments capture stored in an array. The message names the node that writes the list. |
| Document Verifications documents, Send File from a variable, each Send Email attachment | One file or a list (attachment or array) | Only when the variable does not exist or is not a file type. |
| Both kinds | The variable is neither declared in the flow nor written by any node, or it is declared with a non-file type (number, object...). A required source is empty: OCR front image, face selfie (and id when the operation compares), send-file URL or variable, create-asset URL or variable, a document with its chosen source empty, an attachment without a variable. |
Extract Information
The editor mirrors the engine's contract and blocks what the engine would reject or silently neuter:
- No named field to extract at all, neither at the top level nor inside a document type.
- A field without a name, a duplicate field name, an unsupported type, a
dictorlist[dict]field without a named subfield, an unnamed or duplicate subfield, a subfield with a type other thanstring,float,int,boolean,date, or more subfields than the maximum. - A document type without a name, a duplicate document type, or a document type with no named fields.
- No source: neither a file variable nor a text variable, or a URL source with an empty URL.
- One result per file on with a source that is not a list (
array) variable. - No result variable (and no package, or per-file mode where the package does not count).
- A result variable whose type does not match:
arraywith One result per file,objectwith a single read.
Incomplete configuration
Every node type has its own required fields, and a missing one is an error on that node: a Message without text, an Interactive Message without text or options, a Conditional without rules or with a rule missing its operator or value, a Form without a form selected, a Login without an identity, an Opt-in without a policy or label, a Send Email without connector, recipient, subject or body, an HTTP Request without a URL, a Python Executor without code, a Set Variable without assignments, a Formula without an expression, and so on. Referenced resources must still exist: a target flow, a form, a policy, an identity or a workflow that was deleted is an error too. The panel names the missing field; the generic message is "The configuration is incomplete".
There is no rule about the source of a Conditional rule. A rule needs an operator and, for operators that compare against something, a value; when you choose an entity as the source, the entity key is required as well. Nothing else about the source is checked.
What only warns
Warnings are shown on the canvas and in the error panel, but the publish goes through. Read them: each one describes a conversation that will run in a way you probably did not design.
| Warning | What happens if you ignore it |
|---|---|
| Error handle not connected | "If this node fails, the conversation is handed off to a human agent instead of following an Error path." A safe fallback, rarely the intended one. |
| Repeated output id across nodes | Two nodes carry the same option id, almost always because one is a paste of the other. The engine follows whichever branch matches first. Delete that output on one node and create it again. This used to block the publish; it now warns. |
| Variable nobody writes | A node reads a variable, through a picker, that no node in this flow assigns. One warning per variable, on its first reader; when several nodes read it, the count is shown. Ignore it if another flow fills the variable before jumping here or it arrives in a campaign's metadata; otherwise the node runs without that data. |
| Reference to an undeclared variable | {{name}} in a text refers to a variable that is not declared and that no node produces. Built-in names (name, phone, borrower_id, now, today and the rest of the system variables) are excluded. |
| Unreachable node | The node cannot be reached from Start. |
| Customer Update without identification | No earlier node in this flow identifies the customer (Login, Read Borrower, Create Borrower, Read Authorization, Read Incoming...). Ignore it if another flow identifies the customer before calling this one. |
| Ask Location without destination | Save as address is on but no variable is selected; or Fixed address is on without a country or city. |
| Point of Contact Verification | No template selected, a backup method without template, backups enabled with none configured, a manual phone without +, or the timeout handle not connected. |
| Document types with One result per file off | The document types are ignored until you turn the option on. |
| Labels too long | Over 20 characters on interactive options, over 24 on keywords and login results: WhatsApp truncates them; web and email show them in full. |
| More than 3 interactive options | WhatsApp switches from buttons to a list and may reject the message. Above 10 it becomes an error. |
| Target flow archived | The Jump To points at a flow that is archived. |
| Form without WhatsApp integration | The selected form has no WhatsApp form behind it. |
| Missing failure messages | Login without a failure message, Ask Email or Ask Phone without a validation error message. |
| Formula custom outputs unused | Custom output handles exist but the output mode is not custom. |
| Response handler collision | One response handler id contains another as a substring; routing can be ambiguous. |
The engine adds its own warnings to the report, visible through the validation endpoints: node_type_not_executable (a node type the engine no longer implements; it fails on every run), unknown_node_type (a type the engine cannot dispatch; the conversation stops on that node), node_missing_position, ORPHAN_NODE (unreachable from Start), RESPONSE_HANDLER_CONNECTED, ALL_HANDLES_CONNECTED, NO_CONSECUTIVE_INTERACTIVE, INTERACTIVE_MAX_OPTIONS (more than 10), INTERACTIVE_HAS_OPTIONS, INTERACTIVE_HAS_BODY, ROUTER_HAS_ROUTES, COLLECTOR_HAS_SLOTS, KEYWORDS_HAS_OPTIONS, ONE_HANDLE_ONE_TARGET, VALID_SOURCE_HANDLE, NO_DUPLICATE_EDGES, NO_DOTTED_TEMPLATE_REF, extract_information_ignored_rows, extract_information_document_types_ignored, extract_information_response_type_mismatch, rule_checks_unavailable and engine_check_unavailable.
Where the editor is stricter than the engine
Two extraction cases are an error in the editor and only a warning in the engine's report:
- A field to extract without a name. The engine drops the row and warns (
extract_information_ignored_rows); the editor blocks, because a row you typed and lost is a mistake. - A result variable of the wrong type. The engine writes what it writes and warns (
extract_information_response_type_mismatch); the editor blocks, because the declared type is what the rest of the flow will read.
The API validation endpoints therefore accept a couple of graphs the editor would not publish.
What is not part of the gate
- Channel compatibility. Whether every node in the flow, and in the flows it jumps to, can run on the channel you are about to use (email in particular) is checked by the Test Flow dialog when you pick a channel to test or share, not by the publish gate. A flow with an email-incompatible node publishes; the dialog is what warns you and disables the email buttons.
- Message length, delivery, templates. What WhatsApp accepts at send time is not checked at publish time; a rejected message shows up as a notice on that conversation.
After publishing: warnings become notices
A warning you ignore does not disappear. When the situation it described happens in a real conversation (a node fails and falls to a human agent, a variable arrives empty, a message is rejected by WhatsApp), the engine leaves a notice on that conversation. You read them in the Chat notices panel and, aggregated by flow and node, in Flow Notices, which links back to the node in the editor. Fixing the warning before publishing is cheaper than reading the notices afterwards.