HTTP API
tidefetch serve exposes the same REST + WebSocket API its web UI runs on.
It is the supported way to drive Tidefetch from dashboards, scripts and
custom frontends: one origin, password auth, live push updates — and the
aria2 RPC secret never leaves the server.
Base URL: http://<host>:8210/api. Every request and response is JSON.
[!NOTE] The API is stable in spirit but still pre-1.0: additive changes land without notice, breaking changes only with a minor version bump and a changelog entry.
Choosing a surface
| You are building | Use |
|---|---|
| A dashboard widget, script or custom UI | This API |
| A tool that already speaks aria2 JSON-RPC | aria2's own RPC, see below |
Tidefetch adds what raw aria2 does not have: bcrypt password auth with rate
limiting, WebSocket deltas instead of polling, persistent history, link
probing, and file-system browsing. Going straight to
aria2's JSON-RPC
makes sense only when an existing integration already speaks it — in that
case point it at the daemon (default ws://127.0.0.1:6800/jsonrpc) with the
RPC secret from config.json. Do not expose that port beyond localhost;
the secret authorizes full control, including arbitrary file writes.
Authentication
Three modes, decided at startup:
| Bind | Password set | Behaviour |
|---|---|---|
| Loopback | no | Auth disabled — requests need no credentials |
| Any | yes | Login required |
| Non-loopback | no | Server refuses to start (use -password or -no-auth) |
Log in once, then use the returned token:
TOKEN=$(curl -s http://nas:8210/api/login \
-d '{"password":"your-password"}' | jq -r .token)
curl -s http://nas:8210/api/state -H "Authorization: Bearer $TOKEN"
POST /api/login → 200 {"ok":true,"authRequired":true,"token":"…"}.
The same value is also set as an HttpOnly tf_session cookie for browsers;
scripts should prefer the Authorization: Bearer header. Sessions live for
30 days and are held in memory — a server restart signs everyone out.
POST /api/logout revokes the session.
Failed logins are rate limited per IP: 8 wrong attempts block that IP for
5 minutes (429).
Cross-origin, state-changing requests are rejected (403): browsers send an
Origin header and Tidefetch requires it to match the host. Server-side
callers without an Origin header are unaffected.
Errors
Non-2xx responses carry {"error":"human-readable message"}.
| Status | Meaning |
|---|---|
400 |
Malformed request or unknown action |
401 |
Missing/expired session, or wrong password |
403 |
Cross-origin request rejected |
404 |
Unknown GID |
429 |
Login rate limit |
502 |
aria2 rejected the operation or is unreachable |
Real-time updates
GET /api/ws upgrades to a WebSocket. The server pushes; the client never
needs to send anything.
const ws = new WebSocket("ws://nas:8210/api/ws");
ws.onmessage = (e) => {
const msg = JSON.parse(e.data);
// msg.type: "snapshot" | "delta" | "conn"
};
type |
Fields | When |
|---|---|---|
snapshot |
tasks, stat |
On connect, and after bulk changes |
delta |
updated (changed tasks), removed (gone GIDs), stat |
Every poll tick with changes |
conn |
connected (bool) |
aria2 link lost or regained |
Browsers authenticate the upgrade with the session cookie automatically; the
Authorization header works for non-browser clients. If WebSockets are
inconvenient, poll GET /api/state instead.
The Task object
Every task in state, snapshot, delta and task detail responses:
{
"gid": "c37c75438f281a1a",
"name": "ubuntu-24.04.2-desktop-amd64.iso",
"status": "active",
"total": 6343219200, "done": 2617245696, "uploaded": 0,
"downSpeed": 861184, "upSpeed": 0,
"conns": 1, "seeders": 0,
"seeding": false, "torrent": false,
"dir": "/downloads",
"uri": "https://releases.ubuntu.com/…",
"numFiles": 1,
"progress": 0.4126,
"speeds": [812032, 845311, …]
}
status is one of active · waiting · paused · complete · error · removed.
speeds is a downsampled lifetime speed series (≤48 points) for sparklines.
Error tasks add errorCode and errorMsg.
The stat object carries the global picture: downSpeed, upSpeed,
numActive, numWaiting, numStopped, sessionDown, sessionUp,
diskFree, diskTotal.
Endpoints
State
GET /api/state
Full snapshot: version, aria2 (engine version), connected,
downloadDir, authEnabled, tasks, stat. This is the one endpoint a
dashboard widget needs.
Add downloads
POST /api/add
{ "kind": "uri", "uris": ["https://example.com/file.iso", "magnet:?xt=…"],
"options": { "dir": "/downloads/isos", "max-download-limit": "2M" } }
kind is uri (default), torrent or metalink; the latter two send the
file as base64 in payload instead of uris. Each URI becomes its own
task. options accepts any
aria2 input option;
dir defaults to the configured download directory. Returns
{"gids":["…"]}.
Inspect a task
GET /api/tasks/{gid}
Returns task, per-file rows (files), live peers (torrents), servers
(HTTP), a ≤240-bucket pieces completion map, the full speedHistory, and
bt metadata.
Act on a task
POST /api/tasks/{gid}/action
{ "action": "pause" }
action |
Effect |
|---|---|
pause / resume |
Pause or unpause |
remove |
Stop and remove; "deleteFiles": true also deletes data |
removeResult |
Drop a finished/failed entry from the list |
retry |
Re-queue a failed download from its original URI |
Bulk variant — POST /api/tasks/actions with {"action":"pauseAll"},
resumeAll or purge (clear all finished results).
Tune a task
POST /api/tasks/{gid}/files {"indices":[1,3,4]} select torrent files
GET /api/tasks/{gid}/options current aria2 options
PUT /api/tasks/{gid}/options {"max-download-limit":"1M"}
POST /api/tasks/{gid}/position {"pos":0,"how":"POS_SET"} queue order
Global options
GET /api/options
PUT /api/options {"max-overall-download-limit":"5M"}
Reads and writes aria2's global option set — the same 100+ keys the Settings screens edit.
History
GET /api/history?q=ubuntu&category=Software
DELETE /api/history/{gid}
DELETE /api/history
Returns {"entries":[…],"categories":[…]}. Entries persist across
restarts and record name, URL, size, category and completion time.
Filesystem helpers
GET /api/browse?path=/srv list directories (server-side)
POST /api/browse/mkdir {"path":"/srv","name":"isos"}
GET /api/probe?url=https://… pre-download link inspection
browse walks up to the nearest existing directory instead of failing and
reports free space. probe answers with filename, size, content type and
whether the server supports resume:
{ "filename": "file.iso", "size": 6343219200,
"contentType": "application/x-iso9660-image",
"resumable": true, "via": "accept-ranges", "finalUrl": "…" }
Account
POST /api/login {"password":"…"}
POST /api/logout
POST /api/password {"current":"…","new":"…"}
Setting a first password via /api/password turns authentication on.
Recipes
Dashboard widget (Homepage, Homarr, custom): call GET /api/state,
show stat.downSpeed, stat.numActive and the first few tasks.
Add a download from a shell alias:
tfadd() {
curl -s http://nas:8210/api/add \
-H "Authorization: Bearer $TIDEFETCH_TOKEN" \
-d "{\"kind\":\"uri\",\"uris\":[\"$1\"]}"
}
Pause everything at night (cron):
curl -s http://nas:8210/api/tasks/actions \
-H "Authorization: Bearer $TIDEFETCH_TOKEN" \
-d '{"action":"pauseAll"}'
For reverse-proxy setups, TLS and exposing the API safely beyond your LAN, see Reverse proxy & TLS and Homelab operations.