Glossary & Data Model
Terms
Access Key — A revocable credential (format xak_<prefix>_<secret>) a clinic generates to let the desktop app authenticate to Dentolize's REST/GraphQL APIs without a user login. Stored server-side only as an HMAC-SHA256 hash, never in plaintext. Scoped to specific capabilities. Model: AccessKey.
Scope — A specific capability an Access Key is allowed to use. Three exist today: branches.read (list branches and their rooms), files.create (upload files), xray.acquire (drive the acquisition workflow — connect, heartbeat, transitions, upload). Stored as a plain string array (AccessKey.scopes) validated against a fixed server-side allow-list.
Lease token — A short-lived (90-second, heartbeat-renewable) credential minted from an Access Key when a desktop app calls the connect endpoint. Bound to one (company, branch, room, desktop instance) at a time. Used for every subsequent REST/WebSocket call instead of re-presenting the Access Key itself, and immediately invalidated if the underlying Access Key is revoked. Tracked in Redis, not the database.
Room — An arbitrary string identifying a physical location or scanner within a branch (e.g. an operatory). Not a formal Dentolize entity with its own table — it's a string configured on the desktop app that must match a branch's configured room list. A lease guarantees only one desktop instance can hold a given room at a time.
Desktop app / Dentolize Bridge — The companion Electron + native-TWAIN Windows application (repository xolize-core, PR #6) that clinics install on the workstation connected to their scanner. Holds an Access Key, receives capture requests over a GraphQL subscription, drives the real scanner hardware, and uploads the result. A real, working application as of this PR — not external/future scope.
Acquisition (X-Ray Acquisition Request) — One request/response cycle: a staff member asks for a specific X-ray slot to be filled by a connected desktop app. Model: XrayAcquisitionRequest.
Slot / slotOrder — Which position in a multi-image X-ray layout (e.g. a full-mouth series) the captured image should be saved into. Validated against a per-chart-type allow-list (VALID_SLOTS) so a slot number invalid for a given layout is rejected before a request is even created. Corresponds to the existing File.order field used elsewhere in the X-ray gallery.
Upload intent — A single, time-boxed (10-minute) attempt to upload one image for one acquisition, tracked so the server can reserve storage quota up front, hand out a presigned upload URL, and independently re-verify the uploaded object afterward rather than trusting the desktop app's own description of what it sent. Model: XrayUploadIntent.
Feature flag (FEATURE_XRAY_ACQUISITION) — Gates the entire feature to companies flagged isBeta, checked both client-side (hides the Acquire button) and server-side (rejects the mutation and desktop connect/lease calls). Has an independent kill switch separate from the beta-targeting rule.
Data model reference
Company
├─ AccessKey[] (one company can issue many keys)
├─ XrayAcquisitionRequest[]
└─ XrayUploadIntent[]
AccessKey
├─ keyHash String @unique (HMAC-SHA256 of the raw key; raw key is never stored)
├─ keyPrefix String (first 14 chars of the raw key, shown in the UI)
├─ label String
├─ scopes String[] (branches.read | files.create | xray.acquire)
├─ companyId String → Company
├─ createdById String → User
├─ lastUsedAt DateTime? (touched on every successful auth, race-safe)
├─ revokedAt DateTime? (set on revoke; null = active)
└─ XrayAcquisitionRequest[] (via respondedByKeyId — which key fulfilled a request)
XrayAcquisitionRequest
├─ status XrayAcquisitionStatus (WAITING | ACCEPTED | REJECTED | IN_PROGRESS |
│ PREVIEW | CAPTURED | WAITING_UPLOAD | UPLOADING |
│ COMPLETED | FAILED | TIMEOUT | CANCELLED)
├─ statusUpdatedAt, stateDeadlineAt (drives the timeout cron — see Real-Time Status page)
├─ requestIdempotencyKey String (unique per requester, prevents duplicate web requests)
├─ xrayType, slotOrder
├─ patientName, patientDOB, patientPhoto (denormalized snapshot at request time)
├─ patient → Patient
├─ branch → Branch
├─ company → Company
├─ xray → Xray (the target X-ray record the file attaches to)
├─ requestedBy → User (the staff member who made the request)
├─ respondedByKey → AccessKey? (which key/workstation accepted it, if any)
├─ respondedByInstanceId String? (which specific desktop app instance claimed it)
├─ auditLogs → XrayAcquisitionLog[]
├─ uploadIntents → XrayUploadIntent[]
└─ file → File? (set once completed)
XrayAcquisitionLog
├─ event String (REQUESTED, ACCEPTED, CAPTURED, UPLOADING, COMPLETED, CANCELLED, TIMEOUT, ...)
├─ details Json?
├─ userId String? (set when a staff action caused the event)
├─ accessKeyId String? (set when the desktop app caused the event)
├─ idempotencyKey String? (unique per acquisition request — enables safe retries)
└─ acquisitionRequest → XrayAcquisitionRequest
XrayUploadIntent
├─ status XrayUploadIntentStatus (PENDING | UPLOADED | COMPLETED | EXPIRED | CANCELLED)
├─ objectKey String @unique (the S3 key the presigned form targets)
├─ expectedSize, expectedContentType, checksumSha256 (declared up front, verified after upload)
├─ reservedBytes Int (storage quota reserved against Company.sizeLeft)
├─ clientRequestId String (idempotency key, unique per acquisition request)
├─ expiresAt DateTime (10 minutes from creation)
├─ objectDeletedAt DateTime? (set once the cron confirms cleanup)
└─ acquisitionRequest → XrayAcquisitionRequest
See packages/prisma/schema.prisma:279 (AccessKey), :7239-:7360 (XrayAcquisitionStatus, XrayAcquisitionRequest, XrayAcquisitionLog, XrayUploadIntentStatus, XrayUploadIntent) for the authoritative definitions, and packages/prisma/migrations/20260816000000_add_access_keys/ / packages/prisma/migrations/20260816001000_add_xray_acquisition/ for how they were introduced.