/workflows in the app) draws it; the backend validates it, compiles it to a Google ADK
Workflow and runs it, streaming one event per node.
No user code runs inside a workflow: routing rules and {{...}} references are evaluated
by the engine itself.
In the Studio
- New workflow — start from scratch (a manual trigger) or from the Tool → Router → Agents template, and give it a name.
- Drag nodes from the palette, or click one to add it. Link them; on a routing node, pick the route of each edge — an edge without a route is drawn red.
- Select a node to configure it. Every text field offers the upstream nodes as chips:
one click inserts the right
{{...}}reference. - Test opens the run panel: enter a payload (JSON, text or form), Run, and follow each node live — status, duration, route taken, output. Replay steps through the last run on the canvas.
- Publish once the validation badge reads Graph valid. This arms the trigger.
Import and export a JSON file
Export in the Studio downloads the workflow as a JSON file named after it:{version, nodes, edges} — the body the
Definitions API stores. A file without nodes and edges is refused.
An imported workflow always starts as a draft. The file carries no trigger URL or HMAC
secret: publishing the copy issues new ones. Agent, tool and workflow ids are kept as
they are: in a graph from another account, point them at your own — an agent that is not
yours stops the run with agent_not_found.
Lifecycle of a workflow
Every save creates a revision (
edit, publish, unpublish, restore). Restoring a
revision always puts the workflow back to draft. Automatic triggers only ever run the
published version, on behalf of the workflow owner.
Nodes
Rule operators for
router, condition and a loop’s until: eq, ne, gt, gte,
lt, lte (numeric), contains (case-insensitive text), in (value in a list),
exists. A rule whose field is empty tests the node’s input.
Structure rules
- Node
id: a letter, then up to 63 letters, digits,_or-. Ids are unique. - At most one trigger per workflow. Without one, the workflow is manual.
- No cycles: repeat work with a
loop, not a back edge. - Only
router,classifier,conditionandtryput arouteon their outgoing edges, and only a route they declare.conditionandtryallow one edge per route. - An
outputnode ends the flow: it cannot have outgoing edges. - A
looportrybody is its own graph with exactly one trigger. Atrybody cannot contain anothertryor aloop. - A subworkflow cannot call its own workflow; chains stop at depth 3.
Final output of a run
The run output is the value of theoutput node reached, or, without one, the output of
the last node reached. A run may end on several nodes — parallel branches from a fan-out,
or two output nodes — and the output is then an object keyed by node id, like a merge.
Only the nodes actually reached appear: the branch a router, classifier, condition
or try did not take contributes nothing.
An output node that is reached decides alone: the other branches’ last nodes are then
left out of the result.
A route that is chosen but has no outgoing edge ends the run normally; the output is then
empty.
References between nodes
A string in a node’sconfig can read the output of an upstream node with
{{node_id}} or {{node_id.path.to.value}}. Path segments are object keys or list
indexes ({{items.0.name}}).
- Alone in the field, the reference keeps the value’s type:
"{{trigger.amount}}"gives the number1500, an object stays an object. - Inside text, it is converted to text: objects and lists become JSON, a missing value becomes an empty string.
- The referenced node must be an ancestor of the node that reads it, otherwise the graph is refused (“is not upstream, its output does not exist yet”).
{{node_id}} always works. A path is checked against what the engine guarantees:
A path that crosses a plain value at run time (for example
{{agent1.amount}} when the
agent answered with text) stops the run with template_ref_invalid instead of silently
producing nothing.
Inside a loop body, the body’s trigger receives {item, index, previous} — previous is
the previous iteration’s output, or the loop input on the first turn. An until
condition reads {{iteration.output...}} and {{iteration.index}} only.
Example
A webhook receives an order; orders above 1,000 are reviewed by an agent.AGENT_ID is the id of one of your agents.
With the payload {"customer": "ACME", "amount": 1500}, order.amount stays the number
1500, the condition takes the true route and the agent receives
Review the order from ACME for 1500 EUR.
Triggers
The trigger node’sconfig.kind decides how a published workflow starts. The payload is
what {{trigger...}} reads.
Email and file triggers use the account connected under
Integrations; without it the trigger stays inactive
(
integration_missing). The first scan of a watched folder only records the files
already there; later scans start one run per new or modified file.
A schedule tick is skipped while the previous scheduled run is still running.
An agent_tool workflow becomes a tool named workflow:<tool_name> that agents can call.
The call waits for the run (120 s at most) and returns {run_id, output}, or an error.
Workflows that call each other through agent_tool or workflow_done stop at a chain of
5, and never loop back to a workflow already in the chain.
Webhook and form URLs
Publishing a webhook or form workflow issues a secret URL, shown in the Studio and byGET /api/workflows/{workflow_id}/triggers:
- webhook:
POST /api/hooks/workflows/{token}— answers202 {"run_id": "..."}; - form:
/forms/{token}in the app, submitted toPOST /api/hooks/forms/{token}.
POST /api/workflows/{workflow_id}/triggers/rotate issues a new token (the old URL stops
working). With hmac: true, the HMAC secret is shown only when it is generated or
rotated; each call must then carry X-Apowerb-Signature: sha256=<hex>, the HMAC-SHA256 of
the raw body with that secret.
404, a bad signature 401, a body
over 256 KiB 413, and more than 60 calls a minute per workflow 429. A public form
accepts 10 submissions a minute per visitor; fields that the form does not declare are
dropped.
Running and following a run
POST /api/workflows/defs/{workflow_id}/run with {"payload": ...} runs the saved
version and streams Server-Sent Events:
Nodes run one at a time, in dependency order: branches after a fan-out are correct but
serialized. Each node output is capped at 1 MiB (
node_output_too_large); HTTP responses
too. Stop a run with POST /api/workflows/{wid}/cancel, wid being the run id carried by
run_started: the current node finishes, no further node starts.
Runs are kept with a status — running, success, error, cancelled — and listed
newest first by GET /api/workflows/runs; each entry carries its trigger, the cause of its
failure and input_available, which tells whether its input is still on disk and the run can
therefore be replayed. GET /api/workflows/runs/{run_id} returns one run. Per-node outputs
are only in the event stream, not stored.
POST /api/workflows/runs/{run_id}/replay starts a new run from the stored input; a
successful run needs ?force=true, since its side effects already happened.
Error codes
Errors carry a stablecode and params (never the raw exception). The most common:
Security notes
- The
httpnode refuses private and internal addresses and setsHostitself. A4xxor5xxanswer does not fail the node: test{{node.status}}downstream.Authorization,Proxy-Authorization,CookieandX-API-Keyheaders cannot be written in a graph, since anyone who reads or exports it would see them; authenticated HTTP calls are not available yet. - Notifications are limited to 30 sends an hour per owner, all channels together.
appgoes to the workflow owner,teamsto the owner’s Teams webhook. - A subworkflow or
workflow_donesource must belong to the same owner; other owners’ workflows answer as not found.
Definitions API
Runs and triggers
All endpoints require authentication and only see the caller’s workflows.
