Skip to main content
Workflows can be run in a variety of ways:
SurfaceBest for
TriggersThe primary way workflows run in production: webhooks, schedules, polls, and app events start runs automatically
CLIRunning and inspecting workflows during development
Agent toolLetting an agent run a workflow as a single, durable tool
HTTP APIInvoking 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.

Run workflows from triggers

In production, most workflows run from a trigger. You attach a source (a webhook, schedule, poll, or app event) to a workflow, and each matching event starts a run.
src/triggers/signup.ts
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 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:
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:
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:
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 for the full routing model.

Run workflows as an agent tool

Import a workflow into an agent’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.
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.

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:
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() 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:
# 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. 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:
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:
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:
keystroke workflows runs cancel <run-id>
See workflow runs for the full run history view.

Next steps

Triggers

Attach webhooks, schedules, and polls to start workflows.

Workflow runs

Inspect input, output, steps, errors, and traces.

Test workflows

Run workflows in tests before deploying changes.

Deploy a project

Ship workflow changes to the platform.