# UiPath Flow File Format

The `.flow` file is a JSON document at `<ProjectName>.flow` in the project root. It is the **only file you should edit** — other generated files will be overwritten.

## Table of contents

- [Top-level structure](#top-level-structure)
- [Project structure (generated scaffold)](#project-structure-generated-scaffold)
- [Node instance](#node-instance)
- [Layout](#layout)
- [Edge — both ports required](#edge--both-ports-required)
- [Definition entry](#definition-entry)
- [Common node types](#common-node-types)
- [Standard ports by node type](#standard-ports-by-node-type)
- [Implicit error port on action nodes](#implicit-error-port-on-action-nodes)
- [Minimal working example — dice roller](#minimal-working-example--dice-roller)
- [entry-points.json — auto-generated, do not edit](#entry-pointsjson--auto-generated-do-not-edit)
- [Bindings — Orchestrator resource bindings (top-level `bindings[]`)](#bindings--orchestrator-resource-bindings-top-level-bindings)
- [Bindings — Data Fabric entity bindings](#bindings--data-fabric-entity-bindings)
- [Bindings — connector connection binding](#bindings--connector-connection-binding)

## Top-level structure

```json
{
  "id": "<uuid>",
  "version": "<scaffolded file-format version>",
  "name": "MyFlow",
  "nodes": [],
  "edges": [],
  "definitions": [],
  "bindings": [],
  "variables": {},
  "layout": {
    "nodes": {}
  }
}
```

> **Key order is NOT guaranteed — never anchor edits on it.** The skeleton above is illustrative; the CLI does not commit to a stable top-level key sequence or to which optional keys are present. Real flows vary: `runtime` may appear (and has been observed *before* `nodes`, not after `definitions`) or be absent entirely; `bindings`, `variables`, `solutionId`, `projectId`, and a trailing `metadata` object surface in different positions depending on CLI version and what the flow contains. When editing a `.flow`, anchor each `Edit` on the **target array's own key** (`"nodes": [`, `"edges": [`, `"definitions": [`, or `layout.nodes`) located in the text you just `Read` — never on "the key that follows X." See [greenfield.md — Anchoring parallel `.flow` Edits](../author/greenfield.md#anchoring-parallel-flow-edits--anchor-on-what-you-read-not-on-key-order).

Optional top-level `runtime`: a CLI-managed object that appears on some flows (e.g. after `uip maestro flow node add` for an HTTP/connector node) and is absent on others. It is not user-authored — do not add, remove, or anchor on it. Its presence and position are not guaranteed.

**Top-level `version`** = workflow file-format version. **Use the exact value `uip maestro flow init` scaffolds** — do not hand-pick, hardcode, or downgrade it. It is not a semver string; the schema gates on an exact literal for the current file-format version, so an older value (e.g. `"1.0"`, `"1.0.0"`) that worked for a legacy parser will fail for a new flow. `init` always writes the accepted value; preserve it. To see the current value, scaffold a throwaway flow with `init` and read its top-level `version`, or read it from an existing `init`-generated `.flow`.

> **Don't confuse top-level `version` with `definitions[].version` / `typeVersion`.** Node-definition `version` (and matching node-instance `typeVersion`) are validated by `versionSchema`, a regex that accepts both `x.y` and `x.y.z` (`/^\d+\.\d+(\.\d+)?$/`, error `'Version must be in format "x.y" or "x.y.z"'`). Both layers are canonically `x.y`, but the node-level regex still accepts legacy 3-part strings so registry definitions (`"1.0"`) and older scaffolded nodes (`"1.0.0"`) both parse. The two layers report distinct errors, but Zod may collapse a node-level mismatch to path `(root)`. If you see a version-related error at `(root)`, audit the top-level `version` first; if it's correct, check each node's `typeVersion` against the matching `definitions[].version`.

`solutionId` and `projectId` may also appear at the top level — these are auto-populated by the project scaffold and packaging. Preserve the generated values.

> **`bindings[]`** holds Orchestrator resource references for `uipath.core.*` resource nodes (rpa, agent, flow, agentic-process, api-workflow, hitl) and for connector-node connections. See [Bindings — Orchestrator resource bindings](#bindings--orchestrator-resource-bindings-top-level-bindings) below and the [connector plugin](../author/plugins/connector/impl.md) for the connector-binding shape.

## Project structure (generated scaffold)

```
<ProjectName>/
├── project.uiproj          # { "Name": "...", "ProjectType": "Flow" }
├── <ProjectName>.flow      # ← edit this
├── bindings_v2.json        # resource bindings
├── entry-points.json       # input/output schema declarations
├── operate.json            # runtime options
└── package-descriptor.json # packaging manifest
```

## Node instance

```json
{
  "id": "rollDice",
  "type": "core.action.script",
  "typeVersion": "1.0",
  "display": { "label": "Roll Dice" },
  "inputs": {
    "script": "return { roll: Math.floor(Math.random() * 6) + 1 };"
  },
  "outputs": {
    "output": {
      "type": "object",
      "description": "The return value of the script",
      "source": "=result.response",
      "var": "output"
    },
    "error": {
      "type": "object",
      "description": "Error information if the script fails",
      "source": "=Error",
      "var": "error"
    }
  }
}
```

**Required fields on every node**: `id`, `type`, `typeVersion`, **`display`** (with at least a `label`). This applies to **every** node — triggers (`core.trigger.manual`, `core.trigger.scheduled`, connector triggers), action nodes, control-flow nodes (`core.control.end`, `core.logic.terminate`), and human-task nodes. The Zod `nodeSchema` declares `display: displayConfigSchema` without `.optional()`, so no node type is exempt — even ones that "feel" trivial.

`typeVersion` must match the corresponding `definitions[].version` exactly. The registry often returns versions such as `"1.0"` while older examples or scaffolded files may show `"1.0.0"`. If a node uses `typeVersion: "1.0.0"` but the copied definition is `"version": "1.0"`, validation reports "Node type `<type>:1.0.0` has no matching definition." When direct-authoring a new node from `registry get`, set `typeVersion` to the copied definition's `version`; when preserving an existing node, preserve its existing node/definition pair unless you intentionally update both together. <!-- version-check-skip --> (explains the x.y vs x.y.z mismatch mechanic; literals are illustrative, not pinned)

> **Gotcha — vague schema-validation error on missing `display`.** Omitting `display` on any node produces:
>
> ```
> [error] [(root)] Schema validation failed: Invalid input: expected object, received undefined
> ```
>
> The error path is `(root)` and does NOT pinpoint which node or which field is missing. If you see this error after editing a `.flow` file, audit every node for a `display` block before doing anything else.

> **No instance `model` block.** BPMN type, serviceType, event definition, and binding/context templates all live in the node's **definition** (the manifest copied from the registry into `definitions[]`). The runtime hydrates them from the definition at serialization time — instances carry only per-instance data (`inputs`, `outputs`, `display`). This applies to every inline-agent-related node too: `uipath.agent.autonomous` plus every attached `uipath.agent.resource.*` node (tool, escalation, context) carries source identity at `inputs.source`. Their definitions declare `model.source: true`; flow-core hoists that identity onto each instance's `inputs.source`. Do not write a `"model": { "source": ... }` block on the instance.
>
> **No `ui` block on nodes.** Position and size are stored in the top-level `layout` object, not on individual nodes. See [Layout](#layout) below.

### Instance-specific identity fields

A few per-instance identity fields live on the node instance:

| Field | Used by | Purpose |
|-------|---------|---------|
| `inputs.entryPointId` | All trigger nodes (`core.trigger.manual`, `core.trigger.scheduled`, connector triggers) | Stable UUID identifying the entry point |
| `inputs.isDefaultEntryPoint` | Trigger nodes in subflows | Boolean marking the default entry point when a subflow has multiple triggers |
| `inputs.source` | `uipath.agent.autonomous` and every attached `uipath.agent.resource.*` node (tool, escalation, context) | For `uipath.agent.autonomous`: the inline agent's `projectId`. For resource nodes: the attached resource UUID. Definitions declare `model.source: true`; flow-core hoists onto the instance — no instance `model` block. |
| `inputs.color`, `inputs.content` | Sticky-note nodes | Visual content of the sticky note |

Example — manual start trigger:

```json
{
  "id": "start",
  "type": "core.trigger.manual",
  "typeVersion": "1.0",
  "display": { "label": "Manual trigger" },
  "inputs": {
    "entryPointId": "3d4a8c34-5682-4ebe-a6bc-d92a18830bb5"
  },
  "outputs": {
    "output": { "type": "object", "description": "Data passed when manually triggering the process.", "source": "null", "var": "output" }
  }
}
```

### Node outputs

`$vars.<sourceNodeId>.<outputId>` resolution at runtime is driven by **`variables.nodes[]`**, not by the node instance's `outputs` block. The BPMN emitter walks `variables.nodes[]` to write the process-level `<uipath:inputOutput id="<nodeId>.<outputId>">` declarations the runtime needs; the action-node instance `outputs` block is ignored at serialization (the manifest's `outputDefinition` supplies the activity-side mapping). End / terminate nodes are the exception — their instance `outputs` block IS consumed to map workflow-level `out` variables. See [end/impl.md](../author/plugins/end/impl.md).

The canonical recipe for a data-producing node is therefore:

- `definitions[]` entry copied verbatim from `uip maestro flow registry get` (carries the manifest `outputDefinition`).
- `variables.nodes[]` entry per output: `{ "id": "<nodeId>.<outputId>", "type": "object", "binding": { "nodeId": "<nodeId>", "outputId": "<outputId>" } }`.
- Optional instance `outputs` block matching the manifest — harmless and matches the canonical examples below for clarity, but **not** what controls runtime variable visibility.

Skipping `variables.nodes[]` produces a flow that passes `flow validate` but resolves `$vars.<sourceNodeId>.output` to `undefined` at runtime. `uip maestro flow format` regenerates `variables.nodes[]` from `nodes[]` + `definitions[]`, so always run it after structural edits — the omission becomes self-healing.

When you DO author the instance `outputs` block (for documentation / parity with manifest schema), use the shape below. Each output entry has:

- `type` — data type (usually `"object"`)
- `description` — human-readable description
- `source` — runtime binding expression, copied from the manifest `outputDefinition`. `"=Error"` for errors, always.
- `var` — the variable name (matches the output ID, e.g., `"output"`, `"error"`)

**Orchestrator-job nodes (api-workflow, rpa-workflow, agent, agentic-process, function): declare `error` only — `output` is derived.** They are the one family whose instance block the converter reads: it copies an authored `source` verbatim, and injects `{output, jsonSchema, "=this"}` when a non-empty `outputs` omits `output`. So `"=result.response"` there — the connector/script source — leaves `$vars.<nodeId>.output` null at runtime while `flow validate` passes. Studio Web serializes `error`-only instances too (verified on api-workflow, published-flow, and inline-agent nodes). Subflow and published-flow (`uipath.core.flow.*`) instance blocks are never read — still declare `error` only.

The standard `outputs` block for most action nodes (script, HTTP, transform, connector):

```json
"outputs": {
  "output": {
    "type": "object",
    "description": "The return value of the <node type>",
    "source": "=result.response",
    "var": "output"
  },
  "error": {
    "type": "object",
    "description": "Error information if the <node type> fails",
    "source": "=Error",
    "var": "error"
  }
}
```

Trigger nodes (manual, scheduled, connector triggers) have a single output — no error port. A manual trigger carries the literal string `"null"` as its `source`, matching what Studio Web writes:

```json
"outputs": {
  "output": {
    "type": "object",
    "description": "Data passed when manually triggering the process.",
    "source": "null",
    "var": "output"
  }
}
```

End/terminate nodes do **not** use this pattern — their `outputs` maps workflow-level output variables (see the [Author end plugin reference](../author/plugins/end/impl.md)).

## Layout

Node positioning is stored in a **top-level `layout` object**, keyed by node `id`. The same shape applies inside each subflow as `subflows[<id>].layout`. Layout data is owned by `uip maestro flow format` (see [cli-commands.md](cli-commands.md#uip-maestro-flow-format)) — you should not need to hand-write it.

```json
"layout": {
  "nodes": {
    "start": {
      "position": { "x": 200, "y": 144 },
      "size": { "width": 96, "height": 96 },
      "collapsed": false
    },
    "rollDice": {
      "position": { "x": 400, "y": 144 },
      "size": { "width": 96, "height": 96 },
      "collapsed": false
    },
    "end": {
      "position": { "x": 600, "y": 144 },
      "size": { "width": 96, "height": 96 },
      "collapsed": false
    }
  }
}
```

Each key in `layout.nodes` is a node `id`. `flow format` creates an entry for every node and populates `position` + `size`.

**What format does:**
- Arranges nodes horizontally (left-to-right) with `nodeSpacing: 96`, anchored to the leftmost node's original position
- Sets each node's `size` to match its canvas shape: inline agents (`uipath.agent.autonomous` / `uipath.agent.conversational`, `shape: rectangle`) → `{ "width": 288, "height": 96 }`; containers (loops/groups) → `{ "width": 560, "height": 320 }`; everything else — including referenced `uipath.core.agent.<guid>` nodes — → `{ "width": 96, "height": 96 }`. A size that disagrees with the node's shape renders misshapen in Studio Web.
- Skips `stickyNote` nodes from layout (they keep their custom position and size)
- Recurses into every subflow and rewrites its `subflows[<id>].layout` map

**Subflow layout is scoped.** Each subflow entry in `subflows[<id>]` has its **own** `layout.nodes` map for the nodes inside that subflow — they do NOT live in the top-level `layout.nodes`. Format handles both passes. See the [Author subflow plugin reference](../author/plugins/subflow/impl.md).

## Edge — both ports required

```json
{
  "id": "edge-start-rollDice",
  "sourceNodeId": "start",
  "sourcePort": "output",
  "targetNodeId": "rollDice",
  "targetPort": "input"
}
```

> **Gotcha**: `targetPort` is required. Omitting it produces `[error] [edges[N].targetPort] Invalid input: expected string, received undefined` at validate time.
>
> **Gotcha**: the source field is `sourcePort`, not `sourceHandle`. If you write `sourceHandle`, validation fails with `[error] [edges[N].sourcePort] Invalid input: expected string, received undefined` — the path identifies the offending edge entry exactly.
>
> **Gotcha — edge `id` MUST start with a letter (XML NCName).** Never use a bare UUID or any id with a leading digit (`"12bd09dd-…"`, `"1edge-start"`). Edge ids become BPMN `<bpmn:incoming>/<bpmn:outgoing>` IDREFs; a leading digit makes the converter silently drop those references while still emitting the `sequenceFlow`, so `flow validate` passes and upload succeeds — but the engine cannot traverse: the run reports **Completed having executed only the start node**, every output null. Use descriptive ids (`e-<source>-<target>`, e.g. `e-start-agent`); prefixing a letter (`e12bd09dd-…`) also works. Same rule applies to node ids.

## Definition entry

Every node type appearing in `nodes` must have a matching entry in `definitions`. Get the correct definition from:

```bash
uip maestro flow registry get core.action.script --output json
```

Copy the returned node definition object into your `definitions` array. Depending on CLI/plugin version, that object may appear at `Data.Node` or as the top-level object containing fields such as `nodeType`, `version`, and `handleConfiguration`. Do not write definitions by hand — always pull from the registry to ensure schema compliance.

## Common node types

| Type | Purpose | Key inputs |
|------|---------|------------|
| `core.trigger.manual` | Entry point | `entryPointId` |
| `core.trigger.scheduled` | Recurring schedule trigger | `entryPointId`, `timerType`, `timerPreset` |
| `core.action.script` | Run JavaScript | `script` |
| `core.action.http.v2` | HTTP request | `method`, `url`, `headers`, `body` |
| `core.action.transform` | Map/filter/group data | `collection`, `operations` |
| `core.logic.decision` | If/else branch | `expression` |
| `core.logic.switch` | Multi-way branch | `cases` |
| `core.logic.loop` | Iterate collection | `collection`, `parallel` |
| `core.logic.merge` | Sync parallel paths | — |
| `core.control.end` | Graceful end | — |
| `core.logic.terminate` | Abort workflow | — |

> The BPMN type for each node (e.g., `bpmn:StartEvent`, `bpmn:ScriptTask`) lives in the `definitions` entry copied from `uip maestro flow registry get`. Instances do not carry the BPMN type.

For full details on each node (ports, inputs, outputs, when to use), see the [Author planning architecture guide](../author/planning-arch.md). For implementation resolution (registry lookups, connection binding, reference field resolution), see the [Author planning implementation guide](../author/planning-impl.md).

Discover all available types:
```bash
uip maestro flow registry list --output json
uip maestro flow registry search <keyword>
```

## Standard ports by node type

| Node type | Source ports (outgoing) | Target ports (incoming) |
|-----------|------------------------|------------------------|
| `core.trigger.manual` | `output` | — |
| `core.action.script` | `success`, `error` | `input` |
| `core.action.http.v2` | `default`, `error`, `branch-{id}` (dynamic) | `input` |
| `core.action.transform` | `output`, `error` | `input` |
| `core.logic.decision` | `true`, `false` | `input` |
| `core.logic.switch` | `case-{id}` (dynamic), `default` | `input` |
| `core.logic.loop` | `success`, `error` (outer), `start` (inner) | `input` (outer), `continue`, `break` (inner) |
| `core.logic.merge` | `output` | `input` |
| `core.control.end` | — | `input` |
| `core.logic.terminate` | — | `input` |

Connector activities, agent nodes, and RPA nodes follow the same pattern as the generic action nodes above: a primary source port plus an implicit `error` port. The `core.datafabric.*` nodes are the exception — `input` and `output` only.

Verify exact ports for any node type:
```bash
uip maestro flow registry get <node-type> --output json
# Look at Data.Node.handleConfiguration[].handles[].id
# Also check Data.Node.supportsErrorHandling — see "Implicit error port" below
```

## Implicit error port on action nodes

Any node with `supportsErrorHandling: true` in the registry exposes an implicit `error` source port for catching node-level failures. This applies to HTTP, Script, Transform (all variants), connector activities, agent nodes, and RPA nodes — most action nodes, but not all: the `core.datafabric.*` family does not declare it and has no `error` port ([data-fabric/impl.md](../author/plugins/data-fabric/impl.md#no-error-port)). Check the registry rather than assuming.

The port is **not** listed in the registry's `handleConfiguration`. Studio Web only exposes it when the source node has `inputs.errorHandlingEnabled: true`; when the flow contains an outgoing edge with `sourcePort: "error"` from that node, the serializer emits a BPMN boundary error event attached to the node. Because of this gate, `uip maestro flow validate` reports an error when a node has an outgoing `sourcePort: "error"` edge but `inputs.errorHandlingEnabled` is not `true` — so the inconsistency is caught before publish rather than surfacing as a hidden edge in Studio Web.

### Default: off — enable only for a failure the flow actually handles

`inputs.errorHandlingEnabled` is **opt-in, and stays off unless the requirements name a failure fallback.** Turning it on suppresses the node's fault: the node returns instead of faulting and execution continues. Enable it only when both hold:

1. The requirements state what should happen when this node fails ("if the call fails, …", "return X for invalid input", "handle timeouts") — **and**
2. You wire the node's `error` port to a handler that produces an outcome distinguishable from success.

Never set the flag on a node that has no outgoing `error` edge — it suppresses the fault with nothing to catch it, converting a real failure into a run that reports success. Let the CLI own the flag: `uip maestro flow edge add --source-port error` and `uip maestro flow format` set it from the error edges actually present. If you find the flag on a node with no error edge, remove it.

### Do not swallow the failure

An `error` edge must not rejoin the happy path. When it does, every failure walks the success route and the run reports `Completed` while the work never happened — the flow "always looks successful."

| | Error-path target | Result |
| --- | --- | --- |
| ✗ | The next node on the happy path | Failure is invisible; downstream nodes run on missing data |
| ✗ | The same End node the success path reaches | Success path's output mappings run against the failed node's empty output |
| ✓ | A **distinct** End node mapping an error/status `out` variable | Caller can tell failure from success |
| ✓ | `core.logic.terminate` | Aborts the flow when recovery is impossible — see [terminate/impl.md](../author/plugins/terminate/impl.md) |
| ✓ | A recovery branch that rejoins **only after obtaining valid data** — a retry that succeeded, or a fallback source that returned data | Downstream runs on real data, not on the failed node's empty output |

```text
Trigger -> HTTP Request
  |-- default -> Process -> End (success — status: "ok")
  |-- error   -> Log Error -> End (failure — status: "failed", message from $vars.httpCall.error)
```

### When the error port fires

- Network failures, DNS errors, TLS errors
- Request timeouts
- Non-2xx HTTP responses (unless caught by a configured `inputs.branches` entry)
- Script exceptions (`throw`, undefined reference, etc.)
- Transform operation failures (invalid collection, missing field)
- Any unhandled runtime exception inside the node

Without a wired error edge, any of these fails the whole flow with `finalStatus: "Faulted"`. **That is the correct default, not a defect to design around** — a faulted run is visible to the operator; a swallowed failure is not. Only trade the fault for an error path when the requirements say what that path should do.

### Wiring the error port

```bash
# Confirm the node supports error handling
uip maestro flow registry get <node-type> --output json --output-filter "Node.SupportsErrorHandling"

# Add an outgoing edge with sourcePort: "error"
uip maestro flow edge add <Project>.flow <actionNodeId> <errorHandlerId> \
  --source-port error --target-port input --output json
```

`uip maestro flow edge add --source-port error` and `uip maestro flow format` set `inputs.errorHandlingEnabled: true` on the source node automatically — only for nodes that have an error edge. When editing `.flow` JSON directly, set the flag yourself **on those nodes only**:

```json
{
  "id": "<actionNodeId>",
  "inputs": {
    "errorHandlingEnabled": true
  }
}
```

Inside the error handler, `$vars.{actionNodeId}.error` resolves to the error object. For HTTP it's `{ code, message, detail, category, status }`; other nodes have similar shapes — check the node's `outputDefinition.error.schema` in the registry.

### Error port vs other branching

| Mechanism | When to use |
| --- | --- |
| **`error` source port** (any action node) | The node failed (exception, timeout, non-2xx not caught by a branch). Generic "something went wrong" handler. |
| **`branch-{id}` ports** (HTTP only, `inputs.branches`) | The call succeeded and you want to route on response *content* — different paths for e.g. empty vs non-empty results. |
| **`core.logic.decision` downstream** | Simple yes/no routing on the node's successful output. Doesn't help if the node itself fails. |
| **`core.logic.switch` downstream** | Multi-way routing on the node's successful output. Same — doesn't catch failures. |

## Minimal working example — dice roller

Building a flow is a two-step process: write the nodes/edges structure, then populate `definitions` from the registry.

### Step 1 — Write nodes and edges

Replace `<uuid>` with any generated UUID (e.g. `crypto.randomUUID()` in Node.js, or any UUID v4 generator) — this applies ONLY to the top-level flow `id` and `entryPointId` (the same UUID must appear in `entry-points.json` as `uniqueId`). **Node and edge ids are NOT UUIDs** — they must start with a letter (see the Edge gotcha above). Set top-level `version` to the value `uip maestro flow init` scaffolds — never hand-pick it (see [Top-level structure](#top-level-structure)).

```json
{
  "id": "3d4a8c34-5682-4ebe-a6bc-d92a18830bb5",
  "version": "<scaffolded file-format version>",
  "name": "DiceRoller",
  "nodes": [
    {
      "id": "start",
      "type": "core.trigger.manual",
      "typeVersion": "1.0",
      "display": { "label": "Manual trigger" },
      "inputs": {
        "entryPointId": "<uuid>"
      },
      "outputs": {
        "output": {
          "type": "object",
          "description": "Data passed when manually triggering the process.",
          "source": "null",
          "var": "output"
        }
      }
    },
    {
      "id": "rollDice",
      "type": "core.action.script",
      "typeVersion": "1.0",
      "display": { "label": "Roll Dice" },
      "inputs": {
        "script": "return { roll: Math.floor(Math.random() * 6) + 1 };"
      },
      "outputs": {
        "output": {
          "type": "object",
          "description": "The return value of the script",
          "source": "=result.response",
          "var": "output"
        },
        "error": {
          "type": "object",
          "description": "Error information if the script fails",
          "source": "=Error",
          "var": "error"
        }
      }
    },
    {
      "id": "end",
      "type": "core.logic.terminate",
      "typeVersion": "1.0",
      "display": { "label": "End" },
      "inputs": {}
    }
  ],
  "edges": [
    {
      "id": "edge-start-roll",
      "sourceNodeId": "start",
      "sourcePort": "output",
      "targetNodeId": "rollDice",
      "targetPort": "input"
    },
    {
      "id": "edge-roll-end",
      "sourceNodeId": "rollDice",
      "sourcePort": "success",
      "targetNodeId": "end",
      "targetPort": "input"
    }
  ],
  "definitions": [],
  "bindings": [],
  "variables": {},
  "layout": {
    "nodes": {
      "start": {
        "position": { "x": 200, "y": 144 },
        "size": { "width": 96, "height": 96 },
        "collapsed": false
      },
      "rollDice": {
        "position": { "x": 400, "y": 144 },
        "size": { "width": 96, "height": 96 },
        "collapsed": false
      },
      "end": {
        "position": { "x": 600, "y": 144 },
        "size": { "width": 96, "height": 96 },
        "collapsed": false
      }
    }
  }
}
```

### Step 2 — Populate definitions from the registry

Run one command per node type used in `nodes`. Copy the returned node definition object from each response into the `definitions` array, and set each matching node instance's `typeVersion` to the copied definition's exact `version`.

```bash
uip maestro flow registry get core.trigger.manual --output json
uip maestro flow registry get core.action.script --output json
uip maestro flow registry get core.logic.terminate --output json
```

The `definitions` array must contain exactly one entry per unique `type:typeVersion` used — not one per node instance. If two nodes share the same type and version, one definition covers both.

> **Never write definitions by hand.** The registry is the authoritative source; hand-written definitions will fail validation or cause runtime errors.

## entry-points.json — auto-generated, do not edit

`entry-points.json` declares the flow's external interface (input/output schemas and trigger entry points). Preserve its lifecycle-generated contents; project scaffolding creates it, and the Flow lifecycle regenerates it before execution or publication.

Flow input and output parameters are declared through **variables** in the `.flow` file:
- **Flow inputs**: Add entries to `variables.nodes[]` whose `binding.nodeId` is the start node and whose `binding.outputId` names each input value — the start node "outputs" input values to downstream nodes
- **Flow outputs**: Add output variables to the end/terminate node
- Downstream nodes reference inputs via `$vars.start.output.<paramName>`

The packaging/debug step derives `entry-points.json` from these variable declarations.

## Bindings — Orchestrator resource bindings (top-level `bindings[]`)

The top-level `bindings` array (a sibling of `nodes`, `edges`, `definitions`, `variables`, `layout`) holds resource-reference indirections for **Orchestrator resource nodes** — RPA workflows, agents, flows, agentic processes, API workflows, and HITL apps.

Folder-scoped Data Fabric entities also write rows into this same array, but resolve them by a **different rule**. Everything in this section describes the Orchestrator-resource form; see [Bindings — Data Fabric entity bindings](#bindings--data-fabric-entity-bindings) below for how the `Entity` kind differs.

Each resource node needs two binding entries (one for `name`, one for `folderPath`). The node instance itself has no binding or context data — just `inputs`. The definition (copied verbatim from the registry) carries `model.context[]` templates like `<bindings.name>` and `<bindings.folderPath>`. At BPMN emit time the runtime rewrites those placeholders to `=bindings.<id>` by matching the placeholder name against a workflow-level binding, scoped by the definition's `model.bindings.resourceKey`.

```json
"bindings": [
  {
    "id": "<UNIQUE_ID>",
    "name": "name",
    "type": "string",
    "resource": "process",
    "resourceKey": "<FolderPath>.<ResourceName>",
    "default": "<ResourceName>",
    "propertyAttribute": "name",
    "resourceSubType": "Process"
  },
  {
    "id": "<UNIQUE_ID_2>",
    "name": "folderPath",
    "type": "string",
    "resource": "process",
    "resourceKey": "<FolderPath>.<ResourceName>",
    "default": "<FolderPath>",
    "propertyAttribute": "folderPath",
    "resourceSubType": "Process"
  }
]
```

**Rules:**

- Add **two entries** per resource node (one for `name`, one for `folderPath`).
- **Share** entries across node instances that reference the same resource — do not duplicate. Matching is by `(resourceKey, name)`, so any node whose definition has the same `resourceKey` resolves to the same binding pair.
- Entry IDs are unique strings within the file. Descriptive IDs (e.g. `bDepositRpaName`) are preferred over short random IDs.
- The node instance has no `model` block — it carries only `inputs`, `outputs`, and `display`.
- `resourceKey` must exactly match the definition's `model.bindings.resourceKey` (verbatim from the registry). The runtime uses this key to scope placeholder resolution so that binding names like `name` / `folderPath` (shared across resource kinds) don't cross-alias.
- `resourceSubType` mirrors the definition's `model.bindings.resourceSubType`: `Process` (rpa), `Agent` (agent), `Flow` (flow), `ProcessOrchestration` (agentic-process), `Api` (api-workflow), or the app type for HITL.

**Why this is required.** The definition's `model.context[].value` fields are placeholders of the form `<bindings.{name}>` — deliberately invalid as runtime expressions, so they can't be confused with one. Before the BPMN is emitted, the runtime rewrites each placeholder to `=bindings.<id>` by finding a workflow-level binding with `(resourceKey, name)` matching the node's manifest `model.bindings.resourceKey` + the placeholder name. Without matching entries in top-level `bindings[]`, `uip maestro flow debug` fails with "Folder does not exist or the user does not have access to the folder" even though `uip maestro flow validate` passes.

**Definitions stay verbatim.** Do NOT rewrite `<bindings.*>` placeholders inside the `definitions` entry — the definition is the authoring template. See "Every node type needs a `definitions` entry" in [author/CAPABILITY.md](../author/CAPABILITY.md).

See each resource plugin's `impl.md` for the full JSON per node type: [rpa](../author/plugins/rpa/impl.md), [agent](../author/plugins/agent/impl.md), [flow](../author/plugins/flow/impl.md), [agentic-process](../author/plugins/agentic-process/impl.md), [api-workflow](../author/plugins/api-workflow/impl.md), [hitl](../author/plugins/hitl/impl.md).

**Not to be confused with `bindings_v2.json`.** That file holds connector connection bindings for Integration Service nodes — a separate system. A flow may have both: a top-level `bindings[]` for resource references and a `bindings_v2.json` file for connector connections.

## Bindings — Data Fabric entity bindings

A **folder-scoped** Data Fabric entity (`core.datafabric.*` nodes) writes two rows into the same top-level `bindings[]` array, but they are not Orchestrator resource bindings and most of the rules in the section above do not carry over:

| Aspect | Orchestrator resource nodes | Data Fabric `Entity` |
| --- | --- | --- |
| `resource` | `process`, `agent`, … | `Entity` — capitalized; a lowercase row is ignored by packaging, so deploy emits no override |
| Matched by | `(resourceKey, name)` | `(resourceKey, propertyAttribute)`, falling back to `name` only when `propertyAttribute` is absent |
| `name` holds | the placeholder name (`name` / `folderPath`) | a **display label** — the entity name, and `<entityName>Folder` for the folder row |
| `propertyAttribute` | `name` / `folderPath` | `name` / **`folderKey`** — never `folderPath` |
| `resourceSubType` | required, mirrors the definition | **absent** — Entity bindings have no definition-side `model.bindings` |

The value is in `default` for both forms. There is also no `<bindings.{name}>` placeholder to rewrite: the entity has no definition-side `model.context[]`, and the serializer emits `bindings.<id>` tokens directly into the `=datafabric[...]` expression it builds.

`folderKey` rather than `folderPath` is load-bearing. The platform's deploy-time override for an `Entity` resource carries `{ name, folderPath, folderKey }` and rewrites binding defaults by `propertyAttribute`; a `folderPath` row receives the Orchestrator FQN, which the Data Fabric query rejects even in the source org.

Tenant-scoped entities need no bindings at all — they serialize as a dotted literal. See [data-fabric/impl.md — Folder-scoped entities and bindings](../author/plugins/data-fabric/impl.md#folder-scoped-entities-and-bindings) for the full JSON and when to hand-author it.

## Bindings — connector connection binding

When a flow uses connector nodes, the runtime needs to know **which authenticated connection** to use for each connector. This is configured in `content/bindings_v2.json`.

See the relevant node guide in `nodes/` for the full `bindings_v2.json` schema, connection resource field reference, JSON examples, and the connection fetching workflow.
