Mosaic exposes a JSON REST surface plus a single WebSocket fan-out at /api/ws. Every endpoint the desktop SPA calls is reachable from the API; the SPA itself is a client of this surface, with a transport shim that swaps Wails IPC for fetch + WS when running in browser mode.

This page covers the cross-cutting concerns. For the per-endpoint reference, see REST Endpoints and WebSocket.

Base URL

https://<host>:<port>/api

<port> defaults to 8080 and is configurable via WebConfigDTO.port. When the remote interface is enabled in loopback-only mode the only reachable host is 127.0.0.1. With “Bind to all” toggled on, the listener binds 0.0.0.0 (or [::]:<port>).

The cert is self-signed; expect a browser warning on first connect, and pass -k (or pin the cert) for curl-driven scripts.

Transport

Surface Transport
REST application/json over HTTPS
WebSocket wss://<host>:<port>/api/ws
File upload multipart/form-data (one route — POST /api/torrents/file)

Request bodies are JSON unless noted. Response bodies are JSON; errors share an envelope (see below).

Authentication

Two methods, accepted on every gated route:

  1. Session cookie — set on POST /api/login, sent automatically by the browser. Name: mosaic_session. HttpOnly, SameSite=Strict, Secure when behind HTTPS.
  2. Bearer API keyAuthorization: Bearer <key>. For browser WebSocket upgrades that can’t set headers, pass it as ?key=<key> instead.

State-changing cookie-authed requests must satisfy the Origin/Referer CSRF guard (host of Origin or Referer must equal Host). Bearer-keyed requests skip this check. See Authentication for the full flow.

Error envelope

Every error response carries the same shape — JSON regardless of route, no exceptions:

{ "error": "<message>" }
Status Meaning
200 Success.
400 Bad request — missing/invalid body, invalid params, validation failure.
401 Unauthorized — no valid session cookie and no valid bearer token.
403 Forbidden — Origin/Referer didn’t match Host on a cookie-authed state-changing request.
429 Too Many Requests — login rate limiter tripped. Retry-After: 12 header is set.
500 Internal error.
502 Upstream failure — e.g. blocklist URL didn’t return.

A success response is either an explicit shape (DTO or list of DTOs), or a generic ack: {"ok": true} for fire-and-forget commands, {"id": "<value>"} for create endpoints.

Versioning

Mosaic does not currently version its API surface in the URL. The DTOs are documented per release; backwards-incompatible field renames will bump the minor version and call out the change in release notes. The build’s version string is reachable at GET /api/version ({"version": "vX.Y.Z"}).

Quick check

# Replace with your own
export MOSAIC="https://localhost:8080"
export MOSAIC_API_KEY="<paste-from-Settings-WebInterface>"

curl -k "$MOSAIC/api/version" -H "Authorization: Bearer $MOSAIC_API_KEY"
# → {"version":"v0.2.9"}

curl -k "$MOSAIC/api/torrents" -H "Authorization: Bearer $MOSAIC_API_KEY"
# → [...torrent rows...]

If you get {"error":"unauthorized"}, double-check the API key and that the bearer header isn’t being stripped by an intermediate proxy.