Skip to main content

Appointment $find

Alpha

The $find operation is currently in alpha.

The $find operation takes a list of Schedule references and a HealthcareService reference and returns a bundle of proposed Appointment resources within a specified time range. Slots are computed dynamically from each SchedulingParameters extensions on each Schedule and HealthcareService — no Slots need to be pre-generated.

Existing slots from each input Schedule are used to restrict or expand that schedule's availability, based on the slot's status.

Use Cases

  • Patient-facing booking flows: Show a patient the available time windows for a given provider or location
  • Availability checks: Determine whether a provider has open time before attempting to book
  • Multi-provider scheduling: Query multiple Schedules and intersect results to find shared availability

Invoke the $find operation

[base]/R4/Appointment/$find
import { MedplumClient } from '@medplum/core';
import type { Bundle, Appointment } from '@medplum/fhirtypes';

const medplum = new MedplumClient();

const url = medplum.fhirUrl('Appointment', '$find');
url.searchParams.append('start', '2026-03-10T09:00:00-05:00')
url.searchParams.append('end', '2026-03-10T17:00:00-05:00');
url.searchParams.append('service-type-reference', 'HealthcareService/my-healthcareservice-id')
url.searchParams.append('schedule', 'Schedule/my-schedule-id')
const bundle = await medplum.get<Bundle<Appointment>>(url);
const appointments = bundle.entry?.map((e) => e.resource as Appointment) ?? [];

Parameters

NameTypeDescriptionRequired
startdateTimeStart of the search window (inclusive)Yes
enddateTimeEnd of the search window (inclusive)Yes
service-type-referencereference(HealthcareService)The HealthcareService describing the type of appointment to be scheduled.Yes
schedulereference(Schedule)A schedule to check for availability. May be passed multiple times with different schedules.Yes
_countintegerMaximum number of Appointment resources to return. Defaults to 20. Maximum is 1000.No

Constraints

  • start must be before end
  • The search window cannot exceed 31 days
  • At least one schedule must be provided
  • Each schedule must have exactly one actor reference
  • Each schedule's serviceType field must match the requested HealthcareService.type
  • Each schedule's actor (Practitioner, Location, or Device) must have a timezone defined via the http://hl7.org/fhir/StructureDefinition/timezone extension

Output

Returns a Parameters resource wrapping a Bundle of Appointment resources with status: proposed.

The Appointments are virtual — they are not persisted in the FHIR store. Each Appointment has a contained attribute holding virtual (not persisted) Slot resources. These contained resources represent Slot resources that will be created if this Appointment is booked.

Example Response

{
"resourceType": "Parameters",
"parameter": [
{
"name": "return",
"resource": {
"resourceType": "Bundle",
"type": "searchset",
"entry": [
{
"resource": {
"resourceType": "Appointment",
"status": "proposed",
"start": "2026-03-10T09:00:00.000Z",
"end": "2026-03-10T10:00:00.000Z",
"participant": [
{
"actor": { "reference": "Practitioner/my-practitioner-id" },
"required": "required",
"status": "needs-action"
}
],
"serviceType": [
{
"text": "Office Visit",
"coding": [{ "system": "http://example.org/appointment-types", "code": "office-visit" }]
}
],
"contained": [
{
"resourceType": "Slot",
"status": "busy",
"start": "2026-03-10T09:00:00.000Z",
"end": "2026-03-10T10:00:00.000Z",
"serviceType": [
{
"text": "Office Visit",
"coding": [{ "system": "http://example.org/appointment-types", "code": "office-visit" }]
}
],
"schedule": { "reference": "Schedule/my-schedule-id" }
}
]
}
},
{
"resource": {
"resourceType": "Appointment",
"status": "proposed",
"start": "2026-03-10T10:00:00.000Z",
"end": "2026-03-10T11:00:00.000Z",
"participant": [
{
"actor": { "reference": "Practitioner/my-practitioner-id" },
"required": "required",
"status": "needs-action"
}
],
"serviceType": [
{
"text": "Office Visit",
"coding": [{ "system": "http://example.org/appointment-types", "code": "office-visit" }]
}
],
"contained": [
{
"resourceType": "Slot",
"status": "busy",
"start": "2026-03-10T10:00:00.000Z",
"end": "2026-03-10T11:00:00.000Z",
"serviceType": [
{
"text": "Office Visit",
"coding": [{ "system": "http://example.org/appointment-types", "code": "office-visit" }]
}
],
"schedule": { "reference": "Schedule/my-schedule-id" }
}
]
}
}
]
}
}
]
}

Availability Logic

$find calculates available windows by:

  1. Reading each Schedule's SchedulingParameters extension to determine recurring availability windows, slot duration, buffer times, and alignment constraints
  2. Fetching existing Slot resources for each Schedule in the requested range (busy, busy-tentative, busy-unavailable, and free slots)
  3. Subtracting occupied time (including buffer windows around booked slots) from the availability windows
  4. Applying alignment intervals and offsets to produce valid start times
  5. Returning Appointments up to _count

See Defining Availability for full details on how SchedulingParameters are configured.

Error Responses

Invalid Time Range

{
"resourceType": "OperationOutcome",
"issue": [{ "severity": "error", "code": "invalid", "details": { "text": "Invalid search time range" } }]
}

Range Exceeds 31 Days

{
"resourceType": "OperationOutcome",
"issue": [{ "severity": "error", "code": "invalid", "details": { "text": "Search range cannot exceed 31 days" } }]
}

Actor Missing Timezone

{
"resourceType": "OperationOutcome",
"issue": [{ "severity": "error", "code": "invalid", "details": { "text": "No timezone specified" } }]
}

Schedule Has Multiple Actors

{
"resourceType": "OperationOutcome",
"issue": [{ "severity": "error", "code": "invalid", "details": { "text": "$find only supported on schedules with exactly one actor" } }]
}

Schedule.serviceType does not match HealthcareService.type

{
"resourceType": "OperationOutcome",
"issue": [{ "severity": "error", "code": "invalid", "details": { "text": "Schedule is not schedulable for requested service type" } }]
}