# Async function calls

> Use ctx.callFunctionAsync() to hand work to another route as a fresh execution: fire-and-forget semantics, the two-hop chaining cap, and idempotent targets.

`ctx.callFunctionAsync(path, input?)` queues a call to another route in your project and returns as soon as the call is accepted, without waiting for the target to run. The target executes as a brand-new, independent execution. Reach for it when a handler needs to kick off work it doesn't want to wait on, or when a job is better split across a fresh execution than run to completion inside one long-lived handler.

## Calling another function asynchronously

```typescript
// functions/leads/webhook.post.ts, POST /leads/webhook
import { defineFunction } from '@trayai/helix-sdk';

export default defineFunction(async (ctx) => {
  await ctx.callFunctionAsync('leads/enrich', { leadId: ctx.input.leadId });
  return { received: true }; // returns immediately; enrichment runs separately
});
```

The target is an ordinary route: no special export, file location, or registration is required. It can read `ctx.parentExecutionId` to see which execution started it, `null` when it wasn't started this way:

```typescript
// functions/leads/enrich.post.ts, POST /leads/enrich
import { defineFunction } from '@trayai/helix-sdk';

export default defineFunction(async (ctx) => {
  ctx.log.info('Enriching lead', { leadId: ctx.input.leadId, calledBy: ctx.parentExecutionId });
});
```

## Fire-and-forget, not request-response

`callFunctionAsync` resolves once the call is accepted, not once the target finishes. There is no return value, status, or execution ID to get back. If a caller needs the result of the work, it has to fetch it some other way, for example by having the target write to the [key-value store](/documentation/guides/key-value-store/) and polling for it.

## Chaining is capped at two hops

A chain is at most two hops deep. The execution that starts a chain, HTTP-triggered or scheduled, may call `callFunctionAsync`; the execution that call starts may call it again; the execution after that may not. This limits how deep a chain can go, not how many independent calls one execution can make, so design a chain as at most three executions total: the original, one async hop, and one more.

## The call always dispatches as POST

The target route must accept `POST`, regardless of which method the caller itself handled. A target that only exports `.get.ts` or `.put.ts` won't receive the call.

## Delivery and idempotency

Delivery is at-least-once, with no idempotency key. A transient failure can cause the target to run again for the same call, so write target handlers to be safe to run more than once, the same idempotency concern as [scheduled functions](/documentation/guides/scheduled-functions/#retries-and-failures).

:::warning{title="A mistyped target path can fail silently"}
The path isn't validated against your project's real routes at call time. With no matching route and no catch-all, the call is dropped without an error the caller ever sees. With a catch-all route in place, a typo'd path is silently absorbed by it instead. Double-check the target path, since nothing else will catch a typo for you.
:::

## Local development

`helix dev` doesn't create a separate execution for an async call. The call fires directly at the target route on the same dev server and returns immediately, so you can exercise the whole chain locally. `ctx.parentExecutionId` is always `null` in dev, since there's no real calling execution to report, and that's a permanent property of local dev rather than a gap that's expected to close. Don't gate local testing of a target on that field being set.

## Limits

See the [limits reference](/documentation/reference/limits/) for the chaining cap and delivery guarantee. Full signature and the `ctx.parentExecutionId` field are covered in the [context reference](/documentation/reference/context/async/).

---

Canonical: https://helix.tray.ai/documentation/guides/async-functions/
Any link on this page is available as markdown by appending .md to its URL.
Full corpus: https://helix.tray.ai/documentation/llms-full.txt