API Reference

Station

Station is a monitoring dashboard for Station. It connects to your signal and broadcast adapters and provides a web interface for inspecting registered signals, browsing run history, and watching broadcast DAG execution in real time.

Station is a combined:

The API server runs on the configured port (default 4400). The Next.js frontend runs on port + 1 (default 4401). Both start automatically when you launch Station.


Install

pnpm add station-kit

Configuration

Create a station.config.ts (or .js / .mjs) in your project root:

import { defineConfig } from "station-kit";import { SqliteAdapter } from "station-adapter-sqlite";import { BroadcastSqliteAdapter } from "station-adapter-sqlite/broadcast"; export default defineConfig({  port: 4400,  signalsDir: "./signals",  broadcastsDir: "./broadcasts",  adapter: new SqliteAdapter({ dbPath: "./jobs.db" }),  broadcastAdapter: new BroadcastSqliteAdapter({ dbPath: "./jobs.db" }),});

The defineConfig helper provides type checking and autocompletion. It is a pass-through function — it returns the object unchanged.

Config options

OptionTypeDefaultDescription
portnumber4400HTTP port for the API server. The Next.js UI runs on port + 1.
hoststring"localhost"Hostname to bind both servers to. Set to "0.0.0.0" to listen on all interfaces.
signalsDirstringDirectory containing signal definition files. Station imports these to display signal metadata: input/output schemas, timeouts, retry counts, intervals, and concurrency settings. Falls back to a signals/ directory in the working directory if one exists.
broadcastsDirstringDirectory containing broadcast definition files. Station imports these to display DAG structure, failure policies, and node dependencies. Falls back to a broadcasts/ directory in the working directory if one exists.
adapterSignalQueueAdapterMemoryAdapterSignal storage adapter. Must point to the same database as your runner to see its data. See Adapters.
broadcastAdapterBroadcastQueueAdapterBroadcast storage adapter. Required for broadcast monitoring features. If omitted and broadcastsDir is set, a memory adapter is used.
runRunnersbooleantrueWhen true, Station runs its own SignalRunner and BroadcastRunner internally. Set to false for read-only monitoring of an existing runner’s database.
openbooleantrueAutomatically open the dashboard in the default browser on startup.
logLevel"debug" | "info" | "warn" | "error""info"Controls Station’s own console output verbosity.
auth{ username, password, sessionTtlMs? }Dashboard login credentials. When set, the dashboard presents a login screen. sessionTtlMs controls session expiry (default: 86,400,000 ms / 24 hours). Omit to disable auth.
runnerPartial<RunnerConfig>Override signal runner settings. Only applies when runRunners: true. Fields:

Runner config (nested under runner)

OptionTypeDefaultDescription
pollIntervalMsnumber1000Milliseconds between poll ticks for due runs.
maxConcurrentnumber5Maximum number of signal runs executing simultaneously.
maxAttemptsnumber1Default maximum retry attempts for signals that do not specify their own.
retryBackoffMsnumber1000Base delay in milliseconds between retry attempts.

Broadcast runner config (nested under broadcastRunner)

OptionTypeDefaultDescription
pollIntervalMsnumber1000Milliseconds between poll ticks for due broadcast runs.

Running Station

npx station

Station looks for station.config.ts (or .js / .mjs) in the current working directory. If no config file is found, it starts with default settings (MemoryAdapter, no signal directory).

Active mode vs. read-only mode

Station operates in one of two modes depending on the runRunners setting.

Active mode (runRunners: true, default)

Station creates its own SignalRunner and BroadcastRunner. It discovers signals from signalsDir, polls the adapter for due runs, and executes them. Use this when you want Station to be your only runner process. The dashboard provides full functionality: monitoring, triggering signals, and cancelling runs.

Read-only mode (runRunners: false)

Station only reads from the adapter. It does not create runners, does not execute signals, and does not poll for due runs. Use this when you have a separate runner process and want Station purely for monitoring. Trigger and cancel endpoints return 403 in this mode.

In read-only mode, Station still needs signalsDir and broadcastsDir to import signal/broadcast definitions for metadata display (schemas, intervals, DAG structure). Without these directories, the dashboard shows run data but not signal configuration details.

Dashboard features

Signals list

View all registered signals with their name, input/output schemas (rendered from Zod definitions), timeout, retry count, recurring interval, concurrency settings, step names, and source file path.

Scheduled signals

Recurring signals get a dedicated view showing the interval, next scheduled run time, last execution time, and last execution status.

Run history

Browse all past and current signal runs. Filter by status (pending, running, completed, failed, cancelled) or by signal name. Each run shows the full detail: input data, output data, error messages, timing (created, started, completed), attempt count, and step execution records.

Run logs

View stdout and stderr output captured from signal handler execution. Logs are stored in an in-memory buffer and persisted to a separate SQLite database (station-logs.db) for survival across restarts.

Broadcast visualization

See the DAG structure of registered broadcasts — which nodes exist, their signal mappings, and dependency edges. During execution, node statuses (pending, running, completed, failed, skipped) update in real time. Includes skip reasons when nodes are bypassed due to guard conditions, upstream failures, or cancellation.

Real-time updates

A WebSocket connection on /api/events pushes lifecycle events as they happen. The frontend subscribes automatically — no polling required. Events cover the full signal and broadcast lifecycle (see WebSocket events table below).

Actions

In active mode, the dashboard allows triggering signals with custom input and cancelling in-progress runs or broadcast runs.


API endpoints

Station exposes a REST API on the configured port. All responses use the shape { data: ... } on success or { error: string, message: string } on failure.

Health

EndpointMethodDescription
/api/healthGETHealth check. Calls ping() on the signal adapter and broadcast adapter (if configured). Returns { ok, signal, broadcast }.

