| 1 | Slack: message search, people lookup, connection check | POST https://slack.com/api/search.messages POST https://slack.com/api/users.list POST https://slack.com/api/auth.test (connect check) | A search runs while a Slack user token is configured and Slack is neither disabled nor paused; users.list only during identity resolution; auth.test when you connect or open Sources to identify an existing connection (the label is cached). Each connected identity is searched independently. | search.messages: a query of ≤6 keywords, ≤3 person-name variants, from:, after:/before: day bounds, has:link; ≤4 calls per search, ≤2 pages each users.list: ≤3 pages of 200 during identity resolution (no query text) auth.test: the token only | Opt-in Opt-in (connect a token) |
| 2 | Google Drive: file search, Google Docs excerpt, token refresh | GET https://www.googleapis.com/drive/v3/files GET https://www.googleapis.com/drive/v3/files/{id}/export (text/plain) POST https://oauth2.googleapis.com/token (access-token refresh) | A search runs while the keychain holds Drive OAuth tokens and a Google client is configured, and Drive is neither disabled nor paused; identity resolution reads the owners of the 100 most recently modified files. | files.list q= built from ≤6 keywords / person variants / media hints / owner email / time bounds files.export text/plain of ≤5 Google Docs under 1 MiB per search (20 KiB read) Token refresh: the refresh token + client credentials | Opt-in Opt-in (OAuth consent) |
| 3 | Gmail: message search, metadata/body fetch, token refresh | GET https://gmail.googleapis.com/gmail/v1/users/me/messages GET https://gmail.googleapis.com/gmail/v1/users/me/messages/{id} POST https://oauth2.googleapis.com/token (access-token refresh) | A search runs while the keychain holds Gmail OAuth tokens and a Google client is configured, and Gmail is neither disabled nor paused; identity resolution runs one list (10 results) + ≤6 metadata gets. | Short content phrases may first be sent as a subject:"phrase" query to find precise title matches messages.list q= with ≤6 keywords, a from:/to: group, filename:/domain group, after:/before:; ≤25 ids messages.get metadata for ≤12 candidates; full text/plain body (20 KiB cap) for the first 5 only Token refresh: the refresh token + client credentials | Opt-in Opt-in (OAuth consent) |
| 4 | Google OAuth consent (Drive, Gmail): code exchange, client check and account label | https://accounts.google.com/o/oauth2/v2/auth — opened in the SYSTEM BROWSER, not by this process redirect to a loopback listener http://127.0.0.1:<port>/callback (this process listens, never dials out) POST https://oauth2.googleapis.com/token (authorization-code exchange; client check on save) GET …/drive/v3/about or …/gmail/v1/users/me/profile (account email for the confirmation line) | Connect Drive or Gmail from setup or Settings → Sources → Sign in with Google. The listener closes when the flow ends. The client check runs once when a Google client id/secret is saved in Sources. Opening Sources resolves missing account labels with read-only profile requests; labels are cached in the keychain. | Consent URL: client_id, scopes drive.readonly and gmail.readonly, random state, PKCE S256 challenge Code exchange: the one-time code + PKCE verifier + client id/secret. Tokens go to the OS keychain, never to disk or logs Client check: the client id/secret + a deliberately invalid code; nothing is stored unless the client is real | User action User action |
| 5 | Notion (in development, not a live source yet): page search, people lookup, connection check | POST https://api.notion.com/v1/search GET https://api.notion.com/v1/users GET https://api.notion.com/v1/users/me (connect check) | A search runs while a Notion integration token is configured and Notion is neither disabled nor paused; /v1/users only during identity resolution; /v1/users/me on connect or when opening Sources (the label is cached). | search.query for ≤3 sub-searches (≤4 keywords / person / media hint), filter: page. No page content is ever fetched: titles and metadata only /v1/users: ≤3 pages of 100 (no query text) | Opt-in Opt-in (token) |
| 6 | GitHub: code / issue / repo search, restricted to repositories owned by the connected account | GET https://api.github.com/search/code, /search/issues, /search/repositories GET https://api.github.com/user/repos (owner affiliation) GET https://api.github.com/user (connect check and account scope) GET https://api.github.com/user/installations (permission check) | A search runs while a GitHub token is configured and GitHub is neither disabled nor paused (≤3 requests in flight); /user identifies each connected account before search (labels are cached). A failure never permits a public search. Some searches can still time out; a timeout returns no GitHub results for that search. | q= of ≤2 content words (plus a fallback); account/repository scope kept on each request; per_page ≤50 Own-repo listing: ≤2 pages of 100 | Opt-in Opt-in (token) |
| 7 | Download the local brain (Qwen3 1.7B default / Qwen3 4B Instruct 2507 upgrade, Q4_K_M GGUF) | GET https://huggingface.co/unsloth/Qwen3-1.7B-GGUF/resolve/main/Qwen3-1.7B-Q4_K_M.gguf GET https://huggingface.co/unsloth/Qwen3-4B-Instruct-2507-GGUF/resolve/main/Qwen3-4B-Instruct-2507-Q4_K_M.gguf | The user clicks Download (Settings → Models, or onboarding step 2). Never automatic. An interrupted download resumes with an HTTP Range request. | GET only, plus Range: bytes=<offset>- on resume. sha256 and size pinned; a mismatch is discarded. Hugging Face redirects to its LFS CDN inside the same call. | User action User action |
| 8 | Download the embedding model (multilingual-e5-small, int8 ONNX, ~135 MB) | GET https://huggingface.co/Xenova/multilingual-e5-small/resolve/main/onnx/model_quantized.onnx GET …/tokenizer.json, …/config.json, …/tokenizer_config.json | AUTOMATICALLY, once, right after onboarding completes: the semantic index of the ticked folders starts in the background and needs the model. Onboarding states this (size, checksum, disk budget) before the user finishes. Also on the first index build if the model is missing. Never re-downloaded once verified. | GET only. Four files, each sha256-pinned; the model is used only after every hash matches. | Automatic after onboarding Automatic after onboarding (disclosed there); otherwise user action |
| 9 | Download a voice model (whisper.cpp ggml base / small) | GET https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-base.bin GET https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-small.bin | The user clicks Download voice model (Settings → Hotkey & voice / Models, or onboarding step 2). Never automatic. Resumable. | GET only, plus Range: on resume. sha256 and size pinned. | User action User action |
| 10 | Desktop app update check and signed download (Tauri updater) | GET https://askfry.com/updates/latest.json (no newer version = no update) GET the signed package link on askfry.com named in latest.json, only after the install click | The user clicks Update at the bottom of the sidebar, or Check now in Settings › Updates & About. Never on launch, never on a timer. Installing is a separate, second click; the package is minisign-verified against the public key shipped in the app, whatever host serves it. Refused while Paranoid mode is on. Made by the desktop shell, not the engine: askfry.com is the update host, not an engine host. | GET only. The updater sends its User-Agent and the current version/target; nothing about the user or their data. Each check/install is written to the audit log. | User action User action (Update button) |
| 11 | GitHub desktop device authorization and token refresh | System browser: https://github.com/login/device POST https://github.com/login/device/code POST https://github.com/login/oauth/access_token | The user clicks Connect GitHub; refresh only before an enabled GitHub search when its token expires. | Public client id, device code or refresh token; no search query or source content. | Opt-in Opt-in (Connect) |
| 12 | Slack desktop PKCE authorization and token refresh | System browser: https://slack.com/oauth/v2/authorize (public client id, user scopes, random state, PKCE challenge) Local callback: http://localhost:47839/callback POST https://slack.com/api/oauth.v2.access | The user clicks Connect Slack; refresh only before an enabled Slack search when its token expires. | Public client id, authorization code and PKCE verifier or refresh token. No client secret, query or source content. | Opt-in Opt-in (Connect) |
| 13 | Anonymous usage stats (since 1.0.65; on by default, opt-out) | POST https://eu.i.posthog.com/batch/ (PostHog, EU region) | Only after the first-launch consent screen, and only while stats are on. Batches of up to 50 allowlisted events, at most once a minute. Never sent while stats are off or Paranoid mode is on. | Allowlisted events and fields only: counts, ranges, source types, error codes, app version, OS, chip type A random device ID, not linked to email, accounts or hardware. Client IP discarded, GeoIP off, no person profiles Never search text, file names, paths, contents, emails, messages, account names or tokens | Opt-out Opt-out (first-launch screen, Settings › Privacy › Usage stats) |