The slskr HTTP API provides programmatic access to Soulseek client functionality. All endpoints are available at /api/v0/* or /api/* paths and require Bearer token authentication for security-sensitive operations.
All endpoints require a Bearer token in the Authorization header:
Authorization: Bearer <token>
Tokens are configured in your slskr.config.toml file. Request without valid token returns 401 Unauthorized.
All mutating requests (POST, PUT, DELETE) require CSRF origin verification:
Origin: http://127.0.0.1:5030
The origin must match the configured server address. Invalid origin returns 403 Forbidden.
All responses use JSON format with standard HTTP status codes:
200 OK- Successful GET/query request201 Created- Successful POST creating a new resource204 No Content- Successful DELETE or empty response400 Bad Request- Invalid request parameters401 Unauthorized- Authentication failed or token invalid403 Forbidden- CSRF validation failed or permission denied404 Not Found- Resource does not exist409 Conflict- Resource conflict or validation error500 Internal Server Error- Server error
Server health check endpoint. Returns 200 OK if server is running.
Response:
{
"status": "ok",
"timestamp": "2025-05-04T12:00:00Z"
}Get slskr version and build information.
Response:
{
"name": "slskr",
"version": "0.0.0",
"protocol": {
"client_name": "slskr",
"major": 175,
"minor": 8800001
}
}Retrieve current configuration (with sensitive values redacted).
Response:
{
"username": "my_username",
"server_address": "slsk.example.com:2242",
"shared_directories": ["/music", "/downloads"],
"transfer_max_active": 5
}Get aggregate runtime, projection, and durable database statistics.
Response:
{
"session": {"state": "connected"},
"shares": {"files": 100, "bytes": 1000000},
"searches": {"total": 3, "active": 1, "results": 42},
"transfers": {"total": 5, "in_progress": 1},
"database": {
"enabled": true,
"healthy": true,
"searches": 3,
"searchResults": 42,
"transferEvents": 12,
"projections": {
"searches": 3,
"transfers": 5
}
}
}Get list of supported capabilities/features.
Query Parameters:
format(optional):json(default) orcsv
Response:
{
"app": [
"health",
"version",
"config",
"stats",
"session-control"
],
"network": [
"server-session",
"peer-messaging",
"file-transfer"
],
"storage": [
"share-index-sqlite",
"share-index-tsv",
"transfer-events-sqlite",
"transfer-events-tsv",
"transfer-state-json"
]
}List one configured share-root label using virtual share paths only. Local host paths are never included in the response. The default response preserves the flat root file listing used by existing automation clients, and also includes immediate directory summaries for folder navigation.
Query Parameters:
folder,path, orprefix(optional): virtual subfolder under the root.recursive(optional):trueto include descendant files for the selected folder.q(optional): case-insensitive virtual path search.extension(optional): file extension filter.limit(optional): maximum file entries to emit.offset(optional): file entry pagination offset.
Response:
{
"label": "Music",
"folder": "Artist",
"recursive": false,
"entries": [
{
"type": "file",
"path": "Track.flac",
"virtual_path": "Music/Artist/Track.flac",
"size": 42000000,
"extension": "flac"
}
],
"directories": [
{
"type": "directory",
"name": "Album",
"path": "Album",
"virtual_path": "Music/Artist/Album",
"file_count": 12,
"total_bytes": 504000000
}
],
"count": 42,
"filtered_count": 1,
"directory_count": 1,
"total_bytes": 42000000,
"offset": 0,
"limit": 50
}List the scoped downloads or incomplete storage root using the slskd-compatible
directory response shape. Add /{base64-path} to list a nested directory. The
path is UTF-8 encoded as standard Base64 and then percent-encoded as one URL
path segment so /, +, and = inside the encoded value do not change the
route.
Query Parameters:
recursive(optional):trueto include nested directories.limit(optional): maximum entries to emit. Recursive requests default to 256 and are capped at 1,024 entries per request; non-recursive requests default to 1,024 and are capped at 4,096.offset(optional): top-level entry offset for paged compatibility listings.
Responses include entryCount, limit, offset, and truncated metadata so
clients can detect bounded listings and request another page.
Read the single server-session snapshot.
Response:
{
"state": "connected",
"username": "my_username",
"supporter": false,
"privileges_seconds": 0,
"last_error": null,
"last_server_message": null,
"server_messages_seen": 0,
"reconnects": 0,
"connected_at": null,
"updated_at": 0
}Request the daemon to connect using its configured server credentials.
Response: 202 Accepted with {"accepted":true}.
Send a keep-alive command to the server session.
Response:
{
"status": "ok",
"latency_ms": 45
}Request a server-session disconnect.
Response: 202 Accepted with {"accepted":true}.
Request a fresh privilege check for the connected account.
Response:
{
"accepted": true
}List recent searches as a slskd-compatible top-level array. This is the route used by slskd automation clients.
Query Parameters:
limit(optional): Max results (default: 50)offset(optional): Pagination offset (default: 0)status(optional): Filter by status (active, completed, failed)
Response:
[
{
"id": "search-123",
"token": 1,
"query": "song title",
"searchText": "song title",
"status": "active",
"state": "InProgress",
"isComplete": false,
"fileCount": 42,
"lockedFileCount": 0,
"responseCount": 3,
"responses": [],
"result_count": 42,
"startedAt": "1777973673",
"endedAt": null
}
]List recent searches with the slskr metadata envelope used by the dashboard.
Response:
{
"entries": [],
"count": 0,
"filtered_count": 0,
"offset": 0,
"limit": 50,
"next_token": 1
}Create a new search. When persistence is enabled, the search row and result rows are written to SQLite and rehydrated on restart.
Request Body:
{
"query": "song title",
"room": null,
"target": null,
"ttl_seconds": 15
}The default active-search inactivity window is 15 seconds, matching the
Soulseek search contract. Each accepted peer response resets the window. A
caller can provide a different positive ttl_seconds value up to 24 hours.
Response: 201 Created with search details
POST /api/searches/{id}/complete, POST /api/searches/{id}/cancel,
POST /api/searches/{id}/fail, POST /api/searches/{id}/expire,
PUT /api/searches/{id}, POST /api/search-responses,
POST /api/searches/prune, DELETE /api/searches/{id}, and
DELETE /api/searches mutate the in-memory projection and write through to
SQLite when persistence is enabled. Search response rows are persisted in the
search_results table so peer-group/result detail responses survive restart.
Get search details and results.
Query Parameters:
limit(optional): Max results per pageoffset(optional): Pagination offset
Response:
{
"id": "search-123",
"query": "song title",
"status": "active",
"results": [
{
"username": "peer_user",
"filename": "Artist - Song.flac",
"size": 50000000,
"bitrate": 1411,
"length": 240
}
]
}List all messages.
Query Parameters:
limit(optional): Max messages (default: 50)offset(optional): Pagination offset
Response:
{
"entries": [
{
"id": 1,
"username": "username",
"direction": "outbound",
"body": "Hello",
"acknowledged": false,
"created_at": 0,
"updated_at": 0
}
],
"count": 1,
"filtered_count": 1,
"offset": 0,
"limit": null,
"updated_at": 0
}Get messages from a specific user.
Query Parameters:
limit(optional): Max messagesoffset(optional): Pagination offset
Response: List of messages with given username
Send a message to a user. When persistence is enabled, the message projection is written to SQLite and rehydrated on restart.
Request Body:
{
"username": "username",
"body": "Hello"
}Response: 201 Created with message details
Mark message as acknowledged. When persistence is enabled, the acknowledgement state is written to SQLite and rehydrated on restart.
Response: 204 No Content
List all transfers (uploads and downloads).
Query Parameters:
direction(optional):upload,download, or bothstatus(optional):active,completed,failed,cancelledlimit(optional): Max resultsoffset(optional): Pagination offset
Response:
{
"transfers": [
{
"id": "transfer-123",
"direction": "download",
"status": "active",
"peer_username": "uploader",
"filename": "Artist - Song.flac",
"size": 50000000,
"bytes_transferred": 25000000,
"progress_percent": 50,
"speed_bytes_per_sec": 5000000,
"eta_seconds": 5,
"started_at": "2025-05-04T11:40:00Z"
}
]
}Initiate a new transfer. When persistence is enabled, the transfer row is written to SQLite and updated by later lifecycle mutations.
Request Body:
{
"direction": "download",
"peer_username": "uploader",
"filename": "Artist - Song.flac"
}Response: 201 Created with transfer details
Get transfer details.
Response: Transfer object with detailed status
Cancel a transfer. The cancellation writes through to SQLite when persistence is enabled.
Response: 204 No Content
POST /api/transfers/{id}/start, POST /api/transfers/{id}/retry,
POST /api/transfers/{id}/progress, POST /api/transfers/{id}/complete,
slskd-compatible download enqueue/cancel/prune routes, and replacement routes
mutate the transfer projection and write through to SQLite when persistence is
enabled. transfer-state.json remains the reloadable transfer projection used
for restart-safe queued/resume state.
List all chat rooms.
Query Parameters:
limit(optional): Max resultsoffset(optional): Pagination offset
Response:
{
"rooms": [
{
"name": "General",
"user_count": 1234,
"users": []
}
]
}Get room details and user list.
Response:
{
"name": "General",
"user_count": 1234,
"users": ["user1", "user2"]
}Join a room. When persistence is enabled, the room subscription projection is written to SQLite and rehydrated on restart.
Response: 201 Created with room details
Leave a room. When persistence is enabled, the room subscription is marked unsubscribed in SQLite.
Response: 204 No Content
Browse a user's cached shared files. The slskd-compatible route returns directory groups with stable total and filtered counts so clients can page without inferring totals from the current page length.
Query Parameters:
folder(optional): Folder path to browseq(optional): Case-insensitive directory or filename filterlimit(optional): Max resultsoffset(optional): Pagination offset
Response:
{
"directories": [
{
"name": "Artist",
"fileCount": 2,
"filteredFileCount": 1,
"totalBytes": 50000000,
"files": [
{
"filename": "Artist/Song.flac",
"size": 50000000,
"extension": "flac"
}
]
}
],
"directoryCount": 4,
"filteredDirectoryCount": 1,
"fileCount": 12,
"filteredFileCount": 1,
"totalBytes": 50000000,
"offset": 0,
"limit": 50
}Request the user's complete shared-file list. The body is empty.
Response: 202 Accepted with browse request details.
Request one folder from a user.
Request Body:
{
"folder": "Music"
}Response: 202 Accepted with browse request details
Record a failed browse request.
Request Body:
{
"reason": "peer unavailable"
}List cached browse projections. For compatibility request tracking, the
legacy /api/browse/requests alias returns { "requests": [...], "count": n };
its entries identify users by username, not by a synthetic request ID.
Get historical events as a slskd-compatible top-level array. When SQLite
persistence is enabled, recent event rows hydrate on startup and new events
write through to the durable events table.
Query Parameters:
kind(optional): Event kind filtertopic(optional): Event topic filter, using the same topic taxonomy as/api/events/wsq(optional): Case-insensitive kind, topic, resource, or detail searchlimit(optional): Max events (default: 50)offset(optional): Pagination offset
Response:
[
{
"id": "1",
"timestamp": "1777973673",
"topic": "searches",
"type": "search.started",
"resource": "1",
"detail": "query=ambient",
"data": "{\"id\":1,\"kind\":\"search.started\",\"topic\":\"searches\",\"resource\":\"1\",\"detail\":\"query=ambient\",\"created_at\":1777973673}",
"payload": {
"id": 1,
"kind": "search.started",
"topic": "searches",
"resource": "1",
"detail": "query=ambient",
"created_at": 1777973673
}
}
]The response includes X-Total-Count, containing the number of events that
match the supplied filters before limit and offset are applied.
Get historical events with the slskr metadata envelope used by the dashboard.
Response:
{
"entries": [],
"count": 0,
"filtered_count": 0,
"offset": 0,
"limit": 500
}All error responses follow this format:
{
"error": "Error description",
"details": "Additional context if available"
}Invalid Token:
{
"error": "Unauthorized",
"details": "Invalid or missing bearer token"
}CSRF Violation:
{
"error": "Forbidden",
"details": "CSRF origin validation failed"
}Resource Not Found:
{
"error": "Not Found",
"details": "Transfer with id 'invalid-id' not found"
}Conflict:
{
"error": "Conflict",
"details": "Transfer already exists for this peer"
}- No official rate limiting implemented
- Recommend implementing on-client rate limiting for performance
For endpoints returning lists, use limit and offset query parameters:
GET /api/messages?limit=20&offset=0
WebSocket event streaming is available at /api/events/ws. Browser clients
authenticate with the slskr.api-token.<percent-encoded-token> subprotocol;
non-browser clients may also use the normal bearer authorization path. Polling
with /api/events remains available for compatibility clients. WebSocket
frames and historical event APIs share the same topic values for application,
searches, transfers, messages, rooms, users, shares, browse, listeners, relay,
bridge, mesh, security, federation, Solid, integrations, media, player, system,
and settings workflows.
Return live persisted SQLite counts and current in-memory projection counts,
including share index, event log, transfer event trail, user, browse, social/security,
wishlist/contact/sharegroup/share-grant, collection, library, destination,
now-playing, webhook, and pending OAuth-state stores. When runtime compatibility
state has been persisted, the response also includes runtimeState.
When persistence is disabled, the response still reports projection counts and
marks the persisted database as disabled.
Clean old persisted message rows and prune terminal transfer projections.
Request Body:
{
"days": 30
}Run SQLite VACUUM when persistence is enabled. When persistence is disabled,
the route returns a structured skipped result instead of a hard-coded success.
- Bulk operations: Combine multiple operations into single requests where possible
- Pagination: Use reasonable limits to avoid large response payloads
- Polling: Use appropriate intervals (5-30 seconds recommended for events)
-
Build slskr with HTTP API support:
cargo build --release
-
Configure your
slskr.config.toml:[app] http_bind = "127.0.0.1:5030" [auth] api_token = "replace-with-a-random-token"
-
Start the server:
./target/release/slskr
- Use HTTPS in production (reverse proxy with TLS termination)
- Rotate bearer tokens regularly
- Restrict API access to trusted networks only
- Monitor API usage for suspicious patterns
- Use strong, randomly-generated bearer tokens
Monitor these key metrics:
- Request latency (target: <100ms for most requests)
- Error rate (target: <0.1%)
- Active connections
- Bearer token usage
- Endpoint paths are stable and versioned (
/api/v0/*) - New fields in responses are backwards compatible
- Deprecated fields will remain but may be marked as such
- Major breaking changes will include API version bump
- The slskd-compatible
/api/options,/api/options/yaml, and/api/options/yaml/validateroutes apply bounded target-specific overlays, validate and atomically persistslskd.yml, and expose the frozen restart/reconnect lifecycle. Unsupported shapes fail instead of returning a false-success compatibility acknowledgement. - Compatibility shells that are not active in this runtime keep their endpoint
paths and stable response shapes, but may return empty arrays or
compatibility_acknowledgementobjects. Bridge start/stop/config aliases, logs, profile invite/cache warm/backfill/SongID/Lidarr operation counters, unconfigured Lidarr wanted/sync/import fallbacks, destination validation, listening-party content helpers, share-grant token/backfill helpers, profile updates, and MusicBrainz release-radar subscription helpers now project or mutate local slskr stores where equivalent state exists. Collections, collection items, library items, user records, browse cache records, destination records, now-playing records, wishlist items, contacts, share groups and members, share grants, user notes, liked/hated interests, and username/IP bans write through to SQLite when persistence is enabled. Pending OAuth callback states also write through to SQLite and are consumed after a valid callback or pruned after expiry. Webhook configuration changes and queued delivery logs write through to SQLite and hydrate on restart.
Test endpoints with curl:
# Health check
curl http://127.0.0.1:5030/api/health
# With authentication
curl -H "Authorization: Bearer your-token" \
http://127.0.0.1:5030/api/statsOr use the provided test suite:
cargo test --test http_apiAll 71 API tests pass with 100% coverage.