Rats Search API

July 11, 2026 · View on GitHub

Rats Search 2.0 (Qt) includes a built-in REST + WebSocket API server for integration with external applications and custom clients.

The API is a thin transport in front of the unified ApiRouter method surface (src/rest/api_router.cpp). Every method is a dotted name (search.torrents, download.add, config.set, …); the same router backs both HTTP and WebSocket.


Enabling the API

Via Settings UI:

  1. Open File -> Settings
  2. Go to Network section
  3. Enable "Enable REST API server"
  4. Set the HTTP port (default: 8095)
  5. Click Save (restart required)

Via Configuration File (rats.json):

{
    "restApiEnabled": true,
    "httpPort": 8095
}

Transport Protocols

REST API (HTTP)

All methods are accessible via HTTP GET requests:

GET http://localhost:8095/api/{method}?{params}

Query parameters are automatically parsed:

  • Numbers are converted from strings (limit=10 -> 10)
  • Booleans are recognized (safeSearch=true -> true)
  • JSON objects/arrays are parsed when wrapped in {} or []

Response format:

{
    "success": true,
    "data": {},
    "requestId": "..."
}

On error:

{
    "success": false,
    "error": "Error description",
    "requestId": "..."
}

CORS headers are included in all responses, so the API can be called from browser clients.

WebSocket

