Cardinal SDK

Data Model

End-to-end encrypted medical data at scale

Agenda

  1. Shared Structures — The building blocks every entity shares
  2. Medical Record Model — Patient → Contact → Service
  3. Supporting Entities — HealthElement, HealthcareParty, Device
  4. End-to-End Encryption — How data stays private by design

Part 1

Shared Structures

The common DNA across all entities

id — Universal Identifier

Every entity in Cardinal has a unique id field

  • Format: UUID v4 (e.g., 550e8400-e29b-41d4-a716-446655440000)
  • Generated client-side before creation
    • Can also be generated by the server if needed
  • Exception: Code entities use type|code|version format
    • e.g., SNOMED-CT|200773006|1

Why UUID v4? No server coordination needed — clients generate unique IDs independently, enabling offline-first architectures.

rev — Optimistic Concurrency

Revision tracking for conflict detection

  • Format: <number>-<hash> → e.g., 1-abc123, 2-def456
  • Managed entirely by the backend — never set manually
  • Increments on every modification
  • Used for optimistic locking:
Client A reads entity (rev: 3-xyz)
Client B reads entity (rev: 3-xyz)
Client A updates → succeeds (rev: 4-abc)
Client B updates → CONFLICT (stale rev)

⚠️ Only present on root entities — nested entities (Service, Content, SubContact) don't have revisions

Connect Cardinal entities to external systems

Field Purpose Example
system Namespace URI http://hospital.org/mrn
value Unique within system MRN-12345
type CodeStub classification { type: "MR" }
assigner Issuing organization "St. Mary's Hospital"
  • Follows FHIR Identifier specification
  • Searchable via system + value filters
  • Non-encryptable — always stored in clear

Tags & Codes — Organizing Medical Data

Two collections of Code references on every entity

Tags

  • Strictly non-sensitive context
  • Always unencrypted
  • Used for workflow, status, routing, labeling
  • Example: LOINC|97062-4|2.38

Codes

  • May contain sensitive data
  • Unencrypted by default (searchable)
  • Used for medical classification
  • Example: SNOMED-CT|200773006|1

Both reference CodeStub objects — lightweight pointers to full Code entities stored as root-level objects.

Code & CodeStub — The Codification System

Code (Root Entity)

Full codification definition

  • ID: type|code|version
  • label: multilingual names
  • searchTerms: related terms
  • region: ISO country codes

Examples: SNOMED-CT, LOINC, ICD-10, ATC

CodeStub (Nested)

Lightweight reference

  • Matches Code's id, type, code, version
  • Embedded in tags and codes fields
  • No label or search terms
  • Minimal storage footprint
Code:     { id: "SNOMED-CT|200773006|1", label: { en: "Allergic rhinitis" } }
CodeStub: { id: "SNOMED-CT|200773006|1", type: "SNOMED-CT", code: "200773006" }

Common Metadata Fields

Every entity also carries audit and lifecycle metadata

Field Description Managed by
created Creation timestamp Backend (auto)
modified Last modification timestamp Client (updatable)
author User ID of creator Backend (auto)
responsible Data Owner ID of creator Backend (auto)
deletionDate Soft-delete timestamp Set on deletion

Soft deletion: Entities are never physically removed — deletionDate marks them as deleted while preserving the audit trail.

Part 2

The Medical Record

Patient → Contact → Service

The Core Hierarchy

