Risuko
Architecture

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>&params=<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

MethodParametersReturns
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

MethodParametersReturns
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

MethodParametersReturns
risuko.getOption[gid]Options object
risuko.getGlobalOption—Options object
risuko.changeOption[gid, options]"OK"
risuko.changeGlobalOption[options]"OK"

Session

MethodParametersReturns
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.

MethodParametersReturns
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.getVersion reports { version: "1.37.0", product: "aria2-next", rpcVersion: "1", enabledFeatures: [...] } instead of the Risuko engine version
  • aria2.getPeers returns the same peer fields as the primary listener (see getPeers), but sends downloadSpeed and uploadSpeed as decimal strings
  • aria2.setBtPeerBlocklist applies 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/https origins that are not loopback or tauri.localhost, null, and file) and cross-site fetches with 403 Forbidden
  • rejects state-changing methods sent via GET (read-only methods such as tellStatus still work, and jsoncallback is 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.

On this page