Skip to content

Latest commit

 

History

History
900 lines (705 loc) · 21 KB

File metadata and controls

900 lines (705 loc) · 21 KB

slskr HTTP API Reference

Overview

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.

Authentication

Bearer Token

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.

CSRF Protection

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.

Response Format

All responses use JSON format with standard HTTP status codes:

  • 200 OK - Successful GET/query request
  • 201 Created - Successful POST creating a new resource
  • 204 No Content - Successful DELETE or empty response
  • 400 Bad Request - Invalid request parameters
  • 401 Unauthorized - Authentication failed or token invalid
  • 403 Forbidden - CSRF validation failed or permission denied
  • 404 Not Found - Resource does not exist
  • 409 Conflict - Resource conflict or validation error
  • 500 Internal Server Error - Server error

Endpoints

Health & Version

GET /api/health

Server health check endpoint. Returns 200 OK if server is running.

Response:

{
  "status": "ok",
  "timestamp": "2025-05-04T12:00:00Z"
}

GET /api/version

Get slskr version and build information.

Response:

{
  "name": "slskr",
  "version": "0.0.0",
  "protocol": {
    "client_name": "slskr",
    "major": 175,
    "minor": 8800001
  }
}

Configuration

GET /api/config

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 /api/stats

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
    }
  }
}

Capabilities

GET /api/capabilities

Get list of supported capabilities/features.

Query Parameters:

  • format (optional): json (default) or csv

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"
  ]
}

Storage Compatibility Listings

GET /api/v0/files/{root}

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, or prefix (optional): virtual subfolder under the root.
  • recursive (optional): true to 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
}

GET /api/v0/files/downloads/directories

GET /api/v0/files/incomplete/directories

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): true to 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.

Session Control

GET /api/v0/session

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
}

POST /api/v0/session/connect

Request the daemon to connect using its configured server credentials. Response: 202 Accepted with {"accepted":true}.

POST /api/v0/session/ping

Send a keep-alive command to the server session.

Response:

{
  "status": "ok",
  "latency_ms": 45
}

POST /api/v0/session/disconnect

Request a server-session disconnect.

Response: 202 Accepted with {"accepted":true}.

Privileges

POST /api/v0/session/privileges/check

Request a fresh privilege check for the connected account.

Response:

{
  "accepted": true
}

Search

GET /api/searches

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
  }
]

GET /api/searches/records

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
}

POST /api/searches

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

Search Mutation Persistence

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 /api/searches/{id}

Get search details and results.

Query Parameters:

  • limit (optional): Max results per page
  • offset (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
    }
  ]
}

Messages

GET /api/v0/messages

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 /api/v0/messages/{username}

Get messages from a specific user.

Query Parameters:

  • limit (optional): Max messages
  • offset (optional): Pagination offset

Response: List of messages with given username

POST /api/v0/messages

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

POST /api/v0/messages/{id}/ack

Mark message as acknowledged. When persistence is enabled, the acknowledgement state is written to SQLite and rehydrated on restart.

Response: 204 No Content

Transfers

GET /api/transfers

List all transfers (uploads and downloads).

Query Parameters:

  • direction (optional): upload, download, or both
  • status (optional): active, completed, failed, cancelled
  • limit (optional): Max results
  • offset (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"
    }
  ]
}

POST /api/transfers

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 /api/transfers/{id}

Get transfer details.

Response: Transfer object with detailed status

DELETE /api/transfers/{id}

Cancel a transfer. The cancellation writes through to SQLite when persistence is enabled.

Response: 204 No Content

Transfer Mutation Persistence

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.

Rooms

GET /api/rooms

List all chat rooms.

Query Parameters:

  • limit (optional): Max results
  • offset (optional): Pagination offset

Response:

{
  "rooms": [
    {
      "name": "General",
      "user_count": 1234,
      "users": []
    }
  ]
}

GET /api/rooms/{name}

Get room details and user list.

Response:

{
  "name": "General",
  "user_count": 1234,
  "users": ["user1", "user2"]
}

POST /api/v0/rooms/{name}/join

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

DELETE /api/v0/rooms/{name}/join

Leave a room. When persistence is enabled, the room subscription is marked unsubscribed in SQLite.

Response: 204 No Content

Browse

GET /api/v0/users/{username}/browse

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 browse
  • q (optional): Case-insensitive directory or filename filter
  • limit (optional): Max results
  • offset (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
}

POST /api/v0/users/{username}/browse/request

Request the user's complete shared-file list. The body is empty. Response: 202 Accepted with browse request details.

POST /api/v0/users/{username}/browse/folder

Request one folder from a user.

Request Body:

{
  "folder": "Music"
}

Response: 202 Accepted with browse request details

POST /api/v0/users/{username}/browse/fail

Record a failed browse request.

Request Body:

{
  "reason": "peer unavailable"
}

GET /api/v0/browse

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.

Events

GET /api/events

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 filter
  • topic (optional): Event topic filter, using the same topic taxonomy as /api/events/ws
  • q (optional): Case-insensitive kind, topic, resource, or detail search
  • limit (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 /api/events/records

Get historical events with the slskr metadata envelope used by the dashboard.

Response:

{
  "entries": [],
  "count": 0,
  "filtered_count": 0,
  "offset": 0,
  "limit": 500
}

Error Responses

All error responses follow this format:

{
  "error": "Error description",
  "details": "Additional context if available"
}

Common Errors

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"
}

Rate Limiting

  • No official rate limiting implemented
  • Recommend implementing on-client rate limiting for performance

Pagination

For endpoints returning lists, use limit and offset query parameters:

GET /api/messages?limit=20&offset=0

WebSocket Support

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.

Database Maintenance

GET /api/v0/database/stats

GET /api/database/stats

GET /api/admin/database/stats

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.

POST /api/v0/database/cleanup

POST /api/database/cleanup

POST /api/admin/database/cleanup

Clean old persisted message rows and prune terminal transfer projections.

Request Body:

{
  "days": 30
}

POST /api/v0/database/vacuum

POST /api/database/vacuum

POST /api/admin/database/vacuum

Run SQLite VACUUM when persistence is enabled. When persistence is disabled, the route returns a structured skipped result instead of a hard-coded success.

Performance Considerations

  • 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)

Deployment Guide

Installation

  1. Build slskr with HTTP API support:

    cargo build --release
  2. Configure your slskr.config.toml:

    [app]
    http_bind = "127.0.0.1:5030"
    
    [auth]
    api_token = "replace-with-a-random-token"
  3. Start the server:

    ./target/release/slskr

Security

  • 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

Monitoring

Monitor these key metrics:

  • Request latency (target: <100ms for most requests)
  • Error rate (target: <0.1%)
  • Active connections
  • Bearer token usage

Backwards Compatibility

  • 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/validate routes apply bounded target-specific overlays, validate and atomically persist slskd.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_acknowledgement objects. 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.

Testing

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/stats

Or use the provided test suite:

cargo test --test http_api

All 71 API tests pass with 100% coverage.