> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ingestly.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Correlate

> Wait for related documents that arrive as separate runs, then continue with all of them together.

The **Correlate** node waits for a set of related documents, each processed as its own run, and joins them by a shared key. Use it when a packing slip and its invoice arrive by email minutes or days apart: the first one to reach Correlate parks, and the run continues only once every expected document for that key has shown up.

## When to use Correlate

* Related documents **arrive separately**, as their own runs, and you need one run downstream that sees all of them: a packing slip and an invoice matched by PO number, a purchase order and its receiving confirmation.
* You do not know **which document arrives first**, or how far apart. Correlate parks whichever one gets there first and continues when the set is complete, in either order.

If every document you need is already in the same run (for example fan-out branches from one upload), use [Merge](/nodes/merge) instead: Merge joins branches within a run, Correlate joins across runs.

## Roles

Configure 2 to 5 **roles** under **Roles**. A role is one document the set waits for.

| Field      | Required | Description                                                                                                                                                             |
| ---------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Label**  | Yes      | Display name for the role, up to 80 characters, for example `Invoice`                                                                                                   |
| **Slug**   | Yes      | Identifier used to reference this role's data downstream: lowercase letters, digits and underscores, starting with a letter, up to 40 characters, for example `invoice` |
| **Source** | Yes      | The upstream node whose branch delivers this role. Must connect directly to Correlate with an edge                                                                      |
| **Key**    | Yes      | A template that resolves to this role's matching value, for example `{{extractInvoice.payload.poNumber}}`                                                               |

A run's role is decided by which source node ran on that branch, so each role's source must be its own node, directly wired into Correlate. Each role has its own **Key** template because upstream branches often name the same value differently, for example one branch's `poNumber` and another's `purchaseOrderNumber`.

## Timeout

Set **Timeout (minutes)** to how long a parked run waits for the rest of its set. The value must be between **5 and 129600** minutes (90 days) and defaults to **20160** minutes (14 days).

## How keys match

The resolved key is trimmed and matched **exactly**, including case. `PO-123` and `po-123` are different keys; `PO-123` and `PO-123` are the same key once trimmed. A key must resolve to a single value, not a list or an object, and can be at most 256 characters.

A key is scoped to one Correlate node in one workflow. The same key value on a different workflow, or a different Correlate node in the same workflow, never matches this one.

## Behavior

* **First document for a key:** the run parks. Its status shows as [Waiting](/runs/introduction#run-lifecycle), and the run detail shows "Waiting for documents" with each role's arrival state.
* **A later document for the same key:** the run hands its data to the waiting set and ends immediately on the **Handed off** output, which usually needs nothing connected. The run detail links from the handed-off run to the parked run holding the set.
* **Every role in:** the parked run continues on the **Complete** output with the combined data (see [Output](#output) below).
* **The same role again while waiting:** a resend for a role that already arrived replaces that role's data with the newer one. The run detail for the parked run reflects the update right away.
* **After completion:** the next document with that key starts a fresh set from scratch. It does not join the completed one.
* **Cancelling the waiting run** closes its set. A document that later arrives for that key opens a new set instead of joining the cancelled one.
* **Timeout:** if the set is still incomplete when the timeout elapses and the **Timed out** output is connected, the parked run continues on it with the roles that did arrive (see [Output](#output)). With nothing connected to Timed out, the parked run fails with `Correlate: timed out waiting for <labels> (key <key>).`, naming every role still missing, and the run detail shows which roles did arrive.

## Output

The parked run's Complete output carries every role's data under `documents.<slug>`, plus a `runs.<slug>` entry identifying which run and document supplied it:

```json theme={null}
{
  "key": "PO-123",
  "documents": {
    "invoice": { "...": "the invoice branch's output" },
    "packing_slip": { "...": "the packing slip branch's output" }
  },
  "runs": {
    "invoice": { "runId": "...", "documentId": "..." },
    "packing_slip": { "runId": "...", "documentId": "..." }
  }
}
```

Read a role's data downstream with the node's own name, the role's slug, and a path into that role's output, for example:

```
{{correlate.payload.documents.invoice.total}}
{{correlate.payload.key}}
```

The **Timed out** output has the same shape, with `null` under `documents` and `runs` for each role that never arrived, plus `missing`, the slugs of those roles:

```json theme={null}
{
  "key": "PO-123",
  "documents": { "invoice": { "...": "the invoice branch's output" }, "packing_slip": null },
  "runs": { "invoice": { "runId": "...", "documentId": "..." }, "packing_slip": null },
  "missing": ["packing_slip"]
}
```

Connect it to send a partial set somewhere useful, such as a Review or an email, instead of losing it.

A run that handed its document over rather than parked emits `{ "key": "...", "handedOff": true }` on the Handed off output; it carries no `documents` or `runs` data of its own, since that lives on the parked run.

## Testing a workflow with a Correlate

**A Correlate never parks in a test.** In a [test step](/workflows/editor#test-step) or a [dry run](/workflows/editor#dry-run), the node outputs the test run's own document under its role's slug and `null` for every other role, then continues immediately instead of waiting. This lets you validate one branch at a time without needing every role's document on hand.

## Example

```
Email trigger -> Classify -+- packing slip -> Extract slip    -+
                            +- invoice      -> Extract invoice -+-> Correlate (key: PO number) -> HTTP Request
```

An email trigger creates one run per attachment, so a single email carrying both the packing slip and the invoice still produces two runs that meet at Correlate within seconds. A packing slip and an invoice that arrive in separate emails, days apart, take the same path with a longer wait.

## Inputs and outputs

**Allowed inputs:** Every action node except Filter, Parse and Retry From, plus the connector nodes (Business Central and QuickBooks in any operation, and Callback) and the Sub-workflow trigger. A Correlate cannot feed another Correlate. Supports multiple input connections, one per role's source.

**Output:** Three success ports. **Complete** fires once on the parked run once every role has arrived, carrying the combined `documents` and `runs` data above. **Timed out** fires on the parked run when the timeout elapses first, carrying the roles that arrived and `missing`. **Handed off** fires on every other run that delivered a role, carrying just its key. An error output fires whenever the step fails: the parked run timing out with nothing connected to Timed out, or an error such as a key that does not resolve to a valid value, a role that arrives with no payload, a role the set does not expect, or a value over the size limit.

## Limits

* A Correlate cannot go inside a [Loop](/nodes/loop) body.
* 2 to 5 roles per node.
* Timeout: 5 to 129600 minutes (90 days).
* Each role's data is capped at 256 KB. A larger value fails the step with `Correlate: the value for role '<role>' exceeds the maximum size of 256 KB.`

## Credits

Correlate nodes are **free**. They consume 0 credits per execution, however long a run waits.

## Related

<CardGroup cols={2}>
  <Card title="Merge" icon="code-merge" href="/nodes/merge">
    Combine branches that already share one run
  </Card>

  <Card title="Wait" icon="clock" href="/nodes/wait">
    Pause a single run for a timer or a webhook
  </Card>

  <Card title="Store" icon="database" href="/nodes/store">
    Persist a value across runs when you only need one side of the pair
  </Card>

  <Card title="Monitoring runs" icon="chart-line" href="/runs/monitoring">
    Find a waiting run and see which roles have arrived
  </Card>
</CardGroup>
