Tools

The local scripts' three log browsers are built from data it mirrors down from the server over a token-protected API on the server's IP. Like the sync, but read-only: each browser reads one endpoint. The Web Logs tool reads the Caddy access log, the Server Logs tool reads the server-health statistics, and the Cron Logs tool reads the run records the runner writes. The local scripts pulls each source incrementally into a local data dir and renders a static browsable site from it.

GET /api/v2/logs
GET /api/v2/logs/file
GET /api/v2/stats
GET /api/v2/stats/file
GET /api/v2/stats/now
GET /api/v2/runs
GET /api/v2/runs/file
The read-only tools API: list and stream the access-log, server-health and run files.

Private API

Every call is authenticated with a bearer token, the cronhoster-config-apiToken from your machine configuration, sent as Authorization: Bearer <token>. These routes are read-only. The /logs and /stats routes are identical across every hoster and come from the shared localhoster-server/logs-stats.js router (the same file, byte for byte, in sitehoster, datahoster, gamehoster and cronhoster). The /runs routes are cronhoster's own addition in cronhoster-api.js. As with the sync, a valid token answers 200; a version skew on an /api/v<other> path answers 426; anything without a valid token, and any unknown route, returns the identical 404 wall. The routes are versioned under /api/v2/, discovered from /api/version. Files are exposed by timestamp (logs) or exact validated name (stats and runs) only, so no filename ever crosses the wire and there is no path-traversal surface.

MethodPathServes
GET/api/v2/logsWeb Logs. List the access-log files: the active log's size and each rotated file's timestamp.
GET/api/v2/logs/fileStream one access-log file; a Range fetches only the newly-appended tail.
GET/api/v2/statsServer Logs. List the server-health day-files and the sampling interval.
GET/api/v2/stats/fileStream one stat day-file by name; a Range fetches only the growing tail.
GET/api/v2/stats/nowA single live server-health snapshot, for debugging.
GET/api/v2/runsCron Logs. List the job-run day-files by name and date.
GET/api/v2/runs/fileStream one run day-file by name; a Range fetches only the growing tail.

GET /api/v2/logs

The data source for the Web Logs tool. Lists the Caddy access logs so the local scripts can mirror them. The active log is reported by size only; each rotated file is keyed by its rotation timestamp, never its name, so the server cannot influence where the local scripts writes, and a newly-appeared timestamp is how the local scripts detects that the active log rotated.

Request

MethodGET
Path/api/v2/logs
AuthAuthorization: Bearer <token>
ParametersNone.

Response

A 200 JSON object:

{
  "active": { "size": 12345 },
  "rotated": [
    { "ts": 1784370489663, "size": 67108864 },
    { "ts": 1784514159284, "size": 41943040 }
  ]
}
The active log by size, plus each rotated file keyed by its rotation timestamp.
FieldTypeMeaning
activeobjectThe live log as { size } in bytes, or null if there is none.
rotatedarrayOne entry per rotated file, sorted oldest-first.
rotated[].tsintegerThe file's rotation time in milliseconds, how the local scripts names it (access-<ts>.log) and de-duplicates it.
rotated[].sizeintegerIts size in bytes; a size change means the local scripts must re-fetch it.

GET /api/v2/logs/file

Streams the bytes of one access-log file. A Range header is honoured so the local scripts fetches only the tail appended since last time; an immutable rotated file is fetched once, whole. The on your machine download lives in the shared toolkit (common/browse/pull-logs.js).

Request

GET /api/v2/logs/file?which=active HTTP/1.1
Authorization: Bearer <token>
Range: bytes=12345-
Fetch only the tail of the active log appended since last time.
ParameterInMeaning
whichqueryactive for the live log, or a rotated file's timestamp (the ts from /logs).
RangeheaderOptional. bytes=N- fetches only from byte N, so the local scripts passes its local size to append just the new tail.

Plus Authorization: Bearer <token>.

Response

