Specification
Heidi Connector Spec v0.1.
This specification is in preview. The transport (REST or MCP) may change before general availability. We will give connector authors notice before any breaking change.
Transport and auth
| Requirement | Value |
|---|---|
| Protocol | REST (binding) or Model Context Protocol (opens in a new tab) over Streamable HTTP |
| Endpoint | REST: one base URL, e.g. https://emr.example.com/heidi. MCP: one URL, e.g. https://emr.example.com/mcp. |
| TLS | HTTPS only, TLS 1.2+ |
| Data residency | Patient data for a clinic stays in that clinic's region: AU in AU, UK in UK, and so on. data_handling.hosting_regions covers every region in regions. |
| Auth | Authorization: Bearer <connector key> on every request |
| Unauthenticated request | HTTP 401 before any tool runs |
| Tenancy | A key maps to exactly one practice. Never return another practice's data. |
| Sessions | Stateless is fine. Heidi does not rely on MCP sessions or cookies. |
| Timeouts | Respond within 10 s. Heidi times out at 30 s. |
| Rate limits | Support at least 60 requests per minute per practice. Return rate_limited if you throttle. |
OAuth 2.1 (MCP authorization) is planned for v0.2. In v0.1 every connector uses per-practice keys.
Tools
Names are fixed. Every tool returns MCP structuredContent that matches the shapes below. Over REST, each tool is one endpoint returning the same JSON (see REST binding).
Required
| Tool | Arguments | Returns |
|---|---|---|
heidi_get_connector_info | none | ConnectorInfo |
heidi_list_practitioners | none | Practitioner[] |
heidi_list_appointments | practitioner_id, start_date, end_date (ISO dates, inclusive, at most 31 days) | Appointment[] |
heidi_search_patients | name?, date_of_birth?, limit? (default 20, max 50). At least one criterion. | Patient[] |
heidi_get_patient | patient_id | Patient |
heidi_push_note | note: NotePush | NotePushResult |
Optional
| Tool | Arguments | Returns | Advertise capability |
|---|---|---|---|
heidi_get_patient_context | patient_id | PatientContext | patient_context |
heidi_upload_document | document: DocumentUpload | DocumentUploadResult | document_upload |
Shapes
// ConnectorInfo
{ "connector_id": "riverbend-clinic-emr", "name": "Riverbend Clinic EMR", "vendor": "Riverbend",
"spec_version": "0.1", "timezone": "Australia/Sydney", "practice_name": "Riverbend Family Clinic",
"capabilities": ["practitioners","appointments","patient_search","patient_read","note_push"] }
// Practitioner
{ "id": "dr-chen", "display_name": "Dr Maya Chen", "role": "GP", "provider_number": null, "active": true }
// Appointment: start/end MUST carry a UTC offset
{ "id": "a-0001", "practitioner_id": "dr-chen", "start": "2026-10-05T09:00:00+11:00",
"end": "2026-10-05T09:15:00+11:00", "status": "booked", "type": "Standard consult",
"reason": "Diabetes review",
"patient": { "id": "p-1001", "display_name": "Margaret Hughes", "date_of_birth": "1948-03-12" } }
// Patient
{ "id": "p-1001", "first_name": "Margaret", "last_name": "Hughes", "display_name": "Margaret Hughes",
"date_of_birth": "1948-03-12", "sex": "F", "email": null, "phone": null, "address": null,
"identifiers": { "medicare": "2100156789" } }
// NotePush (argument to heidi_push_note)
{ "patient_id": "p-1001", "practitioner_id": "dr-chen", "appointment_id": "a-0001",
"title": "Consult note - Diabetes review", "content": "S: ...\nO: ...\nA: ...\nP: ...",
"content_format": "text", "idempotency_key": "heidi-7f1c2a9e..." }
// NotePushResult
{ "note_id": "n-00042", "url": "https://emr.example.com/patients/p-1001#n-00042" }status is one of booked, arrived, fulfilled, cancelled, noshow. Map your own states onto these.
Behaviour you must guarantee
- Practitioner scoping.
heidi_list_appointmentsreturns only the requested practitioner's appointments. - Timezones. Appointment times include an offset.
ConnectorInfo.timezoneis the practice's IANA zone. - Idempotent writes. If
heidi_push_note(orheidi_upload_document) is called twice with the sameidempotency_key, the second call returns the first result and creates nothing new. Heidi retries on network errors. - Not found. An unknown
patient_idreturns thenot_founderror, not an empty patient. - No PHI in logs or errors. Error messages must not contain patient data.
- Read-only reads. Read tools never modify your system.
Errors
Over MCP, return a tool error (isError: true) whose text content is the body below. Over REST, return it as the JSON body with the HTTP status in the table.
{ "error": { "code": "not_found", "message": "patient p-9 not found" } }| Code | REST status | When |
|---|---|---|
unauthorized | 401 | Key revoked or practice disabled |
not_found | 404 | Unknown patient, appointment or practitioner |
invalid_argument | 400 | Bad input, e.g. an inverted date range or a note that's too long |
rate_limited | 429 | You are throttling. Heidi backs off and retries. |
unsupported | 501 | Optional capability not implemented |
upstream_error | 502 | Your system failed. Heidi shows a generic error. |
REST binding
All paths are relative to your base URL. Every request carries Authorization: Bearer <connector key>. Responses are application/json with exactly the shapes above. Lists are bare JSON arrays.
| Method and path | Tool | Request | Response |
|---|---|---|---|
GET /info | heidi_get_connector_info | ConnectorInfo | |
GET /practitioners | heidi_list_practitioners | Practitioner[] | |
GET /appointments | heidi_list_appointments | query practitioner_id, start_date, end_date | Appointment[] |
GET /patients | heidi_search_patients | query name?, date_of_birth?, limit? | Patient[] |
GET /patients/{patient_id} | heidi_get_patient | Patient | |
POST /notes | heidi_push_note | body NotePush | NotePushResult |
GET /patients/{patient_id}/context | heidi_get_patient_context (optional) | PatientContext | |
POST /documents | heidi_upload_document (optional) | body DocumentUpload | DocumentUploadResult |
curl -H "Authorization: Bearer $KEY" \
"https://emr.example.com/heidi/appointments?practitioner_id=dr-chen&start_date=2026-10-05&end_date=2026-10-05"Optional endpoints you don't implement return 501 with code unsupported, or 404.
Versioning
spec_version is 0.1. Additive changes (new optional tools or fields) stay within 0.x. Breaking changes get a new version and at least 6 months of overlap.