Data flow¶
Where document content goes, and where it does not.
The full path¶
Browser ──(1)── nginx ──(2)── backend ──(3)── LLM endpoint
│
└──(4)── OCR endpoint (scanned PDFs only)
│
└──(5)── in-memory cache (≤15 min)
| # | Hop | Content | Notes |
|---|---|---|---|
| 1 | Browser → frontend | The document (multipart or JSON) | Over your TLS-terminating proxy. |
| 2 | nginx → backend | The same request | Internal to the compose network; the backend publishes no port. |
| 3 | Backend → LLM | Document text, in chunks; then the anonymized output for the re-check | Only when llm is enabled. OPENAI_API_BASE. |
| 4 | Backend → OCR | Rendered page images | Only for scanned PDFs, only when an OCR engine is configured. |
| 5 | Backend → memory | Extracted text + detected spans, keyed by request id | TTL 15 min, max 100 entries, process memory only. |
| ← | Backend → browser | Source text, anonymized text, entities, warnings | Cache-Control: no-store. |
Content flows nowhere else. There is no database, no object storage, no volume, no telemetry endpoint, no CDN, and no analytics.
Per endpoint¶
| Endpoint | Receives | Returns | Leaves the backend? |
|---|---|---|---|
POST /api/v1/anonymize |
Document or pasted text; or a request id + overrides | Source text, anonymized text, entities, validation, timings | To the LLM (detection + re-check) and, for scans, to OCR |
POST /api/v1/anonymize/stream |
Same | Same, as NDJSON with progress events | Same |
POST /api/v1/export/pdf |
The original PDF (re-sent) + overrides | The redacted PDF | Only on a cache miss (re-runs extraction/detection) |
POST /api/v1/export/pdf/pages |
The original PDF (re-sent) | Page PNGs + image boxes | No |
GET /api/v1/status |
— | Detector states, OCR engine, endpoint hosts + locality, limits | No |
GET /health/live, /health/ready |
— | Status only | Readiness may probe configured endpoints |
/api/v1/status returns hosts, never full URLs, keys, or filesystem paths. It
is what the UI uses to warn that content will leave the machine, so it has to
stay safe to expose.
In the browser¶
| Stored | Where | Why |
|---|---|---|
| Document text, results, corrections | Pinia store — memory only | Cleared on reload. Never localStorage/sessionStorage. |
| Object URLs for previews (original PDF, redacted PDF, rendered pages) | Memory, revoked on reset | Needed to display a PDF. |
darkMode, expertMode, keepFilenames |
localStorage |
UI preferences only. |
That split is a hard rule in the codebase: nothing derived from a document is ever persisted client-side.
What is logged¶
Per request: request id, source type, character count, entity count, validation status, timings; for exports also the byte size and the number of redaction areas.
Never logged: document text, extracted text, anonymized text, entity text,
prompts, filenames. A structured logger drops these field names before the line
is written and records rejected_fields=… instead. The escape hatch
(APP_ALLOW_INSECURE_CONTENT_LOGGING) prints a loud warning at startup and is
refused in production mode.
Retention at each stop¶
| Stop | Retention |
|---|---|
| Browser memory | Until reload or Neues Dokument |
Browser localStorage |
UI preferences only, indefinitely |
| Backend memory (request) | The request |
| Backend cache | ≤15 minutes, or until eviction (100 entries) or restart |
| Backend disk | Nothing. Read-only filesystem, tmpfs for /tmp, no volumes |
| Backend logs | Metadata only, per your log retention |
| LLM / OCR endpoint | Whatever that service does — see below |
The one you have to answer yourself¶
The app's retention story ends at hop 3 and 4. Whether your model endpoint logs prompts, caches them, or trains on them is a property of that service, not of this one.
Before processing real data, establish for every configured endpoint: who operates it, whether it logs request bodies, what its retention is, and whether it is inside your organizational boundary. A hosted API is a data transfer to a processor and needs the corresponding agreement. See the DPIA template.