You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This page lists every HTTP endpoint the SearchBox server exposes, what each one does, and what it takes to call it. By the end you will know which routes need a login, which need CSRF protection, which only exist on Windows, and which traffic never touches this API at all (full-text search goes straight to Meilisearch). Everything here was read directly from the route definitions in src/routes/*.rs and the router assembly in src/main.rs.
🔒 SearchBox binds to 127.0.0.1 only (see src/config.rs; override with SEARCHBOX_HOST). Default port is 8080 (SEARCHBOX_PORT). Nothing on this page is reachable from your network unless you explicitly opt in — everything runs on your computer, nothing leaves it.
How requests are authenticated
Session cookie. Logging in stores a user in a SQLite-backed tower-sessions session (30-day inactivity expiry, SameSite=Lax). Handlers that take the CurrentUser extractor reject requests without a valid session: /api/... paths get a 401 JSON body ({"error": "authentication required"}); page paths get a redirect to /login. See src/auth/session.rs.
CSRF on mutations. JSON mutation handlers take the CsrfToken extractor, which compares the X-CSRFToken request header against the token stored in the session (constant-time) and rejects with 403 on mismatch. HTML form posts (/setup, /login, /reset-password) instead carry a csrf_token form field checked by validate_csrf. The token is rendered into every page template as csrf_token.
Errors come back as JSON {"error": "..."} with status 400 (bad request), 401 (unauthorized), 403 (CSRF failure), 404 (not found), 429 (login throttle), or 500 (internal). See src/error.rs.
In the tables below, Auth means the handler requires CurrentUser (session cookie) and CSRF means it requires the CsrfToken extractor (X-CSRFToken header). Anything not marked is stated explicitly in the Notes column.
Where search actually happens
Full-text search is not served by this API. The pages /, /explore, and /images inject a Meilisearch host and API key into the page (templates/index.html sets MEILI_HOST and MEILI_API_KEY from services/meili.rs::client_config), and the browser queries Meilisearch directly — by default http://localhost:7700. The Meilisearch sidecar is launched bound to 127.0.0.1 with a per-install random master key (src/services/meili_process.rs), so it is just as loopback-only as the app itself. The endpoints under "Meilisearch control" below manage the sidecar; they do not proxy queries.
Pages (HTML)
These render the UI. Except where noted, an unauthenticated request is redirected to /login.
Method
Path
What it does
Notes (auth/CSRF/platform quirks)
GET
/
Main search page
Redirects to /setup when no account exists yet
GET
/login
Login form
No auth; redirects to / if already logged in
GET
/setup
First-run account creation form
No auth; redirects away once an account exists
GET
/settings
Settings page
Auth. Accepts ?recovery_key=... to display a freshly generated recovery key
GET
/explore
Explore/browse page
Auth. Gets Meilisearch client config injected
GET
/images
Image gallery page
Auth. Gets Meilisearch client config injected
GET
/view/{doc_id}
Document viewer page
Auth
GET
/demo
In-app copy of the landing page + demo in an iframe
Auth. Loads from /demo-site/..., fully offline
GET
/reset-password
Password-reset form (recovery key)
No auth; redirects to / if logged in
POST
/reset-password
Submit the reset form
No session auth — the recovery key is the credential. CSRF via csrf_token form field. Wraps the /api/auth/reset-password handler
Auth and setup
Method
Path
What it does
Notes (auth/CSRF/platform quirks)
POST
/setup
Create the first (owner) account
Only works while no user exists (400 afterwards). Form fields email, password (min 8 chars), optional name, csrf_token. Also initializes the vault salt and generates the recovery key, then redirects to /settings?recovery_key=... — the only time the key is shown
POST
/login
Log in
Form fields email, password, csrf_token. Failed attempts are throttled per email (429 with a retry-in-seconds message). On success the vault key is derived from the password and sealed into the session
POST
/logout
End the session
Auth implied by the session; CSRF via X-CSRFToken header. Flushes the whole session
GET
/api/auth/status
Report setup_required, authenticated, and the current user
No auth — callable before login so the UI knows which screen to show
POST
/api/auth/change-password
Change the password
Auth + CSRF. JSON {current_password, new_password}. Atomically rewraps every vault file key under the new password in one transaction
POST
/api/auth/reset-password
Reset a lost password with the recovery key
No session auth and no CSRF extractor — possession of the recovery key is the credential. JSON {recovery_key, new_password}. Rewraps all vault file keys and re-issues the session
Health and version
Method
Path
What it does
Notes (auth/CSRF/platform quirks)
GET
/api/health
Liveness check that also touches the database
No auth. Used by the Windows desktop shell to wait for boot before opening the window
GET
/api/version
This build's version string
Auth. Purely local — no network request
Documents, upload, and viewers
Method
Path
What it does
Notes (auth/CSRF/platform quirks)
GET
/api/documents
List indexed documents
Auth. Query limit (default 100) and offset; proxied to Meilisearch's document listing
GET
/api/document/{doc_id}
Fetch one document record from the index
Auth. doc_id is validated (alphanumerics, -, _ only) on every document route
DELETE
/api/document/{doc_id}
Remove a document from the index
Auth + CSRF. For vault files, also deletes the ciphertext on disk and its database row
POST
/api/document/{doc_id}/open
Open the file in its default app
Auth + CSRF. Uses cmd /C start on Windows, open on macOS, xdg-open elsewhere. For vault files this opens the encrypted file on disk
POST
/api/document/{doc_id}/reveal
Open the file's containing folder
Auth + CSRF. Same OS launcher as above
POST
/api/upload
Upload one file (multipart)
Auth + CSRF. Size-capped (SEARCHBOX_MAX_UPLOAD_SIZE, default 100 MB). Text is extracted and indexed; when a vault is configured the bytes are encrypted at rest — and if the vault is locked, the upload is refused (401) rather than stored in the clear
GET
/api/pdf/{doc_id}
Serve a PDF's bytes for the viewer
Auth. Vault files are decrypted in memory (requires an unlocked vault, else 401); folder files stream from their file_path
GET
/api/docx/{doc_id}
Serve a DOCX's bytes for the viewer
Auth. Same decrypt-or-stream logic
GET
/api/html/{doc_id}
Serve an indexed HTML file as a page
Auth. Same logic, plus a script-src 'none' CSP header so the page's own scripts never run
Thumbnails
Method
Path
What it does
Notes (auth/CSRF/platform quirks)
GET
/api/thumbnail/{doc_id}
Serve the generated JPEG thumbnail for an image document
Auth. Thumbnails are generated at index/upload time into the thumbnails directory; 404 if none exists. Deliberately not served from /static so they stay behind the session check
Folders (indexing)
Folder indexing runs as a background job: the start endpoint returns immediately with a job_id you poll.
Method
Path
What it does
Notes (auth/CSRF/platform quirks)
POST
/api/folder/index
Start indexing a folder
Auth + CSRF. JSON {path}; 400 if it is not a directory. Returns {job_id, status: "started", folder}
Auth + CSRF. Returns {job_ids: [...]}, one job per folder
POST
/api/folder/remove
Forget a folder
Auth + CSRF. JSON {path, delete_documents}; when delete_documents is true, issues a Meilisearch delete-by-filter on file_path STARTS WITH and returns that task in deletion_task
💡 Tip: There is no standalone jobs API. Job status lives in one shared in-memory registry (persisted to the database every minute), and both /api/folder/index/status and /api/archive/status read from it — either one can poll any job id.
Archives (ZIP / ZIM)
Archives are extracted under <base_dir>/archives/<name>/ and then run through the same folder-indexing pipeline.
Method
Path
What it does
Notes (auth/CSRF/platform quirks)
POST
/api/archive/index
Extract and index a .zip or .zim archive
Auth + CSRF. JSON {path}; other extensions get 400. Returns {job_id, status: "started", archive}
GET
/api/archive/status
Poll an archive job
Auth. Query ?job_id=...
GET
/api/archive/list
List extracted archives (name + path)
Auth
POST
/api/archive/remove
Delete an extracted archive
Auth + CSRF. JSON {path} must be inside the archives root (400 otherwise). Removes the directory, the .zimsource sidecar, and purges matching index entries
GET
/api/zim/content/{archive}/{*path}
Serve one entry (article, image, CSS) straight out of the source .zim
Auth. Follows ZIM redirects; HTML gets a <base href> injected plus a script-blocking CSP so the viewer iframe acts as a small offline browser
GET
/api/zim/thumb/{archive}/{*url}
Redirect to a ZIM article's first image, for result thumbnails
Auth. 404 when the entry is not an article or has no usable image
GET
/api/archive/raw/{archive}/{*path}
Serve a file from an extracted ZIP directory
Auth. Path is canonicalized and confined to the archive directory (blocks .. and symlink escapes); HTML gets the script-blocking CSP
Auth. "Locked" means a vault exists but this process cannot currently produce its key — which is always the case right after a restart, since the in-memory seal key is regenerated
POST
/api/vault/unlock
Re-derive the vault key from the password and reseal it into the session
Auth + CSRF. JSON {password}; wrong passwords count against the same throttle as login (429)
POST
/api/vault/reset
Delete every encrypted file and the vault configuration
Auth + CSRF. Requires JSON {"confirm": true} or it refuses with 400. Irreversible
AI summaries (Ollama)
AI features are optional and talk to a locally running Ollama — no cloud service is involved.
Method
Path
What it does
Notes (auth/CSRF/platform quirks)
GET
/api/ollama/status
Whether AI search is enabled and Ollama is reachable; lists models
Auth
GET
/api/ollama/models
List available models
Auth. Empty when AI search is disabled
POST
/api/ollama/test
Test a connection
Auth + CSRF. JSON {url?, timeout?}; URL must be http:// or https://
POST
/api/ollama/pull
Pull a model into Ollama
Auth + CSRF. JSON {model}
GET
/api/ollama/recommendations
Suggested searches
Auth. Query ?history=<JSON array>; personalized via Ollama only when AI is enabled and history is non-empty, otherwise a static fallback set
POST
/api/search/summary
Summarize search results (single response)
Auth + CSRF. JSON {query, results}; summarizes the top 5 results with citations
POST
/api/search/summary/stream
Same, streamed
Auth + CSRF. Responds with application/x-ndjson chunks
qBittorrent integration
A thin wrapper over qBittorrent's own Web API (v2). The qBittorrent password is stored encrypted at rest.
Method
Path
What it does
Notes (auth/CSRF/platform quirks)
GET
/api/qbittorrent/status
Enabled + connected + qBittorrent version
Auth
GET
/api/qbittorrent/config
Read saved connection settings
Auth. Never returns the password
POST
/api/qbittorrent/config
Save connection settings
Auth + CSRF. The password is encrypted before it is stored
POST
/api/qbittorrent/test
Test the connection
Auth + CSRF. Currently uses the persisted credentials, not the posted ones
GET
/api/qbittorrent/torrents
List completed + active torrents with indexed status
Auth
GET
/api/qbittorrent/indexed
List torrents SearchBox has indexed
Auth
POST
/api/qbittorrent/remove
Forget an indexed torrent
Auth + CSRF. JSON {torrent_hash}; purges index entries under its save path
POST
/api/qbittorrent/sync
Register completed torrents and index new ones
Auth + CSRF. New torrents' save paths run through the folder indexer (source qbittorrent), one background job each
Native file picker
Method
Path
What it does
Notes (auth/CSRF/platform quirks)
GET
/api/pick
Open a native folder or file dialog and return the chosen absolute path
Auth. Windows-only.?kind=folder picks a directory; anything else picks a .zim/.zip file. Returns {"path": "..."}, or path: null on cancel. On other platforms it returns 400 telling the caller to type the path manually — a web page cannot read a real filesystem path from <input type=file>, which is why this exists
Updates
Method
Path
What it does
Notes (auth/CSRF/platform quirks)
GET
/api/update/check
Compare this build against the latest GitHub release
Auth. The only outbound request besides apply — and only when called. download_url is populated on Windows only; elsewhere the UI shows a release-notes link instead
POST
/api/update/apply
Download the latest MSI and launch the installer
Auth + CSRF. Windows-only — other platforms get a 400. The MSI is verified against its published .sha256 sidecar before msiexec runs; a checksum mismatch refuses to launch. Picks the MSI matching the build's architecture (x64 vs ARM64)
Static assets and the embedded demo site
Method
Path
What it does
Notes (auth/CSRF/platform quirks)
GET
/static/{*path}
Serve CSS/JS/images from the embedded static archive
No auth (assets only). Runtime-generated thumbnails are deliberately excluded — those go through the authed /api/thumbnail/{id}
GET
/demo-site/{*path}
Serve the embedded landing page + screenshots for the /demo iframe
No auth. Same-origin and fully offline — the demo never phones home to GitHub Pages
Stability
This is SearchBox's internal API: it exists to serve the bundled UI, and it changes whenever the UI does. There are no compatibility promises between versions — paths, request shapes, and response fields can all change in any release without notice. If you build against it, pin the version you tested and re-verify after every update.