Tomedo Integration — PMS Data Bridge
Audiences: clinical-buyer, developer, partner
Dudoxx HMS bridges bidirectionally with Tomedo — the dominant German-market practice management system (PMS) — allowing clinics to read patient records, KarteiEinträge (chart entries), and drug catalogues from their existing Tomedo installation while progressively moving workflows into Dudoxx.
Business Purpose
German clinics that already run Tomedo cannot migrate overnight. Dudoxx HMS must co-exist with Tomedo during the transition: read patient demographics, medical record entries (KarteiEinträge), and the drug catalogue from Tomedo, and optionally sync patients into the Dudoxx FHIR repository. This bridge eliminates duplicate data entry during the transition period and allows clinical staff to work in Dudoxx's AI-augmented interface while their legacy Tomedo data remains the source of truth for billing and statutory reporting.
The integration is feature-flagged (ENABLE_TOMEDO_INTEGRATION=true) and is deployed only for clinics that have a Tomedo server accessible over the network (typically via an SSH/ngrok tunnel to the on-premise server). It is an operational opt-in, not a default feature.
Audiences
- Investor: Tomedo holds 40%+ of the German outpatient clinic market. A certified bridge makes Dudoxx HMS the only AI-first platform that DACH clinics can adopt without abandoning Tomedo. This is a major adoption accelerator.
- Clinical buyer (doctor/nurse/receptionist): Doctors see Tomedo patient records in the Dudoxx interface. Drug lookup queries the Tomedo drug catalogue. Once a patient is synced, appointments and clinical notes created in Dudoxx can reference the canonical Tomedo patient identity.
- Developer/partner:
TomedoModulewraps a TypeScript SDK (TomedoSDK, imported from./sdk) originally ported fromdudoxx-saas. All Tomedo API calls go through the SDK which handles retry (5 attempts, 1.5s delay), authentication, and facility filtering.TomedoServiceexposes methods for patients, KarteiEinträge, and drugs. FHIR sync (FhirClientService) is used for optional patient import. - Internal (ops/support): The integration is disabled by default (
ENABLE_TOMEDO_INTEGRATIONunset /'false'). When enabled,onModuleInit()initializes the SDK. A drug cache (24-hour TTL, in-memory) reduces Tomedo API load. Facility filtering (TOMEDO_ENABLE_FACILITY_FILTERING) restricts data to a single facility.
Architecture
TomedoModule owns:
| Member | Responsibility |
|---|---|
TomedoController | REST endpoints (patients, medical records, drugs, health) |
TomedoService | NestJS wrapper around TomedoSDK |
TomedoService dependencies:
| Dependency | Role |
|---|---|
TomedoSDK (./sdk) | HTTP client; retry logic; KarteiEintrag manager; drug manager |
IFhirClient | Injected via FHIR_CLIENT DI token for patient FHIR sync |
PrismaService | Injected for Dudoxx patient lookup by Tomedo ID |
Feature-flag flow:
ENABLE_TOMEDO_INTEGRATION=true→onModuleInit()callsinitializeSDK().initializeSDK()validatesTOMEDO_API_BASE_URLis non-empty, buildsTomedoSDKConfigfrom env vars, callscreateTomedoSDK(config).- If
TOMEDO_API_BASE_URLis empty,sdk = null— all service methods checksdk !== nulland throwHttpException(503)when the SDK is not initialized.
Drug caching:
drugCache: { data: unknown[]; fetchedAt: number } | null— in-memory cache, 24-hour TTL.- Drug catalogue is large and rarely changes; caching avoids repeated expensive Tomedo API calls during medication lookup workflows.
Patient sync (optional):
ENABLE_TOMEDO_PATIENT_SYNC=trueenables copying Tomedo patient records into HAPI FHIR asPatientresources.- Sync preserves the Tomedo patient ID as a FHIR identifier for cross-reference.
Facility filtering:
TOMEDO_ENABLE_FACILITY_FILTERING=true+TOMEDO_MEDICAL_FACILITY_IDrestrict all queries to a single facility partition within the Tomedo server, preventing multi-facility data leakage.
Tech Stack & Choices
| Concern | Choice | Rationale |
|---|---|---|
| API client | TomedoSDK (custom TypeScript, ./sdk/) | Ported from dudoxx-saas; battle-tested against Tomedo's REST API; SDK encapsulates auth header, retry, BigInt ID handling |
| Retry | 5 attempts, 1.5s delay | Tomedo servers close sockets on large payloads (tunnel instability); aggressive retry essential in clinic network conditions |
| Drug cache | In-memory, 24-hour TTL | Drug catalogue is static per release; caching avoids Tomedo server rate limits |
| Patient sync | IFhirClient.executeBatch(bundle) with ifNoneExist conditional-create | Idempotent re-import: a FHIR transaction Bundle keyed on the Tomedo identifier avoids duplicating already-synced patients (not a bare create()) |
| Auth | TOMEDO_API_KEY + TOMEDO_CLIENT_ID | Standard Tomedo REST API authentication |
| ID handling | All Tomedo IDs as string (int64 BigInt) | Tomedo uses int64 patient IDs that overflow JavaScript number; always serialized as strings |
| Feature flag | ENABLE_TOMEDO_INTEGRATION env var | Per-clinic opt-in; disabled by default so non-Tomedo deployments are unaffected |
Data Flow
Patient list read (Tomedo → Dudoxx UI)
- Doctor opens patient search in Dudoxx → frontend calls
GET /api/v1/tomedo/patients?search=.... TomedoController.listPatients()→TomedoService.listPatients(queryDto)(tomedo.service.ts:232).TomedoServicecheckssdk !== null; calls the SDK patient manager with{ search, facilityId, page, pageSize }.- SDK sends authenticated HTTP request to
TOMEDO_API_BASE_URL/patients; handles retry on socket close. - Response mapped to
TomedoPatientListResponse; returned to frontend.
Patient sync (Tomedo → FHIR)
- Admin triggers
POST /api/v1/tomedo/sync/patients(batch/body-driven sync; a single patient is passed in the request body, not the path). TomedoServicesync handler:- Fetches patient(s) from the Tomedo SDK.
- Maps each to a FHIR
Patientresource carrying a Tomedoidentifierfor cross-reference. - Writes via
fhirClient.executeBatch(...)(tomedo.service.ts:682) — a FHIR transaction Bundle withifNoneExistconditional-create so re-imports are idempotent (an existing patient with the same Tomedo identifier is not duplicated). Not a singlecreate('Patient', ...)call. - HAPI FHIR stores the resource in the clinic's partition; the Tomedo→FHIR link is the identifier.
- Returns the batch outcome (created/matched FHIR ids keyed to Tomedo ids).
Drug lookup (Tomedo → Dudoxx prescription flow)
- Doctor types drug name in prescription module →
GET /api/v1/tomedo/drugs?search=.... TomedoService.getDrugs(query):- Checks in-memory
drugCache(24h TTL); if valid, filters locally. - On cache miss: fetches full catalogue from Tomedo SDK, stores in
drugCache.
- Checks in-memory
- Returns
TomedoDrugListResponseto prescription UI.
Health check
GET /api/v1/tomedo/health→TomedoService.checkHealth()(tomedo.service.ts:183) pings the Tomedo API and returns aTomedoHealthResponse(connection + facility info).
Implicated Code
ddx-api/src/integrations/tomedo/tomedo.module.ts:1—TomedoModule; importsConfigModule,AuthModule,PatientsModule,PrismaModule; injects globalFHIR_CLIENT(no local FHIR module import needed)ddx-api/src/integrations/tomedo/tomedo.service.ts:56—TomedoService; feature flag check at:100;onModuleInit()at:118; drug cache TTL config at:68ddx-api/src/integrations/tomedo/tomedo.service.ts:132—initializeSDK(); SDK config builder; retry paramsdelayMs: 1500at:152(5 attempts, 1.5s)ddx-api/src/integrations/tomedo/tomedo.controller.ts:64—@Controller('tomedo')(all routes under the globalapi/v1prefix, i.e./api/v1/tomedo/*); routes:GET health(:76),GET config(:116),GET patients(:153),GET patients/stream(:203),GET patients/:id(:245),POST sync/patients(:296),POST sync/drugs(:437),GET patients/:id/medical-records(:500),GET medical-records(:603),GET drugs(:717)ddx-api/src/integrations/tomedo/sdk/—TomedoSDK;createTomedoSDK(config); patient manager, KarteiEintrag manager, drug manager; HTTP auth headers; BigInt ID serializationddx-api/src/integrations/tomedo/types/—TomedoPatient,TomedoKarteiEintrag,TomedoDrug,TomedoConfig,TomedoRecordType— typed response shapesddx-api/src/integrations/tomedo/interceptors/— request interceptors (auth header injection, request logging)ddx-api/src/integrations/tomedo/trace/— trace/debugging utilities for Tomedo API calls
Operational Notes
- Feature flag required:
ENABLE_TOMEDO_INTEGRATION=truemust be set; without it the SDK never initializes and all endpoints return HTTP 503. - Network access requirement: Tomedo servers are typically on-premise. A reverse proxy or SSH tunnel (e.g. ngrok, FRP) is required to expose
TOMEDO_API_BASE_URLto the Dudoxx API server. Socket instability is the primary failure mode — the 5-retry config handles most transient errors. - PHI logging:
TOMEDO_LOG_PHI=falseby default — never enable in production.TOMEDO_DEBUG_LOG_RAW=truelogs raw Tomedo API responses (also PHI-sensitive; dev only). - BigInt IDs: All Tomedo patient IDs are
stringthroughout the codebase. Never cast tonumber— JavaScriptNumbercannot represent int64 values above 2^53. - Drug cache invalidation: The 24-hour in-memory cache is per-process. In a multi-instance deployment, each instance builds its own cache. To force refresh, restart the API process. A future improvement would move this to Redis.
- Env vars summary:
TOMEDO_API_BASE_URL,TOMEDO_API_KEY,TOMEDO_CLIENT_ID,TOMEDO_CHANGING_USER(defaultddx-api),TOMEDO_API_TIMEOUT(default 30000ms),TOMEDO_MEDICAL_FACILITY_ID,TOMEDO_MEDICAL_FACILITY_NAME,TOMEDO_MEDICAL_FACILITY_CODE,ENABLE_TOMEDO_INTEGRATION,ENABLE_TOMEDO_PATIENT_SYNC,TOMEDO_ENABLE_FACILITY_FILTERING,TOMEDO_ENABLE_LOGGING,TOMEDO_DEBUG_LOG_RAW,TOMEDO_LOG_PHI.
Related Topics
- FHIR ResourceFacade — Tomedo patient sync uses
IFhirClientto create FHIRPatientresources; ResourceFacade pattern - Patients — Registration and Demographics — Tomedo sync creates Dudoxx Patient records; overlap with direct patient registration flow
- Organization Management — facility filtering (
TOMEDO_ENABLE_FACILITY_FILTERING) maps to the org's Tomedo facility; must be configured at tenant onboarding time