Data flows
The architecture in one sentence
MedJot is local-first: clinical data is created, stored and used on the device, and the server exists for authentication, licensing, support and optional AI — not for clinical storage.
What never leaves the device
Held in browser local storage and an on-device IndexedDB database:
- Patient findings and examination results
- Observation sets, vital signs and NEWS2 scores
- Free-text notes in every section
- The generated report and any manual edits
- The clinician notepad, including sketches
- Scores, care bundles, frailty assessments
- Ingestions and patient weight
Autosave writes to the device only. There is no upload, no server-side backup and no sync of report content between devices. We hold no copy and have no mechanism to retrieve one.
What crosses the network, and when
| Data | When | Destination |
|---|---|---|
| Authentication and refresh tokens | Sign-in, and on session refresh | Google Firebase |
| Subscription and licensing status | On load and periodically | MedJot API |
| Usage counters — a count and a date | On starting a report | MedJot API |
| Account profile and consent answers | When set or changed | MedJot API |
| Redacted clinical summary | Only when a clinician runs an AI feature | MedJot API → Google Gemini |
| Report content, in memory for ≤10 min | Only on an explicit device transfer | MedJot API |
| Saved templates | When a user saves one | MedJot API |
| Support enquiries | When sent | MedJot API |
| Product analytics events | Only after explicit opt-in | MedJot API + Google Analytics |
| Payment details | At checkout | Stripe directly — never through us |
The AI path in detail
This is the main route by which clinical content leaves a device, so it warrants precision:
- Triggered only by a clinician pressing the button on an AI feature. Nothing happens in the background.
- The request is assembled from structured application state, not from the report text on screen — meaning the content sent is an enumerable set of fields rather than arbitrary screen content.
- Redaction runs on both the client and the server before transmission: emails, UK phone numbers, NHS numbers, postcodes, dates of birth, and names introduced by a title or a relationship word are stripped. The patient is referred to only as "Pt".
- Neither the request text nor the response is stored. We log the feature name, token counts and cost — for billing and abuse prevention — and nothing else.
How the redaction is implemented
Worth stating precisely, because "we redact PII" is a claim reviewers are right to press on:
| Property | Implementation |
|---|---|
| Two independent passes | In the browser before the request is made, and again on the server before the prompt is assembled. The client pass means identifiable text does not cross the network; the server pass exists because a server must not trust a client's assurance. |
| No drift between them | The two copies of the ruleset are run against a shared corpus by an automated test that fails the build if their output differs at all. |
| Whole-payload sweep | The request object is walked recursively and every string in it is redacted, rather than a maintained list of fields being cleaned. A field added later is covered by default. |
| Identifiers removed unconditionally | NHS number, phone, email, postcode and date-of-birth stripping applies on every AI route, in every mode, including inside observation blocks. |
| Name handling is tuned per route | Narrative routes additionally collapse any capitalised word pair to initials. Diagnostic routes do not, because collapsing clinical terminology degrades output and pushes users toward the least-protective feature. The identifier rules are unaffected. |
| Output redaction | The safeguarding tool redacts the model's response as well as the request, and its prompts forbid producing a full name. |
| Bounded, closed-vocabulary inputs | Free-text lengths and conversation turns are capped; category fields accept only values from a fixed list, and drop anything else rather than sanitising it. |
| Tested against regression and ReDoS | Tests assert identifier removal, zero false positives on a standard set of vitals, the specific parsing traps the rules exist for, and linear-time behaviour on adversarial input. |
The full rule-by-rule account, with worked examples, is in What gets sent, and what doesn't.
Redaction is a best-effort heuristic, not a guarantee. It is pattern matching rather than named-entity recognition, and cannot reliably catch a name typed into free text with no surrounding cue. It is a defence-in-depth layer over the actual control, which is the product's standing instruction to clinicians to keep patient-identifiable information out of MedJot entirely — see Privacy and governance.
The device transfer path
Report content is sent to our server, held in memory only — never written to disk or a database — under a one-time code for a maximum of 10 minutes, then deleted automatically. Users can delete it sooner. See Device transfer.
What we hold about a user
Account record: name, email, phone, month and year of birth, country and county, role and sector, account creation and last sign-in dates, Premium status and Stripe customer reference, trial usage, daily report count and date, saved templates, consent answers, and a small set of hashed anti-fraud signals.
Not held: day of birth, employer, trust, station, professional registration number, address, town or postcode — none of which is collected. And no clinical content of any kind.
Anti-fraud signals
A deliberately short list: a random browser-generated device identifier stored as a one-way hash; a salted one-way hash of the IP address — the address itself is not stored; a normalised form of the email address; and flags for automatically-generated-looking addresses or names.
No browser fingerprinting, no tracking pixels, no third-party trackers. Retained 12 months from last sign-in.
Retention
| Data | Retention |
|---|---|
| Clinical data on the device | Until the user clears it. Not ours. |
| Device transfer payloads | 10 minutes, in memory, then deleted |
| Account, billing and template data | Life of the account, or until deletion is requested |
| AI usage logs — feature, tokens, cost; never content | Currently indefinite, for billing and abuse prevention |
| Detailed analytics events (opt-in only) | Up to 90 days, or until opt-out |
| Anonymous daily analytics totals | Indefinite — no identifier, not linked to anyone |
| Anti-fraud device and IP hashes | 12 months from last sign-in |
| Support enquiries | As long as needed to administer them |
Analytics, if you are assessing it
Off unless the user opts in. Events come from a closed catalogue: every parameter is an enum, a boolean or a bounded integer. There is no free-text field anywhere in the system — not filtered, but structurally absent. The server revalidates every event against that catalogue and discards anything unrecognised.
Attribution — role, sector, region, entitlement, organisation, platform — is added server-side from the account record; the browser never sends it, so it cannot be spoofed.
No account identifier is ever sent to Google. Advertising features, Google Signals and ad personalisation are all disabled, and IP is anonymised. If the user has not opted in, the Google analytics code is never loaded.
Opting out purges that user's detailed event log.
Data residency
Our infrastructure is UK-hosted. Google (Firebase, Gemini) and Stripe may process data outside the UK and EEA as part of their own global infrastructure, under their respective data protection commitments. See Subprocessors.
The consequence for inspection policy
Because there is no clinical content in transit, decrypting MedJot yields no clinical visibility — while placing authentication and refresh tokens in front of the inspecting appliance. See TLS inspection.