The raw log bytes, Caddy's JSON access lines, one per request, Content-Type: application/octet-stream with Accept-Ranges: bytes.

  • 206 Partial Content with a Content-Range header when a Range was sent; 200 otherwise.
  • An absent file, or a Range at or past the end, returns an empty 200 body.

GET /api/v2/stats

The data source for the Server Logs tool. Lists the server-health day-files the API process samples every 15 seconds through localhoster-server/stats-recorder.js, and the sampling interval, so the local scripts can mirror them. Files are named <type>-YYYY-MM-DD.log with one type per server metric; a past day is frozen and mirrored once, today's file grows.

Request

MethodGET
Path/api/v2/stats
AuthAuthorization: Bearer <token>
ParametersNone.

Response

A 200 JSON object:

{
  "interval": 15000,
  "files": [
    { "name": "cpu-2026-07-20.log",    "type": "cpu",    "date": "2026-07-20", "size": 82944 },
    { "name": "memory-2026-07-20.log", "type": "memory", "date": "2026-07-20", "size": 61200 }
  ]
}
The sampling interval, plus one entry per server-health day-file.
FieldTypeMeaning
intervalintegerMilliseconds between samples (15000 = every 15 seconds).
filesarrayOne entry per day-file, sorted by name.
files[].namestringThe exact filename to request from /stats/file.
files[].typestringThe statistic type: cpu, memory, disk, diskio or netio.
files[].datestringThe file's UTC date, YYYY-MM-DD.
files[].sizeintegerIts size in bytes.

GET /api/v2/stats/file

Streams one stat day-file by its exact name, which is validated against <type>-YYYY-MM-DD.log, the only thing that reaches the filesystem, so there is no path-traversal surface. A Range fetches only the growing tail of the current day. The on your machine download lives in the shared toolkit (common/browse/pull-stats.js).

Request

GET /api/v2/stats/file?name=cpu-2026-07-20.log HTTP/1.1
Authorization: Bearer <token>
Range: bytes=61200-
Fetch only today's newly-appended samples.
ParameterInMeaning
namequeryThe exact day-file name from /stats, e.g. cpu-2026-07-20.log.
RangeheaderOptional. bytes=N- to fetch only from byte N.

Plus Authorization: Bearer <token>.

Response

The raw bytes of the day-file, one JSON sample per line, Content-Type: application/octet-stream with Accept-Ranges: bytes.

  • 206 Partial Content with a Content-Range when a Range was sent; 200 otherwise.
  • An unknown or absent name, or a Range at or past the end, returns an empty 200 body.

GET /api/v2/stats/now

Returns a single live health snapshot for debugging, the same shape the recorder samples on its interval, but taken on demand and never stored. Counter fields (CPU core times, disk and network bytes) are cumulative; a consumer diffs two snapshots to get a rate. Metrics a host cannot report (disk or network IO off Linux) are null rather than an error.

Request

MethodGET
Path/api/v2/stats/now
AuthAuthorization: Bearer <token>
ParametersNone.

Response

A 200 JSON snapshot (values below are illustrative):

{
  "ts": 1784514159,
  "uptime": 864000,
  "hostname": "vps-1",
  "load": [0.12, 0.09, 0.05],
  "cpu":    { "model": "…", "cores": [ { "user": 0, "nice": 0, "sys": 0, "idle": 0, "irq": 0 } ] },
  "memory": { "total": 0, "used": 0, "available": 0, "free": 0 },
  "disk":   { "path": "/", "total": 0, "used": 0, "free": 0 },
  "diskIo": { "readBytes": 0, "writeBytes": 0 },
  "netIo":  { "rxBytes": 0, "txBytes": 0 }
}
One live server-health sample, the shape the recorder writes each interval.
FieldTypeMeaning
tsintegerThe sample's server Unix time, in seconds.
uptimeintegerSeconds since boot.
hostnamestringThe machine's hostname.
loadarrayThe 1, 5 and 15-minute load averages.
cpuobjectmodel plus cores: per-core cumulative times (user, nice, sys, idle, irq).
memoryobjecttotal / used / available / free bytes.
diskobjectUsage of the filesystem holding / in bytes, or null.
diskIoobjectCumulative readBytes / writeBytes, or null.
netIoobjectCumulative rxBytes / txBytes, or null.

