Documentation
  1. 01Getting started
  2. 02Next.js and Vercel
  3. 03Servers and scripts
  4. 04Schedules, grace and timeouts
  5. 05What it catches
  6. 06Alerts
  7. 07Stores
  8. 08Dashboard and API
  9. 09MCP server
  10. 10Agent skill
  11. 11AI triage
  12. 12API reference
  13. 13Limits and design notes

Dashboard and API

Dashboard and API

cw.routes() returns fetch-style handlers for a dashboard and a JSON API. They are the same handler; GET, POST and DELETE are aliases so a Next.js route file can export them directly.

export const { GET, POST, DELETE } = cw.routes({ token: process.env.CRONWATCH_TOKEN, basePath: "/cronwatch" });

basePath defaults to /cronwatch and is only used to build links. token defaults to CRONWATCH_TOKEN.

Access

Every request needs the token, as Authorization: Bearer <token>, or as a ?token= query once for the dashboard, after which an HttpOnly cookie keeps you signed in for thirty days. Comparison is constant-time.

With no token configured, the routes are open when NODE_ENV is not production and answer 503 when it is.

The check endpoint additionally accepts the client’s cronSecret as a bearer, so a platform cron can call it.

The token grants everything, including silencing and forgetting jobs. Treat it like a password.

Pages

Path What
/ every job: health, schedule, last run, next due, durations
/jobs/:name one job: definition, stats, and the last fifty runs with errors, output and metrics

Pages refresh every minute and are marked noindex.

Endpoints

Method and path Does Returns
GET /api/jobs list jobs { jobs: JobSummary[] }
GET /api/jobs/:name?runs=20 one job with recent runs { job: JobSummary, runs: Run[] }
DELETE /api/jobs/:name forget the job and its runs { ok: true }
POST /api/jobs/:name/silence body { "for": "2h" } { state: JobState }
POST /api/jobs/:name/unsilence { state: JobState }
GET or POST /api/check run the check now { checkedAt, jobs, alerts, pruned }
GET /api/runs/:id one run { run: Run }

Errors are { ok: false, error } with 401, 404 or 503.

JobSummary

interface JobSummary {
  name: string;
  definition: StoredJobDefinition;
  health: "healthy" | "late" | "failing" | "stuck" | "silenced" | "never_ran";
  open: Condition[];              // conditions currently open
  lastRun: Run | null;
  nextExpectedAt: number | null;  // epoch ms
  consecutiveFailures: number;
  silencedUntil: number | null;
  stats: { runs: number; okRate: number; p50Ms: number | null; p95Ms: number | null };
}

Run

interface Run {
  id: string;
  job: string;
  status: "running" | "ok" | "failed" | "timeout";
  startedAt: number;
  finishedAt: number | null;
  durationMs: number | null;
  error: string | null;
  output: string | null;          // capped at 16 KB, tail kept
  metrics: Record<string, number>;
  trigger: string;                // "handler", "run", or what you passed
}