KodeMed KodeMed

Full documentation, downloads, and deployment tools available

Sign in with your KodeMed account to access DevOps guides, Windows installer, and Linux packages.

Sign in for full access

Platform Overview

Here you can see the system overview. Downloads are available in the sections below.

Server

REST API, WebSocket, session management, audit logging

Spring Boot • Port 8080

DataServer

ICD-10, CHOP, ATC catalogs, SwissDRG classification data

Spring Boot • Port 8081

Grouper

SwissDRG, TARPSY, ST Reha real-time grouping engine

Spring Boot • Port 8082

CodingUI

React web interface with 14+ data blocks, multilingual

React / Vite • Port 3000

COM DLL

In-process HIS integration for .NET, VB6, VBA, C#, Delphi

Windows • KodeMed.dll

CodingClient

System tray app, WebSocket, webhook, auto-reconnect

Windows • WebView2 • MSI

Security & Compliance

Authentication

  • OAuth2 / OpenID Connect (SSO)
  • Keycloak, Azure AD, Okta compatible
  • JWT token validation on every request
  • Per-client credentials (DLL, UI, Portal)

Data Protection

  • GDPR Art. 32 — AES encryption at rest
  • TLS 1.2+ for all network communication
  • No patient data stored on client
  • Configurable data retention policies

Audit & Logging

  • Full audit trail for all coding sessions
  • User, timestamp, action, IP address
  • Structured JSON logs (ELK / Splunk ready)
  • 90-day default retention (configurable)

Container Security

Hardening varies by deployment mode. The canonical demo compose is a development convenience and is not hardened. Production must use Helm or the wizard preset.

Setting Helm (OpenShift) Wizard preset Demo compose
runAsNonRootyes (1001 / 101)UI only (101)no
readOnlyRootFsyesUI, Keycloak, Postgresno
cap_drop ALLyesUI, Keycloak, Postgresno
no-new-privilegesyesyes (all services)no
Network policyyesno (flat network)no
seccomp profileyesnono

Wizard and Helm hardening for Java services (Server, DataServer, Grouper) is being aligned in issue #506.

Licensing

  • RSA-4096 signed offline license file
  • Per-component validation (Server, DataServer, Grouper)
  • License types: DEMO, TRIAL, PRODUCTION
  • API status: GET /api/v1/license/status

Documentation

Deutsch

  • KodeMed AG PräsentationPDF
  • SystemarchitekturPDF
  • IntegrationsleitfadenPDF
  • Update- & Lebenszyklus-RichtliniePDF
  • OpenShift-BereitstellungsrichtliniePDF

Français

  • Présentation KodeMed AGPDF
  • Architecture du systèmePDF
  • Guide d’intégrationPDF
  • Politique de mise à jourPDF
  • Politique de déploiement OpenShiftPDF

Italiano

  • Presentazione KodeMed AGPDF
  • Architettura del sistemaPDF
  • Guida all’integrazionePDF
  • Politica di aggiornamentoPDF
  • Politica di distribuzione OpenShiftPDF

English

  • KodeMed AG PresentationPDF
  • System ArchitecturePDF
  • Integration GuidePDF
  • Update & Lifecycle PolicyPDF
  • OpenShift Deployment PolicyPDF

Markdown Documentation

  • System Architecture MD
  • Integration Guide MD
  • HIS REST Integration Specification Partners MD PDF
  • Update & Lifecycle Policy MD
  • OpenShift Deployment Policy MD

Windows Client

MSI Installer

No admin rights required. Installs per-user to %LOCALAPPDATA%\KodeMed. Supports silent mode for enterprise deployment (GPO, SCCM, Intune).

Download MSI

Silent install:  msiexec /i KodeMed.msi /quiet /norestart SERVERURL="https://kodemed.hospital.ch"

COM DLL Partners · Delphi / VB6 / .NET COM

For HIS / 3rd-party Windows applications that integrate via the COM DLL path (Integration Guide §3 — Option A). The same KodeMed.dll the MSI installs, bundled as a ZIP so you can ship both files inside your own application directory without a separate installer step.

  • KodeMed-COM-DLLs.zip (KodeMed.dll + KodeMed.comhost.dll)Download ZIP

Bundled as ZIP so Windows / Edge / Chrome accept the download cleanly — individual .dll downloads are quarantined by SmartScreen.

Register for COM

Extract the ZIP, drop both DLLs in the same folder, then register the COM host with admin rights:

tar -xf KodeMed-COM-DLLs.zip       :: or right-click → Extract All
regsvr32 /i KodeMed.comhost.dll        :: register per-machine
regsvr32 /i /n /u KodeMed.comhost.dll  :: unregister

Then create the COM object — ProgID = KodeMed.Coding, CLSID = {F6E73053-8D49-480F-AC06-4E0A4882373F}.

// Delphi
var Coding: Variant;
Coding := CreateOleObject('KodeMed.Coding');
Coding.DoCoding(spigesXml);

' VB6
Dim Coding As Object
Set Coding = CreateObject("KodeMed.Coding")
Coding.DoCoding spigesXml

Full reference: Integration Guide §3 Option A — COM DLL Integration.

Windows 10 / 11 .NET Runtime included ~150 MB disk space
Advanced Options — Enterprise deployment (IT administrators)

Silent Install — MSI (GPO / SCCM / Intune)

# Basic — server URL only (language auto-detected, autostart enabled)
msiexec /i KodeMed.msi /quiet /norestart SERVERURL="https://kodemed.hospital.ch"
# Full options — all parameters
msiexec /i KodeMed.msi /quiet /norestart SERVERURL="https://kodemed.hospital.ch" LANGUAGE="de" AUTOSTART=1
# Citrix / shared desktop — disable autostart
msiexec /i KodeMed.msi /quiet /norestart SERVERURL="https://kodemed.hospital.ch" AUTOSTART=0
# Custom install directory
msiexec /i KodeMed.msi /quiet /norestart SERVERURL="https://kodemed.hospital.ch" INSTALLDIR="D:\Apps\KodeMed"
# Silent install with log file (for troubleshooting)
msiexec /i KodeMed.msi /quiet /norestart /l*v "%TEMP%\KodeMed-install.log" SERVERURL="https://kodemed.hospital.ch"
# Uninstall
msiexec /x KodeMed.msi /quiet /norestart

