Open source FHIR-compliant gateway and test server for schedule and appointment availability.
FHIRTogether Scheduling Synapse is a TypeScript + Fastify server designed to help modernize legacy scheduling systems by offering a standards-compliant FHIR interface over pluggable backend stores. It supports FHIR Schedule, Slot, and Appointment resources and can ingest HL7v2 SIU messages, making it ideal for:
- Acting as a gateway between legacy/proprietary EHRs and modern clients
- Serving as a test server to prototype or simulate provider group schedules
- Enabling public applications to discover and book available time
Text version of the architecture diagram
flowchart LR
subgraph internal["π₯ Inside the Firewall"]
A[Legacy EHR]
E[New System β HL7]
F[New System β REST]
end
B[π FHIRTogether Synapse Gateway]
subgraph external["π Outside the Firewall"]
C[Scheduling Broker - BlueHive]
D[Users and Provider Apps]
end
E -->|SIU with MSH-4/MSH-8 β auto-registers| B
F -->|POST /System/register β TLS verify| B
A -->|HL7v2 SIU S13 S15| B
B -->|FHIR Schedule Slot Appointment| C
C -->|Book and Update via FHIR| B
B -->|HL7v2 ACK or Updates| A
D -->|Discover availability and request booking| C
D -->|GET /Directory β public| B
C -->|Notifications and confirmations| D
style internal fill:#fef3c7,stroke:#f59e0b
style external fill:#dbeafe,stroke:#3b82f6
We are hosinting a public sandbox environment for testing and development at: https://fhirtogether.os.mieweb.org/
FHIRTogether is not a system of record for patient data. It acts as a scheduling gateway that relays information between patients and providers. PHI/PII is handled with a trap door model:
- PHI goes in but doesn't come out. Patient information (name, phone, email, date of birth, reason for visit) can be submitted when booking an appointment, but the API never returns it in subsequent responses.
- API responses are redacted. All
GET /AppointmentandGET /Appointment/:idresponses strip patient participant display names, comments (which may contain reason for visit), and contained resources (which may contain questionnaire responses with PHI). - POST responses are also redacted. Even the response to a booking request does not echo back patient information.
- The provider view shows blocked time, not patient identities. The provider appointment view displays when time slots are booked, their status, and their type β but not who booked them.
- PHI in the database is transient. Patient information is stored only long enough to be relayed to the receiving system (e.g., via HL7v2 SIU messages). It is not intended for long-term storage in FHIRTogether.
In short: You can enter patient information through the scheduler, but FHIRTogether will never show it back to you or anyone else through the API or UI. The source EHR/PM system remains the authoritative source for patient data.
flowchart LR
Patient[Patient enters PHI via Scheduler]
DB[(FHIRTogether DB β transient)]
EHR[Receiving EHR / PM System]
API[API Responses β PHI redacted]
PV[Provider View β blocked time only]
Patient -->|POST /Appointment| DB
DB -->|HL7v2 relay| EHR
DB -.->|GET /Appointment| API
DB -.->|provider-view| PV
style DB fill:#fef3c7,stroke:#f59e0b
style API fill:#dcfce7,stroke:#22c55e
style PV fill:#dcfce7,stroke:#22c55e
- β
Full support for FHIR R4 RESTful APIs:
/Schedule,/Slot,/Appointment
- π HL7v2 message ingestion (
SIU^S12,S13,S15) via HTTP and MLLP socket - π₯ Multi-tenant synapse gateway β systems self-register via HL7 or REST
- π Two onboarding paths: zero-friction HL7 (MSH-4/MSH-8) and REST with TLS challenge-response
- π Public provider directory (
/Directory) in FHIR, JSON, YAML, and HL7v2 MFN formats - β±οΈ System evaporation β inactive systems auto-expire after configurable TTL
- π§© Pluggable backend support: MongoDB, MySQL, PostgreSQL, MSSQL
- π OpenAPI 3.1 (Swagger UI) auto-generated from routes
- π§ͺ Test mode for seeding schedules, clearing data, or simulating providers
- π€ MCP server β expose scheduling tools to LLM agents via Model Context Protocol
- β‘ Fastify-based, modern TypeScript stack
FHIRTogether operates as a unified multi-tenant gateway. Every system (EHR, clinic, hospital) that connects becomes a tenant β either automatically via HL7 or explicitly via REST registration.
Just start sending SIU messages. FHIRTogether auto-registers the system using:
- MSH-4 (Sending Facility) as identity
- MSH-8 (Security) as shared secret
Try it now: The HL7 Message Tester page provides editable example SIU messages you can send directly to the API β no tools required.
On first contact, the system is created as unverified. Subsequent messages with the same MSH-4 must match MSH-8 or are rejected. Locations are auto-created from AIL segments.
POST /System/registerwith your name and URL β receive a challenge token- Serve the token at
{url}/.well-known/fhirtogether-verify POST /System/{id}/verifyβ FHIRTogether fetches the token over TLS, verifies, and returns a one-time API key- Use
Authorization: Bearer <api-key>for all subsequent requests
Systems follow a trust progression: unverified β pending β active β expired
- Unverified: Auto-created via HL7. Can send data but not yet trusted.
- Pending: Registered via REST, awaiting TLS challenge verification.
- Active: Verified. Full API access.
- Expired: Evaporated after TTL days of inactivity. All data cascade-deleted.
Inactive systems automatically expire. Configurable via:
SYSTEM_TTL_DAYS(default: 7) β days of inactivity before expirationEVAPORATION_CHECK_INTERVAL_HOURS(default: 1) β how often the eviction job runs
GET /Directory is public (no auth required) and supports four output formats:
| Format | Content-Type | _format param |
|---|---|---|
| FHIR Bundle (Organization + Location + PractitionerRole) | application/fhir+json |
fhir |
| JSON | application/json |
json |
| YAML | text/yaml |
yaml |
| HL7v2 MFN^M02 | x-application/hl7-v2+er7 |
hl7 |
Query parameters: zip, specialty, name, status
We have an Enterprise Health EHR and WebChart EHR sandbox environment available for testing and development. If you want to test with real EHR data or simulate HL7v2 message ingestion, please reach out to us to get access. If you would like to add your own EHR sandbox environment to the public testing environment, please reach out to us as well.
- https://masterdaily.dev.webchart.app/ - WebChart EHR Sandbox
- https://ehbhdemo.enterprise.health - Enterprise Health EHR Sandbox
An embeddable FHIR Scheduler React component for browsing provider availability and booking appointments. See the package README for usage and integration details.
If you're working with a proprietary scheduling system that stores appointment data in a non-standard format (e.g., mainframe, custom RDBMS, HL7v2-only systems), FHIRTogether allows you to:
- Implement a storage engine that adapts your system's internal schema to the FHIR
Slot,Schedule, andAppointmentstructure. - Optionally translate HL7v2
SIUmessages into storage-compatible entries. - Instantly expose a FHIR-native API over your legacy data.
No need to re-architect your legacy system β just implement a backend adapter.
git clone https://github.com/mieweb/FHIRTogether.git
cd FHIRTogether
npm install
npm run devSwagger UI: http://localhost:4010/docs
Important: The store interface is not for connecting directly to your EHR or Practice Management system. Instead, it's a working data repository for the scheduling portal that holds appointment data representing schedules for providers/resources.
The store you select is:
- β A storage system you're comfortable with (SQLite, PostgreSQL, MySQL, MongoDB, etc.)
- β A working cache that syncs FROM your EHR or PM system
- β Used to hold appointment scheduling data for fast queries and updates
- β NOT the source of truth β your EHR/PM system remains authoritative
- β NOT a direct database connection to your production EHR/PM
This architecture allows the scheduling portal to provide fast, responsive scheduling while keeping your source system's data isolated and protected.
To integrate with your preferred storage system, implement the FhirStore interface:
interface FhirStore {
getSlots(query: FhirSlotQuery): Promise<Slot[]>;
createSlot(slot: Slot): Promise<Slot>;
getSchedules(query: FhirScheduleQuery): Promise<Schedule[]>;
createSchedule(schedule: Schedule): Promise<Schedule>;
createAppointment(appt: Appointment): Promise<Appointment>;
}Backend modules are located in /src/store/:
mongoStore.ts- Example for your mongoDB as your backendpostgresStore.ts- for postgresmysqlStore.ts- MariaDB and MySQL examplemssqlStore.ts- Microsoft SQL Serversimulator.ts- a Simulator showing specific results for testing with an in-memory backend (no persistance) and examples initiated at launch.
Use the .env file to select your backend (defaults to simulator):
STORE_BACKEND=postgresSend HL7v2 scheduling messages (e.g., SIU^S12, S13, S15) to:
POST /hl7/siu
Interactive testing: Use the HL7 Message Tester to try example messages with editable MSH fields and one-click submission.
Option 1: Raw HL7 text (Content-Type: text/plain or x-application/hl7-v2+er7)
MSH|^~\&|LEGACY_EHR|MAIN_HOSPITAL|FHIRTOGETHER|SCHEDULING_GATEWAY|20231209120000||SIU^S12|12345|P|2.3
SCH|10001^10001|10001^10001|||10001|OFFICE^Office visit|reason|OFFICE|30|m|...
PID|1||42||DOE^JOHN||19800101|M|||123 Main St^^City^ST^12345||5551234567
...
Option 2: JSON wrapper (Content-Type: application/json)
{
"message": "MSH|^~\\&|SCHED|...<raw HL7v2>",
"wrapMLLP": false
}Response for text requests: Raw HL7 ACK message
MSH|^~\&|FHIRTOGETHER|SCHEDULING_GATEWAY|LEGACY_EHR|MAIN_HOSPITAL|20231209120001||ACK^S12|ACK12345|P|2.3
MSA|AA|12345|Message processed successfully
Response for JSON requests: JSON-wrapped ACK
{
"message": "MSH|^~\\&|FHIRTOGETHER|SCHEDULING_GATEWAY|...\rMSA|AA|12345|Message processed successfully|||"
}ACK Codes:
AA(Application Accepted) - Message processed successfullyAR(Application Rejected) - Message format error, do not retryAE(Application Error) - Processing failure, can retry
The server parses the message and converts it into FHIR Slot and Schedule resources internally.
FHIR-compliant endpoints (all responses follow FHIR Bundles or resource schemas):
| Method | Path | Auth | Description |
|---|---|---|---|
| POST | /System/register |
Public | Register a new system (REST onboarding) |
| POST | /System/:id/verify |
Public | Complete TLS challenge-response |
| GET | /System |
Bearer/Admin | Get own system details (admin: list all) |
| PUT | /System |
Bearer | Update system name |
| DELETE | /System |
Bearer | Voluntary de-registration (cascade) |
| POST | /System/rekey |
Bearer | Rotate API key |
| PUT | /System/:id/status |
Admin | Change system verification status |
| GET | /Directory |
Public | Public provider directory (FHIR/JSON/YAML/HL7) |
| POST | /Location |
Bearer | Create a location |
| GET | /Location |
Bearer | List locations (FHIR Bundle) |
| GET | /Location/:id |
Bearer | Get location by ID |
| PUT | /Location/:id |
Bearer | Update location |
| DELETE | /Location/:id |
Bearer | Delete location |
| GET | /Slot |
Bearer | Search for free/busy slots |
| POST | /Slot |
Bearer | Block a time slot |
| GET | /Schedule |
Bearer | Retrieve provider availability |
| POST | /Schedule |
Bearer | Define provider planning horizon |
| POST | /Appointment |
Bearer | Book an appointment |
| POST | /hl7/siu |
MSH-8 | Ingest HL7v2 SIU message (text or JSON) |
Endpoints also support administrative operations (in test mode only):
DELETE /SlotDELETE /SchedulePOST /$simulate-weekβ generate random provider availability
FHIRTogether supports three authentication modes:
| Mode | Header | Use Case |
|---|---|---|
| API Key (Bearer) | Authorization: Bearer <api-key> |
System-to-system API access |
| Basic Auth (Admin) | Authorization: Basic <base64> |
Admin operations via AUTH_USERNAME/AUTH_PASSWORD |
| MSH-8 (HL7) | HL7 Security field | Auto-registration via HL7v2 messages |
API keys are 64-character hex strings generated during verification (POST /System/:id/verify). They are stored as SHA-256 hashes β the plaintext is returned exactly once and never stored. Keys can be rotated via POST /System/rekey.
Public endpoints (no auth required): /health, /docs, /demo, /Directory, /System/register, /System/:id/verify
MIT
If you're modernizing a legacy EHR or want to contribute HL7v2 mappings, backend drivers, or scheduler logic β PRs welcome!
-
Multi-tenant synapse gateway with system registration
-
Zero-friction HL7 onboarding (MSH-4/MSH-8 auto-registration)
-
REST onboarding with TLS challenge-response verification
-
API key authentication (Bearer token)
-
Public provider directory (/Directory) with FHIR, JSON, YAML, HL7v2 formats
-
System evaporation (auto-expire inactive systems)
-
Location management (CRUD + HL7 AIL auto-creation)
-
Implement an optional no-login scheduling portal for browsing schedules and booking like https://cal.com/ or Calend.ly.
-
Add login/authentication support for admins
-
Implement google/microsoft/apple login for the scheduling portal for end users
-
Implement the ability for an admin to define custom appointment types with different durations and constraints
- Admin UI for managing providers, appointment types, and schedules
- Implement yaml import/export for schedule definitions including API to update (see Schedule YAML format)
-
FHIR Subscription support for appointment updates
-
Add SMART-on-FHIR / OAuth support - review https://github.com/mieweb/poc-auth-architecture
-
$find-appointmentoperation -
HL7v2 SRM^S03 request/response handling
-
FHIR Bulk Export for schedules
Bring legacy scheduling infrastructure into the FHIR world β one appointment at a time.

