# Subroutines and calling automations

> How APIANT automations call subroutines and other automations, the difference between synchronous and asynchronous calls, passing inputs, reading results, calling by name, and nesting limits.

A subroutine is an automation with no trigger that other automations call, pass inputs to, and read results from. Use one for logic several automations share, such as "find or create a contact". An automation can also call an ordinary automation, or launch it in the background.

## Two ways to call another automation

| Method | Targets | Use it for |
|---|---|---|
| Call automation step | One automation or subroutine by its ID, in the same account | Reusable logic, sequential pipelines, background launches |
| **Execute automation by mapped name** (Automations app) | An automation by name, in this account or linked accounts | Launching by name, reaching linked accounts, or passing a query string or webhook body |

Prefer the call automation step when the target is fixed. A name is looked up when the run reaches the step, so renaming the target breaks a call by name.

## Building a subroutine

Ask for one directly: "build a subroutine that takes a customer email, finds the contact in HubSpot, and creates it if it does not exist". A subroutine:

- Has no trigger and never runs on its own. It is created off and runs only when called.
- Reads the values the caller passes as `trigger.input.<name>`, for example `{{ trigger.input.customer_email }}`.
- Returns the output of all of its steps, keyed by step ID.

A subroutine can declare its inputs and outputs, each with a name, datatype, help text and, for inputs, whether it is required. A call that leaves a required input missing or empty fails with an error naming the input. Declared outputs describe the result; the caller receives every step's output either way.

## Calling a subroutine

A call automation step names the target and passes `inputs`:

```json
{
  "step_id": "find_or_create_contact",
  "type": "call_automation",
  "automation_uuid": "<subroutine id>",
  "inputs": {
    "customer_email": "{{ trigger.body.email }}",
    "source": "webshop"
  }
}
```

Input values can be expressions, fixed text, numbers, or nested objects and lists. Input names are case-sensitive and must match what the subroutine reads.

### Reading the result

A later step in the caller reads the subroutine's step outputs under `outputs`:

```text
{{ steps.find_or_create_contact.output.outputs.lookup_contact.id }}
```

Here `lookup_contact` is a step ID inside the subroutine. When that inner step is a script, its value is one level deeper, under `result`:

```text
{{ steps.find_or_create_contact.output.outputs.build_name.result }}
```

## Synchronous and asynchronous calls

A call automation step waits for the target by default. Setting `launch_async` launches it in the background instead:

| | Synchronous (default) | Asynchronous (`launch_async: true`) |
|---|---|---|
| Caller | Waits until the target finishes | Continues immediately |
| Result | `outputs` holds the target's step outputs | `outputs` is empty |
| Target's errors | Fail the call step, so the caller's error handling applies | Not reported to the caller |
| Requires | The target's ID | The target's ID and a latch group name |

Every call step reports the same fields: `automation_uuid`, `executed_automation_uuid`, `launched`, `latch_group` and `outputs`. Check `launched` to tell the two modes apart: it is `false` for a synchronous call and `true` for a background launch.

To launch several automations in the background and then wait for all of them, see [Fan-out and fan-in](/docs/automations/fan-out-fan-in).

### Subroutine or ordinary automation as the target

What a synchronous call does depends on how the target was built:

| Target | Runs | Can pause | Tasks count against |
|---|---|---|---|
| Subroutine | Inside the caller's run | Yes | The caller's run |
| Ordinary automation | As its own child run | No. Snooze, form waits and latch waits fail inside it. | The target automation |

If the called logic needs to wait, build it as a subroutine, or launch it asynchronously.

## Calling by name

The Automations app's **Execute automation by mapped name** actions find the target by name when the step runs. The match ignores case but must otherwise be exact, and a name that matches no automation or more than one fails the step. The target's on/off state is not checked.

| Input | Purpose |
|---|---|
| name | The target automation's name |
| query_string | Parameters such as `order=10482&rush=true`. The target reads them as `{{ trigger.params.order }}`, or the whole string as `{{ trigger.query_string }}`. |
| webhook_payload | A JSON or XML body delivered to the target as its trigger body |
| wait | `true` to wait for the target and receive its result |

The target account's monthly task allowance is checked before the launch.

### Calling automations in linked accounts

> **Plan:** Enterprise

Three variants reach beyond the running account:

| Action | Runs the named automation in |
|---|---|
| Execute automation by mapped name (includes linked parent) | This account or its linked parent |
| Execute automation by mapped name in all linked accounts | The parent and every linked child account, always in the background |
| Execute automation by mapped name in specified account | One linked account you identify |

The all-linked-accounts variant launches in every account that has an automation with that name and reports the accounts it could not resolve or launch. The step fails only when no account could be launched at all. See [Linked and associated accounts](/docs/account/linked-accounts).

## Limits

- Calls can nest 16 levels deep. A deeper call fails.
- A call automation step or error handler cannot create a loop back to the same automation, directly or through other automations. Saving such a change is refused. A loop made through **Execute automation by mapped name** is not caught when saving and stops at the 16-level limit when it runs.

## Finding and reusing subroutines

While you view an automation diagram, the navigation bar shows **Subroutines**. It opens a read-only catalog with a **Mine** tab for your own subroutines, a **Community** tab for public ones, and a search box.

The Subroutines page lists subroutines in folders, with Mine and Community tabs above the list.

Before building a new subroutine, ask Claude whether a matching one exists; building never warns about an existing subroutine with the same name.

To see which automations use a subroutine, open its gear menu and choose **Manage > Referenced by…**. The list includes automations that call it and automations that use it as an error handler.

From Claude Code, Claude can test a subroutine on its own with sample inputs. The assistant tests a subroutine by running an automation that calls it. See [Testing an automation](/docs/automations/testing).

## Next steps

- [Fan-out and fan-in](/docs/automations/fan-out-fan-in)
- [Error handling and retries](/docs/automations/error-handling)
- [Field mapping](/docs/automations/field-mapping)
- [Delays, snooze and waits](/docs/automations/delays)