┌─────────────────────────────────────────────────────────┐
│  PATIENT                                                │
│  The person receiving care                              │
│                                                         │
│  ┌───────────────────────────────────────────────────┐  │
│  │  CONTACT                                          │  │
│  │  One encounter / event                            │  │
│  │                                                   │  │
│  │  ┌─────────────┐ ┌─────────────┐ ┌─────────────┐  │  │
│  │  │  SERVICE    │ │  SERVICE    │ │   SERVICE   │  │  │
│  │  │  Blood      │ │  Diagnosis  │ │   Prescr.   │  │  │
│  │  │  pressure   │ │             │ │             │  │  │
│  │  │  ┌───────┐  │ │  ┌───────┐  │ │  ┌───────┐  │  │  │
│  │  │  │CONTENT│  │ │  │CONTENT│  │ │  │CONTENT│  │  │  │
│  │  │  └───────┘  │ │  └───────┘  │ │  └───────┘  │  │  │
│  │  └─────────────┘ └─────────────┘ └─────────────┘  │  │
│  └───────────────────────────────────────────────────┘  │
│                                                         │
│  ┌───────────────────────────────────────────────────┐  │
│  │  CONTACT  (another visit)                         │  │
│  │  ...                                              │  │
│  └───────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────┘

Patient

The subject of medical treatment — the central entity

Administrative Data

  • firstName, lastName
  • dateOfBirth (FuzzyDate)
  • gender, birthSex
  • addresses, ssin
  • nationality, languages
  • picture (few KB max)

Key Properties

  • Encryptable root entity
  • Can be linked to a User for login
  • Becomes a Data Owner when linked
  • One-to-many with Contacts
  • One-to-many with HealthElements

🔒 Encrypted by default: notes[].markdown • FuzzyDate format: YYYYMMDD (e.g., 19850315)

Contact

A single encounter or event — consultation, lab result, phone call

  • Groups all information from one event, one patient, one or more HCPs
  • Can occur without direct interaction (e.g., lab report integration)
Field Type Description
openingDate FuzzyDateTime When the encounter started
closingDate FuzzyDateTime When it ended
descr String 🔒 Human-readable description
encounterType CodeStub Consultation, emergency, etc.
services Service[] The actual medical data
subContacts SubContact[] Links to structuring elements

🔒 Encrypted by default: descr, notes[].markdown, and all Services

Service

The atomic unit of medical information

Represents any observation or action relevant to a patient's health:

Subjective

  • Patient complaints
  • Feelings, symptoms
  • Reason for encounter

Objective

  • Biometric measures
  • Physical exam findings
  • Lab results

Actions

  • Prescriptions
  • Treatment plans
  • Referrals

Key Fields

  • label: description/code
  • content: the actual data
  • valueDate: when measured

Content — The Data Container

Service stores its value in a Content object — supports many data types

Type Field Example
Number numberValue 120.5
String stringValue "Normal sinus rhythm"
Boolean booleanValue true
Measure measureValue { value: 120, unit: "mmHg", min: 90, max: 140 }
Medication medicationValue Prescription details
Time Series timeSeries EEG signals, ECG data
Date fuzzyDateValue 20240315
Binary binaryValue Small binary data (KB)
Document ref documentId Reference to large files
Compound compoundValue Nested Services

Putting It Together — A Consultation

Patient: John Doe (id: p-123)
  │
  └── Contact: "Annual checkup — 2024-03-15" (🔒 descr encrypted)
        │
        ├── Service: "Blood Pressure"
        │     └── Content: measureValue { value: 120, unit: "mmHg" }
        │
        ├── Service: "Heart Rate"
        │     └── Content: measureValue { value: 72, unit: "bpm" }
        │
        ├── Service: "Diagnosis"
        │     └── Content: stringValue "Mild hypertension"
        │     └── codes: [SNOMED-CT|38341003|1]
        │
        └── Service: "Prescription"
              └── Content: medicationValue { ... }

SubContact — Structuring Medical Context

Links Services to persistent structures. Logical organisation of the services.
As opposed to contacts that provide a temporal organisation.

        HealthElement: "Hypertension" (ongoing condition)
 │ Time                         │ Logical link
 │     Contact 2024-01   ─── SubContact ─── Service: BP 140/90
 │     Contact 2024-03   ─── SubContact ─── Service: BP 130/85
 │     Contact 2024-06   ─── SubContact ─── Service: BP 125/80
 │     Contact 2024-09   ─── SubContact ─── Service: BP 120/78
 ▼

