# ctx.callFunctionAsync()

> Signature, parameters, and constraints for ctx.callFunctionAsync(), the fire-and-forget way to hand work off to another route.

`ctx.callFunctionAsync(path, input?)` queues a call to another route in the same project, which runs as a new, independent execution. The call resolves once accepted, not once the target finishes.

## Signature

```typescript
callFunctionAsync(path: string, input?: unknown): Promise<void>
```

| Parameter | Type | Description |
|---|---|---|
| `path` | `string` | The target route's path, for example `'leads/enrich'`. Not validated against your project's real routes at call time. |
| `input` | `unknown` (optional) | Passed to the target as its `ctx.input`. |

Returns a `Promise<void>` that resolves once the call is accepted. There is no return value, status, or execution ID from the target.

## ctx.parentExecutionId

Available on the `ctx` a target route receives:

| Field | Type | Description |
|---|---|---|
| `parentExecutionId` | `string \| null` | The execution ID of the caller that started this execution with `callFunctionAsync`, or `null` when this execution was not started by an async call (a direct request or a schedule). |

## Constraints

| Constraint | Behavior |
|---|---|
| Dispatch method | Always `POST`, regardless of the caller's own method. The target must accept `POST`. |
| Chaining depth | A chain is at most two hops deep: the execution that starts it may call `callFunctionAsync`, and the execution that call starts may call it again, but the execution after that may not. |
| Delivery | At-least-once, with no idempotency key. A transient failure can cause the target to run again for the same call. |
| Unmatched path | Not validated at call time. With no matching route and no catch-all, the call is dropped silently. A catch-all route absorbs a mistyped path instead of surfacing an error. |
| Result | None. The call is fire-and-forget: no return value, status, or execution ID is available to the caller. |

## Example

```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 };
});
```

```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 });
});
```

## Local development

`helix dev` fires the call directly at the target route on the same dev server and returns immediately: there's no separate execution locally, so `ctx.parentExecutionId` is always `null`. This is a permanent property of local dev, not a gap that's expected to close, so don't gate local testing of a target on that field being set.

Idempotency concerns, the chaining limit, and the mistyped-path gotcha are covered in [the async function calls guide](/documentation/guides/async-functions/).

---

Canonical: https://helix.tray.ai/documentation/reference/context/async/
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