REST API
Everything in Boardwalk is an HTTP call. The dashboard, the CLI, and the MCP server all sit on the same REST surface, so anything they do, your own server can do too: fire a run when an event happens in your product, read its status and events, deploy from CI, or manage your org. The hosted API lives at https://api.boardwalk.sh; self-hosted is your own host. Every path is under /v1, requests and responses are JSON, and auth is a bearer token.
Authentication
For server-to-server calls, use an API key. Create one in the dashboard under Settings → API keys (key minting needs a logged-in session, so it is not something a key can do to itself). The full key begins with bwk_ and is shown once at creation; store it as a secret. Send it as a bearer token:
curl https://api.boardwalk.sh/v1/orgs/acme/workflows \
-H "Authorization: Bearer bwk_your_key_here"A key belongs to one org, so org-scoped routes still take your org slug in the path. The CLI and CI both read the same value from the BOARDWALK_API_KEY environment variable. Two other credentials reach the same routes: a Clerk session JWT (the dashboard) and the CLI's OAuth token (boardwalk login). Credential-minting actions in the endpoint reference are marked session only; an API key can never perform them at any scope.
What each credential can do
An API key can do anything its role and scopes allow, with one permanent exception: it can never mint another credential. Those actions require a logged-in session (see Orgs, roles & audit for the rule and the role each action needs):
| Action | API key | Session |
|---|---|---|
| Trigger runs; read run status and events | Yes | Yes |
| Deploy and manage workflows | Yes | Yes |
| List secret names and provider metadata (never values) | Yes | Yes |
| Revoke an API key | Yes | Yes |
| Stage, rotate, or delete a secret | No | Yes |
| Register or remove an inference provider | No | Yes |
| Mint an API key; set or clear its spend cap | No | Yes |
| Invite a member | No | Yes |
| Update or delete your account | No | Yes |
Key scopes
A key is least-privilege. Leave its scopes empty for full access at the key's role, or list scopes to restrict it to exactly the actions you need. For a key that only kicks off runs from your app, grant run:trigger and nothing else. Scopes are <resource>:<action> strings:
| Resource | Scopes |
|---|---|
| Workflows | workflow:read, workflow:create, workflow:update, workflow:delete, workflow:trigger, workflow_version:read, workflow_version:create |
| Runs | run:read, run:trigger, run:cancel, run:delete |
| Inference | inference:invoke (call the managed inference gateway directly) |
| Read-only | audit_log:read, billing:read |
Conventions
The whole surface is consistent: JSON in and out, ISO-free millisecond-epoch timestamps, cursor pagination, and one error shape.
Pagination & polling
List endpoints take ?limit (1 to 100, default 50) and an opaque ?cursor, and return a nextCursor that is null on the last page. Pass it back to page forward:
GET /v1/orgs/acme/runs?limit=50
// → { "runs": [ ... ], "nextCursor": "eyJ0IjoxNz..." }
GET /v1/orgs/acme/runs?limit=50&cursor=eyJ0IjoxNz...
// → { "runs": [ ... ], "nextCursor": null } // last pageList and detail reads support a conditional GET: send the ETag you last saw back as If-None-Match and an unchanged resource answers 304 Not Modified with no body. That makes status polling cheap; for a live view, prefer the event stream over a poll loop (see Runs & observability).
Errors
Every non-2xx response is the same envelope: a stable code, a human message, and an optional detail object.
{
"error": {
"code": "VALIDATION_FAILED",
"message": "triggers must contain at least one entry",
"detail": { "path": "triggers" }
}
}400 VALIDATION_FAILED: the request body or query failed schema validation.401 UNAUTHENTICATED/403 FORBIDDEN: no usable credential, or the credential lacks the role or scope.404 NOT_FOUND: the resource is missing or belongs to another org. Cross-tenant access is a 404, never a 403, so a key can't probe for ids it can't see.405: the path exists but not for that method; the response carries anAllowheader.429: rate limited (a per-user and per-org bucket); back off and retry.
Trigger a run
This is the call you want when your own server or web app needs to start a workflow on demand. POST to the workflow's runs collection; the optional input body becomes the run's trigger payload, passed to the program's run function as its first parameter. An optional environment (a name) selects the environment the run executes in, with its secrets and variables; omit it for the organization base.
curl -X POST \
https://api.boardwalk.sh/v1/orgs/acme/workflows/wf_123/runs \
-H "Authorization: Bearer bwk_your_key_here" \
-H "Content-Type: application/json" \
-d '{ "input": { "pr_url": "https://github.com/acme/app/pull/42" } }'Or from your application code:
const res = await fetch(
"https://api.boardwalk.sh/v1/orgs/acme/workflows/wf_123/runs",
{
method: "POST",
headers: {
Authorization: `Bearer ${process.env.BOARDWALK_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ input: { pr_url: prUrl } }),
},
);
const { run } = await res.json(); // 201 Created
console.log(run.id, run.status); // the new run's id + "queued"The trigger returns immediately with the new run's id and status; the run executes asynchronously. To react to a workflow event instead of polling, point a workflow webhook at it, or subscribe with a watch.
Read a run
Poll the run by id for its terminal status, and read its event log for the full timeline (phases, agent turns, output):
GET /v1/runs/{runId} # status, timing, token + cost totals
GET /v1/runs/{runId}/input # the trigger payload this run was called with
GET /v1/runs/{runId}/events # the run's event log (phases, agent, output)
POST /v1/runs/{runId}/cancel # stop a queued or in-flight run
POST /v1/runs/{runId}/retry # re-run with the same inputThe value the program's runfunction returned is the run's output; it lands in the run's events under the output channel. For the full status enum, the event model, channels, and the live stream, see Runs & observability.
Inbound webhooks
The trigger endpoint above is for code you control. When a third-party system you don't control needs to start a run (a GitHub push, a Stripe event), give it a workflow webhook instead: Boardwalk provisions a per-workflow URL and secret, and verifies the incoming request (a bearer token in a header, or an HMAC signature over the body; the secret never rides the URL) before firing. Fetch the URL and auth mode with GET /v1/orgs/:slug/workflows/:id/webhook, or rotate its secret with the /rotate route.
The runner API
A running workflow reaches the control plane over a separate /runner/v1/* surface (secrets, artifacts, child runs, telemetry). It is authorized by a short-lived per-run token the platform mints for each run, not by your API key, and is an internal contract between the runner and the broker. It is not part of the public API and not for general use; your programs reach all of it through the SDK instead.
Endpoint reference
The full public surface, grouped by resource, lives on its own page: API endpoints. Every path for workflows, runs, schedules, webhooks, connections, watches, artifacts, secrets, environments, inference, API keys, billing, your org, and your account.