SubContacts can reference:

  • HealthElement — persistent medical condition
  • PlanOfAction — therapeutic approach
  • Form — data entry structure

Part 3

Supporting Entities

HealthElement, HealthcareParty, Device & User

HealthElement — Persistent Conditions

A medical condition or event that persists over time

Duration Example
Days Flu, acute infection
Months Pregnancy, fracture recovery
Permanent Allergy, chronic disease, disability
  • Encryptable root entity (🔒 descr, note, notes[].markdown)
  • Links to Contacts via SubContacts
  • Tracks openingDate / closingDate
  • Has careTeam, episodes, plansOfAction

HealthcareParty, Device & User

HealthcareParty

Any actor in patient care

  • Doctors, nurses
  • Hospitals, clinics
  • Government authorities
  • Is a data owner

Patient

The patient themselves:

  • Is a data owner

Device

Medical device or software

  • MRI, smartwatch, app
  • Is a data owner

User

Login identity, linked to max. one of:

  • HealthcareParty
  • Patient
  • Device
  • Is not a data owner

Part 4

End-to-End Encryption

Privacy by design, not by policy

Why E2E Encryption?

Medical data requires the highest level of protection

The Cardinal guarantee: Even if the server is fully compromised, patient data remains encrypted. The server never sees plaintext medical content.

  • NLPD / GDPR / HIPAA compliance by architecture
  • Zero-knowledge server — processes encrypted blobs
  • Patients control who sees their data
  • No single point of trust

Three-Layer Key Hierarchy

┌─────────────────────────────────────────────────────────┐
│  Layer 1: PERSONAL KEYS (RSA)                           │
│  One per Data Owner — stored on device only             │
│  Used to decrypt exchange keys                          │
├─────────────────────────────────────────────────────────┤
│  Layer 2: EXCHANGE KEYS (AES)                           │
│  One per delegator → delegate pair                      │
│  Encrypted with both parties' public RSA keys           │
│  Signed with HMAC-SHA256                                │
├─────────────────────────────────────────────────────────┤
│  Layer 3: ENTITY KEYS (AES)                             │
│  One per encryptable entity                             │
│  Encrypts actual medical content                        │
│  Stored encrypted in SecureDelegation                   │
└─────────────────────────────────────────────────────────┘

How Encryption Flows

   Dr. Alice                                          Dr. Bob
   ┌──────┐                                          ┌──────┐
   │ RSA  │                                          │ RSA  │
   │ Keys │                                          │ Keys │
   └──┬───┘                                          └──┬───┘
      │              ┌──────────────────┐               │
      │    decrypt   │  Exchange Key    │   decrypt     │
      ├────────────> │  (AES)           │ <─────────────┤
      │              │  encrypted with  │               │
      │              │  both RSA keys   │               │
      │              └────────┬─────────┘               │
      │                       │                         │
      │                       ▼                         │
      │              ┌──────────────────┐               │
      │              │  Entity Key      │               │
      │              │  (AES)           │               │
      │              └────────┬─────────┘               │
      │                       │                         │
      │                       ▼                         │
      │              ┌──────────────────┐               │
      │              │  🔒 Encrypted    │               │
      │              │  Patient Data    │               │
      │              └──────────────────┘               │

What Gets Encrypted?

Entity Encrypted Fields
Patient notes[].markdown
Contact descr, notes[].markdown, Services
Service notes[].markdown (+ Content via Contact)
HealthElement descr, note, notes[].markdown

Internal encrypted metadata (all encryptable entities):

  • encryptedSelf — serialized encrypted content
  • encryptionKeys — entity AES keys
  • securityMetadata — secure delegations
  • secretForeignKeys — encrypted entity links
  • cryptedForeignKeys — encrypted owning entity IDs

Secure Delegations — Access Control

