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

# Run workflows

> Start workflow runs from triggers, the CLI, the API, and agent tools.

Workflows can be run in a variety of ways:

| Surface        | Best for                                                                                                         |
| -------------- | ---------------------------------------------------------------------------------------------------------------- |
| **Triggers**   | The primary way workflows run in production: webhooks, schedules, polls, and app events start runs automatically |
| **CLI**        | Running and inspecting workflows during development                                                              |
| **Agent tool** | Letting an agent run a workflow as a single, durable tool                                                        |
| **HTTP API**   | Invoking a workflow from your own services                                                                       |

Every run, no matter the surface, validates the input against the workflow's schema and is recorded in [run history](/learn/logs/workflow-runs).

## Run workflows from triggers

In production, most workflows run from a [trigger](/learn/triggers/overview). You attach a source (a webhook, schedule, poll, or app event) to a workflow, and each matching event starts a run.

```ts src/triggers/signup.ts theme={null}
import { defineWebhookSource } from "@keystrokehq/keystroke/trigger";
import { z } from "zod";
import workflow from "../workflows/signup-pipeline";

export default defineWebhookSource({
  key: "signup",
  endpoint: "signup",
  request: z.object({ name: z.string(), email: z.string().email() }),
}).attach({
  workflow,
  transform: (payload) => ({ name: payload.name, email: payload.email }),
});
```

The optional `transform` maps the source payload to the workflow's input. See [triggers](/learn/triggers/overview) for webhooks, schedules, and polling.

## Run workflows from the CLI

The CLI is the most practical way to run a workflow while you build. Deploy your project, then invoke the workflow against the deployed project:

```bash theme={null}
keystroke deploy --project <slug>   # or --filter workflows/signup-pipeline
keystroke workflows run signup-pipeline --input '{"name":"Ada","email":"ada@example.com"}'
```

The `signup-pipeline` value is the workflow `slug` from `defineWorkflow()`, and `--input` is JSON validated against the workflow's `input` schema.

List the workflows in the active project:

```bash theme={null}
keystroke workflows list
```

`keystroke workflow` follows the CLI's resolved target — your deployed cloud project by default, or a local server if you're running `keystroke dev`:

```bash theme={null}
keystroke workflows run signup-pipeline --input '{"name":"Ada","email":"ada@example.com"}'          # cloud (default after deploy)
keystroke --local workflow run signup-pipeline --input '{"name":"Ada","email":"ada@example.com"}'  # local server
```

See [targets: local vs cloud](/cli#targets-local-vs-cloud) for the full routing model.

## Run workflows as an agent tool

Import a workflow into an [agent](/learn/agents/overview)'s `tools` array and it becomes a tool automatically. The agent calls it like any other tool, and the workflow runs with its normal step recording.

```ts theme={null}
import { defineAgent } from "@keystrokehq/keystroke/agent";
import refundOrder from "../workflows/refund-order";

export default defineAgent({
  slug: "support",
  systemPrompt: "Help customers. To issue a refund, call the refund-order tool.",
  model: "anthropic/claude-sonnet-4.6",
  tools: [refundOrder],
});
```

Workflows run as a tool execute inline, so they cannot suspend; a workflow used this way should not call `ctx.sleep()` or `ctx.hook()`. See [workflows as tools](/learn/agents/build-agents#workflows-as-tools).

## Run workflows via API

You can also invoke a deployed workflow over HTTP. The route validates the input, enqueues the run, and returns a `runId` to inspect later:

```bash theme={null}
curl -X POST https://<your-project>/api/projects/<project-id>/workflows/signup-pipeline \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <org-api-key>" \
  -d '{ "name": "Ada", "email": "ada@example.com" }'
```

This is mainly useful for wiring workflows into your own services. For most internal automation, triggers and the CLI cover what you need.

## Resume a suspended run

A workflow that calls [`ctx.hook()`](/learn/workflows/build-workflows#hooks) suspends until something resumes it. The hook handle exposes a `token` and a `resumeUrl`. Resume the run with `POST` for structured payloads, or `GET` for simple link/button flows (Slack, email). No API key or session is required — the hook token in the URL is the credential:

```bash theme={null}
# POST — JSON body (structured payloads)
curl -X POST https://<your-project>/api/projects/<project-id>/hooks/<token>/resume \
  -H "Content-Type: application/json" \
  -d '{ "approved": true }'

# GET — query params (buttons and links)
curl "https://<your-project>/api/projects/<project-id>/hooks/<token>/resume?approved=true"
```

With `POST`, the JSON body becomes the value `ctx.hook()` returns and the route returns `202` with the `runId`. With `GET`, query params become the payload (shallow) and the route returns a minimal HTML confirmation page. Query values arrive as strings, but if the hook declared a `schema` they are coerced to the schema's primitive types first — so `?approved=true` resolves to the boolean `true`. Values that can't be coerced (e.g. `?approved=banana`) are rejected with `400` and the run stays suspended.

If the hook declared a `schema`, the resume endpoint validates the payload against it first. An invalid payload is rejected with `400` and the run stays suspended — you can fix the payload and resume again, instead of failing the run. (Validation is structural; the in-workflow `schema.parse` remains the authoritative parse for coercion and refinements.)

Runs waiting on a hook show **Waiting on hook** in [run history](/learn/logs/overview). Runs paused by `ctx.sleep()` show **Sleeping** and resume on their own when the timer is due, so you don't resume those manually.

### Find the resume token for a suspended run

If you didn't capture the `resumeUrl` when the hook was created, list the pending hooks for a run to get its token and resume URL:

```bash theme={null}
keystroke workflows runs hooks <workflow-key> <run-id>
```

This returns each hook's `token`, `status`, and a ready-to-use `resumeUrl`.

## Review workflow runs

Every surface that runs a workflow creates a workflow run you can review later. Open **History** in the web app and filter **Type** to **Workflow**. The detail panel shows the input, output or error, steps, usage, trigger context, timing, and trace data.

From the CLI, use run commands while debugging:

```bash theme={null}
keystroke workflows runs list signup-pipeline --status failed
keystroke workflows runs get signup-pipeline <run-id> --include trigger,steps,trace
```

Cancel a queued or running run by ID:

```bash theme={null}
keystroke workflows runs cancel <run-id>
```

See [workflow runs](/learn/logs/workflow-runs) for the full run history view.

## Next steps

<CardGroup cols={2}>
  <Card title="Triggers" href="/learn/triggers/overview">
    Attach webhooks, schedules, and polls to start workflows.
  </Card>

  <Card title="Workflow runs" href="/learn/logs/workflow-runs">
    Inspect input, output, steps, errors, and traces.
  </Card>

  <Card title="Test workflows" href="/learn/workflows/test-workflows">
    Run workflows in tests before deploying changes.
  </Card>

  <Card title="Deploy a project" href="/learn/projects/deploy-a-project">
    Ship workflow changes to the platform.
  </Card>
</CardGroup>
