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

# Recording Runs and Steps with the Failpath TypeScript SDK

> Use run() to start a flow execution and step() to instrument individual operations. Learn how to correlate steps, skip them, and record manually.

A **run** represents one execution of a flow — a single request, background job, or webhook invocation. A **step** represents one operation within that run. Together they give Failpath everything it needs to reconstruct the shape and outcome of your flow in the dashboard. This page covers all four methods you use to record that data: `run()`, `step()`, `skip()`, and `recordStep()`.

The examples use `failpathFlows` from the generated `.failpath/sdk.ts` helper. See [Typed Keys](/sdk/typed-keys) for setup and autocomplete details.

## `failpath.run(flowKey, options)`

Call `failpath.run()` at the start of your handler to create a run object. Pass the flow key and, optionally, a `runId` and per-run metadata.

```typescript theme={"dark"}
const run = failpath.run(failpathFlows.checkout.slug, {
  runId: requestId,
  metadata: { route: "/checkout" },
});
```

The `runId` is the most important option to get right. Failpath uses it to group all steps that belong to the same execution together in the dashboard. Choose a value that is already unique per request in your system — a request ID, job ID, or webhook delivery ID all work well.

<Tip>
  Prefer `failpathFlows` from `.failpath/sdk.ts` for flow and step keys. Raw strings still work, and the typed client autocompletes valid string literals when they exist in `.failpath/flows.json`.
</Tip>

### Options

| Option     | Type     | Description                                                                                                   |
| ---------- | -------- | ------------------------------------------------------------------------------------------------------------- |
| `runId`    | `string` | Correlates every step in one request, job, or webhook. Reuse the same ID for all steps in the same execution. |
| `metadata` | `object` | Arbitrary key/value data attached to the run. Merged with `defaultMetadata` from the client.                  |

## `run.step(stepKey, fn, options)`

Wrap each business operation with `run.step()`. The SDK queues a `running` event before calling your function, then queues `success` with the return value or `error` with the thrown exception when your function finishes.

```typescript theme={"dark"}
const cart = await run.step(failpathFlows.checkout.steps.validateCart, async () => {
  return validateCart();
}, {
  metadata: { cartId: "cart_123" },
});
```

`step()` returns the value your function returns, so you can assign it and use it in subsequent steps exactly as you would without instrumentation. If your function throws, the SDK records the error and rethrows the original exception — your existing error handling continues to work without modification.

Telemetry sends in the background by default. Call `failpath.flush()` before a short-lived process exits, or at the end of a serverless request when your platform does not expose a `waitUntil` hook.

### Options

| Option     | Type     | Description                                           |
| ---------- | -------- | ----------------------------------------------------- |
| `metadata` | `object` | Arbitrary key/value data attached to this step event. |

<Warning>
  Never include secrets, tokens, authentication headers, full request bodies, payment card data, or private customer information in step metadata. Metadata is transmitted to and stored by Failpath.
</Warning>

## `run.skip(stepKey)`

Use `run.skip()` when your flow reaches a branch where a step is intentionally not executed. Skipping a step records a `skipped` event so your flow trace in the dashboard remains complete even when a path is not taken.

```typescript theme={"dark"}
await run.skip(failpathFlows.checkout.steps.sendReceiptEmail);
```

Skipping is preferable to simply omitting a step call. When a step is never recorded, Failpath cannot distinguish between a skipped branch and a step that failed silently before it could be instrumented.

## `failpath.recordStep(options)`

Use `failpath.recordStep()` to record a step event outside the normal `run.step()` wrapper — for example, when integrating with a third-party queue, recording the result of a webhook callback, or bridging an event from another service.

```typescript theme={"dark"}
await failpath.recordStep({
  flowKey: failpathFlows.checkout.slug,
  runId: "req_123",
  stepKey: failpathFlows.checkout.steps.sendReceiptEmail,
  status: "success",
});

await failpath.flush();
```

### Parameters

| Parameter | Type                                             | Required | Description                                                         |
| --------- | ------------------------------------------------ | -------- | ------------------------------------------------------------------- |
| `flowKey` | `string`                                         | ✓        | The slug of the flow this step belongs to.                          |
| `runId`   | `string`                                         | ✓        | The run ID that groups this step with others in the same execution. |
| `stepKey` | `string`                                         | ✓        | The key identifying this step within the flow.                      |
| `status`  | `"running" \| "success" \| "error" \| "skipped"` | ✓        | The status to record for this step.                                 |

## Putting it all together

The example below shows all four methods working together in a single checkout handler.

```typescript theme={"dark"}
import { createTypedFailpathClient, failpathFlows } from "../.failpath/sdk";

const failpath = createTypedFailpathClient({
  projectKey: process.env.FAILPATH_PROJECT_KEY!,
});

export async function handleCheckout(requestId: string) {
  const run = failpath.run(failpathFlows.checkout.slug, {
    runId: requestId,
    metadata: { route: "/checkout" },
  });

  const cart = await run.step(failpathFlows.checkout.steps.validateCart, async () => {
    return validateCart();
  });

  await run.step(failpathFlows.checkout.steps.chargeCard, async () => {
    return chargeCard(cart);
  }, {
    metadata: { cartId: cart.id },
  });

  // Skip the receipt if the customer opted out
  if (!cart.receiptOptIn) {
    await run.skip(failpathFlows.checkout.steps.sendReceiptEmail);
  } else {
    await run.step(failpathFlows.checkout.steps.sendReceiptEmail, async () => {
      return sendReceipt(cart);
    });
  }

  await failpath.flush();
}
```
