# Outputs, alerts and actions

> Where a workflow's result goes: the Output node, the Alert node, watchlist and metric writes, HTTP calls and child workflows.

Source: https://orbitalaip.com/docs/workflows/outputs-alerts-and-actions

## Output

An **Output** node names the run's result. A dashboard widget binds to an output by its **Output Name**, and **Run workflow** returns it to a calling workflow. Give each Output its own name; the default is Output.

**Output Format** is **JSON (Structured)** for charts and tables, or **Text** for narratives and summaries. Text takes the incoming data's narrative, summary, response or text field, and otherwise prints the data as JSON. Columns and expectations apply to **JSON (Structured)** only; a **Text** output skips them, even though the settings still show both.

### Columns

**Use upstream schema** is ticked by default and passes the data through untyped. Untick it and click **Add column** for each column; **Pin typed columns** unticks it and adds the first column in one click. To return to the upstream shape, delete the columns first: saved columns still apply when the box is back on.

Each column has a name, a type (**string**, **number**, **boolean**, **timestamp**, **array** or **json**) and a **required** box. Its source is a field or a dot path on the incoming data, such as signal.score; left blank, it is the column name. Values are converted to the declared type. A value that cannot be converted, or a required value that is missing, fails the node.

Columns named symbol, action and asOf mark the node **Produces trade signals**; see [how a saved workflow becomes a strategy](https://orbitalaip.com/docs/backtesting/overview#saving-a-strategy-enrols-it).

### Expectations

**Add expectation** adds a check on the shaped output. **Not null**, **Unique**, **In range**, **In set** and **Fresh within** each check one column; **Min rows** and **Max rows** check the whole output. Each is **Blocking — fails the run** or **Advisory — warns only**.

A failed blocking check fails the node, and the error names the output and each failed check; the run then follows **On node failure** in **Workflow Settings**.

## Alert

An **Alert** node fires a notification as its **Decision mode** says, held back by **Cooldown (seconds)** and **Content-aware dedup**. Its **Fired**, **Event**, **Message** and **Verdict (passthrough)** outputs let later nodes act on whether it fired. See [When the node fires](https://orbitalaip.com/docs/alerts-and-notifications/alerts-from-workflows#when-the-node-fires).

Every fire writes one row in **Notifications** and reaches every destination your **Notifications** settings route **Alerts** to, and **Channels** adds destinations for that node only. See [Where notifications are delivered](https://orbitalaip.com/docs/alerts-and-notifications/notifications#where-notifications-are-delivered).

## Post to a channel

**Post to a channel** cannot post, because the Slack plugin it needs is not available.

## Write to a watchlist

**Watchlist Write** replaces, adds, removes, creates or deletes a saved list on every run, and refuses rather than guesses. See [Update a watchlist from a workflow](https://orbitalaip.com/docs/watchlists/use-with-workflows-and-dashboards#update-a-watchlist-from-a-workflow).

## Write a metric

**Metric Write** stores numbers from the run as a time series. **Metric name** starts with a letter and uses letters, digits, dots, dashes and underscores, such as discovery.score. **Value path**, default value, is where the number sits in each row, and **Entity key path**, default symbol, says what each number belongs to.

Rows with no finite number at the value path, or no key, are skipped and counted in **Summary**. Read a series back with the **Metric History** node, or chart it on a dashboard; see [Widgets](https://orbitalaip.com/docs/dashboards/widgets).

## Send a webhook or HTTP request

**Send Webhook / HTTP Request** calls an external HTTP API with data from the run. Set **Method** (the default is **POST**), **URL**, **Headers** as a JSON object and **Body**, which is ignored for **GET**. **URL**, the header values and **Body** accept double-brace placeholders, with the incoming data under data, such as {{data.symbol}}.

```json title="Body"
{ "symbol": "{{data.symbol}}", "score": {{data.score}} }
```

**Auth** is **None**, **Bearer token**, **Header token (custom header)** or **HMAC-sign the body (X-Orbital-Signature)**. **Auth secret / token** holds the token or the signing secret.

> [!WARNING] The secret is saved with the workflow
> **Auth secret / token** is stored in the node's configuration. Anyone who can open the workflow can read it, so use a token you can revoke.

**On failure** is **Best-effort** by default: a failed call is logged and the run goes on. **Fail the run on a non-2xx / error (transactional)** fails the node instead. **Timeout (ms)**, default 10000, is held between 1 and 60 seconds, and **Retries (network errors)**, default 0, goes up to 5 and never retries a response.

The URL must be http or https. Private, local and loopback addresses are refused, redirects are not followed, and a call back to Orbital's own address is refused: use **Run workflow** instead. The outputs are **Status**, **OK (2xx)**, **Headers**, **Body (parsed)**, **Body (raw)** and **Error**, so an **If / else** can branch on the answer.

## Run workflow

**Run workflow** runs another workflow in the same project to completion and returns its outputs. Pick it in **Workflow**, and wire a JSON object to **Inputs** to fill the values the child's own **Run** dialog would ask for. **Fail if the child fails** is on by default; turn it off to branch on a failed child, whose **Status** and **Outputs** still come back.

The child runs its [main](https://orbitalaip.com/docs/glossary#main) version, or its canvas when it has no main. Its time budget is the parent's **Node timeout (minutes)**, cut short when the parent's **Run timeout (minutes)** is nearly spent. Children nest at most three levels below the first workflow, and a **Run workflow** node that would go deeper fails.

## Behind an If / else

An **If / else** node sends its data down the branch it chose and hands the other branch nothing, but the nodes on that branch still run. **Alert**, **Post to a channel**, **Send Webhook / HTTP Request** and **Watchlist Write** skip when every wired input comes from the branch that was not taken. **Run workflow** skips when its **Inputs** port is on that branch, and **Metric Write** writes no rows. The skip reaches one node only, so a node downstream of a skipped node runs as normal.