Signals

EndpointMethodDescription
/api/signalsGETList all registered signals with metadata — name, file path, input/output schemas, interval, timeout, max attempts, max concurrency, step names.
/api/signals/scheduledGETList recurring signals with their interval, next scheduled run, last run time, and last run status.
/api/signals/:nameGETGet details for a specific signal. Returns 404 if not found.
/api/signals/:name/triggerPOSTTrigger a signal with optional input. Body: { "input": { ... } }. Returns the new run ID. Returns 403 in read-only mode, 404 if signal not found.
/api/signals/:name/runsGETList all runs for a specific signal.

Runs

EndpointMethodDescription
/api/runsGETList runs. Query params: ?status=pending|running|completed|failed|cancelled, ?signalName=name. Sorted by createdAt descending.
/api/runs/statsGETAggregate run counts by status. Returns { pending, running, completed, failed, cancelled }.
/api/runs/:idGETGet a single run’s full details including input, output, error, timing, and attempts. Returns 404 if not found.
/api/runs/:id/stepsGETGet step execution records for a run. Each step includes name, status, input, output, error, and timestamps.
/api/runs/:id/logsGETGet captured stdout/stderr log lines for a run.
/api/runs/:id/cancelPOSTCancel a run. Returns 403 in read-only mode, 400 if the run cannot be cancelled.

Broadcasts

EndpointMethodDescription
/api/broadcastsGETList all registered broadcasts with DAG structure — node names, signal mappings, dependency edges, failure policy.
/api/broadcasts/:nameGETGet a single broadcast’s metadata and DAG structure. Returns 404 if not found.
/api/broadcasts/:name/triggerPOSTTrigger a broadcast with optional input. Body: { "input": { ... } }. Returns the new broadcast run ID. Returns 403 in read-only mode.
/api/broadcasts/:name/runsGETList all runs for a specific broadcast.
/api/broadcast-runs/:idGETGet a broadcast run’s full details. Returns 404 if not found.
/api/broadcast-runs/:id/nodesGETGet all node runs for a broadcast execution. Each node includes name, signal name, signal run ID, status, skip reason, input, output, error, and timestamps.
/api/broadcast-runs/:id/logsGETGet aggregated logs from all node signal runs in a broadcast execution. Sorted by timestamp.
/api/broadcast-runs/:id/cancelPOSTCancel a broadcast run. Returns 403 in read-only mode, 400 if it cannot be cancelled.

WebSocket events

Connect to /api/events on the API server port. Each message is a JSON object with type, timestamp, and data fields.

Signal events

Event typeDescription
signal:discoveredA signal file was found during directory scanning.
run:dispatchedA run was picked up from the queue and dispatched for execution.
run:startedA run’s handler began executing in the child process.
run:completedA run finished successfully. Includes output data.
run:failedA run failed after exhausting all retry attempts. Includes error message.
run:timeoutA run exceeded its timeout and was killed.
run:retryA run failed but has remaining attempts. Includes current attempt and max attempts.
run:cancelledA run was cancelled via the API or programmatically.
run:skippedA recurring run was skipped (e.g. previous run still active).
run:rescheduledA recurring run was rescheduled. Includes the next run time.
step:startedA step within a multi-step signal began executing.
step:completedA step finished successfully.
step:failedA step failed.
log:outputA line of stdout or stderr was captured from a running signal handler. Includes run ID, signal name, level, and message.
run:completeErrorAn error occurred while trying to mark a run as complete (e.g. adapter failure during finalization).

Broadcast events

Event typeDescription
broadcast:discoveredA broadcast file was found during directory scanning.
broadcast:queuedA broadcast run was added to the queue.
broadcast:startedA broadcast began executing its DAG.
broadcast:completedAll nodes in the broadcast finished successfully.
broadcast:failedThe broadcast failed. Includes error message.
broadcast:cancelledThe broadcast was cancelled.
node:triggeredA DAG node’s signal was triggered for execution.
node:completedA DAG node’s signal completed successfully.
node:failedA DAG node’s signal failed. Includes error message.
node:skippedA DAG node was skipped. Includes the reason: guard (guard function returned false), upstream-failed (a dependency failed), or cancelled.

Using Station with an existing runner

A common setup: one process runs signals, another runs Station for monitoring. Both point at the same SQLite database.

// runner.ts — executes signalsimport path from "node:path";import { SignalRunner } from "station-signal";import { SqliteAdapter } from "station-adapter-sqlite"; const runner = new SignalRunner({  signalsDir: path.join(import.meta.dirname, "signals"),  adapter: new SqliteAdapter({ dbPath: "./jobs.db" }),}); runner.start();
// station.config.ts — read-only monitoringimport { defineConfig } from "station-kit";import { SqliteAdapter } from "station-adapter-sqlite";import { BroadcastSqliteAdapter } from "station-adapter-sqlite/broadcast"; export default defineConfig({  port: 4400,  signalsDir: "./signals",  broadcastsDir: "./broadcasts",  adapter: new SqliteAdapter({ dbPath: "./jobs.db" }),  broadcastAdapter: new BroadcastSqliteAdapter({ dbPath: "./jobs.db" }),  runRunners: false, // Don't execute signals — just monitor});
SQLite with WAL mode supports concurrent readers and a single writer. The runner process writes; Station reads. Both can open the same database file simultaneously without conflict.

Graceful shutdown

Station listens for SIGINT and SIGTERM. On shutdown, it stops the broadcast runner first (it may query the database during cleanup), then the signal runner, then closes the WebSocket server, log store, and HTTP server. Both runners are given a 5-second grace period to finish in-flight work.