Unified.to
All articles

How to Build Online Appointment Booking Across EHRs and Practice Management Systems with

Your product can show open appointment slots and book, reschedule and cancel appointments in a practice's own scheduling system through one connection. Slots are available from Canvas Medical, Cliniko, Dentally, Docplanner, DrChrono, EMIS, Gentu, Halaxy, Healthie, Medtech ALEX, ModMed, Open Dental, Oracle Health, Semble, SMART on FHIR and TELUS Health CHR, and appointments can be created on 19 integrations, including Cliniko, DrChrono, Elation Health, Healthie, Open Dental and Tebra (as of 24 October 2026).

sdk.healthcare.listHealthcareSlots and sdk.healthcare.createHealthcareAppointment work the same way on every one of them. Every request is executed directly against the practice's system, so a slot you show is open now and a booking is on the practice's calendar when the request returns.

Why booking is the hard part of a healthcare integration

Patient-acquisition marketplaces, digital front doors, telehealth platforms, referral tools and AI receptionists all end in the same action: put a patient in a real time slot on a real clinician's calendar. A booking that lives only in your product, or reaches the practice as an email, is a booking someone has to re-enter, and sometimes double-book.

Each scheduling system models availability differently: some publish slots, some publish working hours you have to subtract appointments from, and each has its own rules about which fields a booking needs. Unified.to's Healthcare API returns availability as a Slot object and bookings as an Appointment object, so you build the flow once.

What you are building

  1. Authorize the practice's system and store one connection ID
  2. Let the patient choose a clinician and location
  3. Show open slots for a date range
  4. Book the appointment, then reschedule or cancel it on request
  5. Keep your view of the calendar current

Connect once

import { UnifiedTo } from '@unified-api/typescript-sdk';
import * as errors from '@unified-api/typescript-sdk/sdk/models/errors';

const sdk = new UnifiedTo({
  security: { jwt: process.env.UNIFIED_API_KEY },
});

Each practice authorizes their own system through Unified.to's authorization component, which returns a connection ID. Store one per practice. List clinicians with listHealthcarePractitioners and locations with listHealthcareLocations to build the choices (see How to Build a Provider Directory).

Show open slots

const slots = await sdk.healthcare.listHealthcareSlots({
  connectionId,
  practitionerId,
  locationId,
  startGte: '2026-11-02T00:00:00Z',
  endLt: '2026-11-09T00:00:00Z',
});

const open = slots.filter((slot) => slot.status === 'FREE');

status is FREE, BUSY, BUSY_UNAVAILABLE, BUSY_TENTATIVE or ENTERED_IN_ERROR; offer only FREE slots. start_at and end_at are the slot's time, service_types the kinds of visit it can be booked for, and is_overbooked marks a slot that's already been double-booked.

Always pass a date range. Docplanner requires locationId, startGte and endLt; DrChrono requires startGte; Gentu requires startGte and endLt (as of 24 October 2026). DrChrono, Halaxy, ModMed, Oracle Health and Semble filter slots by practitioner and location; Gentu by practitioner only and Docplanner by location only; elsewhere, filter the returned slots on practitioner_id and location_id in your own code. The Supported Integrations page lists filters per integration.

Book the appointment

const appointment = await sdk.healthcare.createHealthcareAppointment({
  connectionId,
  healthcareAppointment: {
    patient_id: patientId,
    practitioner_ids: [slot.practitioner_id!],
    location_id: slot.location_id,
    slot_ids: [slot.id!],
    start_at: slot.start_at,
    end_at: slot.end_at,
    status: 'BOOKED',
    service_types: slot.service_types,
    description: 'New patient visit',
  },
});

Systems differ in what a booking needs. For example, Elation Health requires patient_id, practitioner_ids and start_at; IntakeQ also requires location_id and service_types; EMIS books into a slot and requires slot_ids and reasons. Send everything you have from the slot, and read the required fields per integration from the capability matrix before you design your booking form.

patient_id must already exist in the practice's system. Look the patient up first, and register them if they're new (see How to Find and Register Patients).

Reschedule and cancel

Reschedule by updating the time:

await sdk.healthcare.updateHealthcareAppointment({
  connectionId,
  id: appointment.id!,
  healthcareAppointment: { start_at: newSlot.start_at, end_at: newSlot.end_at },
});

Cancel by setting status: 'CANCELLED' with an update (and a cancellation_reason where the system keeps one), or by removing the appointment where the system supports delete. Cliniko, Dentally, Docplanner, DrChrono, Elation Health, Healthie, IntakeQ, Medtech ALEX, Semble and Tebra accept deletes (as of 24 October 2026); on the others, cancel by status.

Read the calendar

const upcoming = await sdk.healthcare.listHealthcareAppointments({
  connectionId,
  patientId,
  startGte: new Date().toISOString(),
});

Appointment lists filter by patientId, practitionerId, locationId, status and a startGte/endLt window; which filters each system honours is in the capability matrix. Epic, Oracle Health, Jane and several others return appointments read-only, which is enough for reminders and visit preparation.

Handling coverage that varies by system

Treat 501 as information, not failure. A system that doesn't publish slots returns 501 Not Implemented for slots; fall back to a request-a-time form for those practices instead of showing an empty calendar.

try {
  return renderSlots(await sdk.healthcare.listHealthcareSlots({ connectionId, startGte, endLt }));
} catch (error) {
  if (error instanceof errors.UnifiedToError && error.statusCode === 501) {
    return renderRequestForm();
  }
  throw error;
}

Expect the slot to be gone. Another patient can book between your read and your write. Treat a failed create as "slot taken" and re-read availability.

Stay current when the calendar changes

Halaxy pushes appointment changes as native webhooks. On Canvas Medical, Cliniko, Dentally, DrChrono, IntakeQ, Open Dental, Semble, SMART on FHIR and TietoEVRY Lifecare, virtual webhooks for healthcare_appointment detect changes for you, and updatedGte returns only appointments changed since a timestamp (as of 24 October 2026). On the others, read on a schedule.

→ Start your 30-day free trial → Book a demo

Frequently asked questions

Can I book on a system that doesn't return slots?

Where appointments can be created, yes: send start_at, end_at and the clinician, and the system applies its own rules. Without slots you can't show availability, so confirm the time with the practice's own response.

Does Unified.to hold the booking?

No. The appointment is written to the practice's system in real time, and Unified.to keeps no patient records at rest. Unified.to is HIPAA compliant.

All articles