Skip to content

REST API

Kheopsian edited this page Oct 13, 2026 · 3 revisions

🇬🇧 English · 🇫🇷 Français

REST API

Applies to Hydranos 4.4.

For scripting Hydranos: every native API route (/api/*) in 4.4, which work, which are stubs, which were removed, curl examples. The qBittorrent-compatible API (/api/v2/*) is documented on qBittorrent Shim and Automation, and the MCP endpoint on MCP Server.

This page is the map. Full request and answer bodies are in the repository's docs/API.md; the pages linked from each section explain what the settings do.

Basics

Base URL and format

One port, 8199 by default ([daemon] api_port), serves the web UI, /api, the qBittorrent shim and /mcp. Examples use:

export HY=http://hydranos.example.org:8199
export KEY=YOUR_API_KEY

Bodies and answers are JSON unless a row says otherwise. Responses over 32 bytes are gzip-compressed when the client accepts it (curl --compressed).

Authentication

There is one credential, the API key ([daemon] api_key, 48 hex characters, generated at first start). Where to find it, how to rotate it and how to expose Hydranos safely: Security and Access.

How the key travels Example Notes
X-Api-Key header -H "X-Api-Key: $KEY" The normal way.
apikey query parameter "$HY/api/status?apikey=$KEY" For clients that cannot set headers. Lands in proxy logs.
SID session cookie from POST /api/v2/auth/login Form username + password (admin password or API key). Unlocks the whole native API. Expires after 3600 s idle; lost on restart.
Authorization: Bearer -H "Authorization: Bearer $KEY" Accepted by /mcp only.

POST /api/login (JSON {"username","password"}) does not set a cookie: it returns {"api_key": "..."}, which the web UI then sends as X-Api-Key. An empty api_key in the config refuses every caller.

Refusal shapes (test the status code, not the text):

Where Status Body
/api/* (almost every route) 401 {"error":"Invalid or missing API key"}
/api/selection/*, /api/network/egress 401 {"error":"unauthorized"}
/api/v2/* (qBittorrent shim) 403 plain text Forbidden. (Sonarr and Radarr expect 403 before login)
/mcp 401 JSON as above + header WWW-Authenticate: Bearer
POST /api/nodes/register 401 {"error":"enrolment token unknown, already used, or expired"}
POST /api/setup 409 once an admin exists; 403 if the socket peer is not on loopback or a private network, or if a private peer (a reverse proxy) names a public client in X-Forwarded-For JSON

Public routes (no key): /health, /metrics, /, /static/*, /changelog.md, /install.sh, GET|POST /api/setup, POST /api/login, GET /api/startup, POST /api/v2/auth/login, POST /api/v2/auth/logout, POST /api/nodes/register (enrolment token instead of the key), and POST /api/auth/api-key/confirm (the pending key of a rotation instead of the current key, see Nodes).

While Hydranos starts

The port opens before the catalogue is loaded. Until loading ends, only /, /static/*, /health ("status":"starting", 200), /metrics, GET /api/startup and GET /api/setup answer. Every other path, /api/v2/* and /mcp included, answers 503 with Retry-After: 5 and {"error":"Hydranos is starting (<phase>): N of M torrents restored","startup":{…}}; another method on one of the paths above (for example POST /api/setup) is not routed and is refused. Poll GET /api/startup until "ready":true, or retry after Retry-After. Phases: Operating Hydranos.

Errors

Errors are JSON {"error": "<message>"}, sometimes with more fields:

  • 400: bad or missing field (strict bodies name the unknown field).
  • 404: {"error":"torrent not found"}, or no such engine/node/job.
  • 405: the path exists, not with this method (for example POST /api/race/settings since 4.4).
  • 409: refused because of state. Data moves add "reason" (hardlinks, space, shared, destination_taken, …) and a plan; a filter selection that grew answers "reason":"grew" with the new count; adding a torrent that is already there answers 409.
  • 410: a 3.x route removed in 4.4; the body names the replacement (Routes removed in 4.4).
  • 503 + Retry-After: 5: still starting (above).

POST /api/torrents answers {"error","targets"} with a status that says why:

Status When
400 Body not JSON; not exactly one of torrent_path, torrent_url, magnet_uri; unknown engine or mode; .torrent unparsable or not fetched (the URL shows as <url>: it may carry a passkey); no save path (none sent, no category with one); bad magnet (a malformed, missing or v2-only info hash included).
404 torrent_path does not exist on the Hydranos host.
409 Already added (torrent already added), and the other refusals: race disk full: … and, with [dedup] path_collision = "refuse", N file(s) of this torrent are already another torrent's with other bytes ….
500 A store failure only (store: …).

POST /api/torrents/upload answers the same refusals with 400, and 409 for already added. Test the status class, and read error for the reason.

Hashes, engines and copies

  • :info_hash is the 40-character hex v1 hash. Some routes resolve a shorter prefix and act on the first match: always send the full hash.
  • Engines are addressed by id under /api/engines/:id/...; /api/race/... and /api/hoard/... are aliases for the two engines that always exist. Exception: GET /api/race/torrents lists every engine with the race role.
  • The same torrent can be in several engines (one copy per engine). Torrent routes pick a copy with ?engine=<id>. The legacy spelling ?agent= (a 3.x name) is also accepted: local or empty means the default, local-<id> means engine <id>.
  • List rows carry a label in the field agent (legacy name): local-<engine> for a local copy, <node>-<engine> for a row merged from a declared node. Per-torrent routes answer 404 for a node row; /api/selection/* relays the action to the node that holds it.

Quick examples

Status (fields: Monitoring):

curl -s -H "X-Api-Key: $KEY" "$HY/api/status"

First 100 hoard torrents of category tv, newest first (server-side filter and paging):

curl -s --compressed -H "X-Api-Key: $KEY" \
  "$HY/api/hoard/page?category=tv&limit=100&sort=added_time"

Create a category (there is no default save path, so adds need one):

curl -s -H "X-Api-Key: $KEY" -H "Content-Type: application/json" \
  -d '{"name":"tv","save_path":"/data/tv","mode":"hoard"}' "$HY/api/categories"

Add a .torrent file with a category (multipart):

curl -s -H "X-Api-Key: $KEY" \
  -F "torrents=@show.s01e01.torrent" -F "category=tv" "$HY/api/torrents/upload"

Add -F "seed_mode=true" to seed data already on disk without hashing it (seed mode; risks: Adding Torrents and Existing Data).

Stop every hoard torrent of category old (a bulk action by filter; count first, send the count as expect):

N=$(curl -s -H "X-Api-Key: $KEY" "$HY/api/hoard/page?category=old&limit=1" | jq .filtered)
curl -s -H "X-Api-Key: $KEY" -H "Content-Type: application/json" \
  -d "{\"selection\":{\"filter\":\"category=old\",\"view\":\"hoard\",\"expect\":$N},\"params\":{}}" \
  "$HY/api/selection/stop"
# 202 {"job":"...","total":N}  ->  poll GET /api/selection/jobs/<job>

Torrents and adding

Status: ✓ works · ◐ works with a caveat. Stub routes appear only in Stubs and removed routes in 4.4.

Route Method Purpose Status
/api/torrents/upload POST multipart Add a .torrent. File part torrents, torrent or file; fields category, savepath/save_path, tags, engine or mode (race/hoard), paused/stopped, skip_checking/seed_mode/skip_recheck, create_subfolder. Body limit 2 MB. ✓
/api/torrents POST JSON Add from exactly one of torrent_path (a file on the Hydranos host), torrent_url (fetched by Hydranos, through the daemon's way out) or magnet_uri; plus category, save_path, tags, engine or mode, stopped, seed_mode (or skip_recheck), create_subfolder (default [daemon] create_torrent_folder). Magnet → 202 {"info_hash","status":"resolving"}, with seed_mode and stopped applied once resolved. Status codes: Errors above. ◐ some refusals are 500
/api/torrents/:info_hash DELETE Remove. ?delete_files=true deletes data; ?engine= removes one copy, otherwise all. Files another torrent still reads are kept. Also removes a torrent an engine runs with no store row. ✓
/api/torrents/:info_hash/{files,torrent} GET File list (index, path, size, done, progress, priority, plus sequential, first_last_piece, complete); the .torrent bytes from the store. ✓
/api/torrents/:info_hash/download-order GET, POST New in 4.4. File priorities (1, 6, 7; 0 → 409), sequential, first_last_piece (Download order). ✓
/api/torrents/:info_hash/limits GET, POST New in 4.4. Own speed caps {"up_kib","down_kib"} (0 = none), live and saved. ✓
/api/torrents/:info_hash/share-limits GET, POST New in 4.4. Own ratio_limit, seeding_time_limit, inactive_seeding_time_limit (-2 = engine's, -1 = none). ✓
/api/torrents/:info_hash/trackers GET, POST Read / edit. POST {"op":"add"|"remove","urls":[...]}, {"op":"replace","from","to"} or {"op":"set","tiers":[ ["u1"], ["u2"] ]}. .../add-tracker takes {"url"}. ✓
/api/torrents/:info_hash/reannounce POST Announce now, one copy only: the ?engine= one, else the first engine holding it. 429 in the 60 s cooldown. ✓
/api/torrents/:info_hash/peers POST {"peers":["203.0.113.5:16172"]}: dial these peers. ✓
/api/torrents/:info_hash/copy POST {"engine":"vpn1"}: add a second copy in another engine, same files. 409 while the source is not complete or is being checked. ✓
/api/torrents/:info_hash/engine POST {"engine":"vpn1"}: move the torrent to another engine, files stay where they are. ?engine= names the source copy. The target takes the source's state (pieces, trackers, counters, stopped) before the source lets go; if either refuses, nothing changes. ✓
/api/torrents/:info_hash/graduate POST {"engine","category","save_path","allow_breaking_hardlinks":false}: queue a graduate job (data move + engine change). save_path defaults to the category's. Same checks as a category move (409 with reason). ✓
/api/torrents/add-defaults GET {"create_subfolder","skip_recheck"}: what the Add tab starts from (create_subfolder = [daemon] create_torrent_folder; skip_recheck always false). ✓
/api/torrents/export POST form format=zip|txt|csv, selection (JSON) or hashes, strip_trackers=1. Streams a download. ✓
/api/{hoard,race}/torrents/:info_hash GET One row with live detail. ✓
/api/{hoard,race}/torrents/:info_hash/category POST {"category","move_files":false,"allow_breaking_hardlinks":false}. With move_files, 202 + job (or a graduation if the mode differs). ✓
/api/{hoard,race}/torrents/:info_hash/location POST Set location: {"location":"/abs/path","allow_breaking_hardlinks":false}. 202 + job when bytes move, 200 "moved":false if already there. ✓
/api/{hoard,race}/torrents/:info_hash/move-preview GET What a move would do, without doing it. ✓
/api/{hoard,race}/torrents/:info_hash/tags POST {"tags":[...]} replaces the whole set. ✓
/api/{hoard,race}/torrents/:info_hash/{pause,resume} POST Stop / start one torrent. ✓
/api/hoard/torrents/:info_hash/{pin,unpin} POST Force download (started, runs past the download queue) / stop forcing. ✓
/api/hoard/torrents/:info_hash/verify POST Recheck (?engine= picks the copy), guarded (shared files). ✓
/api/race/torrents/:info_hash/purge POST Remove the copy in engine race with its files (files another torrent reads are kept); other copies untouched. 404 torrent not in race. Extra race engines: DELETE /api/torrents/:info_hash?engine=<id>. ✓
/api/race/timeline/:info_hash, /api/trackers GET Race timeline; Trackers tab table. ✓

How an add picks its engine and save path: Categories and Routing. Without engine or mode, the category decides. create_subfolder gives a single-file torrent a folder of its own, named after the torrent; it is not applied to magnets on this route.

Lists and engines

Each /api/engines/:id/... row also answers as /api/hoard/... and /api/race/..., except pinned, pause-all and resume-all (hoard alias only) and rate-limits, share-limits (no alias: use /api/engines/race/… or /api/engines/hoard/…).

Route Method Purpose Status
/api/engines GET Local engines (id, role, listen_port, torrents…), each with live stats (states, rates, peers, announces, caps, listening, held…). ✓
/api/engines POST {"id","role":"race"|"hoard","listen_port","bind_interface"}: add an extra engine to the config. Answers restart_required: true. A non-empty bind_interface outside Linux → 400. ✓ (after restart)
/api/engines/:id DELETE Remove an extra engine from the config. 409 with the count while it still holds torrents; race/hoard → 400. An empty engine keeps its listener until restart. ✓ (after restart)
/api/engines/:id/page GET Paged list (see below). ✓
/api/engines/:id/torrents GET The engine as one array. Without a filter, offset/limit are ignored; with a filter, they are not (see below). ✓
/api/engines/:id/pinned GET Forced-download hashes. ✓
/api/engines/:id/pause POST {"hashes":[...],"paused":true}: stop/start exact hashes. ✓
/api/engines/:id/torrents/bulk POST {"action":"stop"|"start","hashes":[...],"exclude":[...],"all":false}. Unknown fields → 400; an empty hashes is not "all". ✓
/api/engines/:id/{pause-all,resume-all} POST Stop / start every torrent of the engine. ✓
/api/engines/:id/listen-port POST {"port":16172}: moves TCP and uTP live, announces follow, written to the config. Port that cannot be bound, taken, or set by gluetun/WireGuard → 409; engine off the network → 503. ✓
/api/engines/:id/dial-limits POST {"max_dials_per_sec","max_connections"} (0 = unlimited): live and written to the config. ✓
/api/engines/:id/rate-limits GET, POST New in 4.4. {"upload_kib","download_kib"} (0 = unlimited), live and saved. ✓
/api/engines/:id/share-limits GET, POST New in 4.4. max_ratio, max_seeding_time, max_inactive_seeding_time (-1 = off, minutes), share_limit_action. ✓
/api/hoard/stats GET Totals. ✓
/api/hoard/download-slots GET Configured active_downloads; the other counters are 0. ◐
/api/race/settings GET Race settings echo (upload_rate_limit/download_rate_limit = configured caps, bytes/s). GET only since 4.4. ◐
/api/startup-pause, /api/startup-pause/release GET, POST {"held":[engines],"holding"}; POST releases the hold (released). With start_paused, a held engine dials and announces nothing until released. ✓

What the queue, share limits and speed limits do: Queue, Share and Speed Limits.

Paging parameters of .../page: offset (default 0), limit (default 500, clamped to 1–5000), sort (a row field, default added_time), order=asc (default descending), facets=1 (adds category/tag/tracker counts), fields=hash (hashes only). Filters: search (words ANDed, or a hex hash prefix of 6+ characters), category, tag, tracker, error_class and their _not variants (comma-separated lists, __none__ = without one), state (a row state such as seeding, or __active__, __error__, __tracker_err__, __pinned__). The answer is {"total","filtered","offset","limit","rows","facets"}. A page also merges the other local engines of the same role and the rows of declared nodes. Since 4.4 the same filters also apply to the whole-list routes GET /api/{hoard,race}/torrents and /api/engines/:id/torrents; without a filter they return the whole engine (/api/race/torrents: every race-role engine) and ignore offset/limit. With a filter, do not send offset/limit: a limit under 5000 cuts the answer to one page.

Known bug in 4.4. On a whole-list route, a filter plus an offset (with limit absent or 5000 and up) loops forever once 5000 rows match from that offset: the route pages internally by appending its own offset, and the caller's comes first and wins, so the window never moves.

Selections (bulk actions)

POST /api/selection/:action runs one action on many torrents as an in-memory bulk task (the web UI's context menu uses it). It answers 202 {"job","total"}; poll GET /api/selection/jobs/:id (kept 1 h after it ends, lost on restart). POST /api/selection/jobs/:id/cancel stops between two torrents. These tasks are not the persistent jobs of the Jobs tab: see Jobs, Bulk Actions and Moving Data.

Body: {"selection": ..., "params": {...}}, strict (unknown keys → 400).

  • By rows: "selection":{"items":[{"hash":"<40 hex>","agent":"local-hoard"}]}. Each row acts on its own copy (the engine in its agent label).
  • By filter: "selection":{"filter":"category=tv&state=seeding","view":"hoard","expect":<count>}, optional "exclude":[items]. filter takes only the filter keys listed under Lists and engines; view is an engine id (default hoard); expect is required.
Action params
stop, start, pin, unpin, reannounce, recheck {} (recheck covers race copies too)
tags {"tags":["a"],"op":"add"|"remove"}
category {"category","move_files":false,"allow_breaking_hardlinks":false}
location {"location":"/abs/path","allow_breaking_hardlinks":false}
remove {"delete_files":false}
copy, move-engine {"engine":"vpn1"}
handoff {"node","engine","then":"keep"|"remove"}
node-fetch {"node","from_engine","engine"}
node-move {"node","engine"}
limits (4.4) {"up_kib","down_kib"}: each torrent's own speed caps.
share_limits (4.4) {"ratio_limit","seeding_time_limit","inactive_seeding_time_limit"}.
download_order (4.4) {"sequential","first_last_piece"} (file priorities are per torrent, on download-order).

Categories and tags

Route Method Purpose Status
/api/categories GET, POST List / create {"name","save_path","mode":"race"|"hoard","graduate_to","transit"} → 201. mode case-insensitive, default race, other value → 400; existing name → 409. ✓
/api/categories/:name PUT, DELETE Update: the fields sent replace the stored ones, the others (mode included) are kept; a different name in the body renames it, relabels its torrents (relabelled; 409 if taken) and renames every reference to it (followed: counts of rss_rules, watch_folders, workflows, magnets, jobs) / delete: the label is cleared from its torrents (cleared). ✓
/api/categories/orphans GET Labels worn by torrents that match no category: [{"name","torrents","mode","save_path"}]. ✓
/api/tags GET Tags. ✓

Trackers and announces

Route Method Purpose Status
/api/announce/{health,policy}, /api/announce/errors?host= GET Tracker health, live policy, error details. ✓
/api/announce/ip-modes GET, POST {"host","mode"} IP family per tracker. ✓
/api/announce/passkeys GET, POST {"host","passkey"} (empty passkey clears). ✓
/api/announce/min-seed POST {"host","hours"} or {"host","clear":true}. ✓
/api/announce/{mute,hidden} POST {"host","muted"} / {"host","hidden"} or {"hosts":[...],"hidden"}. ✓

Meaning of each setting: Trackers and Announces.

Workflows and jobs

Route Method Purpose Status
/api/workflows GET, POST List / create or replace a workflow. ✓
/api/workflows/:id, /api/workflows/:id/run DELETE, POST Delete; run now (?dry=1: dry run). ✓
/api/workflows/{fields,activity,links}, .../preview GET, POST Editor fields, activity log, link status; POST preview: torrents a draft would touch. ✓
/api/jobs, /api/jobs/:id GET Persistent jobs (?limit=, default 100); one job. ✓
/api/jobs/:id DELETE Cancel: cancelled (queued, or a waiting move to a node) or cancelling (a running graduation, rolled back); another running move → 409 (it finishes); unknown id → 404. ✓

Bodies: Workflows, Jobs, Bulk Actions and Moving Data.

Race drain

Route Method Purpose Status
/api/drain/{status,history,graduations} GET Volumes, past drains, graduations in progress. ✓
/api/drain/now?volume=, /api/drain/policy POST Drain now; {"volume","enabled","high_watermark","low_watermark"} or {"volume","inherit":true}, plus optional quota_gb (declared capacity, 10^9 bytes, 0 removes it) and trim_after_drain (bool), each on its own or with either form. ✓

See Race Drain, Graduation and Seed Obligations.

Nodes

Route Method Purpose Status
/api/nodes GET, POST List (probed live) / declare {"name","url","api_key"}. ✓
/api/nodes/test POST Probe without saving. ✓
/api/nodes/:name DELETE, PATCH Forget a node (the remote is untouched) / edit it (4.4): any of {"name","url","api_key"}; a new URL or key is probed first (400 if it does not answer), a name taken → 409, waiting moves follow a rename. ✓
/api/nodes/:name/rotate-key POST New in 4.4 (node on 4.4). The node gets a new key, stored here and returned once; the old one is refused. 502 with the reason if the node refused (it keeps its old key). ✓
/api/auth/api-key/rotate, .../confirm POST Node side of a rotation: a pending key (10 min) / adopt it, authenticated by that key (written to [daemon] api_key). ✓
/api/nodes/enrol, /api/nodes/register POST One-time token + install command; called by the new machine with the token. ✓
/api/nodes/:name/{handoff,fetch,move-engine} POST Push / pull / move between that node's engines (bodies: Nodes and Multiple Machines). ✓
/node/:name/open GET Redirect to the node's UI with its key. ✓
/install.sh GET Enrolment script (public). ✓

The legacy 3.x routes /api/agents* were removed in 4.4: any path and method under /api/agents answers 410 Gone with "see":["/api/engines","/api/nodes"]. Use /api/engines for this machine's engines and /api/nodes for other machines (Upgrading from 3.x).

Magnets, watched folders, import, dedup

Route Method Purpose Status
/api/magnets, /api/magnets/:hash, /api/magnets/:hash/retry GET, DELETE, POST Magnets resolving or failed; drop; retry. ✓
/api/watch GET, PUT Watched folders. PUT takes the whole list [{"path","category","engine","paused","enabled"}]; path absolute and existing, category must exist. ✓
/api/import/qbit/{preview,start}, .../{status,events} POST, GET Import from a qBittorrent instance; progress (events is SSE). ✓
/api/import/transmission/{upload,preview,start} POST Import from Transmission (upload multipart, up to 4 GiB). ✓
/api/import/retry POST New in 4.4. No body; retries what the last import failed with the same choices and answers {"job_id","torrents"} (the new job, the torrents retried); 404 no import, 409 one running, 400 nothing to retry or the qBittorrent login failed (the retry stays available). ✓
/api/import/check-paths POST {"paths":[...]}: which paths this host can see. ✓
/api/provenance GET Where the library was imported from; written by every qBittorrent or Transmission import that added at least one torrent. ✓
/api/dedup/config, /api/dedup/stats POST, GET {"enabled":true} (restart to apply); duplicate-data counters. ✓

Import wizard: Migrating from qBittorrent. Magnets, watched folders, dedup: Adding Torrents and Existing Data.

RSS

New in 4.4. Feeds, folders and download rules; a rule is qBittorrent's rule document (rss/setRule). The qBittorrent clients' /api/v2/rss/* routes reach the same feeds and rules.

Route Method Purpose Status
/api/rss GET Everything the RSS tab shows: settings, feeds, folders, rules. ✓
/api/rss/feeds, /api/rss/feeds/:id GET, POST / PATCH, DELETE List, add {"url","path","refresh_secs"} (409 taken) / edit; remove. ✓
/api/rss/feeds/:id/{refresh,articles,read,download}, /api/rss/refresh POST, GET Fetch now; articles; mark read; add one article by hand; fetch all (202). ✓
/api/rss/folders POST {"path"}. ✓
/api/rss/rules, /api/rss/rules/:name, .../rename GET, PUT, DELETE, POST List; create or replace (400 bad regex or episode filter); remove; rename. ✓
/api/rss/rules/:name/matching, /api/rss/preview GET, POST What a saved rule, or a draft, would take from the articles already fetched. Adds nothing. ✓

Rules, filters and what happens to an article: RSS and Automatic Downloads.

Network and IP filter

Route Method Purpose Status
/api/network/{interfaces,engines}, /api/public-ip GET Interfaces that are up; exit IP per engine (?refresh=1); process exit IP. ✓
/api/network/mode GET, POST Network tab. POST writes the chosen mode's keys and removes the other modes'; restart_required. bind_interface outside Linux → 400. ✓
/api/network/egress GET, POST New in 4.4. The kill switch (armed, why, blocked engines) and the way out of the daemon's own requests (auto, direct, proxy, engine:<id>). ✓
/api/network/wireguard, .../configs, .../configs/:name, .../engines GET, POST, DELETE, POST Managed WireGuard: state and live tunnels; upload a provider .conf; remove one; assign engines (tunnel, direct, none). ✓ (Linux)
/api/network/check POST Leak test per engine: fail with LEAK: when an engine meant to leave by an interface or proxy shows the default route's address. ✓
/api/port-forward GET Per engine: port held, inbound_peers, reachable (yes/no/unproven), who forwards the port. ✓
/api/ipfilter GET, PUT Status / {"enabled","sources":[...],"refresh_hours"}. ✓
/api/ipfilter/bans, /api/ipfilter/reload POST, DELETE Ban {"ip","reason"} / unban {"ip"}; POST reload reloads block lists. ✓

Bodies: docs/API.md. Modes and the Network tab: Networking; kill switch and the daemon's way out: VPN and Egress; tunnels: Managed WireGuard.

Settings, setup, restart

Route Method Purpose Status
/api/setup GET, POST First run: {"needs_setup",...} / create the admin {"username","password"} (8+ chars) → API key. Public. ✓
/api/login POST {"username","password"} → {"api_key"}. Public. ✓
/api/auth/password POST {"password"} (8+ chars): stored hashed in [auth] password_hash. ✓
/api/settings GET, POST Whole config as JSON, the API key included. SOCKS5 passwords and the gluetun API key read ******** (posting the mask back keeps the stored value); an announce_proxy URL (with any user:pass in it) is still returned in clear / {"changes":[{"section","key","value"}]} edits existing keys only, except the live engine keys of [race]/[hoard] (rates, choker, queue, share limits; enable_lsd at next start), created when absent. Answers applied_live, restart_required. ✓
/api/settings/reset POST Reset to defaults (keeps login, key, data dir). ✓
/api/settings/restart, /api/restart POST Answer, then stop cleanly (as on SIGTERM: stopped to trackers, resume data flushed) and exit with code 75 for the supervisor to restart (Operating Hydranos). ✓
/api/fs/browse?path= GET List folders on the Hydranos host. ✓
/api/update-check GET New release available. ✓

A forgotten password: hydranos reset-password on the host (Security and Access).

Monitoring

Route Method Purpose Status
/health GET Public. status healthy (200), degraded (200: an engine without a listener, held by start_paused or blocked by the kill switch), unhealthy (503: the store does not answer, or no online engine listens), starting (200); plus checks and problems. ✓
/metrics GET Public, Prometheus, per engine (series: Prometheus Metrics). ✓
/api/startup GET Public. {"ready","phase","total","restored","engines","uptime"} (While Hydranos starts). ✓
/api/status GET Header and Overview figures. ✓
/api/events GET SSE stream of status snapshots. Ends itself when the daemon stops. ✓
/api/logs, /api/logs/stream GET The in-memory ring (last 2000 lines), filtered server side: level (minimum), module, q, since (5m, 1h, 2d or Unix s), after (a seq), limit; an unreadable filter → 400. SSE tail with the same filters. ✓
/api/stats/baseline GET, POST Lifetime counter baseline {"total_uploaded","total_downloaded"}. ✓
/api/health/anomalies GET Anomaly report: torrent checks from a background pass every 5 min (generated_at), live tracker, announce, engine and disk counts. ✓
/api/benchmark/{records,range,current,race-events}, .../trackers/{current,range} GET Benchmark data, tracker stats. ✓
/api/benchmark/{engines,compare}, .../race-snapshots/:info_hash GET Extra engines' samples; A/B comparison; race timeline snapshots. ✓
/api/vpn-speedtest/run, .../{latest,history} POST, GET iperf3 test (400 unconfigured, 409 one running, 502 iperf3 error); stored results. ✓

Fields of /api/status, /api/benchmark/current, .../trackers/current and /api/health/anomalies: docs/API.md › Monitoring. /api/status covers race and hoard only (extra engines: /api/engines stats); in /api/benchmark/current, *_announce_rate, *_announce_fail_rate, race_avg_share and hoard_session_uploaded are always 0.

What each monitoring route reports: Monitoring and Logs.

Stubs and removed routes in 4.4

These routes answer but do not do what their name says:

Route What it answers Use instead
GET /api/hoard/download-slots The configured active_downloads and zeros. active_downloads in the config, read live (Queue, Share and Speed Limits).
GET /api/arr-cleanup/scan An empty scan ("candidates":null,"count":0). —

Routes removed in 4.4. They were stubs up to 4.3 (a success-shaped answer that did nothing) and no screen called them. A removed path answers 404; a removed method on a path that keeps its GET answers 405; the 3.x routes answer 410 with a body naming the replacement.

Route Since 4.4 Use instead
POST|DELETE /api/hoard/download-slots 405 active_downloads in the config, read live.
POST /api/race/settings 405 POST /api/settings or POST /api/engines/race/rate-limits.
GET|POST /api/opt/flags, GET /api/race/choking 404 —
POST /api/hoard/verify-downloading 404 POST /api/selection/recheck.
POST /api/hoard/restart-stuck 404 stop then start through /api/selection/*.
POST /api/arr-cleanup/execute 404 —
/api/agents, /api/agents/* (every method) 410, "see":["/api/engines","/api/nodes"] /api/engines, /api/nodes.
/api/jobs/move-remote (every method) 410, "see":["/api/selection/handoff",…] POST /api/selection/handoff.

The other routes listed as stubs or constants on the 4.3 page work in 4.4 (rows above).

Hydranos 4.4 · 🇬🇧

Start

Install

Use

Integrate

Network & machines

Operate

Reference


Hydranos 4.4 · 🇫🇷

Démarrer

Installer

Utiliser

Intégrer

Réseau & machines

Exploiter

Référence


Clone this wiki locally