Skip to main content
createClient is how code outside a runner talks to Duraton: trigger events, read and control runs, tail the event log, and read the numbers behind the console’s charts - all typed, over plain HTTP.
string
default:"DURATON_URL, then https://run.duraton.dev"
Your Duraton base URL. A trailing slash is fine.
string
default:"DURATON_API_KEY"
The project API key, sent as a Bearer token.
typeof fetch
default:"the global fetch"
Override the fetch implementation - a custom agent, a test double.
A public key can call the GET-backed methods. Writes - events.send, cancel, pause, resume, replay, retryFromStep, and bulkReplay - need a secret key.

events

runs

list accepts the same filters as the runs API: app, workflow, status, runType, eventId, session, replayOf, parentRunId, scoreName, minScore, maxScore, q, deep, since (a string or Date), sort, dir, limit, cursor. parentRunId is the child-run filter: runs.list({ parentRunId }) returns the runs one run’s step.runWorkflow calls spawned, the reverse of the parentRunId each child carries. find(opts) is the single-run lookup: the same filters as list minus pagination, capped at one row, returning Run | null - parentRunId included, so find({ parentRunId }) picks one child rather than a page of them. When several runs match, sort/dir pick which one - the default is the most recent.

Trigger and await

runs.wait polls a run until it reaches a stop status and returns it - the “fire an event, get the result” primitive. It stops at any terminal status by default; pass until to also stop at a resting state like waiting or paused.
RunStatus[]
default:"the terminal statuses only"
Extra non-terminal statuses to also stop at, e.g. [“waiting”]. The terminal set is always honoured on top, so the wait still resolves if the run finishes first.
number
default:"30000"
Reject if the run has not reached a stop status within this many ms.
number
default:"150"
Poll cadence in ms.
AbortSignal
Aborting it rejects the pending wait.
wait rejects on timeout or an aborted signal - not on an unhappy outcome. A run that failed or was cancelled resolves normally, so branch on run.status.

Live tails

Control

timeseries

Run counts and latency bucketed over time - the numbers behind the console’s Overview charts.
string
Narrow the series to one app.
string
Narrow the series to one workflow, by exact name.
string
Only runs started at or after this timestamp.
number
default:"3600 (one hour)"
Bucket width in seconds.
Each bucket carries ts, per-status counts, a total, and avgMs / maxMs over the bucket’s terminal runs - both absent when the bucket has none.

flowState

The live buffer state of the flow controls: how many events are currently coalescing in a debounce window, and how many are buffered toward a batch flush.
In-flight and queued counts are not here - those come from runs.stats.

sessions, ai, approvals

workflows, apps, runners, health

Errors

Any non-2xx response throws a DuratonApiError carrying the status and the raw body, with helpers so you branch on intent instead of on status numbers.
isBadRequest() (400), isUnauthorized() (401), isForbidden() (403), isNotFound() (404), and isConflict() (409) map to Duraton’s status codes.

Runtime

The client is built on the global fetch and runs on any fetch-native runtime (Node 18+, Bun, Deno, Cloudflare Workers, browsers). The streaming methods - events.stream, runs.watch, runs.watchAll - additionally need a streaming fetch body, which all of those provide. No dependencies.