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
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.
| Method | Path | Serves |
|---|---|---|
GET | /api/v2/logs | List the access-log files: the active log's size and each rotated file's timestamp. |
GET | /api/v2/logs/file | Stream one access-log file; a Range fetches only the newly-appended tail. |
GET | /api/v2/stats | List the server-health day-files and the sampling interval. |
GET | /api/v2/stats/file | Stream one stat day-file by name; a Range fetches only the growing tail. |
GET | /api/v2/stats/now | A single live server-health snapshot, for debugging. |
GET | /api/v2/tasks | List the durable task-event day-files. |
GET | /api/v2/tasks/file | Stream one task-event day-file by name; a Range fetches only the growing tail. |
GET | /api/v2/tasks/payload | Stream 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
| Method | GET |
|---|---|
| Path | /api/v2/logs |
| Auth | Authorization: Bearer <master-token> |
| Parameters | None. |
Response
A 200 JSON object:
{
"active": { "size": 12345 },
"rotated": [
{ "ts": 1784370489663, "size": 67108864 },
{ "ts": 1784514159284, "size": 41943040 }
]
}
| Field | Type | Meaning |
|---|---|---|
active | object | The live log as { size } in bytes, or null if there is none. |
rotated | array | One entry per rotated file, sorted oldest-first. |
rotated[].ts | integer | The file's mtime in milliseconds — how the package names it (access-<ts>.log) and de-duplicates it. |
rotated[].size | integer | Its 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-
| Parameter | In | Meaning |
|---|---|---|
which | query | active for the live log, or a rotated file's timestamp (the ts from /logs). |
Range | header | Optional. 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 Contentwith aContent-Rangeheader when aRangewas sent;200otherwise.- An absent file, or a
Rangeat or past the end, returns an empty200body — 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
| Method | GET |
|---|---|
| Path | /api/v2/stats |
| Auth | Authorization: Bearer <master-token> |
| Parameters | None. |
Response
A 200 JSON object:
{
"interval": 15000,
"files": [
{ "name": "cpu-2026-07-26.log", "type": "cpu", "date": "2026-07-26", "size": 82944 }
]
}
| Field | Type | Meaning |
|---|---|---|
interval | integer | Milliseconds between samples (15000 = every 15 seconds). |
files | array | One entry per day-file, sorted by name. |
files[].name | string | The exact filename to request from /stats/file. |
files[].type | string | The statistic type: cpu, memory, disk, diskio or netio. |
files[].date | string | The file's UTC date, YYYY-MM-DD. |
files[].size | integer | Its 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-
| Parameter | In | Meaning |
|---|---|---|
name | query | The exact day-file name from /stats, e.g. cpu-2026-07-26.log. |
Range | header | Optional. 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 Contentwith aContent-Rangewhen aRangewas sent;200otherwise.- An unknown or absent name, or a
Rangeat or past the end, returns an empty200body.
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
| Method | GET |
|---|---|
| Path | /api/v2/stats/now |
| Auth | Authorization: Bearer <master-token> |
| Parameters | None. |
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 }
}
| Field | Type | Meaning |
|---|---|---|
ts | integer | The sample's server Unix time, in seconds. |
uptime | integer | Seconds since boot. |
hostname | string | The machine's hostname. |
load | array | The 1, 5 and 15-minute load averages. |
cpu | object | model plus cores: per-core cumulative times (user, nice, sys, idle, irq). |
memory | object | total / used / available / free bytes. |
disk | object | Usage of the filesystem holding / in bytes, or null. |
diskIo | object | Cumulative readBytes / writeBytes, or null. |
netIo | object | Cumulative 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
| Method | GET |
|---|---|
| Path | /api/v2/tasks |
| Auth | Authorization: Bearer <master-token> |
| Parameters | None. |
Response
A 200 JSON object:
{
"files": [
{ "name": "tasks-2026-07-26.log", "date": "2026-07-26", "size": 204800 }
]
}
| Field | Type | Meaning |
|---|---|---|
files | array | One entry per task-event day-file, sorted by name. |
files[].name | string | The exact filename to request from /tasks/file. |
files[].date | string | The file's UTC date, YYYY-MM-DD. |
files[].size | integer | Its 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-
| Parameter | In | Meaning |
|---|---|---|
name | query | The exact day-file name from /tasks, e.g. tasks-2026-07-26.log. |
Range | header | Optional. 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 Contentwith aContent-Rangewhen aRangewas sent;200otherwise.- An unknown or absent name, or a
Rangeat or past the end, returns an empty200body.
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>
| Parameter | In | Meaning |
|---|---|---|
host | query | The queue host (a domain), validated against the stored definitions. |
queue | query | The queue name on that host. |
id | query | The task id (16 hex characters). |
kind | query | Which body: item, progress, result or error. |
Plus Authorization: Bearer <master-token>.
Response
The raw payload bytes as archived, Content-Type: application/octet-stream.
200with the body when it exists.- An absent payload — a queue without archiving, or a body that was never written — returns an
empty
200body, 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.