Skip to content

Latest commit

 

History

History
192 lines (146 loc) · 17.9 KB

File metadata and controls

192 lines (146 loc) · 17.9 KB

HTTP API reference

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}
GET /api/folder/index/status Poll a job Auth. Query ?job_id=...; 404 for unknown ids. Reports totals, processed, indexed, failed, errors
GET /api/folders List indexed folder paths Auth
POST /api/folders/sync Re-index every registered 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

Meilisearch control and client config

Method Path What it does Notes (auth/CSRF/platform quirks)
GET /api/meilisearch/status Sidecar status: running, host, port, auto-start, version, stats Auth
POST /api/meilisearch/start Start the Meilisearch sidecar process Auth + CSRF. The sidecar also auto-starts at boot unless the auto_start setting is off
POST /api/meilisearch/stop Stop the sidecar Auth + CSRF
GET /api/meilisearch/config Read the saved config Auth. Returns the "safe" view — reports master_key_set: true/false but never the key itself
POST /api/meilisearch/config Patch the config Auth + CSRF. Accepts binary path, host, port, auto-start, master key, data path, plus the AI (Ollama) settings
POST /api/meilisearch/clear Delete every document from the index Auth + CSRF

Settings and bookmarks

Method Path What it does Notes (auth/CSRF/platform quirks)
GET /api/settings/search-history Read recent searches (max 5 kept) Auth
POST /api/settings/search-history Add a search to history Auth + CSRF. JSON {query}; deduplicates case-insensitively
DELETE /api/settings/search-history Clear history Auth + CSRF
GET / PUT /api/settings/ai-enhancement Read / set the AI history-enhancement toggle Auth; PUT adds CSRF. JSON {enabled}
GET / PUT /api/settings/last-sync-time Read / set the last folder-sync timestamp Auth; PUT adds CSRF. JSON {timestamp}
GET / PUT /api/settings/bookmarks-enabled Read / set the bookmarks toggle Auth; PUT adds CSRF. JSON {enabled}
POST /api/settings/factory-reset Wipe everything: all tables, vault files, thumbnails, and the search index Auth + CSRF. Requires JSON {"confirm": true} or it refuses with 400
POST /api/settings/generate-recovery-key Generate and return a new recovery key Auth + CSRF. Requires a configured, unlocked vault. The key is returned once — in plaintext — and never again
GET /api/bookmarks List bookmarks Auth. Five slots
POST /api/bookmarks Create or replace a bookmark Auth + CSRF. JSON {slot (1–5), doc_id, title, file_type, file_path?}; re-bookmarking a doc moves it between slots
DELETE /api/bookmarks/{slot} Remove the bookmark in a slot Auth + CSRF
GET /api/bookmarks/document/{doc_id} Is this document bookmarked, and where Auth

Vault

Method Path What it does Notes (auth/CSRF/platform quirks)
GET /api/vault/status Report encryption_enabled, files_encrypted, locked 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.


← Back to the manual