RPC Protocol
The JSON-RPC interface used by the CLI and external clients.
Risuko exposes a JSON-RPC 2.0 server over HTTP. The CLI uses this interface, and you can also build custom clients.
Connection
The RPC server listens on http://127.0.0.1:16800/jsonrpc by default. Host, port, and secret come from the rpc-host, rpc-listen-port, and rpc-secret config keys.
Besides POST, the same endpoint accepts GET /jsonrpc?method=<name>&id=<id>¶ms=<base64(JSON array)>. An optional jsoncallback query parameter wraps the response in JSONP. A GET with an Upgrade: websocket header opens a WebSocket instead (see below).
To block DNS rebinding, requests whose Host header names a DNS host other than localhost or the configured rpc-host are rejected with 403 Forbidden. IP literals are always accepted. Request bodies are limited to 32 MiB.
Request Format
{
"jsonrpc": "2.0",
"id": "1",
"method": "risuko.addUri",
"params": [
["https://example.com/file.zip"],
{ "split": "16", "dir": "/downloads" }
]
}params must be a positional array; named (object) params are rejected with -32602. The body may also be a batch array of request objects.
Response Format
{
"jsonrpc": "2.0",
"id": "1",
"result": "a1b2c3d4e5f6"
}Available Methods
Task Management
| Method | Parameters | Returns |
|---|---|---|
risuko.addUri | [uris[], options?] | GID string |
risuko.addTorrent | [base64Data, uris[], options?] | GID string |
risuko.addMetalink | [base64MetalinkData, options?] | GID string |
risuko.addNzb | [base64NzbData, options?] | GID string |
risuko.addEd2k | [uri, options?] | GID string |
risuko.addMedia | [url, options?] | GID string |
risuko.pause | [gid] | GID string |
risuko.unpause | [gid] | GID string |
risuko.remove | [gid] | GID string |
risuko.forcePause | [gid] | GID string |
risuko.forceRemove | [gid] | GID string |
risuko.pauseAll | — | "OK" |
risuko.unpauseAll | — | "OK" |
risuko.forcePauseAll | — | "OK" |
risuko.changePosition | [gid, pos?, how?] | New position |
risuko.updateTask | [gid, patch] | { restarted, trackersAdded, progressPreserved } |
risuko.addMedia accepts URLs from supported media sites (downloaded via yt-dlp). Other URLs are rejected with "Not a supported media URL" unless the force-ytdlp option is set. Methods that take a gid also accept a unique GID prefix of at least 4 characters.
risuko.addTorrent takes the uris argument for aria2 compatibility but ignores it. risuko.addMetalink accepts a standard-base64 Metalink document (.meta4 / .metalink). risuko.updateTask applies a { uris, dir, out, trackers, options } patch; see updateTask.
risuko.addNzb accepts a standard-base64 NZB document, not a URL. It creates
one Usenet task from the parsed manifest. The options object may select a
non-secret provider reference or cleanup policy. Provider credentials and
archive passwords (username, password, usenet-username,
usenet-password, archive-password, usenet-archive-password) are silently
stripped from the options rather than rejected. Configure those in the desktop
app's Provider Profiles editor instead.
{
"jsonrpc": "2.0",
"id": "nzb-1",
"method": "risuko.addNzb",
"params": [
"PD94bWwgdmVyc2lvbj0iMS4wIj8+Li4u",
{
"dir": "/downloads",
"usenet-profile-id": "primary",
"usenet-cleanup-mode": "keep-all"
}
]
}Queries
| Method | Parameters | Returns |
|---|---|---|
risuko.tellStatus | [gid, keys?] | Status object |
risuko.tellActive | [keys?] | Status object array |
risuko.tellWaiting | [offset?, num?, keys?] | Status object array |
risuko.tellStopped | [offset?, num?, keys?] | Status object array |
risuko.getGlobalStat | — | Stat object |
risuko.getFiles | [gid] | File array |
risuko.getPeers | [gid] | Peer array |
risuko.setBtPeerBlocklist | [entries[]] | { revision, ruleCount, disconnectedPeers, removedPeers } |
risuko.getUris | [gid] | URI array |
risuko.getServers | [gid] | Server array |
risuko.getVersion | — | Version object |
risuko.getSessionInfo | — | Session info object |
risuko.tellWaiting returns waiting and paused tasks. Scheduled tasks are a
desktop/Tauri list (tell_scheduled), not a JSON-RPC method. risuko.getGlobalStat
does count scheduled tasks in numWaiting, matching aria2's broad
"not active yet" bucket. Besides the aria2 fields it also returns
numCompleted, numStoppedError (stopped minus completed),
numScheduled, and numPaused. Paused and scheduled tasks are also counted in
numWaiting.
risuko.setBtPeerBlocklist full-replaces the process-wide BitTorrent IP/CIDR
blocklist. Each string is a plain IP (1.2.3.4, 2001:db8::1) or CIDR
(10.0.0.0/8, 2001:db8::/32); invalid entries are ignored. Connected peers
that match are disconnected and dropped from the swarm. PeerBanHelper drives
this method through the Aria2Next-compat listener (see below).
Options
| Method | Parameters | Returns |
|---|---|---|
risuko.getOption | [gid] | Options object |
risuko.getGlobalOption | — | Options object |
risuko.changeOption | [gid, options] | "OK" |
risuko.changeGlobalOption | [options] | "OK" |
Session
| Method | Parameters | Returns |
|---|---|---|
risuko.saveSession | — | "OK" |
risuko.purgeDownloadResult | — | "OK" |
risuko.removeDownloadResult | [gid] | "OK" |
risuko.shutdown | — | "OK" |
risuko.forceShutdown | — | "OK" |
Both stop the engine, which saves the session as it shuts down; shutdown also saves explicitly before signalling the stop.
Task Routing
Pattern-based rules that override the download directory and assign a tag based on the inferred output filename. See the Task Routing guide.
| Method | Parameters | Returns |
|---|---|---|
risuko.listRoutingRules | — | Rule array |
risuko.addRoutingRule | [rule] | The rule, with a UUID id assigned when id is sent as "" |
risuko.updateRoutingRule | [rule] | "OK" |
risuko.removeRoutingRule | [ruleId] | "OK" |
risuko.resolveRouting | [filename] | { tag, dir } decision preview against the current rules |
A rule has the shape:
{
"id": "rule-videos",
"label": "Videos",
"pattern": "*.mkv",
"dir": "/downloads/videos",
"enabled": true
}id is required on every rule. pattern is a case-insensitive glob (glob crate syntax) matched against
the inferred filename only — not the URL or directory. A trailing .part on the filename is ignored.
Example: curl
# Add a download
curl -X POST http://localhost:16800/jsonrpc \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": "1",
"method": "risuko.addUri",
"params": [["https://example.com/file.zip"], {"split": "16"}]
}'
# Check status
curl -X POST http://localhost:16800/jsonrpc \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": "2",
"method": "risuko.tellActive",
"params": []
}'Error Responses
{
"jsonrpc": "2.0",
"id": "1",
"error": {
"code": 1,
"message": "Task a1b2c3d4 not found"
}
}Protocol errors use the standard JSON-RPC codes: -32700 (parse error), -32600 (invalid request), -32601 (method not found), -32602 (invalid params). Application errors — including failed authentication — use code 1.
aria2 Compatibility
The RPC server accepts both risuko.* and aria2.* method prefixes. For example, aria2.addUri is treated as risuko.addUri. This makes it compatible with existing aria2 RPC clients. system.listMethods returns every method under both prefixes.
PeerBanHelper / Aria2Next listener
When pbh-enable is true, the engine starts a second JSON-RPC server on
pbh-listen-port (default 16801), bound to the same rpc-host as the
primary RPC. The engine refuses to start unless pbh-rpc-secret is non-empty
and pbh-listen-port differs from rpc-listen-port. That listener uses
Aria2Next compatibility mode:
POST /is accepted in addition to/jsonrpc(PeerBanHelper posts to the root)aria2.getVersionreports{ version: "1.37.0", product: "aria2-next", rpcVersion: "1", enabledFeatures: [...] }instead of the Risuko engine versionaria2.getPeersreturns the same peer fields as the primary listener (see getPeers), but sendsdownloadSpeedanduploadSpeedas decimal stringsaria2.setBtPeerBlocklistapplies the IP/CIDR blocklist described above
In PeerBanHelper, add an Aria2Next downloader pointed at
http://127.0.0.1:16801/jsonrpc (or the configured port) and set the RPC token
to pbh-rpc-secret. These keys are startup-only.
Authentication
When rpc-secret is set, every call must prefix its params array with "token:<secret>":
{
"jsonrpc": "2.0",
"id": "1",
"method": "risuko.addUri",
"params": ["token:my-secret", ["https://example.com/file.zip"]]
}The token is stripped from params before dispatch.
On first run the engine generates a random 256-bit (64 hex character) rpc-secret and saves it in system.json together with an rpc-secret-initialized marker. A secret you later clear on purpose stays cleared. Installs that upgrade with an empty secret get one generated too, so third-party aria2 clients (AriaNg and similar) need the token from system.json or the app's settings page.
When rpc-secret is empty, the token: prefix is still accepted (and stripped) for compatibility with aria2 clients that always send one, but the server also:
- rejects requests from web-page origins (
http/httpsorigins that are not loopback ortauri.localhost,null, andfile) and cross-site fetches with403 Forbidden - rejects state-changing methods sent via
GET(read-only methods such astellStatusstill work, andjsoncallbackis ignored)
With a secret set, none of these restrictions apply.
system.multicall
Batch any number of calls into a single round-trip. Per aria2 convention, the outer params array is not authenticated; each nested call is authenticated independently and must carry its own "token:<secret>" prefix when rpc-secret is set.
{
"jsonrpc": "2.0",
"id": "1",
"method": "system.multicall",
"params": [[
{ "methodName": "risuko.tellActive", "params": ["token:my-secret"] },
{ "methodName": "risuko.getGlobalStat", "params": ["token:my-secret"] }
]]
}Returns an array where each element is either [result] (success) or { "code", "message" } (per-call error). The legacy ["token:...", [calls]] shape is also accepted, but the outer token is never trusted as authentication.
WebSocket Subscribe
The server upgrades GET /jsonrpc requests with a Connection: Upgrade / Upgrade: websocket header to a WebSocket. Once connected, the engine streams JSON-RPC notifications for every task transition: risuko.onDownloadStart, risuko.onDownloadPause, risuko.onDownloadStop, risuko.onDownloadComplete, risuko.onDownloadError, risuko.onBtDownloadComplete. Pushed notifications always use the risuko. prefix, even though system.listNotifications lists each name under both prefixes. Each notification carries params: [{"gid": "<gid>"}] and no id. Regular request/response calls also work over the same socket.