GET /api/v2/runs

Cronhoster's own endpoint, the data source for the Cron Logs tool. Lists the job-run day-files the runner writes through cronhoster-runner/run-recorder.js, one JSON line per finished run. Files are named runs-YYYY-MM-DD.log; the day whose date has rolled over is frozen and mirrored once, today's file grows. The server prunes these after about 14 days.

Request

MethodGET
Path/api/v2/runs
AuthAuthorization: Bearer <token>
ParametersNone.

Response

A 200 JSON object:

{
  "files": [
    { "name": "runs-2026-07-23.log", "date": "2026-07-23", "size": 48210 },
    { "name": "runs-2026-07-24.log", "date": "2026-07-24", "size": 17664 }
  ]
}
One entry per run day-file, sorted by name.
FieldTypeMeaning
filesarrayOne entry per run day-file, sorted by name.
files[].namestringThe exact filename to request from /runs/file, validated as runs-YYYY-MM-DD.log.
files[].datestringThe file's UTC date, YYYY-MM-DD.
files[].sizeintegerIts size in bytes; a size change means the local scripts must fetch the new tail.

GET /api/v2/runs/file

Streams one run day-file by its exact name, validated against runs-YYYY-MM-DD.log, the only thing that reaches the filesystem, so there is no path-traversal surface. A Range fetches only the growing tail of the current day. The on your machine download lives in cronhoster's own local/pull-runs.js.

Request

GET /api/v2/runs/file?name=runs-2026-07-24.log HTTP/1.1
Authorization: Bearer <token>
Range: bytes=17664-
Fetch only today's newly-recorded run lines.
ParameterInMeaning
namequeryThe exact day-file name from /runs, e.g. runs-2026-07-24.log.
RangeheaderOptional. bytes=N- to fetch only from byte N.

Plus Authorization: Bearer <token>.

Response

The raw bytes of the day-file, one JSON run record per line, Content-Type: application/octet-stream with Accept-Ranges: bytes.

  • 206 Partial Content with a Content-Range when a Range was sent; 200 otherwise.
  • An unknown or absent name, or a Range at or past the end, returns an empty 200 body.

Each line is one completed run: the cron and run names, the run's schedule, wall-clock duration, exit code or kill signal, whether it was killed for overrunning, and how many of its own ticks it skipped while it held a slot. The Runner page describes the record in full.

How the local scripts renders

Each tool pulls its endpoint incrementally, statelessly, deriving everything from what it already has on disk, the counterpart of how the sync compares trees. It negotiates the version from /api/version, then:

1

List

/logs, /stats or /runs returns what the server holds: sizes and timestamps (logs), or day-files (stats and runs).

2

Fetch what's frozen

An immutable file, a rotated log or a past day-file, is downloaded once, whole, when it is missing locally or its size differs.

3

Append the tail

The growing file, the active log or today's day-file, is fetched with a Range from the local size, appending only the new bytes.

The downloads live in the shared toolkit for Web Logs and Server Logs (common/browse/pull-logs.js and common/browse/pull-stats.js) and in cronhoster's own local/pull-runs.js for Cron Logs, so every source mirrors the same way. Each pull writes into a local data dir, and the tool then renders a static browsable site from it: common/browse/web.js for Web Logs, common/browse/server.js for Server Logs, and cronhoster's own local/browse/cron.js for Cron Logs. The server keeps only a bounded window (run records are pruned after about 14 days, stats after 60), but the local scripts keeps every day it ever pulled, so its history outlives the server's retention and a restart.

The Cron Logs tool is cronhoster's service-specific browser, the analog of sitehoster's Automation Logs, datahoster's Data Logs and gamehoster's Game Logs. It joins the run records with the cron directories in the content root and shows every cron with its health, and a page per cron listing its recent runs: each run's duration, whether it was killed for overrunning, and how many ticks it skipped.