Appointment $find
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
- TypeScript
- cURL
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) ?? [];
curl -G 'https://api.medplum.com/fhir/R4/Appointment/$find' \
-H "Authorization: Bearer MY_ACCESS_TOKEN" \
--data-urlencode "start=2026-03-10T09:00:00-05:00" \
--data-urlencode "end=2026-03-10T17:00:00-05:00" \
--data-urlencode "service-type-reference=HealthcareService/my-healthcareservice-id" \
--data-urlencode "schedule=Schedule/my-schedule-id"
Parameters
| Name | Type | Description | Required |
|---|---|---|---|
start | dateTime | Start of the search window (inclusive) | Yes |
end | dateTime | End of the search window (inclusive) | Yes |
service-type-reference | reference(HealthcareService) | The HealthcareService describing the type of appointment to be scheduled. | Yes |
schedule | reference(Schedule) | A schedule to check for availability. May be passed multiple times with different schedules. | Yes |
_count | integer | Maximum number of Appointment resources to return. Defaults to 20. Maximum is 1000. | No |
Constraints
startmust be beforeend- 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
serviceTypefield 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/timezoneextension
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:
- Reading each Schedule's
SchedulingParametersextension to determine recurring availability windows, slot duration, buffer times, and alignment constraints - Fetching existing Slot resources for each Schedule in the requested range (busy, busy-tentative, busy-unavailable, and free slots)
- Subtracting occupied time (including buffer windows around booked slots) from the availability windows
- Applying alignment intervals and offsets to produce valid start times
- 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" } }]
}
Related
- Appointment
$book- Book one of the returned Appointments - Defining Availability - How to configure
SchedulingParameterson a Schedule - Scheduling Overview - High-level scheduling concepts
ScheduleresourceAppointmentresourceSlotresource