Tools

The local log views are built from data the local package mirrors down from the server over a token-gated API on the server's IP. Like the sync, but read-only, and behind the same 404 wall. The /logs and /stats endpoints are identical across every hoster (from the shared common/server/logs-stats.js); /tasks and /tasks/payload are taskhoster's own, the source for the Task Logs browser. Every file is exposed by timestamp, exact validated name, or validated host / queue / id / kind only — no filename crosses the wire freely, so there is no path-traversal surface.

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/tasks
GET /api/v2/tasks/file
GET /api/v2/tasks/payload
The read-only tools API: list and stream the access-log, server-health and task-event files.

Private API

Every call carries the master token as Authorization: Bearer <master-token>, the same gate as the sync. These routes are read-only: with a valid token they answer 200 (a version skew on a /api/v<other> path answers 426), and anything without a valid token — bad token, unknown route — returns the identical 404 wall, so a probe cannot tell the API is there (see Server). Files are exposed by timestamp (logs), exact validated name (stats and task day-files), or validated host / queue / id / kind (payloads) only, so no free filename ever reaches the filesystem. The log, stat and task files are streamed with HTTP Range support, so the local package fetches only the appended tail.

MethodPathServes
GET/api/v2/logsList 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/statsList 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/tasksList the durable task-event day-files.
GET/api/v2/tasks/fileStream one task-event day-file by name; a Range fetches only the growing tail.
GET/api/v2/tasks/payloadStream one archived payload body, for queues with archiving on.

GET /api/v2/logs

Lists the Caddy access logs so the local package can mirror them for the Web Logs browser. The active log is reported by size only; each rotated file is keyed by its mtime, never its name — so the server can't influence where the package writes, and a newly-appeared file is how the package detects the active log rotated.

Request

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

Response

A 200 JSON object:

{
  "active": { "size": 12345 },
  "rotated": [
    { "ts": 1784370489663, "size": 67108864 },
    { "ts": 1784514159284, "size": 41943040 }
  ]
}
The active access log by size, and each rotated file keyed by mtime.
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 mtime in milliseconds — how the package names it (access-<ts>.log) and de-duplicates it.
rotated[].sizeintegerIts size in bytes; a size change means the package must re-fetch it.

GET /api/v2/logs/file

Streams the bytes of one access-log file. A Range header is honoured so the package fetches only the tail appended since last time; an immutable rotated file is fetched once, whole.

Request

GET /api/v2/logs/file?which=active HTTP/1.1
Authorization: Bearer <master-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 — the package passes its local size to append just the new tail.

Plus Authorization: Bearer <master-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 — never an error.

GET /api/v2/stats

Lists the server-health day-files and the sampling interval, so the local package can mirror them for the Server Logs browser. Files are named <type>-YYYY-MM-DD.log; a past day is frozen and mirrored once, today's file grows.

Request

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

Response

A 200 JSON object:

{
  "interval": 15000,
  "files": [
    { "name": "cpu-2026-07-26.log", "type": "cpu", "date": "2026-07-26", "size": 82944 }
  ]
}
The server-health day-files and the milliseconds between samples.
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.

Request

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

Plus Authorization: Bearer <master-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/network bytes) are cumulative; a consumer diffs two snapshots to get a rate. Metrics a host can't report (e.g. disk/network IO off Linux) are null rather than an error.

Request

MethodGET
Path/api/v2/stats/now
AuthAuthorization: Bearer <master-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 }
}
A live server-health snapshot, the same shape the recorder stores.
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/tasks

Lists the durable task-event day-files, the source for the Task Logs browser and the durable task history that outlives a queue's short retention. Files are named tasks-YYYY-MM-DD.log; a past day is frozen, today's file grows.

Request

MethodGET
Path/api/v2/tasks
AuthAuthorization: Bearer <master-token>
ParametersNone.

Response

A 200 JSON object:

{
  "files": [
    { "name": "tasks-2026-07-26.log", "date": "2026-07-26", "size": 204800 }
  ]
}
One entry per task-event day-file.
FieldTypeMeaning
filesarrayOne entry per task-event day-file, sorted by name.
files[].namestringThe exact filename to request from /tasks/file.
files[].datestringThe file's UTC date, YYYY-MM-DD.
files[].sizeintegerIts size in bytes.

GET /api/v2/tasks/file

Streams one task-event day-file by its exact name, validated against tasks-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.

Request

GET /api/v2/tasks/file?name=tasks-2026-07-26.log HTTP/1.1
Authorization: Bearer <master-token>
Range: bytes=204800-
Fetch only today's newly-appended task events.
ParameterInMeaning
namequeryThe exact day-file name from /tasks, e.g. tasks-2026-07-26.log.
RangeheaderOptional. bytes=N- to fetch only from byte N.

Plus Authorization: Bearer <master-token>.

Response

The raw bytes of the day-file — one JSON task event 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/tasks/payload

Streams one archived payload body — the item, a progress note, the result or an error — for a task, addressed by validated host, queue, id and kind. Payloads are present only for queues with taskhoster-queue-archivePayloads on; a queue without archiving has none. Each of the four parameters is validated (a host and queue against the stored definitions, an id against the 16-hex item format, a kind against the fixed set), so nothing free reaches the filesystem.

Request

GET /api/v2/tasks/payload?host=task.example.com&queue=resize&id=1f3c…9a&kind=result HTTP/1.1
Authorization: Bearer <master-token>
Fetch one task's archived result body.
ParameterInMeaning
hostqueryThe queue host (a domain), validated against the stored definitions.
queuequeryThe queue name on that host.
idqueryThe task id (16 hex characters).
kindqueryWhich body: item, progress, result or error.

Plus Authorization: Bearer <master-token>.

Response

The raw payload bytes as archived, Content-Type: application/octet-stream.

  • 200 with the body when it exists.
  • An absent payload — a queue without archiving, or a body that was never written — returns an empty 200 body, never an error.

How the local package renders

Each log browser mirrors its source statelessly, deriving everything from what it already has on disc. It lists the day-files, fetches each frozen file once, and appends only today's tail with a Range from the local size. It keeps its local copy forever, even as the server prunes older files — so the Web Logs, Server Logs and Task Logs browsers hold a history longer than the server's own retention.