Last updated 21 August 2026
Privacy
nodes is one person's app, in closed beta. This page says exactly what leaves your device, what does not, who else touches it, and how to stop the parts that are optional. It is written to be read, not to be survived — if something here is unclear, that is a bug in the page.
The short version
- Your cards go to the cloud when you sync. That is what sync is. They sit in a database only your account can read.
- They do not go anywhere else — not to diagnostics, not to any analytics product, not to a model, unless you use the AI features and supply your own key.
- During the beta the app reports crashes and usage counts — which features you opened, how many cards were in a session — tied to your account. One switch in Settings turns it off.
- Nothing here is sold, shared with advertisers, or used to train anything. There is no ad network and no third-party tracker on this site or in the app.
What stays on your device
On desktop and mobile, your whole collection lives in a SQLite file on the machine, one per signed-in account. Studying, editing, importing, the knowledge graph and the scheduler all run locally and work offline. A few things never leave at all:
- The original media files from an
.apkgimport, and the.apkgyou exported. - The 112 MB embedding model, and the act of embedding a card: the text is turned into a vector on your own CPU, never by a service.
- Your diagnostics preference, your Gemini API key if you set one, and the interface settings kept in the OS preference store.
What goes to the cloud when you sync
Sync is manual on desktop and mobile — it happens when you press the button. The web client at /app/ is different: it has no local database, so it reads and writes the cloud copy directly, and reviews you do there are saved as you go.
What is uploaded, all of it under your account:
- Decks and cards — the front and back of every card as HTML, its tags, and the original Anki note it came from if it was imported. This is your card content. It goes up because it has to for the same collection to appear on another device.
- Your review history — every answer, its rating and timestamp, plus exam attempts.
- Scheduling and settings — per-deck FSRS parameters, learning steps, limits, easy-day configuration.
- Card embeddings — the vectors computed on your device. A vector is derived from card text and is not readable as text, but treat it as card-derived data, because it is.
- Deletion records — when you delete a card or deck, a small record of the deletion is synced so the deletion reaches your other devices, then the row is removed on the server.
- Card images, if you have any. A downscaled, re-encoded copy is uploaded for display on the web client. The private part is the map from an image's name to where its bytes are; the bytes themselves sit in a shared store under a path derived from a SHA-256 of the file, which is what lets the same image be stored once for everyone who has it.
That last one deserves the detail: the image store is readable by anyone who knows the 256-bit path, and the path carries no filename, no deck name and no account id. Only your own account can read the map that produces those paths. It is not a secret vault — it is a store where finding your image requires already knowing its exact contents.
Diagnostics during the beta
The app had no way to see a crash. A tester whose screen went red produced nothing at all, which is a poor way to run a beta, so the app now reports two kinds of thing. Both are covered by a single switch, which starts on, and which the app offers to turn off the first time you open it.
Usage counts
Seven event names, and no more — a name the app has not declared is rejected by the database. Each event carries numbers, flags and fixed labels; never text you typed:
app_opened— app version and platform.study_session_completed— which pacing mode, how many cards, how long, whether you finished.import_finished/import_failed— the format, how many cards, how long, and for a failure, which stage and the class name of the error.generation_run— how far a card-generation run got and how many claims and cards were proposed, kept and accepted, as counts.sync_finished— whether it worked, how many rows moved, how long.feature_used— one label from a fixed list: the graph view, card chat, close-up, undo, backlog burn-down, more angles, the shortcut sheet.
Events are queued on your device and uploaded at launch and after a sync, so nothing is sent while you are offline. The queue holds 500 at most and drops anything older than 30 days.
Crash reports
When something throws, the app can send the error message, the stack trace, a short fixed label for where it happened, the app version and the platform. IP address, username and device name are explicitly switched off, and no performance tracing is collected.
"Can" is doing real work in that sentence. Crash reporting only installs itself in a build made with a reporting key, and no build script in the app's public repository supplies one — including the one that produces the beta downloads. So unless you were handed a specially-built copy, the crash reporter is not merely switched off on your machine: it was never installed, and no crash report has ever left it. Usage counts are separate and are not affected by this.
Two things the app's own wording gets slightly wrong
"Anonymous" is not quite right. Reports and events carry your account id, so that "this tester keeps hitting the same crash" is answerable. They carry no name, no email and no address — but they are linked to your account, and the honest word for that is pseudonymous, not anonymous. The wording inside the app will be corrected to match this page.
"Cards never leave your device" is a promise about intent, plus a ceiling. No event or crash report is designed to carry card text, and the fixed event list above is the whole of what is sent. But a parser that chokes on a malformed card can quote part of it in its own error message. So every free-text field in a crash report is cut at 240 characters before it is sent. That turns "a whole note ends up in an error report" into "a fragment might" — which is a real difference, and less than an absolute promise. You should read it as: card content is never sent deliberately, and cannot be sent wholesale.
Turning it off
Settings → Diagnostics → the first switch. Usage events stop being recorded the moment you flip it, and anything already queued stays on your device unsent. Crash reporting stops the next time you open the app, because it installs itself before the first screen is drawn.
Diagnostics are desktop and mobile only. The web client at /app/ collects neither crashes nor usage events.
The AI features, and your own key
Card chat, generating cards from text, and "more angles" on an existing card send text to Google's Gemini API. That text is what the feature is about: the passage you pasted, or the card you asked about. These features do nothing at all until you enter your own Gemini API key in Settings, and the key is stored only on your device. If you never enter one, nothing is ever sent to Google by the app.
What Google does with that text is governed by Google's terms for the key you are using, not by this page.
The invite form on the front page
"Request an invite" stores the email address you type, and nothing else — no name, no cookie, no analytics on the submission. It is used once, to send you an invite. The key the page uses can add a row to that list and cannot read it back.
Who processes it, and where
- Supabase — the account, the synced collection, the usage events and the invite list. Hosted in the EU (Ireland). Every table is protected by row-level security, so a signed-in client can only read its own rows.
- Sentry — crash reports only, and only from a build carrying a reporting key, which as of the date above no published build does. The account it would report to is on Sentry's EU service.
- Google (Gemini API) — only the text you pass to an AI feature, only if you have entered a key.
- Cloudflare Pages — serves this site and the web client. Ordinary web-server logs, no analytics script.
- Google Fonts — this page and the front page load two typefaces from Google's font CDN, which means your browser makes a request there and Google sees the IP address it came from. That is the only third-party request either page makes.
There is no advertising network, no tracking pixel, no session recorder, and no data broker anywhere in that list.
Deleting things
A card or deck — delete it in the app. On the next sync it is removed from the cloud as well, not merely hidden.
Your account and everything in it — there is no self-service delete button yet; it is on the list. Until it exists, ask and it will be done by hand: the account and every row keyed to it in the database, which is your collection, your history and your usage events. Crash reports live in a second system and are deleted separately, in the same request. Write to privacy@nodes.cards.
One thing that deletion does not reach, because it cannot: the deduplicated image bytes described above. The map from your card's image to its bytes is yours and goes with the account. The bytes stay in the shared store, now unreferenced — they sit under a path that is a hash of the file itself, so they are unreachable without already having the file, and removing them would break every other account that happens to hold the same image.
Deleting the app without deleting the account leaves the cloud copy in place — which is the point of a cloud copy, and also why the request above exists.
Changes
This is a beta and this page will change with it. It lives in the app's public repository, so every edit to it is a dated, readable commit rather than a silent replacement. The date at the top is the last one.