MSI properties: SERVERURL (required, must be https://) • LANGUAGE — de, fr, it, en (default: auto-detect) • AUTOSTART — 1 or 0 (default: 1) • TRUSTSELFSIGNED — 1 for self-signed certs (default: 0) • INSTALLDIR (default: %LOCALAPPDATA%\KodeMed)
Config location: %LOCALAPPDATA%\KodeMed\kodemed-client-config.json (edit this file to change settings after installation)
Prerequisites: Windows 10+, WebView2 Runtime (pre-installed on Windows 10 21H2+ and Windows 11)

Server URL Format

The Server URL must use HTTPS. The format depends on your server deployment:

Docker + Caddy (IP)https://192.168.1.100:8443
Docker + Caddy (hostname)https://kodemed.hospital.ch:8443
Docker + subdomain proxyhttps://server.kodemed.hospital.ch
OpenShift / Kuberneteshttps://kodemed-server.apps.hospital.ch
Native + reverse proxyhttps://kodemed.hospital.ch (proxy forwards to port 8080)

Note: HTTP URLs are not supported except for http://localhost during development. Use TRUSTSELFSIGNED=1 only with self-signed certificates (Caddy IP mode).

Individual Files

Linux Server

Two Deployment Options

Docker Compose (recommended)Native Install
HTTPSCaddy via overlay (docker-compose.caddy.yml) or your own reverse proxyRequires your own reverse proxy (nginx, Apache, HAProxy)
DatabasePostgreSQL included in containersYou must install and configure PostgreSQL 15+ separately
SSOAny OIDC IDP (Keycloak / Azure AD / Okta / Auth0 / Ping / Authelia / …); or check “Include bundled Keycloak” in the wizard for a turnkey IDP with LDAP / AD federation built inAny OIDC IDP required (no bundled option)
Updatesdocker compose pull && docker compose up -dRe-run install-kodemed.sh
Best forMost deployments, quick setupHospitals that require native services (no Docker)

For the full picture of how the compose layers, TLS termination and the OIDC IDP (bundled Keycloak or your own) fit together, see DevOps Guide → Compose layers.

Docker Compose Deployment (recommended)

Use the Deployment Wizard to generate a ready-to-use package. All services, database, and HTTPS are included.

Docker 24+ Docker Compose 2.x Linux x86_64 4 GB RAM minimum

Native Install (no Docker)

Contains Server, DataServer, GrouperServer, CodingUI, and installer scripts. The installer creates systemd services and generates all configuration files interactively.

  • kodemed-linux-2026.5.15.59502.tar.gzDownload
$ tar -xzf kodemed-linux-2026.5.15.59502.tar.gz
$ sudo ./install-kodemed.sh
Ubuntu 22.04+ / Debian 12+ PostgreSQL 15+ (must be running) Java 21 (auto-installed) Node.js 20 (auto-installed) 4 GB RAM minimum Linux x86_64

Before You Start

  1. Install PostgreSQL and create the database:
    sudo apt install -y postgresql postgresql-contrib
    sudo systemctl enable --now postgresql
    sudo -u postgres psql -c "CREATE USER kodemed WITH PASSWORD 'YOUR_SECURE_PASSWORD';"
    sudo -u postgres psql -c "CREATE DATABASE kodemed OWNER kodemed;"
  2. Verify PostgreSQL is running before running the installer:
    sudo systemctl status postgresql
    sudo -u postgres psql -c "SELECT 1"
  3. The installer will ask for the database URL, username, and password. Have these ready.
  4. The installer will ask if you are behind a reverse proxy. For production, answer yes and provide your HTTPS URLs. HTTP is only for localhost testing.

After Installation — Verify

# Check all services are running
sudo systemctl status kodemed-server kodemed-dataserver kodemed-grouperserver kodemed-ui
 
# Check server logs (should show "Started KodeMedServerApplication")
sudo journalctl -u kodemed-server -n 20
 
# Test API (should return JSON)
curl -s http://localhost:8080/actuator/health | head -1
 
# Test UI (should return HTML)
curl -s http://localhost:3000 | head -5

First startup takes 2–5 minutes while the database schema is created and classification data is imported. Check progress with journalctl -u kodemed-server -f.

License File — per-service requirement

The native installer creates one systemd unit per service, each with its own WorkingDirectory (/opt/kodemed/server/, /opt/kodemed/dataserver/, /opt/kodemed/grouperserver/). The license validator resolves ./kodemed.license relative to the running service’s WorkingDirectory, not to the install root. A single license at $INSTALL_DIR/kodemed.license will be ignored and services will fail with HTTP 402.

Choose one of these options:

  1. Copy the same kodemed.license into each service directory:
    sudo cp kodemed.license /opt/kodemed/server/
    sudo cp kodemed.license /opt/kodemed/dataserver/
    sudo cp kodemed.license /opt/kodemed/grouperserver/
    sudo systemctl restart kodemed-server kodemed-dataserver kodemed-grouperserver
  2. OR set KODEMED_LICENSE_FILE to an absolute path in /etc/kodemed/kodemed.env so all three services read the same file:
    sudo install -d /etc/kodemed
    sudo cp kodemed.license /etc/kodemed/
    echo 'KODEMED_LICENSE_FILE=/etc/kodemed/kodemed.license' | sudo tee -a /etc/kodemed/kodemed.env
    sudo systemctl restart kodemed-server kodemed-dataserver kodemed-grouperserver

Verify with journalctl -u kodemed-server | grep -i license. A successful load logs "License loaded: customer=… expires=…". HTTP 402 on any endpoint means the license was not found.

Support

Technical support and documentation: support.kodemed.ch

Deployment Wizard Docker Compose

This wizard generates a ready-to-use deployment package for your hospital’s on-premise server. Fill in the fields below and click Download ZIP to get:

  • docker-compose.yml — orchestrates all KodeMed services (Server, DataServer, Grouper, CodingUI, PostgreSQL)
  • .env — passwords, URLs, and settings (keep this file secure)
  • runtime-config.js — browser configuration for the CodingUI
1. Public URLs (browser-accessible — used by CodingUI and DLL clients)

These are the URLs your users will access from their browser or DLL client. They must be reachable externally (via reverse proxy, load balancer, or direct).

2. Internal Service URLs (Docker network — usually keep defaults)

These are the internal Docker network URLs used in docker-compose.yml. Keep the defaults unless you use an external reverse proxy or custom network.

3. Database

KodeMed requires PostgreSQL 15+. You can use the bundled container or connect to your hospital’s existing database server.

4. Authentication (OIDC / SSO)

KodeMed authenticates every request against an OIDC-compliant Identity Provider. Point it at the one you already run (Keycloak, Azure AD, Okta, Auth0, Ping, Authelia, …), or include the bundled Keycloak below.

Bundled Keycloak is included so hospitals with an existing LDAP or Active Directory get SSO for KodeMed without standing up a separate IDP — Keycloak federates straight into the directory and users sign in with their hospital credentials. Already running Keycloak / Azure AD / Okta / …? Leave the box unchecked and point the Issuer URI above at your existing IDP.

5. Security & Advanced Options

Optional settings for GDPR compliance, CORS, and logging. Sensible defaults are provided — only change if your infrastructure requires it.

Quick Start

After downloading the ZIP, deploy on your Linux server. These steps mirror the README.md shipped in the zip — single source of truth.

$ unzip kodemed-deployment.zip # includes demo license (90 days) + import-data/ specs/ catalogues/ dirs
 
# 1. Log in to Harbor (required before any pull)
$ docker login harbor.mieresit.com -u 'YOUR_USERNAME' # credentials from KodeMed AG — single quotes mandatory for robot$... accounts
 
# 2. Pull classification data (ICD/CHOP/DRG/TARPSY/ST Reha) and extract
$ oras pull harbor.mieresit.com/kodemed/data:2026 -o import-data/ # oras.land/docs/installation
$ cd import-data && mkdir -p 2026 && unzip -o *.zip -d 2026/ 2>/dev/null || tar -xzf *.tar.gz -C 2026/ && rm -f *.zip *.tar.gz && cd ..
$ sudo chown -R 1001:1001 import-data/
# the artifact is a single archive — DataServer matches individual icd10*.zip / dz-d-*chop*.zip etc. AND requires them in a year-named subdir (inbox/2026/), so extraction-into-2026/ is mandatory. The chown hands ownership to the container UID 1001 so the DataServer can MOVE files from inbox/<year>/ to processing/ during the scan (otherwise: AccessDeniedException, every file ends with status=ERROR)
 
# 3. Pull Grouper specs + catalogues and extract
$ oras pull harbor.mieresit.com/kodemed/grouper:2026 -o ./
$ unzip -o kodemed-grouper-*.zip 2>/dev/null || tar -xzf kodemed-grouper-*.tar.gz && rm -f kodemed-grouper-*.zip kodemed-grouper-*.tar.gz
# the OCI artifact ships as a single archive — extraction lands .sgs into specs/ and .csv into catalogues/; skip it and Grouper returns loaded:false for every system
 
# 4. (SKIP this step if you used the IP / self-signed preset — Caddy auto-generates the certs)
$ bash certs/generate-certs.sh # ONLY when tlsMode=custom: the script is in the zip; the file `certs/cert.pem` / `certs/key.pem` placeholders need real certs
 
# 5. Start the stack
$ sudo docker compose up -d # requires root, sudo, or docker group membership
$ docker compose logs -f kodemed-dataserver # watch the first-run import (~3-5 min)

Keycloak Admin Console (bundled SSO)

After deployment, manage users and SSO settings:

Admin Consolehttps://HOST:9443/admin
Usernameadmin
Passwordsee KEYCLOAK_ADMIN_PASSWORD in .env

Demo users (for KodeMed application login):

demo-admin / KodeMed2026! (administrator)
demo-coder / KodeMed2026! (medical coder)
demo-approver / KodeMed2026! (approver + coder)

Classification Data (required for coding)

Classification data is distributed as a pre-packaged OCI artifact in Harbor. Pull it with the ORAS CLI before first start:

oras pull harbor.mieresit.com/kodemed/data:2026 -o import-data/

The package contains all required files (~45 files, ~50 MB):

  • ICD-10-GM / CIM-10-GM — ClaML XML + metadata + alphabetical indexes (DE/FR/IT)
  • CHOP — systematic + alphabetical indexes (DE/FR/IT)
  • SwissDRG — Fallpauschalenkatalog + Begleitdokumente (CCL, POA, plausibility)
  • TARPSY + ST Reha — PCG/RCG catalogs + Zusatzentgelte
  • High-Cost Medications — substance lists + Technisches Begleitblatt
  • KodeMed Abbreviations — medical abbreviation dictionaries (DE/FR/IT)
  • Coding Rules — ICD-FAQ + CHOP-FAQ coding guidelines (DE/FR/IT)

The DataServer imports these files automatically on startup. Without data, KodeMed starts but search is not available.

Data sources: ICD-10-GM from BfArM, Swiss translations from BFS, DRG/TARPSY/ST Reha from SwissDRG AG. Packaged by KodeMed AG.

Docker Registry Access

Each customer receives unique credentials for the KodeMed Docker registry (harbor.mieresit.com). You must log in before running docker compose up.

# Login (credentials are stored in ~/.docker/config.json)
docker login harbor.mieresit.com -u 'YOUR_USERNAME'
 
# Or non-interactive (for CI/scripts)
echo 'YOUR_PASSWORD' | docker login harbor.mieresit.com -u 'YOUR_USERNAME' --password-stdin

Important: Robot account usernames contain a $ character (e.g., robot$demo-kodemed). On Linux/macOS, you must wrap the username in single quotes to prevent shell expansion:

docker login harbor.mieresit.com -u 'robot$demo-kodemed'

No credentials yet? Contact support@kodemed.ch to request your Harbor registry access.

Common error: authorization failed: no basic auth credentials — run docker login first. Credentials persist until docker logout.

License: A demo license (3 months) is included in the ZIP. For production, contact support@kodemed.ch for a production license. Without a valid license, all API endpoints return HTTP 402.

All services start automatically. The CodingUI will be available on port 3000 (or on HTTPS port 443 if using the Caddy reverse proxy).

Service URLs — wizard localhost preset only

These URLs apply to the bundle generated by the wizard’s Localhost preset, which ships its own Keycloak. The canonical demo compose at kodemed.ch uses an external Keycloak (sso.kodemed.ch) and exposes services through Caddy on TLS — do not follow this table when inspecting the live demo.

CodingUIhttp://localhost:3000
Server APIhttp://localhost:8080
DataServer APIhttp://localhost:8081
GrouperServer APIhttp://localhost:8082
Keycloak Adminhttp://localhost:9080 — admin / admin

Demo users: demo-admin, demo-coder, demo-approver, demo-viewer, demo-auditor, demo-data-admin — password: KodeMed2026!

IP + HTTPS (Caddy) — Service URLs

CodingUIhttps://SERVER_IP
Server APIhttps://SERVER_IP:8443
DataServer APIhttps://SERVER_IP:8444
GrouperServer APIhttps://SERVER_IP:8445
Keycloak Adminhttps://SERVER_IP:9443 — admin / admin

Self-signed certificate: Caddy generates internal TLS certificates. On first access, the browser will show a security warning. You must accept the certificate on each port before the application works:

  1. Open https://SERVER_IP:9443 → accept certificate (Keycloak)
  2. Open https://SERVER_IP:8443 → accept certificate (Server API)
  3. Open https://SERVER_IP → accept certificate and use the application

DLL client config (kodemed-client-config.json):

{
"serverUrl": "https://SERVER_IP:8443",
"codingUIUrl": "https://SERVER_IP",
"oauth2Url": "https://SERVER_IP:9443",
"trustSelfSignedCertificates": true
}

EN: trustSelfSignedCertificates allows the DLL to connect to servers with self-signed TLS certificates. Only use for internal testing — do not enable in production.
DE: trustSelfSignedCertificates erlaubt der DLL, sich mit selbstsignierten TLS-Zertifikaten zu verbinden. Nur für interne Tests verwenden — nicht in Produktion aktivieren.
FR: trustSelfSignedCertificates permet au DLL de se connecter aux serveurs avec des certificats TLS auto-signés. À utiliser uniquement pour les tests internes — ne pas activer en production.
IT: trustSelfSignedCertificates consente alla DLL di connettersi a server con certificati TLS autofirmati. Da utilizzare solo per test interni — non attivare in produzione.

Custom / Wildcard Certificate (production)

To use your own certificate (e.g., wildcard *.hospital.ch) instead of the self-signed certificate, select “Custom certificate” in the wizard’s TLS mode selector. The wizard generates the correct Caddyfile and docker-compose volumes automatically.

Manual setup (if you already have a deployment with self-signed certificates):

  1. Place your certificate files in a certs/ directory next to docker-compose.yml:
    • certs/cert.pem — full chain (server certificate + intermediate CA)
    • certs/key.pem — private key
  2. Add volume mounts to the caddy service in docker-compose.yml:
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - caddy-data:/data
      - caddy-config:/config
      - ./certs/cert.pem:/certs/cert.pem:ro
      - ./certs/key.pem:/certs/key.pem:ro
  3. In Caddyfile, remove local_certs and replace every tls internal { on_demand } with:
    tls /certs/cert.pem /certs/key.pem
  4. In kodemed-client-config.json, set "trustSelfSignedCertificates": false
  5. Restart: docker compose down && docker compose up -d

EN: A wildcard certificate (e.g., *.hospital.ch) covers a hostname like kodemed.hospital.ch on all ports. The certificate must be in PEM format. Caddy does not auto-renew custom certificates — you are responsible for renewal.
DE: Ein Wildcard-Zertifikat (z.B. *.hospital.ch) deckt einen Hostnamen wie kodemed.hospital.ch auf allen Ports ab. Das Zertifikat muss im PEM-Format vorliegen. Caddy erneuert benutzerdefinierte Zertifikate nicht automatisch.
FR: Un certificat wildcard (ex. *.hospital.ch) couvre un nom d’hôte comme kodemed.hospital.ch sur tous les ports. Le certificat doit être au format PEM. Caddy ne renouvelle pas automatiquement les certificats personnalisés.
IT: Un certificato wildcard (es. *.hospital.ch) copre un hostname come kodemed.hospital.ch su tutte le porte. Il certificato deve essere in formato PEM. Caddy non rinnova automaticamente i certificati personalizzati.

Note: The GrouperServer requires specs/ and catalogues/ directories with SwissDRG/TARPSY/ST Reha specification files. Contact support for the grouper data package, or download from SwissDRG.org.

Integration REST API

This section documents how a Hospital Information System (HIS) integrates with KodeMed via REST + iframe + outbound webhook, against any OIDC-compliant Identity Provider (Keycloak, Azure AD, Okta, Auth0, Ping, Authelia, …). The contract is HIS-agnostic and IDP-agnostic — KodeMed is not bound to any particular IDP product. The same paths and payloads apply on a self-hosted on-premise deployment and on the cloud-hosted demo.

Some features below ship across multiple releases. Each subsection labels what is available today versus what is planned (with the tracking ticket).

REST API for HIS Integration

For each case to code, the HIS opens a coding session, embeds the returned URL in an iframe, and either receives the result via webhook or pulls it from a GET endpoint.

1. HIS -> KodeMed : POST /api/v1/coding/session (case payload + Bearer JWT)
2. KodeMed -> HIS : 201 Created { sessionId, redirectUrl, ... }
3. HIS UI : embed redirectUrl in <iframe> (CSP must allow it)
4. Coder works in the embedded KodeMed UI, clicks Apply or Discard
5a. Push: KodeMed -> HIS : POST <webhook url> { event_type, instance_id, result_data, ... }
5b. Pull: HIS -> KodeMed : GET /api/v1/coding/session/{sessionId}/result/data

Open a coding session

The HIS POSTs the patient SPIGES XML wrapped as data. instanceId is HIS-supplied — it correlates the submission (one POST), not each case inside it. The data payload can carry one or many SPIGES <Fall> elements; they all belong to the same coding session and share the same instanceId. The id MUST be unique — a second POST with an instanceId still in use returns 409 Conflict with the existing sessionId in the body; the id becomes free once the original session completes. KodeMed echoes it back in the webhook so the HIS matches the result against its original submission. sessionId is server-generated.

POST https://kodemed.<your-host>/api/v1/coding/session
Authorization: Bearer <coder JWT from your OIDC IDP>
Content-Type: application/json
 
{
  "data": "<SPIGES XML, one or more <Fall>>",
  "format": "spiges",
  "instanceId": "<your unique correlator for this submission>",
  "source": "API"
}

The 201 response carries sessionId (UUID v4) and redirectUrl. The HIS embeds the redirectUrl in an iframe.

Pull the result (fallback when not using a webhook)

GET https://kodemed.<your-host>/api/v1/coding/session/{sessionId}/result/data
Authorization: Bearer <coder JWT>

Two identifiers, one source of truth

  • instanceId — HIS-supplied correlator for the submitted data payload. One instanceId maps to one POST; the payload may carry one or many SPIGES <Fall> cases — all share this id. MUST be unique across active sessions (duplicate → 409 Conflict). Echoed back in the webhook for HIS-side matching.
  • sessionId — UUID v4 generated by KodeMed when accepting the session. Used by the iframe URL, the result GET, and the webhook payload. Use it for KodeMed-side tracing.

There is no separate correlationId in the contract — only the two above. The OpenAPI spec (linked below) is the source of truth.

Webhook Contract Server-side dispatch — #493 in flight

For HIS REST integrations, KodeMed fires the webhook server-side when the coder clicks Apply or Discard. The URL, signing strategy, retry schedule and event filter are configured per-IntegrationProfile via the admin API (#492); there are no env vars for this flow.

For the legacy DLL flow, the HTTP POST is fired by the DLL itself and configured via the KODEMED_HOOK_* env vars on the Server. See the Configuration Reference → DLL Webhook for that path.

Outbound payload

POST https://your-his-host/your/webhook
Authorization: HMAC-SHA256 t=<unix>,v1=<hex> (when signing=HMAC_SHA256; signs body + timestamp; 5-min skew)
Idempotency-Key: <sessionId>:<event_type> (for HIS-side dedupe)
Content-Type: application/json
 
{
  "event_type": "case.coded" | "case.discarded",
  "occurred_at": "2026-05-08T...",
  "session_id": "<uuid v4>",
  "instance_id": "<the same correlation the HIS sent in step 1>",
  "spiges": { "ent_id": "...", "burnr": "...", "fall_id": "..." },
  "result_data": "<final SPIGES XML>"
}

Retry schedule

  • 4 attempts: ~30s, ~2min, ~10min between attempts (configurable per profile).
  • HTTP 5xx, 429, network errors → retried.
  • HTTP 4xx (except 429) → terminal → payload moves to the dead-letter queue (DLQ).
  • DLQ replay through the admin API (#494): POST /api/v1/admin/webhook-failures/{id}/replay.

Signature verification (HMAC-SHA256)

When webhook.signing=HMAC_SHA256 is set on the IntegrationProfile, the HIS reads the Authorization: HMAC-SHA256 t=<unix>,v1=<hex> header and re-computes HMAC-SHA256 over <t>.<raw body> with the shared secret. Reject the request if the timestamp is older than 5 minutes (replay protection).

Node receiver:

const crypto = require('crypto');
function verify(req, secret) {
  const auth = req.headers['authorization'] || '';
  const m = auth.match(/^HMAC-SHA256 t=(\d+),v1=([a-f0-9]+)$/i);
  if (!m) return false;
  const [, t, sig] = m;
  if (Math.abs(Date.now()/1000 - Number(t)) > 300) return false;
  const expected = crypto.createHmac('sha256', secret).update(t + '.' + req.rawBody).digest('hex');
  return crypto.timingSafeEqual(Buffer.from(sig,'hex'), Buffer.from(expected,'hex'));
}

Python receiver:

import hmac, hashlib, re, time
def verify(headers, raw_body, secret):
  auth = headers.get('Authorization', '')
  m = re.match(r'^HMAC-SHA256 t=(\d+),v1=([a-f0-9]+)$', auth, re.I)
  if not m: return False
  t, sig = m.group(1), m.group(2)
  if abs(time.time() - int(t)) > 300: return False
  mac = hmac.new(secret.encode(), (t + '.' + raw_body).encode(), hashlib.sha256).hexdigest()
  return hmac.compare_digest(mac, sig)

External OIDC IDP Setup Multi-issuer — #495 in flight

The HIS keeps its own OIDC-compliant Identity Provider — Keycloak, Azure AD, Okta, Auth0, Ping, Authelia, KodeMed’s bundled Keycloak, any vendor that implements OpenID Connect Discovery + RS256-signed JWTs. KodeMed validates the JWT against the configured issuer; no second login on the KodeMed side, no federation, no token exchange.

Issuer configuration

  • Issuer URL format: whatever your IDP publishes. Common shapes — Keycloak: https://<host>/realms/<realm>; Azure AD: https://login.microsoftonline.com/<tenant>/v2.0; Okta: https://<org>.okta.com/oauth2/default. The exact value is what appears in the iss claim of issued tokens.
  • JWKS auto-discovery via <issuer>/.well-known/openid-configuration — no manual key exchange. KodeMed reads jwks_uri from the discovery document and refreshes keys on rotation.
  • Algorithm: RS256 (asymmetric). HS256 / symmetric secrets are not accepted — they require sharing the signing key with KodeMed, which defeats the IDP boundary.
  • Audience (aud) check: optional; configurable per IntegrationProfile (oidc.audience).
  • Multi-issuer: KodeMed can validate tokens from N issuers (test + prod, or several HIS partners). Each issuer corresponds to one IntegrationProfile — different partners can run different IDP products side by side.
  • The KodeMed host needs HTTPS reachability to the issuer URL.

Required token claims

ClaimUseRequired
subStable user id; used to map the coder to a local KodeMed user record on first login.yes
issMust match the configured IntegrationProfile issuer URL.yes
exp, iatStandard JWT lifetime claims.yes
realm_access.roles or groupsRole names for authorisation (see below). KodeMed reads realm_access.roles first, falls back to groups.yes
audAudience; checked when oidc.audience is configured on the profile.conditional
preferred_username, emailUseful for the coder mapping on first login (audit trail).recommended

Required role names

Configure the following role (or group) names in your IDP so authorisation works by default. On Keycloak that’s a realm role; on Azure AD an app-role mapped through groups; on Okta a group claim. Override the names per profile if the partner cannot use these defaults.

  • kodemed-coder — access to the coding UI and POST /api/v1/coding/session.
  • kodemed-admin — access to admin endpoints (/api/v1/admin/*) including DLQ replay.
  • kodemed-viewer — read-only access (optional; not enforced today, planned).

Iframe Embed + CSP Per-profile CSP — #496 in flight

The HIS opens KodeMed’s redirectUrl in an <iframe>. KodeMed sets Content-Security-Policy: frame-ancestors to allow only the configured HIS hostnames; everything else is blocked.

  • Configure per IntegrationProfile: embed.csp_frame_ancestors: ["https://your-his-test.example.com", "https://your-his.example.com"] (admin API).
  • Cookies on the KodeMed UI are already SameSite=None; Secure, so cross-origin iframe sessions work without changes on the HIS side.
  • Legacy: the demo deployment historically used X-Frame-Options: DENY. The new flow replaces this with the per-profile CSP frame-ancestors.

OpenAPI Specifications

The full contract is published as OpenAPI 3.1 by every service. Same paths apply on a self-hosted installation — substitute your KodeMed hostname.

Coding Server (REST + webhook) Swagger UI · raw spec
DataServer (classifications, ICD-10, CHOP, ATC) Swagger UI · raw spec
Grouper (DRG, TARPSY, ST Reha) Swagger UI · raw spec

The Authorize button on Swagger UI accepts a test JWT directly, once your realm issuer is configured.

Interconnection Matrix

Which component talks to which. Use this to size a deployment, open firewall holes, or scope a security review. Read each section for a different concern — A is what to expose externally, B is egress your network must allow, C is intra-host wiring (Docker compose / OpenShift), D is local-machine COM bridges.

A. Customer-facing — partner / browser ↔ KodeMed via Caddy on port 443

Open these in the partner firewall. Public URL hosts are the canonical ones (kodemed.ch subdomains); your on-prem install will substitute its own.

FromToPublic URLPortAuth
HISKodeMed Serverserver.kodemed.ch443OIDC JWT (partner IdP)
HISKodeMed UI (iframe)coding-ui.kodemed.ch443session cookie + CSP frame-ancestors
Browser (coder)KodeMed UIcoding-ui.kodemed.ch443OIDC code flow
BrowserKodeMed Server (REST + WSS)server.kodemed.ch443OIDC JWT (user)
BrowserDataServer (via Caddy)data.kodemed.ch443OIDC JWT (user)
BrowserGrouper (via Caddy)grouper.kodemed.ch443OIDC JWT (user)
CodingClient (tray)KodeMed Serverwss://server.kodemed.ch/ws/dll443OIDC JWT
BrowserOIDC IdP (auth code flow)your IdP host443cookies / TLS

B. Outbound from KodeMed — egress rules

Allow these in the KodeMed host's outbound firewall. Endpoints reach the public internet (or partner-side webhooks).

FromToProtocolPortAuth
KodeMed ServerHIS webhookHTTPS POSTpartner-definedHMAC-SHA256 over body
KodeMed ServerSMTP relay (feedback)SMTP / STARTTLS587 (or 465)SMTP basic auth
KodeMed ServerGitBucket API (feedback alt)HTTPS443bot token (optional transport)
Keycloak (KodeMed SSO)SMTP relaySMTP / STARTTLS587SMTP basic auth
All servicesOIDC IdP — JWKS fetchHTTPS443public, periodic re-fetch
Customer Docker hostHarbor registryHTTPS443robot account / read-only token

C. Internal — container ↔ container on the host network

These ports are NOT exposed externally. Docker compose wires them on the kodemed network; OpenShift wires them via Services. Open them in firewall ONLY if you split components across hosts.

FromToProtocolInternal portAuth
CaddyKodeMed UI (nginx)HTTP80
CaddyKodeMed ServerHTTPS REST + WSS8080JWT pass-through
CaddyDataServerHTTPS REST8081JWT pass-through
CaddyGrouperHTTPS REST8082JWT pass-through
KodeMed ServerDataServerHTTPS REST8081service JWT (internal)
KodeMed ServerGrouperHTTPS REST8082service JWT (internal)
GrouperDataServerHTTPS REST8081service JWT (internal)
Server / DataServer / KeycloakPostgreSQLTCP / TLS5432password (encrypted at rest)

D. Local-machine — COM DLL bridge (Windows host only)

In-process COM activation + a loopback WebSocket between the COM DLL and the CodingClient tray. Nothing leaves the workstation.

FromToProtocolPortAuth
HIS (in-process)KodeMed COM DLLCOM / DCOMlocal user
KodeMed COM DLLCodingClient (tray)WebSocket loopback9876local token

Auth column conventionsOIDC JWT (user) is the browser-bearer access token (audience: kodemed-ui). service JWT (internal) is the audience-restricted token issued by KodeMed Server to call DataServer / Grouper. HMAC-SHA256 applies only to HIS webhooks. All HTTPS hops carry standard security headers (HSTS, CSP, X-Frame-Options) enforced by SecurityConfig. See Roles & Permissions for the role-name table.

Sequence — HIS REST integration

Happy-path flow from HIS-initiated session create to webhook delivery + result fetch.

sequenceDiagram
    autonumber
    participant HIS as HIS
    participant IDP as Partner OIDC IDP
    participant S as KodeMed Server
    participant UI as KodeMed UI
    participant WH as HIS webhook

    HIS->>IDP: client_credentials
    IDP-->>HIS: JWT
    HIS->>S: POST /api/v1/coding/session
{ data, format:"spiges", source:"API", instanceId } S->>S: validate JWT (JWK) S->>S: persist session S-->>HIS: 201 { sessionId, redirectUrl, expiresAt } HIS->>UI: load redirectUrl in iframe Note over UI: coder edits, presses Apply UI-->>S: result S->>WH: HMAC-sign + POST
{ event_type, occurred_at, session_id,
instance_id, spiges, result_data } WH-->>S: 2xx opt HIS without webhook HIS->>S: GET /api/v1/coding/session/{id}/result/data S-->>HIS: 200 result_data end

If the webhook gets a 5xx/timeout, KodeMed retries with the per-tenant webhookRetryDelaysSeconds (default 30s, 2m, 10m) and persists to the dead-letter queue if all attempts fail (replay via POST /api/v1/admin/webhook-failures/{id}/replay).

Sequence — DLL / CodingClient flow Legacy

In-process integration via the COM DLL for HIS that cannot make HTTPS calls but can load a Windows DLL. Same end-state (modified SPIGES returned to HIS) but the transport is COM ↔ loopback WebSocket ↔ KodeMed Server.

sequenceDiagram
    autonumber
    participant HIS as HIS process
    participant DLL as COM DLL
    participant CC as CodingClient (tray)
    participant S as KodeMed Server
    participant B as Browser

    HIS->>DLL: StartCoding(spiges)
    DLL->>CC: ws://127.0.0.1:9876
    CC->>S: wss /ws/dll (JWT)
    S-->>CC: ack
    CC->>S: CODING_SESSION_LAUNCH { sessionId, data }
    S->>B: open in browser
    Note over B: coder edits, presses Apply
    S-->>CC: CODING_SESSION_COMPLETE (sessionId, result)
    CC-->>DLL: result
    DLL-->>HIS: result (SPIGES)

The DLL writes the modified SPIGES back to the HIS in-process. No outbound webhook is fired in this flow — the HIS already has the result by the time StartCoding returns.

Sequence — Webhook retry + dead-letter

Failure path. Server retries on 5xx / 408 / 429 / network errors with the per-tenant webhookRetryDelaysSeconds schedule; after the last attempt the payload is persisted to the WebhookFailure dead-letter table and an admin can replay it.

sequenceDiagram
    autonumber
    participant S as KodeMed Server
    participant WH as HIS webhook
    participant DLQ as WebhookFailure (DLQ)
    participant ADM as Admin

    S->>WH: attempt 1 (POST)
    WH-->>S: 503
    Note over S: wait 30s
    S->>WH: attempt 2 (POST)
    WH-->>S: timeout
    Note over S: wait 2m
    S->>WH: attempt 3 (POST)
    WH-->>S: 502
    Note over S: wait 10m
    S->>WH: attempt 4 (POST)
    WH-->>S: 502
    S->>DLQ: persist row
{ profileSlug, sessionId, instanceId,
eventType, payload, attempts:4,
lastError, lastHttpStatus } ADM->>DLQ: GET /api/v1/admin/webhook-failures DLQ-->>ADM: 200 [ {id,…}, … ] ADM->>S: POST .../{id}/replay S->>WH: replay POST WH-->>S: 200 S->>DLQ: mark row delivered

Retry schedule + max attempts are stored on the IntegrationProfile per partner. Default is 4 attempts at 30s / 2m / 10m / 1h. Idempotency: the Idempotency-Key header is stable across retries so the HIS can dedupe.

Sequence — REST polling fallback

For HIS that cannot host a public webhook endpoint. Create the session, poll for completion. Same end-state as the webhook flow but driven by the HIS.

sequenceDiagram
    autonumber
    participant HIS as HIS
    participant S as KodeMed Server
    participant UI as KodeMed UI

    HIS->>S: POST /api/v1/coding/session
    S-->>HIS: 201 { sessionId, redirectUrl, expiresAt }
    HIS->>UI: open redirectUrl in iframe
    Note over UI: coder edits, presses Apply
    UI-->>S: result
    loop poll every 5-30s
        HIS->>S: GET /api/v1/coding/session/{id}/status
        S-->>HIS: 200 { status:"COMPLETED", action:"APPLIED" }
    end
    HIS->>S: GET /api/v1/coding/session/{id}/result/data
    S-->>HIS: 200 result_data (SPIGES)

Recommended poll interval: 5s while the iframe is visible, 30s after it closes. The expiresAt on the create response upper-bounds how long the session is pollable.

Sequence — CodingClient WebSocket /ws/dll

For native desktop integrations that already speak WebSocket. Skip the DLL transport; speak /ws/dll directly to the Server. Same message vocabulary as the DLL flow.

sequenceDiagram
    autonumber
    participant NC as Native client
    participant S as KodeMed Server
    participant B as Browser (UI)

    NC->>S: wss /ws/dll ?token=JWT
    S-->>NC: HELLO { instanceId }
    NC->>S: REGISTER { user, instance }
    S-->>NC: REGISTERED
    NC->>S: CODING_SESSION_LAUNCH
{ instanceId, data:SPIGES } S-->>NC: SESSION_OPENED { sessionId } S->>B: open in browser Note over B: coder Apply B-->>S: result S-->>NC: CODING_SESSION_COMPLETE
{ sessionId, result_data }

The token is validated on every frame; an expired JWT closes the socket with code 4001. Reconnect with a refreshed token to resume; the server holds the session-id mapping in-memory for sessionExpiryMinutes.

Sequence — DataServer thesaurus lookup

For HIS that wants to autocomplete ICD-10 / CHOP / ATC codes in its own UI before opening a coding session. Direct REST call against the DataServer.

sequenceDiagram
    autonumber
    participant UI as HIS UI
    participant DS as DataServer
    participant PG as PostgreSQL

    UI->>DS: GET /api/thesaurus/search?q=J96.0
Authorization: Bearer JWT DS->>PG: SELECT … LIMIT 20 PG-->>DS: rows DS-->>UI: 200 [ { code, label, version, … } ] UI->>DS: GET /api/thesaurus/rules/code/ICD10/J96.0?version=2026 DS->>PG: SELECT rules WHERE code=$1 AND version=$2 PG-->>DS: rows DS-->>UI: 200 { rules:[ { kind:"INCLUSION", text:"…" }, … ] }

Cached at If-None-Match / ETag; year-versioned so a 2026 lookup can never silently fall back to 2025 data. Full schema in the DataServer Swagger UI.

Sequence — Grouper grouping

For HIS that wants to run a real-time SwissDRG / TARPSY / ST Reha grouping without involving a coder. Direct REST call against the Grouper.

sequenceDiagram
    autonumber
    participant HIS as HIS
    participant G as Grouper
    participant DS as DataServer

    HIS->>G: POST /api/v1/grouper/group
{ tariff:"SWISSDRG", version:"15.0",
case:{ Fall:…, Diag:[…], Proc:[…] } } G->>G: load specs + catalogue (cached) G->>DS: POST /api/zusatzentgelt/match DS-->>G: supplements [] G-->>HIS: 200 { drg:"E62A", kostengewicht:1.234,
supplements:[…], trace:[…] }

The trace field carries the grouping decision path so a HIS audit log can reconstruct why a given DRG was assigned. GET /api/v1/grouper/versions lists the tariff × year combinations the deployment supports.

Sequence — OIDC login + iframe session cookie

How the clinician's JWT becomes a same-site cookie that survives the iframe load when the UI is embedded inside the HIS page.

sequenceDiagram
    autonumber
    participant B as Browser (HIS page)
    participant IDP as Partner OIDC IDP
    participant UI as KodeMed UI
    participant S as KodeMed Server

    B->>UI: iframe src=redirectUrl
    UI->>S: /actuator/info
    S-->>UI: 200
    UI->>UI: load runtime-config (issuer URL, etc.)
    B->>IDP: (silent) /authorize?prompt=none
    IDP-->>B: 302 with code
    B->>IDP: /token (PKCE)
    IDP-->>B: id_token + access_token + refresh
    B->>UI: set SameSite=None Secure cookie
    UI->>S: REST + WS calls
Authorization: JWT

The HIS site must be listed in IntegrationProfile.embedCspFrameAncestors for the browser to accept the iframe. The SameSite=None cookie attribute is mandatory for cross-origin iframe sessions and is the default for HIS REST integrations.

SPIGES XSD Schema

Validate caseData / resultData against the SPIGES XML schema before posting to /api/v1/coding/session or before accepting a webhook payload. Pin to a specific version — KodeMed will publish each new revision side-by-side.

XSD validation samples in C# / Java / Python are bundled in the Test.UI partner sample below.

Reference Sample — KodeMed.Test.UI C# / WinForms Apache 2.0

A single-window C# WinForms app that exercises every integration entry point in one place: REST session create, iframe embed, WebSocket signalling, webhook receiver stub, DLL/COM call. Use it as a partner reference implementation — the screen labels match the REST and WebSocket field names so you can map your own HIS code 1:1.

Clone or download:

# Option 1 — clone the GitBucket repo (always up to date)
git clone ssh://git@www.mieresit.com:7777/kodemed/KodeMed.Test.UI.git
cd KodeMed.Test.UI
dotnet run

# Option 2 — offline zip
unzip KodeMed.Test.UI-sources.zip
cd KodeMed.Test.UI
dotnet run

Technical Documentation Confidential

Integration & API

  • GetResults() Return Format MD PDF

Classification Data

All classification data is pre-packaged and distributed via Harbor OCI artifacts. Pull with the ORAS CLI (one-line install for Linux/macOS/Windows):

# Classification data (ICD-10, CHOP, SwissDRG, TARPSY, ST Reha, medications, abbreviations, coding rules)
oras pull harbor.mieresit.com/kodemed/data:2026 -o import-data/
 
# Grouper specifications (.sgs files) — provided separately with customer contract
# Contact support@kodemed.ch for grouper spec files

On first deployment, files in import-data/ are imported automatically when the DataServer starts. For re-imports, use the DataServer admin API. See the DevOps Guide §14 for details.

Original data sources (packaged by KodeMed AG):

Deployment Architecture

  • Native Linux PNG
  • Docker Compose PNG
  • OpenShift / Kubernetes PNG

DevOps Guide Confidential

Server Administration & Deployment

  • DevOps Guide (DE) PDF
  • DevOps Guide (FR) PDF
  • DevOps Guide (IT) PDF
  • DevOps Guide (EN) PDF

Markdown Documentation

  • DevOps & Server Admin Guide MD

Compose layers

KodeMed ships in three independently composable layers. The same container images run in every customer deployment; what differs is which layers you compose alongside and where TLS terminates.

+----------------------------------------------------------------+
| IDP layer (OIDC Identity Provider) |
| bundled Keycloak (turnkey, federates LDAP/AD) |
| or your existing Keycloak / Azure AD / Okta / Auth0 / ... |
+----------------------------------------------------------------+
^
| OIDC (JWT validation via JWK)
+----------------------------------------------------------------+
| TLS layer (terminate HTTPS at the edge — one of): |
| bundled Caddy (auto Let's Encrypt or custom cert) |
| your reverse proxy (nginx / Apache / HAProxy / Traefik) |
| OpenShift / Kubernetes Ingress |
+----------------------------------------------------------------+
^
| HTTP on the Docker bridge
+----------------------------------------------------------------+
| App layer (always present — kodemed-* services + Postgres) |
| postgres (state) |
| kodemed-server : 8080 (REST + WebSocket) |
| kodemed-dataserver : 8081 (thesaurus / classifications) |
| kodemed-grouper : 8082 (DRG / TARPSY / ST Reha) |
| kodemed-ui : 3000 (React frontend) |
| kodemed-downloads : 8090 (this portal, optional) |
+----------------------------------------------------------------+

Important callouts:

  • The app layer’s compose file does not include the TLS layer or the IDP layer — both are pluggable. The Deployment Wizard ships overlay compose files that opt-in to bundled Caddy and / or bundled Keycloak.
  • Run with overlays via docker compose -f docker-compose.yml -f docker-compose.caddy.yml -f docker-compose.keycloak.yml up -d. Drop any overlay you don’t need.
  • App-layer containers listen only on the Docker bridge by default — they never bind to public ports without an explicit TLS layer in front. That holds for the bundled Caddy overlay, your own reverse proxy, and the OpenShift / Kubernetes path.
  • The wizard preset localhost bundles everything for evaluation (Keycloak on :9443, services on localhost ports, no Caddy). The wizard preset saas-domain bundles Caddy with Let’s Encrypt and points at an external IDP. See the Deployment Wizard section.

Troubleshooting

"authorization failed: no basic auth credentials" on docker compose up

You need to log in to the Harbor registry before pulling images:

docker login harbor.mieresit.com

Enter the Harbor username and password provided by KodeMed AG. The login persists in ~/.docker/config.json and only needs to be done once per machine.

DLL client opens wrong URL (404 on coding page)

KODEMED_PUBLIC_UI_URL is not set. The server returns its own URL instead of the CodingUI URL.

KODEMED_PUBLIC_UI_URL=https://coding-ui.hospital.ch

The server logs a warning at startup: CONFIG ⚠ KODEMED_PUBLIC_UI_URL is not set

WebSocket returns HTTP 200 instead of 101

Reverse proxy is not upgrading WebSocket connections. Enable mod_proxy_wstunnel (Apache) or add Upgrade headers (nginx).

# Apache
a2enmod proxy_wstunnel rewrite
RewriteCond %{HTTP:Upgrade} websocket [NC]
RewriteRule ^/ws/(.*) ws://localhost:8080/ws/$1 [P,L]
Login fails after entering credentials (page reloads or shows error)

After entering the correct username and password, the page reloads back to the login form or shows a connection error. This is usually caused by a KC_HOSTNAME mismatch in the .env file.

Cause: Keycloak uses KC_HOSTNAME and KC_HOSTNAME_PORT to construct redirect URLs after authentication. If these default to localhost:9080, the browser is redirected to https://localhost:9080/... which does not exist.

Fix: In .env, set the correct values for your setup:

# IP-based access via Caddy
KC_HOSTNAME=192.168.1.100
KC_HOSTNAME_PORT=9443
# Hostname-based access via Caddy
KC_HOSTNAME=kodemed.hospital.ch
KC_HOSTNAME_PORT=9443

Then restart: docker compose down && docker compose up -d

Tip: Also verify OIDC_ISSUER_URI matches. It should be https://YOUR_HOST:9443/realms/kodemed.

"admin" account does not work on the login page

The admin account is the Keycloak administrator (master realm). It only works at the Keycloak admin console: https://HOST:9443/admin.

To log into the KodeMed application, use one of the demo accounts:

demo-admin / KodeMed2026! (administrator)
demo-coder / KodeMed2026! (medical coder)
demo-approver / KodeMed2026! (approver + coder)
demo-viewer / KodeMed2026! (read-only)
demo-auditor / KodeMed2026! (audit trail access)
demo-data-admin / KodeMed2026! (data import admin)

The Keycloak admin password is in your .env file: KEYCLOAK_ADMIN_PASSWORD.

Self-signed certificate: browser blocks API calls or login

When using Caddy with self-signed certificates, each port has a separate certificate. The browser must accept each one individually before the application works.

Open these URLs in order and accept the certificate warning on each one:

  1. https://HOST:9443 — Keycloak (required for login)
  2. https://HOST:8443 — Server API (required for data)
  3. https://HOST:8444 — DataServer API (required for classifications)
  4. https://HOST — CodingUI (the application)

If you skip a port, the browser silently blocks requests to that service and the app appears broken. For production, use a custom certificate (see the Deployment Wizard → TLS Certificate Mode → Custom certificate).

Login works but UI is empty / shows "CORS Failed" / WebSocket error 1015 in console

You log in successfully (the console shows Logged in as: demo-coder), but the UI stays empty or hangs on a spinner. The browser console contains one or more of:

  • CORS Failed on XHROPTIONS https://HOST:8443/api/v1/...
  • Code d'état : (null) / Status: (null) — the preflight never received a response
  • [WebSocket] Connection error followed by code: 1015
  • Firefox ne peut établir de connexion avec le serveur à l'adresse wss://HOST:8443/ws/dll...

Cause: WebSocket close-code 1015 is the standard signal for TLS handshake failure. The browser has accepted the self-signed cert for the UI port (:443) but NOT for the API port (:8443), so the XHR preflight and the WSS upgrade both die before they reach the server — that's why the XHR shows Status: (null) instead of a CORS-specific 403. The JWT itself is fine (you can decode it and confirm allowed-origins covers your host); the issue is purely TLS trust at the browser level.

Fix: open https://HOST:8443/actuator/health in a new tab, click Advanced → Proceed, confirm the response is {"status":"UP"}, then reload the UI tab. Do the same for :8444 and :8445 if their endpoints are called from the UI. Self-signed cert exceptions are tracked per origin (scheme + host + port), so the exception you clicked through on :443 does NOT cover :8443.

Verify it's TLS and not CORS: if the response status were a non-null number (e.g. 403) you'd be looking at a real CORS misconfiguration — check CORS_ALLOWED_ORIGINS in .env. The (null) status is the giveaway that the request never even completed the TLS handshake.

Custom certificate: Caddy refuses to start or shows TLS error

Check these common causes:

  • Wrong format: Certificates must be PEM-encoded (base64 text, not binary DER). Convert with: openssl x509 -in cert.der -inform DER -out cert.pem -outform PEM
  • Incomplete chain: cert.pem must contain the full chain: server certificate + intermediate CA(s). Missing intermediates cause “certificate signed by unknown authority” on some clients.
  • Key mismatch: Verify cert and key match: openssl x509 -noout -modulus -in certs/cert.pem | md5sum must equal openssl rsa -noout -modulus -in certs/key.pem | md5sum
  • File permissions: Caddy runs as non-root. Ensure certs/cert.pem and certs/key.pem are readable: chmod 644 certs/*.pem

Check Caddy logs: docker compose logs caddy

UI shows blank page (no content loads)

The browser opens the KodeMed URL but the page is empty or shows a white screen. This is usually caused by a misconfigured runtime-config.js.

What is runtime-config.js? This file tells the CodingUI where to find the backend services (Server, DataServer, Grouper, Keycloak). If the URLs are wrong or the file is missing, the UI cannot load.

Docker deployment: Check the mounted file:

docker exec kodemed-ui cat /usr/share/nginx/html/runtime-config.js

Native install: Check the generated file:

cat /opt/kodemed/ui/runtime-config.js

All URLs must be reachable from the user’s browser (not from the server). Common fixes:

  • Replace localhost with the server’s IP or hostname
  • Ensure the protocol matches your setup: https:// for Caddy/proxy, http:// only for localhost testing
  • After editing, restart the UI: docker compose restart kodemed-ui or sudo systemctl restart kodemed-ui
ERR_SSL_PROTOCOL_ERROR on port 3000

You are accessing https://HOST:3000 but port 3000 is the internal UI port that only speaks HTTP. This port should never be accessed directly with HTTPS.

Docker + Caddy: Access the UI via Caddy on port 443: https://HOST (no port needed).

Native install (localhost only): Use http://localhost:3000. For remote access, set up a reverse proxy (nginx, Apache) that terminates TLS and forwards to port 3000.

Important: HTTP is only supported for localhost testing. All production deployments must use HTTPS via a reverse proxy or Caddy.

Native install: services fail with “Connection refused” (PostgreSQL)

The server cannot connect to PostgreSQL. The native installer does not install PostgreSQL — you must set it up before running install-kodemed.sh.

# Install PostgreSQL
sudo apt install -y postgresql postgresql-contrib
sudo systemctl enable --now postgresql
# Create database and user
sudo -u postgres psql -c "CREATE USER kodemed WITH PASSWORD 'YOUR_PASSWORD';"
sudo -u postgres psql -c "CREATE DATABASE kodemed OWNER kodemed;"
# Verify connection works
psql -h localhost -U kodemed -d kodemed -c "SELECT 1"

Then update /etc/kodemed/kodemed.env with the correct password and restart: sudo systemctl restart kodemed-server kodemed-dataserver kodemed-grouperserver

Login redirect loop: "Invalid scopes: openid profile email"

The browser keeps redirecting between the app and Keycloak with error=invalid_scope in the URL. This means the OIDC client scopes are not assigned in Keycloak.

Docker deployment (bundled Keycloak): The keycloak-init container fixes this automatically. Check its logs:

docker compose logs keycloak-init

If it shows "WARNING: kodemed-ui not found", stop everything and start fresh:

docker compose down -v
docker compose up -d

Native / external Keycloak: In Keycloak Admin → Realm "kodemed" → Clients → kodemed-ui (and kodemed-dll) → Client scopes tab, verify these default scopes are assigned: openid, profile, email, web-origins, acr, basic, roles.

CORS error after login: "Access-Control-Allow-Origin missing"

The token exchange from the UI to Keycloak is blocked by CORS. In Keycloak Admin → Realm "kodemed" → Clients → kodemed-ui → Settings, add your UI origin to Web Origins:

http://localhost:3000
https://coding-ui.hospital.ch

The + wildcard should resolve origins from redirect URIs, but adding explicit origins is more reliable.

UI loads but shows blank page or login loop (network deployment)

The runtime-config.js uses localhost but you are accessing the UI from another machine. The browser tries to reach Keycloak/Server on localhost (the user's PC), which fails.

Fix: Replace localhost with the server's IP or hostname in runtime-config.js:

sed -i 's|localhost|192.168.1.100|g' runtime-config.js
docker compose restart kodemed-ui

Also update KC_HOSTNAME and CORS_ALLOWED_ORIGINS in .env to match the server IP/hostname, then restart Keycloak:

KC_HOSTNAME=192.168.1.100
CORS_ALLOWED_ORIGINS=http://192.168.1.100:3000
WEBSOCKET_ALLOWED_ORIGINS=http://192.168.1.100:3000
Portal returns 500 on login

Check oauth2-proxy logs: docker compose logs kodemed-downloads-auth

unauthorized_client → Client secret mismatch with OIDC provider
invalid_scope → Remove groups from OAUTH2_PROXY_SCOPE
invalid_redirect_uri → Check redirect URI matches OIDC client config

Server startup configuration warnings

Check logs after deployment:

docker compose logs kodemed-server | grep "CONFIG"

CONFIG ✓ All public URL configuration present — all good
CONFIG ⚠ KODEMED_PUBLIC_* — specific variable is missing

Required Environment Variables

VariableRequiredPurpose
KODEMED_PUBLIC_UI_URLYesCodingUI URL for browser redirect
KODEMED_PUBLIC_SERVER_URLRecommendedServer URL for DLL clients
KODEMED_PUBLIC_DATASERVER_URLRecommendedDataServer URL for classification data
KODEMED_PUBLIC_WEBSOCKET_URLRecommendedWebSocket URL for real-time communication
KODEMED_PUBLIC_GROUPER_URLRecommendedGrouperServer URL for DRG grouping
OIDC_ISSUER_URIYesOIDC issuer for token validation
CORS_ALLOWED_ORIGINSYesAllowed origins for API requests

License Generator Admin

Generate License File

Fill in the customer details below. The tool generates a signed kodemed.license file ready to send to the customer.

CLI Command

Run this on a machine with the private key and the kodemed-license-cli.jar:


        
        Copied!
      

License CLI Tool

Download the CLI JAR to generate and verify license files.

  • kodemed-license-cli.jar Download JAR
  • Quick Reference java -jar kodemed-license-cli.jar generate --private-key key.pem --type DEMO --org "Hospital" --days 90 --output kodemed.license

Configuration Reference

Complete list of all environment variables, settings, and config keys across all KodeMed components. Use the filter to search.

KodeMed Server Port 8080

Main coding API — Spring Boot backend handling DLL sessions, WebSocket, persistence, webhooks.

Environment VariableDefaultDescription
SERVER_PORT8080HTTP server port
SPRING_PROFILES_ACTIVE(none)Active Spring profiles (use prod for production)
SPRING_DATASOURCE_URLjdbc:h2:mem:kodemedJDBC connection URL (PostgreSQL in prod)
SPRING_DATASOURCE_USERNAMEsaDatabase username
SPRING_DATASOURCE_PASSWORD(empty)Database password
SPRING_DATASOURCE_DRIVER_CLASS_NAMEorg.h2.DriverJDBC driver class (org.postgresql.Driver in prod)
SPRING_DATASOURCE_HIKARI_MAXIMUM_POOL_SIZE15HikariCP max connection pool size
SPRING_DATASOURCE_HIKARI_MINIMUM_IDLE5HikariCP minimum idle connections
SPRING_DATASOURCE_HIKARI_IDLE_TIMEOUT300000HikariCP idle timeout (ms)
SPRING_DATASOURCE_HIKARI_CONNECTION_TIMEOUT20000HikariCP connection timeout (ms)
SPRING_JPA_HIBERNATE_DDL_AUTOupdateHibernate DDL strategy (consider validate for prod)
Authentication is mandatory and not configurable. The legacy KODEMED_AUTH_ENABLED / GROUPER_AUTH_ENABLED flags have been removed — JWT validation runs on every endpoint that is not explicitly listed as public in SecurityConfig.
SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI(Keycloak URL)OIDC issuer URI for JWT validation
SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_JWK_SET_URI(Keycloak certs URL)JWK Set URI for JWT validation
CORS_ALLOWED_ORIGINS(empty)Comma-separated CORS allowed origins
WEBSOCKET_ALLOWED_ORIGINS(empty)WebSocket allowed origins
KODEMED_ENCRYPTION_KEY(empty)AES-256 key (base64, 32 bytes). Generate: openssl rand -base64 32
KODEMED_LICENSE_FILE(auto-search)Path to kodemed.license file
KODEMED_PUBLIC_SERVER_URL(auto-detected)Public URL for the server API (returned to DLL)
KODEMED_PUBLIC_DATASERVER_URL(auto-detected)Public URL for DataServer (returned to DLL)
KODEMED_PUBLIC_GROUPER_URL(auto-detected)Public URL for Grouper (returned to DLL). Required in multi-host deployments where Grouper is not on the same hostname as Server.
KODEMED_PUBLIC_WEBSOCKET_URL(auto-detected)Public WebSocket URL wss:// (returned to DLL)
KODEMED_PUBLIC_UI_URL(auto-detected)Public URL for CodingUI (returned to DLL)
KODEMED_OAUTH2_REALMkodemedOAuth2 realm name (returned to DLL)
KODEMED_OAUTH2_CLIENT_IDkodemed-dllOAuth2 client ID for DLL authentication
KODEMED_PERSISTENCE_ENABLEDtrueEnable persistent storage of coding sessions
KODEMED_AUDIT_ENABLEDtrueEnable audit trail (requires persistence)
KODEMED_RETENTION_DAYS90Retention for completed sessions (days, 0=forever)
KODEMED_STORE_ORIGINALtrueStore original case data alongside changes
KODEMED_PERSISTENCE_SESSION_EXPIRY_MINUTES60Session expiry timeout (minutes)
KODEMED_PERSISTENCE_OFFLINE_TIMEOUT_MINUTES15Sessions without heartbeat expire after this (minutes)
DLL Webhook — published by Server via GET /api/v1/config and fired by the DLL (KodeMed.CodingClient/WebhookService.cs). Applies only when the customer uses the Windows tray client. For HIS REST integrations, see the Server Webhook card below.
KODEMED_HOOK_ENABLEDfalseEnable the DLL post-coding webhook
KODEMED_HOOK_URL(empty)Webhook target URL the DLL will POST to
KODEMED_HOOK_AUTH_TYPEnoneDLL webhook auth: none, bearer, header
KODEMED_HOOK_TIMEOUT_SECONDS30DLL webhook HTTP timeout (seconds)
KODEMED_HOOK_RETRY_COUNT3DLL webhook retry count on failure
KODEMED_HOOK_INCLUDE_RESULT_DATAfalseInclude result data in DLL webhook payload
KODEMED_HOOK_INCLUDE_ORIGINAL_DATAfalseInclude original data in DLL webhook payload
KODEMED_HOOK_INCLUDE_GROUPER_RESULTSfalseInclude DRG grouper results in DLL webhook payload
KODEMED_HOOK_EVENTSappliedComma-separated event types to fire the DLL webhook on
KODEMED_RATE_LIMIT_ENABLEDtrueEnable rate limiting (DoS protection)
KODEMED_RATE_LIMIT_CAPACITY100Default token bucket capacity per IP (general endpoints)
KODEMED_RATE_LIMIT_REFILL100Tokens refilled per period (general endpoints)
KODEMED_RATE_LIMIT_PERIOD1Refill period in minutes (default 100 req/min)
KODEMED_RATE_LIMIT_AUTH_CAPACITY20Token bucket capacity for auth-sensitive endpoints (stricter)
KODEMED_RATE_LIMIT_AUTH_REFILL10Tokens refilled per period for auth endpoints
KODEMED_RATE_LIMIT_AUTH_PERIOD1Refill period in minutes for auth endpoints (10 req/min)
KODEMED_UNDO_MAX_PER_CASE50Max undo entries kept per coding case
KODEMED_UNDO_RETENTION_DAYS7Undo entry retention (days)
KODEMED_GROUPER_PORT8082Grouper port (used to derive internal URL published to DLL)
KODEMED_UI_PORT3000UI port (used to derive internal URL)
CONTEXT_PATH(empty)Servlet context path (e.g. /api when behind a path-prefixing reverse proxy)
SERVER_CONNECTION_TIMEOUT20000Tomcat connection timeout (ms)
SERVER_MAX_CONNECTIONS8192Tomcat max simultaneous connections
ASYNC_REQUEST_TIMEOUT60000Async request timeout (ms)
H2_CONSOLE_ENABLEDfalseEnable embedded H2 console (development only — never enable in production)
INSTANCE_DISCONNECT_TIMEOUT60Minutes before disconnected DLL instance is terminated
HEARTBEAT_INTERVAL30WebSocket heartbeat interval (seconds)
RECONNECT_INTERVAL60DLL WebSocket reconnect interval (seconds)
KODEMED_DATASERVER_PORT8081DataServer port (used to derive internal URL)
KODEMED_UI_URL(empty)CodingUI URL for redirects (e.g. https://coding.hospital.ch)
SWAGGER_SERVER_URL(empty)Swagger UI base URL (useful behind reverse proxy)
SWAGGER_OAUTH2_CLIENT_IDkodemed-serverOAuth2 client ID for Swagger UI
TABLE_PREFIXkm_app_Database table prefix
LOGGING_LEVEL_ROOTINFORoot log level (WARN in prod profile)
MANAGEMENT_ENDPOINTShealth,info,metrics,prometheusExposed Spring Actuator endpoints

Server — Feedback / GitBucket integration

The Server forwards user feedback to GitBucket (issues) and SMTP (email). All keys can be left empty in environments where neither is desired. Disable the entire subsystem with KODEMED_FEEDBACK_ENABLED=false.

Environment VariableDefaultDescription
KODEMED_FEEDBACK_ENABLEDtrueMaster toggle for the feedback subsystem
KODEMED_FEEDBACK_LABELtester-feedbackGitBucket label applied to created issues
KODEMED_FEEDBACK_GITBUCKET_URLhttps://www.mieresit.com/gitbucketGitBucket base URL
KODEMED_FEEDBACK_REPOmieres-it/kode-medGitBucket repo (org/repo)
KODEMED_FEEDBACK_MILESTONE(empty)Milestone title to attach to created issues
KODEMED_FEEDBACK_USER(empty)GitBucket user (basic auth)
KODEMED_FEEDBACK_PASSWORD(empty)GitBucket password / API token
KODEMED_FEEDBACK_SMTP_HOST(empty)SMTP server (empty disables email path)
KODEMED_FEEDBACK_SMTP_PORT587SMTP port
KODEMED_FEEDBACK_SMTP_USER(empty)SMTP username
KODEMED_FEEDBACK_SMTP_PASSWORD(empty)SMTP password
KODEMED_FEEDBACK_SMTP_AUTHtrueSMTP authentication enabled
KODEMED_FEEDBACK_SMTP_STARTTLStrueSMTP STARTTLS enabled
KODEMED_FEEDBACK_SMTP_TOsupport@kodemed.chDestination address for feedback emails
KODEMED_FEEDBACK_SMTP_FROM(empty)Sender address (defaults to SMTP user)
KODEMED_FEEDBACK_SMTP_SUBJECT_PREFIX[KodeMed Feedback]Subject prefix for feedback emails
KODEMED_FEEDBACK_QUEUE_DIR./feedback-queueOn-disk queue for retry when GitBucket/SMTP are unreachable

Server Webhook (HIS REST integration) Per-IntegrationProfile · #493 in flight

For HIS REST integrations the Server fires the webhook itself. Configuration is per-IntegrationProfile in the database (set via the admin API, #492). There are no env vars for this flow — the values below are profile fields, not .env entries.

POST /api/v1/admin/integration-profiles
Authorization: Bearer <admin JWT>
Content-Type: application/json
 
{
  "slug": "<your-his-slug>-test",
  "webhook": {
    "url": "https://your-his-host/your/webhook",
    "signing": "HMAC_SHA256",
    "secret_ref": "<encrypted at rest>",
    "retry_max": 4,
    "retry_backoff": "exponential",
    "retry_delays_seconds": [30, 120, 600],
    "event_types": ["case.coded", "case.discarded"]
  }
}

For the full payload shape, retry semantics, DLQ replay path and HMAC signature verification snippets, see Integration → Webhook Contract.

KodeMed DataServer Port 8081

Data import/export service — ICD-10, CHOP, thesaurus, hot-folder import.

Environment VariableDefaultDescription
DATASERVER_PORT / SERVER_PORT8081HTTP port (DATASERVER_PORT takes priority)
SPRING_DATASOURCE_URLjdbc:h2:mem:kodemedJDBC connection URL
SPRING_DATASOURCE_USERNAMEsaDatabase username
SPRING_DATASOURCE_PASSWORD(empty)Database password
Authentication is mandatory. The legacy KODEMED_AUTH_ENABLED flag has been removed.
KODEMED_ADMIN_API_KEY(empty)API key for admin endpoints (import/reimport scripts). Generate: openssl rand -hex 32
KODEMED_LICENSE_FILE(auto-search)Path to kodemed.license file
KODEMED_IMPORT_ENABLEDtrueEnable hot-folder import system
KODEMED_IMPORT_AUTO_SCANfalseAuto-scan inbox on timer (disabled by default for safety)
KODEMED_IMPORT_BASEDIR./importBase directory for import files
KODEMED_IMPORT_INBOX_DIR./import/inboxInbox — place files here for processing
KODEMED_IMPORT_SUCCESS_DIR./import/successSuccess — processed files moved here
KODEMED_IMPORT_ERROR_DIR./import/errorError — failed files moved here
KODEMED_IMPORT_SCAN_INTERVAL_MS30000Scan interval in ms (only when auto-scan=true)
KODEMED_IMPORT_BATCH_SIZE1000Batch size for data imports
KODEMED_THESAURUS_BATCH_SIZE2000Batch size for thesaurus index inserts
KODEMED_THESAURUS_AUTO_BUILDtrueAuto-build thesaurus on startup
KODEMED_THESAURUS_SIMILARITY_THRESHOLD0.85Cosine-similarity floor for fuzzy thesaurus matches (0.0–1.0)
KODEMED_IMPORT_WAIT_TIMEOUT300Max seconds to wait for import to finish before timing out
KODEMED_IMPORT_NO_STATUS_THRESHOLD10Seconds without status update before an import is considered stalled
SPRING_SERVLET_MULTIPART_MAX_FILE_SIZE100MBMaximum multipart upload size (single file)
SPRING_SERVLET_MULTIPART_MAX_REQUEST_SIZE100MBMaximum multipart upload size (whole request)
TABLE_PREFIXkm_data_Database table prefix (different from Server!)
CORS_ALLOWED_ORIGINS(empty)CORS allowed origins
SWAGGER_SERVER_URL(empty)Swagger UI base URL
SWAGGER_OAUTH2_CLIENT_IDkodemed-serverOAuth2 client ID for Swagger UI
LOGGING_LEVEL_ROOTINFORoot log level
MANAGEMENT_ENDPOINTShealth,info,metrics,prometheus,cachesActuator endpoints (includes cache management)

KodeMed GrouperServer Port 8082

DRG grouper — stateless service for SwissDRG, TARPSY, ST Reha grouping. No database required.

Environment VariableDefaultDescription
SERVER_PORT8082HTTP port
Authentication is mandatory. The legacy GROUPER_AUTH_ENABLED flag has been removed.
GROUPER_SPECS_PATH./specsPath to grouper specification files (.sgs)
GROUPER_CATALOGUE_PATH./cataloguesPath to catalogue files (.csv) for cost weights
GROUPER_DEFAULT_SWISSDRG15.0Default SwissDRG version
GROUPER_DEFAULT_TARPSY6.3Default TARPSY version
GROUPER_DEFAULT_STREHA3.4Default ST Reha version
GROUPER_DATASERVER_URLhttp://kodemed-dataserver:8081Required. URL to DataServer for high-cost-medication (Zusatzentgelt) lookups. Without it, supplements silently return empty.
SERVER_CONNECTION_TIMEOUT20000Tomcat connection timeout (ms)
SERVER_MAX_CONNECTIONS8192Tomcat max simultaneous connections
ASYNC_REQUEST_TIMEOUT60000Async request timeout (ms)
KODEMED_LICENSE_FILE(auto-search)Path to kodemed.license file
CORS_ALLOWED_ORIGINS(empty)CORS allowed origins
SWAGGER_SERVER_URL(empty)Swagger UI base URL
SWAGGER_OAUTH2_CLIENT_IDkodemed-serverOAuth2 client ID for Swagger UI
LOGGING_LEVEL_ROOTINFORoot log level
LOGGING_LEVEL_COM_MIERESIT_GROUPERINFOGrouper application log level
LOGGING_LEVEL_SWISSDRGINFOSwissDRG library log level
MANAGEMENT_ENDPOINTShealth,info,metrics,prometheusActuator endpoints

KodeMed CodingUI Port 3000

React frontend served via nginx. Configured at runtime via runtime-config.js.

Config KeySourceDescription
window.__KODEMED_CONFIG__ (runtime-config.js — injected at deploy time)
apiUrlruntime-config.jsMain server API URL (e.g. https://server.hospital.ch/api/v1)
dataServerUrlruntime-config.jsDataServer URL (e.g. https://data.hospital.ch)
grouperServerUrlruntime-config.jsGrouperServer URL (reserved, UI proxies via Server)
wsUrlruntime-config.jsWebSocket URL (e.g. wss://server.hospital.ch/ws/dll)
oauth2Urlruntime-config.jsOAuth2/Keycloak base URL (e.g. https://sso.hospital.ch)
oauth2Realmruntime-config.jsOAuth2 realm name (e.g. kodemed)
oauth2ClientIdruntime-config.jsOAuth2 client ID (e.g. kodemed-ui)
URL Query Parameters (injected by DLL when opening embedded browser)
apiUrlURL paramOverride API URL
wsUrlURL paramOverride WebSocket URL
dataServerUrlURL paramOverride DataServer URL
oauth2UrlURL paramOverride OAuth2 base URL
oauth2RealmURL paramOverride OAuth2 realm
oauth2ClientIdURL paramOverride OAuth2 client ID
langURL paramUI language (de, fr, it)
classificationVersionURL paramClassification year (e.g. 2026)
Config Resolution Priority: URL params > runtime-config.js > VITE_* build vars > /api/v1/config > defaults

Caddy / reverse proxy Compose overlay

These variables shape Caddyfile.* and the per-service routing in docker-compose.caddy.yml. They have no effect on KodeMed services themselves — only on the TLS / hostname layer.

Environment VariableDefaultDescription
DOMAINkodemed.chApex domain for the deployment
SSO_DOMAINsso.kodemed.chFQDN of the Keycloak instance Caddy fronts
UI_SUBDOMAINdemo-coding-uiSubdomain for CodingUI on apex
SERVER_SUBDOMAINdemo-serverSubdomain for the Server REST API + WebSocket
DATASERVER_SUBDOMAINdemo-dataSubdomain for DataServer
GROUPER_SUBDOMAINdemo-grouperSubdomain for GrouperServer
PORTAL_SUBDOMAINdemo-portalSubdomain for the demo (admin) portal
PROD_PORTAL_SUBDOMAINportalSubdomain for the customer-facing portal
SSO_SUBDOMAINdemo-ssoSubdomain for the Keycloak instance fronted by Caddy. Override per environment.
DOCS_SUBDOMAINdemo-docsSubdomain for documentation site
SUPPORT_SUBDOMAINsupportSubdomain for support portal redirect
WWW_SUBDOMAINwwwSubdomain for marketing website
COOKIE_DOMAIN(empty)Cookie domain shared across subdomains (e.g. .kodemed.ch)
RUNTIME_CONFIG_FILEruntime-config.demo.jsWhich runtime-config.*.js the UI mounts. Use runtime-config.ch.js on CH.
IMAGE_TAGlatestTag pulled from harbor.mieresit.com/kodemed/* images
DOWNLOADS_VERSION(release version)Version baked into the downloads image; surfaced on the portal Overview
SUPPORT_URLhttps://support.kodemed.chPublic support URL (link target only)
WWW_URLhttps://www.kodemed.chPublic marketing URL (link target only)
DEPLOY_HOST(per env)SSH target used by scripts/ci/deploy-docker-compose.sh to push the stack

Portal — OAuth2-Proxy Demo + prod

The portal pages (downloads demo + prod) are protected by oauth2-proxy sidecars in docker-compose.demo.yml. Two distinct OIDC clients allow demo-portal and prod-portal to live behind the same Keycloak realm without sharing cookies.

Environment VariableDefaultDescription
OAUTH2_DOWNLOADS_CLIENT_IDkodemed-downloadsOIDC client id for the demo portal proxy
OAUTH2_DOWNLOADS_CLIENT_SECRET(secret)OIDC client secret for the demo portal proxy
OAUTH2_DOWNLOADS_COOKIE_SECRET(32-byte secret)Cookie signing secret. Generate: openssl rand -base64 32 | tr -d '=' | head -c 32
OAUTH2_DEMO_REDIRECT_URLhttps://demo-portal.kodemed.ch/oauth2/callbackRedirect URL for demo portal — must match the realm client config
OAUTH2_PROD_CLIENT_IDkodemed-downloadsOIDC client id for the prod portal proxy
OAUTH2_PROD_CLIENT_SECRET(secret)OIDC client secret for the prod portal proxy
OAUTH2_PROD_COOKIE_SECRET(32-byte secret)Cookie signing secret for the prod portal proxy
OAUTH2_PROD_REDIRECT_URLhttps://portal.kodemed.ch/oauth2/callbackRedirect URL for prod portal
OIDC_PROD_ISSUER_URIhttps://sso.kodemed.ch/realms/kodemedOIDC issuer used by the prod portal oauth2-proxy
OIDC_PROD_TOKEN_URLhttps://sso.kodemed.ch/realms/kodemed/protocol/openid-connect/tokenToken endpoint for the prod portal proxy
OIDC_PROD_JWKS_URLhttps://sso.kodemed.ch/realms/kodemed/protocol/openid-connect/certsJWKS endpoint for the prod portal proxy
DEMO_PORTAL_URLhttps://demo-portal.kodemed.chPublic URL of the demo portal (used for cross-links)
PROD_PORTAL_URLhttps://portal.kodemed.chPublic URL of the prod portal

Keycloak (bundled mode) SSO

Used by the wizard’s localhost preset and by the optional docker-compose.keycloak.yml overlay. Customers using their own Keycloak (HIS REST integrations) do not need any of these — only the issuer URL matters then.

Environment VariableDefaultDescription
KC_HOSTNAMElocalhostPublic hostname Keycloak advertises in tokens and redirect URIs
KC_HOSTNAME_PORT9080Public port (9080 plain, 9443 TLS, omit when behind Caddy on 443)
KC_HOSTNAME_STRICTfalseReject mismatching Host: headers. Keep false behind a reverse proxy.
KC_HTTP_ENABLEDtrueAllow plain HTTP on the internal port. TLS is terminated by Caddy.
KC_PROXY_HEADERSxforwardedTrust X-Forwarded-* headers from Caddy for scheme/host detection
KC_HEALTH_ENABLEDtrueExpose /health for compose healthchecks
KC_DBpostgresDatabase backend (only postgres supported in our compose)
KC_DB_URLjdbc:postgresql://kodemed-keycloak-db:5432/keycloakJDBC URL for the bundled Keycloak DB
KC_DB_NAMEkeycloakKeycloak database name
KC_DB_USERNAMEkeycloakKeycloak database user
KC_DB_PASSWORD(secret)Keycloak database password (auto-generated by the wizard)
KEYCLOAK_ADMINadminKeycloak master realm admin user
KEYCLOAK_ADMIN_PASSWORD(secret)Keycloak master realm admin password
KC_IMPORT_REALM/opt/keycloak/data/import/kodemed-realm.jsonRealm JSON imported on first start
KC_LOG_LEVELinfoKeycloak log level
KC_METRICS_ENABLEDtrueExpose Prometheus metrics on /metrics
KC_IMAGEquay.io/keycloak/keycloak:26.0Override the Keycloak image (pin per release)

KodeMed DLL/Client C# Desktop

Windows desktop DLL integration. Config file: kodemed-client-config.json (next to DLL or %APPDATA%\KodeMed\).

JSON KeyDefaultDescription
serverUrl(required)KodeMed Server URL (e.g. https://kodemed.hospital.ch)
codingUIUrl(from server)CodingUI URL (fetched from /api/v1/config if not set)
grouperServerUrl(from server)GrouperServer URL for DRG grouping (fetched from /api/v1/config if not set)
trustSelfSignedCertificatesfalseTrust self-signed TLS certs (Caddy IP mode only, NOT for production)
oauth2Url(from server)OAuth2/Keycloak base URL
oauth2RealmkodemedOAuth2 realm name
oauth2ClientIdkodemed-dllOAuth2 client ID for desktop auth
language(system)UI language override (de, fr, it)
webSocketAutoReconnecttrueAuto-reconnect WebSocket on disconnect
webSocketReconnectIntervalSeconds60Reconnect interval (seconds, min 5)
webSocketHeartbeatIntervalSeconds30Heartbeat interval (seconds)
hook sub-object (post-coding webhook — auth credentials stored locally, NOT on server)
hook.enabled(from server)Override: enable/disable webhook
hook.url(from server)Override: webhook target URL
hook.authType(from server)Auth type: none, bearer, header
hook.authToken(local only)Bearer token for webhook auth (SENSITIVE)
hook.authHeaderName(local only)Custom header name (SENSITIVE)
hook.authHeaderValue(local only)Custom header value (SENSITIVE)

Important Notes

  • Authentication: mandatory and not configurable. The legacy KODEMED_AUTH_ENABLED and GROUPER_AUTH_ENABLED flags were removed; every endpoint that is not explicitly listed as public in SecurityConfig requires a valid JWT against the configured OIDC issuer.
  • TABLE_PREFIX: Server uses km_app_, DataServer uses km_data_. Both share the same PostgreSQL database.
  • LOGGING_LEVEL_ROOT is the correct Spring Boot env var (not LOG_LEVEL).
  • KODEMED_LICENSE_FILE: search order (first match wins): $KODEMED_LICENSE_FILE if set, then ./kodemed.license (resolved relative to each service’s WorkingDirectory — on native installs that is the per-service directory, not the install root), then /etc/kodemed/kodemed.license, then %APPDATA%/KodeMed/kodemed.license (Windows DLL only). See the Linux Server section for the per-service copy requirement on native installs.
  • KODEMED_ENCRYPTION_KEY: Only used by Server. DataServer and GrouperServer do not encrypt data at rest.
  • Webhook auth credentials (token, header) are stored in the DLL config only, never on the server. The server only stores non-sensitive webhook settings.

Health Check & Smoke Test Endpoints

Use these public endpoints (no authentication required) to verify your deployment:

ServiceEndpointExpected Response
ServerGET /actuator/health{"status":"UP"}
ServerGET /api/v1/configJSON with serverUrl, dataServerUrl, version info
DataServerGET /actuator/health{"status":"UP"}
DataServerGET /api/v1/healthJSON with "UP" status, service name, version
GrouperServerGET /actuator/health{"status":"UP"}
GrouperServerGET /api/v1/grouper/versionsJSON with loaded grouper versions (SwissDRG, TARPSY, ST Reha)
All servicesGET /api/v1/license/statusLicense status (VALID / EXPIRED / INVALID)

HTTP 402 on any endpoint means the license is invalid or missing. Check KODEMED_LICENSE_FILE path and verify with: java -jar kodemed-license-cli.jar verify --public-key kodemed-public.pem --license kodemed.license