WebSocket server runs on HTTP port + 1 (e.g., ws://localhost:8096).

Request format (JSON-RPC style):

{
    "method": "search.torrents",
    "params": { "text": "ubuntu", "limit": 10 },
    "id": "optional-request-id"
}

Response format:

{
    "success": true,
    "data": [],
    "requestId": "optional-request-id"
}

Server-push events are automatically sent to all connected WebSocket clients:

{
    "event": "eventName",
    "data": {}
}

See WebSocket Events for details.


Method Summary

MethodPurpose
search.torrentsSearch torrents by name (with filters; DHT fallback for info-hash queries)
search.filesSearch by file names inside torrents
search.topTop torrents by seeders for a content type
search.recentRecently added torrents
torrent.getGet a single torrent by hash (DHT fallback)
torrent.removeRemove torrents by hash list
torrent.cleanupRe-apply the filter policy to the index and drop torrents that no longer pass
torrent.createCreate a .torrent file (optionally start seeding)
torrent.importParse a .torrent file and index it
download.addStart downloading (hash or magnet)
download.addFileStart downloading from a .torrent file
download.pausePause a download
download.resumeResume a download
download.removeRemove/cancel a download
download.listList all active downloads
download.selectFilesChoose which files to download
feed.getGet the voted/popular feed
vote.castUp/down-vote a torrent
vote.getGet vote counts for a torrent
config.getGet the full configuration
config.setUpdate configuration keys
stats.getDatabase + peer statistics
peers.listConnected P2P peers
tracker.checkTrigger a tracker scrape for a torrent
update.checkCheck GitHub for an application update

API Methods

search.torrents - Search torrents by name

GET http://localhost:8095/api/search.torrents?text=ubuntu&limit=10
ParameterTypeRequiredDefaultDescription
text (or query)stringyesSearch query. Also accepts magnet links and 40-char info hashes
index (or offset)int0Pagination offset
limitint10Max results per page
orderBy (or sort)stringField to order by
orderDescbooltrueSort descending
safeSearchboolfalseEnable safe-search filter
type (or contentType)stringContent type filter (e.g. video, audio, pictures, books, software, games, archive)
sizeobjectSize filter: {"min": 0, "max": 1000000000}
filesobjectFile count filter: {"min": 1, "max": 100}

Response - data is an array of torrent objects (see Torrent Object Format).

Note: When the query is a bare 40-char info hash and nothing matches locally, a DHT/BEP 9 metadata lookup is attempted automatically; a hit is indexed and returned with "fromDHT": true.


search.files - Search by file names inside torrents

GET http://localhost:8095/api/search.files?text=readme.txt&limit=10

Same parameters as search.torrents. The query must be longer than 2 characters.

Response - torrent objects that matched on a file path, carrying extra fields:

{
    "success": true,
    "data": [
        {
            "hash": "abc123...",
            "name": "Some Torrent",
            "fileMatch": true,
            "matchingPaths": ["path/to/<b>readme.txt</b>"],
            "files_list": [
                { "path": "file1.txt", "size": 1024 },
                { "path": "path/to/readme.txt", "size": 512 }
            ]
        }
    ]
}

search.top - Get top torrents by seeders

GET http://localhost:8095/api/search.top?type=video&limit=20&time=week
ParameterTypeRequiredDefaultDescription
typestringContent type (video, audio, pictures, books, software, games, archive)
timestringTime period: hours, week, month
indexint0Pagination offset
limitint20Max results

Response - array of torrent objects.


search.recent - Get recently added torrents

GET http://localhost:8095/api/search.recent?limit=20
ParameterTypeRequiredDefaultDescription
limitint10Number of recent torrents

Response - array of torrent objects.


Torrent Lifecycle

torrent.get - Get a single torrent by hash

GET http://localhost:8095/api/torrent.get?hash=29ebe63feb8be91b6dcff02bacc562d9a99ea864&files=true
ParameterTypeRequiredDefaultDescription
hashstringyes40-character hex info hash
filesboolfalseInclude the file list (files_list)
peerstringPeer id that offered this torrent in a remote search hit. When files=true and the file list isn't available locally, it is fetched from this peer (and cloned into the local index).

Response - a single torrent object. If the torrent is currently downloading, a download object (progress/paused state) is attached.

Remote search hits carry metadata only — never a file list. Requesting files=true for such a hit resolves the files on demand: it fetches the full torrent from peer (if given), otherwise via DHT, and clones the result — with its files — into the local index. Resolution order when the file list is missing locally is peer → DHT; on a miss the call fails with "Torrent not found". A torrent fetched via DHT is returned with "fromDHT": true.


torrent.remove - Remove torrents by hash

GET http://localhost:8095/api/torrent.remove?hashes=["29ebe63...","abc123..."]
ParameterTypeRequiredDefaultDescription
hashesarrayyesArray of 40-character info hashes to remove

Emits torrentRemoveProgress WebSocket events during a large removal.

Response:

{
    "success": true,
    "data": { "total": 2, "removed": 2 }
}

torrent.cleanup - Re-apply filters to the whole index

Walks every indexed torrent and checks it against the current filter policy (filters.* config keys), removing the ones that no longer pass. Use it after tightening the adult/size/content-type filters.

GET http://localhost:8095/api/torrent.cleanup?dryRun=true
ParameterTypeRequiredDefaultDescription
dryRunboolnofalseOnly count the matches; remove nothing

Emits torrentCleanupProgress WebSocket events while it sweeps.

Response:

{
    "success": true,
    "data": { "dryRun": true, "scanned": 12045, "matched": 317, "removed": 0 }
}

removed is always 0 when dryRun is set; otherwise it equals matched.


torrent.create - Create a .torrent file

GET http://localhost:8095/api/torrent.create?path=C:/Media/Album&output=C:/album.torrent
ParameterTypeRequiredDefaultDescription
pathstringyesFile or directory to build the torrent from
outputstringrequired unless seedDestination .torrent file path
trackersarrayTracker announce URLs
commentstringOptional comment
seedboolfalseIf true, create and immediately start seeding

Response - { "file": "<output>" }, or when seed is true { "hash": "...", "seeding": true }.


torrent.import - Parse a .torrent file and index it

GET http://localhost:8095/api/torrent.import?path=C:/Downloads/example.torrent
ParameterTypeRequiredDefaultDescription
path (or file)stringyesPath to a .torrent file on disk

Parses the file and inserts it through the single IndexingService path (content detection + filters). Does not start downloading.

Response - the indexed torrent object plus:

{
    "alreadyExists": false,
    "imported": true
}

Downloads

download.add - Start downloading a torrent

GET http://localhost:8095/api/download.add?hash=29ebe63feb8be91b6dcff02bacc562d9a99ea864&savePath=C:/Downloads
ParameterTypeRequiredDefaultDescription
hash (or magnet)stringyes40-character info hash or a magnet link
savePathstringCustom save directory (default download path otherwise)

Indexed metadata (name/size) is reused when the torrent is already known.


download.addFile - Start downloading from a .torrent file

GET http://localhost:8095/api/download.addFile?path=C:/Downloads/example.torrent&savePath=C:/Downloads
ParameterTypeRequiredDefaultDescription
path (or file)stringyesPath to a .torrent file
savePathstringCustom save directory

download.pause / download.resume - Pause or resume a download

GET http://localhost:8095/api/download.pause?hash=29ebe63...
GET http://localhost:8095/api/download.resume?hash=29ebe63...
ParameterTypeRequiredDefaultDescription
hashstringyesInfo hash of the download

Fails with "Download not found" if the hash is not an active download.


download.remove - Remove (cancel) a download

GET http://localhost:8095/api/download.remove?hash=29ebe63...&saveResumeData=false
ParameterTypeRequiredDefaultDescription
hashstringyesInfo hash of the download
saveResumeDataboolfalsePersist resume data before removing

download.list - Get all active downloads

GET http://localhost:8095/api/download.list

No parameters. Returns an array of active-download objects with progress info.


download.selectFiles - Select files for download

GET http://localhost:8095/api/download.selectFiles?hash=29ebe63...&files=[0,2,5]
ParameterTypeRequiredDefaultDescription
hashstringyesInfo hash
filesarrayyesArray of file indices to download

Feed

feed.get - Get the feed (voted/popular torrents)

GET http://localhost:8095/api/feed.get?index=0&limit=20
ParameterTypeRequiredDefaultDescription
indexint0Pagination offset
limitint20Number of items

Response - array of feed items (torrent objects ranked by recency + votes).


Voting

Votes are stored in the librats distributed store (one record per peer per torrent) and aggregated swarm-wide. These methods answer asynchronously.

vote.cast - Vote on a torrent

GET http://localhost:8095/api/vote.cast?hash=29ebe63...&good=true
ParameterTypeRequiredDefaultDescription
hashstringyes40-character info hash
good (or isGood)booltruetrue for up-vote, false for down-vote

Response - the aggregated vote result (e.g. hash, good, bad, self-vote / distributed flags).


vote.get - Get vote counts for a torrent

GET http://localhost:8095/api/vote.get?hash=29ebe63...
ParameterTypeRequiredDefaultDescription
hashstringyes40-character info hash

Response - the aggregated good/bad counts for the torrent.


Configuration

config.get - Get current configuration

GET http://localhost:8095/api/config.get

Returns the full configuration object (all ConfigStore settings).


config.set - Update configuration

GET http://localhost:8095/api/config.set?darkMode=false&httpPort=9000

Pass any configuration keys as parameters. Returns the list of keys that actually changed:

{
    "success": true,
    "data": { "changed": ["darkMode", "httpPort"] }
}

Statistics & Peers

stats.get - Database and peer statistics

GET http://localhost:8095/api/stats.get

Response:

{
    "success": true,
    "data": {
        "torrents": 150000,
        "files": 2500000,
        "size": 890000000000000,
        "peers": 12
    }
}

peers.list - Connected P2P peers

GET http://localhost:8095/api/peers.list

Response - array of connected peers, each carrying its advertised PeerStats:

{
    "success": true,
    "data": [
        {
            "peerId": "abc123...",
            "clientVersion": "2.0.0",
            "torrents": 50000,
            "files": 800000,
            "totalSize": 120000000000000,
            "peersConnected": 8,
            "connectedAt": 1700000000000
        }
    ]
}

Trackers

tracker.check - Trigger a tracker scrape

GET http://localhost:8095/api/tracker.check?hash=29ebe63feb8be91b6dcff02bacc562d9a99ea864
ParameterTypeRequiredDefaultDescription
hashstringyes40-character info hash

Fire-and-forget: queues a scrape of the swarm (seeders/leechers/completed) and website info; the results are written back into the index asynchronously.

Response:

{
    "success": true,
    "data": { "hash": "29ebe63...", "status": "checking" }
}

Updates

update.check - Check for an application update

GET http://localhost:8095/api/update.check

No parameters. Queries GitHub releases asynchronously and answers once.

Response when an update is available:

{
    "success": true,
    "data": {
        "available": true,
        "version": "2.1.0",
        "downloadUrl": "https://github.com/.../RatsSearch.zip",
        "releaseNotes": "…",
        "downloadSize": 45000000,
        "publishedAt": "2026-01-01T00:00:00Z",
        "prerelease": false
    }
}

When up to date: { "available": false }.


WebSocket Events

Push events are broadcast to all connected WebSocket clients as { "event": name, "data": {…} }. Events are emitted by ApiRouter::event() as work progresses. Event names and payload shapes are part of the published contract.

EventDescriptionData
downloadProgressA download advanced (emitted at most once per second, and only when the payload changed){ "hash": "…", "downloaded": 1024, "total": 4096, "progress": 0.25, "downloadSpeed": 512, "paused": false, "removeOnDone": false, "timeRemaining": 6 }
downloadCompletedA download finished{ "hash": "…", "cancelled": false }
filesReadyA magnet's metadata arrived, so its file list is now known{ "hash": "…", "files": [ { "path": "a.mkv", "size": 1024, "index": 0, "selected": true, "progress": 0.0 } ] }
torrentIndexedA genuinely new torrent entered the index{ "hash": "…", "name": "…" }
votesUpdatedVote counts for a torrent changed (local or remote vote){ "hash": "…", "good": 3, "bad": 1 }
feedUpdatedThe feed changed{ "feed": [ …torrent objects… ] }
configChangedOne or more config keys were writtenThe full config object plus "changedKeys": ["filters.adultFilter"]
remoteSearchResultsA peer answered a P2P search{ "searchId": "…", "torrents": [ …torrent objects… ] }
torrentRemoveProgressProgress of a bulk torrent.remove{ "processed": 100, "removed": 98, "total": 500 }
torrentCleanupProgressProgress of a torrent.cleanup sweep{ "scanned": 5000, "matched": 120, "total": 12045 }

Torrent Object Format

Most search/torrent methods return objects with these fields (serialized by rats::domain::codec):

FieldTypeDescription
hashstring40-character hex info hash
namestringTorrent name
sizeint64Total size in bytes
filesintNumber of files
pieceLengthintPiece size in bytes
addedint64Timestamp (milliseconds since epoch)
contentTypestringDetected content type (video, audio, pictures, books, software, games, archive, …)
contentCategorystringContent sub-category
seedersintNumber of seeders (from tracker)
leechersintNumber of leechers (from tracker)
completedintDownload count (from tracker)
trackersCheckedint64Last tracker check timestamp (ms)
goodintUp-vote count
badintDown-vote count
infoobjectScraped extras (poster, description, tracker payloads); included when present
files_listarray[{ "path", "size" }]; included when files are requested

Search hits (search.torrents / search.files) may additionally carry fileMatch, matchingPaths, peer (source peer id) and remote for results found by file name or received from a P2P peer. Results resolved via DHT carry fromDHT: true.


Examples

cURL - Search torrents

curl "http://localhost:8095/api/search.torrents?text=ubuntu&limit=5"

cURL - Get statistics

curl "http://localhost:8095/api/stats.get"

cURL - Start a download

curl "http://localhost:8095/api/download.add?hash=29ebe63feb8be91b6dcff02bacc562d9a99ea864"

WebSocket (JavaScript)

const ws = new WebSocket('ws://localhost:8096');

ws.onopen = () => {
    // Search for torrents
    ws.send(JSON.stringify({
        method: 'search.torrents',
        params: { text: 'ubuntu', limit: 10 },
        id: 'req-1'
    }));
};

ws.onmessage = (event) => {
    const msg = JSON.parse(event.data);

    if (msg.requestId) {
        // This is a response to our request
        console.log('Response:', msg);
    } else if (msg.event) {
        // This is a server-push event
        console.log('Event:', msg.event, msg.data);
    }
};

Python

import requests

# Search for torrents
r = requests.get('http://localhost:8095/api/search.torrents', params={
    'text': 'ubuntu',
    'limit': 10
})
data = r.json()

if data['success']:
    for torrent in data['data']:
        print(f"{torrent['name']} - {torrent['seeders']} seeders")

Legacy Version (Node.js)

The legacy Electron/Node.js version is located in the legacy/ folder. It uses a different API format (socket.io + polling queue). See the legacy README for details.