How data owners share access while preserving privacy

┌───────────────────────────────────────────────┐
│  SecurityMetadata                             │
│                                               │
│  ┌──────────────────────────────────────────┐ │
│  │  SecureDelegation (Alice → Bob)          │ │
│  │  • Exchange data ID                      │ │
│  │  • Encrypted entity key                  │ │
│  │  • Access level: READ or READ_WRITE      │ │
│  │  • Delegation key (hashed)               │ │
│  └──────────────────────────────────────────┘ │
│                                               │
│  ┌──────────────────────────────────────────┐ │
│  │  SecureDelegation (Alice → Patient)      │ │
│  │  • Exchange data ID                      │ │
│  │  • Encrypted entity key                  │ │
│  │  • Access level: READ                    │ │
│  │  • Delegation key (hashed, anonymous)    │ │
│  └──────────────────────────────────────────┘ │
└───────────────────────────────────────────────┘

Anonymous vs. Explicit Data Owners

Explicit

HCPs & some Devices

  • ID visible in delegations
  • Server knows who has access
  • Simpler & faster lookups
  • No privacy concern (HCPs are public)

Anonymous

Patients

  • ID hidden in delegations
  • Server cannot infer relationships
  • Access via hashed delegation keys
  • Protects patient privacy

Key insight: Even if the server knows Dr. Alice shared data with someone, it cannot determine that the someone is Patient Jane — unless it has the exchange key.

How Patient ↔ Contact links stay private

                    ┌─────────────────────┐
                    │  Patient            │
                    │  id: p-123          │
                    │  secretId: s-xyz 🔒 │
                    └────────┬────────────┘
                             │  (encrypted link)
              ┌──────────────┼──────────────┐
              ▼              ▼              ▼
       ┌────────────┐ ┌────────────┐ ┌────────────┐
       │ Contact A  │ │ Contact B  │ │ Contact C  │
       │ secretId:  │ │ secretId:  │ │ secretId:  │
       │ s-xyz      │ │ s-xyz      │ │ s-xyz      │
       └────────────┘ └────────────┘ └────────────┘
  • Secret ID generated per patient, stored encrypted
  • Contacts reference patient via secret ID (not public ID)
  • Only authorized users can decrypt and follow the link
  • Server sees s-xyz but cannot map it to p-123

SDK Makes It Simple

The SDK handles all cryptographic complexity automatically

// Initialize encryption metadata before creating an entity
const contact = new DecryptedContact({
  descr: "Annual checkup"
})

// SDK encrypts, creates delegations, manages keys
const created = await sdk.contact.createContactWithPatient(
  patient,
  contact,
  { delegates: { [otherDoctorId]: AccessLevel.Write } }
)

// SDK decrypts transparently on retrieval
const retrieved = await sdk.contact.getContact(created.id)
console.log(retrieved.descr) // "Annual checkup" ← decrypted

Key Management Summary

Operation What happens
Entity creation Entity key generated, encrypted with exchange keys, stored in SecureDelegation
Data sharing New (Secure)Delegation created, entity key re-encrypted for delegate
Data access RSA → Exchange Key → Entity Key → Decrypt content
Key recovery User loads saved RSA key; verified before re-enabling encryption
Key validation Authenticate pub key (QR code, emoji hash, etc.) before data sharing

Recap

Cardinal Data Model — Key Takeaways

  1. Shared structures (id, rev, identifiers, tags, codes) provide consistency across all entities

  2. Patient → Contact → Service models the chronological medical record with Content as the data container

  3. HealthElement + SubContact add persistent medical context across encounters

  4. Three-layer encryption (RSA → AES Exchange → AES Entity) ensures zero-knowledge server architecture

  5. Secure delegations enable fine-grained sharing with anonymous patient support

  6. The SDK abstracts it all — developers focus on medical data, not cryptography

Thank You

Cardinal SDK Documentation
docs.icure.com