0.1.0 - ci-build
fhirig - Local Development build (v0.1.0) built by the FHIR (HL7® FHIR® Standard) Build Tools. See the Directory of published versions
This guide explains how a Slot Publisher makes an appointment slots available across multiple EHRS or bookings portals to a Slot Discovery Client. For background and role definitions, see README.md.
Key Actors
Slot Publisher – EHRs or booking portals provide a list of the available providers and slots that are available via provider organizations. This will include all the available FHIR resources including practitioner, practitioner role, health service, location, and organizations. Slot publishers will make vias avaiable via bulk publish speficiation.
Directories – Could be a directory of slots or can include complete provider directories. This actor will serve as both a client and servier will consume the slots via the bulk publish API. Publishers can expose available slots with nothing more than static web hosting (e.g., from a cloud storage bucket or off-the-shelf web server). This low effort approach does not require a full FHIR server.
Client Scheduling Applications –Apps can then connect to the directory and find available appointments that best suite their needs eliminating the back and forth need to call providers to book appointments. Patients or the consumer can more easily find the best slot availale to suite their need rather a docters appointment, booking a vaccine or a vaerity of other healthcare services based on provider, location, time, find the appointment slot that best fits their need without having to pick-up the phone.
sequenceDiagram
participant EHR
participant Directory as Directory of Slots
participant Client as Client Scheduling App
EHR->>Directory: Publish Slots
Client->>Directory: Find Open Slots
activate Directory
Directory-->>Client: Return Open Slot
deactivate Directory
Client->>EHR: Book Open Slot via SMART Scheduling Link
The EHR or booking portal will provide the data via the bulk publish to allow for NDJSON files for consumption for the directory of slots. The directory of slots will then consume the NDJSON to make available to the client application. The directory will presnt slots to client discovery app to allow for the selection of available slots from multiple booking portals. The slots will then available for presenation to client scheduling applications. The patient will leverage the deep link to book directly back into the EHR or booking portal. This light and easily approach allows for simple rendering of availabilty of many appointment types.
A Slot Publisher hosts not only appointment slots, but also Locations, PractitionerRoles, and Schedules associated with these slots:

Concretely, a Slot Publisher hosts six kinds of files:
Practitioner,Practitinoer Role, Healthcare Service, location, scheduled, slot
$bulk-publish (a convention used when publishing data sets using FHIR; this convention applies any time a potentially large data set needs to be statically published).
Example file showing ten locations for the fictional "SMART Urgent Care". Each line provides details about a single physical location in the MA area.
Schedule Files. Each line contains a minified JSON object representing the calendar for a healthcare service offered at a specific location or by a specific practitioner role.
A client queries the manifest on a regular basis, e.g. once every 1-5 minutes. The client iterates through the links in the manifest file to retrieve any PractitionerRole, Location, Schedule, or Slot files it is interested in. (Clients SHOULD ignore any output items with types other than PractitionerRole, Location, Schedule, or Slot.)
Wherever “timestamps” are used in this specification, they SHALL be in the format YYYY-MM-DDThh:mm:ss.sss+zz:zz (e.g. 2015-02-07T13:28:17.239+02:00 or 2017-01-01T00:00:00Z). The time SHALL specified at least to the second and SHALL include a time zone offset (for UTC, the offset MAY be Z). These timestamps match FHIR’s “instant” format, and are also valid ISO 8601 timestamps.
Accept HeadersFor Bulk Publication Manifest requests, servers SHALL support at least the following Accept headers from a client, returning the same FHIR JSON payload in all cases:
Accept header presentAccept: application/jsonFor Bulk Output File requests, servers SHALL support at least the following Accept headers from a client, returning the same FHIR NDJSON payload in all cases:
Accept header presentAccept: application/fhir+ndjsonCache-Control: max-age=<seconds> header as a hint to clients about how long (in seconds) to wait before polling next. For example, Cache-Control: max-age=300 indicates a preferred polling interval of five minutes.If-None-Match or If-Modified-Since with each query to prevent retrieving data when nothing has changed since the last query.?_since={} query parameter with a timestamp when retrieving a manifest file to request only changes since a particular point in time. Servers are free to ignore this parameter, meaning that clients should be prepared to retrieve a full data set.$bulk-publish content at open, publicly accessible endpoints when sharing general healthcare appointment availability (no required access keys or client credentials). This pattern ensures that data can be used widely and without pre-coordination to meet public health and consumer access use cases.
$bulk-publish pattern, Slot Publishers host static files, which scale well to open publicationYou can test your implementation with https://inferno.healthit.gov/suites/smart_scheduling_links
The manifest file is the entry point for a client to retrieve scheduling data. The manifest JSON file includes:
| field name | type | description |
|---|---|---|
transactionTime |
timestamp as string | the time when this data set was published. See "timestamps" for correct formatting. |
request |
url as string | the full URL of the manifest |
output |
array of JSON objects | each object contains a type, url, and extension field |
→ type |
string | whether this output item represents a "PractitionerRole","Location", "Schedule", or "Slot" file |
→ url |
url as string | the full URL of an NDJSON file for the specified type of data |
→ extension |
JSON object | contains tags to help a client decide which output files to download |
→ → state |
JSON array of strings | state or jurisdiction abbreviations (e.g., ["MA"] for a file with data pertaining solely to Massachusetts) |
(For more information about this manifest file, see the FHIR bulk data spec.)
{
"transactionTime": "2021-01-01T00:00:00Z",
"request": "https://example.com/covid-vaccines/$bulk-publish",
"output": [
{
"type": "Schedule",
"url": "https://example.com/data/schedule_file_1.ndjson"
},
{
"type": "PractitionerRole",
"url": "https://example.com/data/practitionerrole_file_1.ndjson"
},
{
"type": "Location",
"url": "https://example.com/data/location_file_1.ndjson"
},
{
"type": "Slot",
"url": "https://example.com/data/slot_file_MA.ndjson",
"extension": {
"state": ["MA"]
}
},
{
"type": "Slot",
"url": "https://example.com/data/slot_file_NEARBY.ndjson",
"extension": {
"state": ["CT", "RI", "NH"]
}
}
],
"error": []
}
Each line of the Location File is a minified JSON object that conveys a physical location where appointments are available.
Each Location includes at least:
| Field Name | Type | Required | Description |
|---|---|---|---|
resourceType |
string | Yes | fixed value of "Location" |
id |
string | Yes | Logical id of this artifact (from Resource) |
status |
code | No | active | suspended | inactive |
operationalStatus |
Coding | No | The operational status of the location (typically only for a bed/room) |
name |
string | No | Name of the location as used by humans |
alias |
array of strings | No | A list of alternate names that the location is known as, or was known as, in the past |
description |
string | No | Additional details about the location that could be displayed as further information to identify the location beyond its name |
mode |
code | No | instance | kind |
type |
array of CodeableConcept | No | Type of function performed |
telecom |
array of ContactPoint | No | Contact details of the location |
address |
Address | Yes | Physical location |
physicalType |
CodeableConcept | No | Physical form of the location |
position |
object | No | The absolute geographic location |
→ longitude |
decimal | Yes* | Longitude with WGS84 datum (Required if position is provided) |
→ latitude |
decimal | Yes* | Latitude with WGS84 datum (Required if position is provided) |
→ altitude |
decimal | No | Altitude with WGS84 datum |
managingOrganization |
Reference(Organization) | No | Organization responsible for provisioning and upkeep |
partOf |
Reference(Location) | No | Another Location this one is physically a part of |
hoursOfOperation |
array of objects | No | What days/times during a week is this location usually open |
→ daysOfWeek |
array of codes | No | mon | tue | wed | thu | fri | sat | sun |
→ allDay |
boolean | No | The Location is open all day |
→ openingTime |
time | No | Time that the Location opens |
→ closingTime |
time | No | Time that the Location closes |
availabilityExceptions |
string | No | Description of availability exceptions |
endpoint |
array of Reference(Endpoint) | No | Technical endpoints providing access to services operated for the location |
virtualService |
array of objects | No | Connection details of a virtual service (e.g. conference call) for telehealth appointments |
→ channelType |
Coding | No | The type of virtual service to connect to (e.g. Teams, Zoom, WhatsApp). Use codes from http://hl7.org/fhir/ValueSet/virtual-service-type |
→ addressUrl |
url | No** | URL to the virtual service connection (e.g., meeting link) |
→ addressString |
string | No** | Address to reach the virtual service as a string (e.g., phone number) |
→ addressContactPoint |
ContactPoint | No** | Address to reach the virtual service as a ContactPoint |
→ addressExtendedContactDetail |
ExtendedContactDetail | No** | Extended contact details for the virtual service |
→ additionalInfo |
array of urls | No | Additional information about the virtual service connection (e.g., instructions, alternate URLs) |
→ maxParticipants |
positiveInt | No | Maximum number of participants supported by the virtual service |
→ sessionKey |
string | No | Session key required to access the virtual service |
Each identifier object includes a system and a value.
If a PractitionerRole or Location is associated with organization-specific identifiers (such as facility numbers, site codes, or store numbers), publishers SHOULD include these. The system should be a URL that identifies the identifier system, preferably a page on the publisher's web site (e.g. {"system": "https://healthsystem.example.com/facility-directory", "value": "FAC-123"})
If a Location participates in external registry programs that assign location identifiers, publishers MAY include these identifiers using the appropriate system URL for the registry.
Additional identifiers: Any number of additional identifiers MAY be included. Each should populate system and value as appropriate, following FHIR identifier conventions.
Location{
"resourceType": "Location",
"id": "123",
"identifier": [{
"system": "https://healthsystem.example.com/facility-directory",
"value": "FAC-PITT-001"
}],
"name": "Berkshire Family Medicine - Pittsfield",
"description": "Primary care clinic located in downtown Pittsfield",
"telecom": [{
"system": "phone",
"value": "413-555-0123"
}, {
"system": "url",
"value": "https://berkshirefamilymedicine.example.com"
}],
"address": {
"line": ["173 Elm St"],
"city": "Pittsfield",
"state": "MA",
"postalCode": "01201-7223"
}
}
| Field Name | Type | Required | Description |
|---|---|---|---|
resourceType |
string | Y | Fixed value of "Practitioner" |
id |
string | Y | Unique identifier for this practitioner (up to 64 alphanumeric characters and may include - and .) |
identifier |
array of JSON objects | N | An identifier for the person as this agent |
→ use |
string | N | "usual", "official", "temp", "secondary", "old" |
→ system |
string | N | The namespace for the identifier value (e.g., "http://hl7.org/fhir/sid/us-npi") |
→ value |
string | N | The value that is unique within the system |
active |
boolean | N | Whether this practitioner's record is in active use |
name |
array of JSON objects | N | The name(s) associated with the practitioner |
→ use |
string | N | "usual", "official", "temp", "nickname", "anonymous", "old", "maiden" |
→ text |
string | N | Full text representation of the name |
→ family |
string | N | Family name (surname) |
→ given |
array of strings | N | Given names (not always 'first'). Includes middle names |
→ prefix |
array of strings | N | Parts that come before the name (e.g., "Dr.", "Prof.") |
→ suffix |
array of strings | N | Parts that come after the name (e.g., "Jr.", "MD") |
→ period |
JSON object | N | Time period when name was/is in use |
→ → start |
timestamp as string | N | Start time with inclusive boundary |
→ → end |
timestamp as string | N | End time with inclusive boundary |
telecom |
array of JSON objects | N | Contact details for the practitioner (e.g., phone, email) |
→ system |
string | N | "phone", "fax", "email", "pager", "url", "sms", "other" |
→ value |
string | N | The actual contact point details |
→ use |
string | N | "home", "work", "temp", "old", "mobile" |
→ rank |
integer | N | Specify preferred order of use (1 = highest) |
→ period |
JSON object | N | Time period when the contact point was/is in use |
→ → start |
timestamp as string | N | Start time with inclusive boundary |
→ → end |
timestamp as string | N | End time with inclusive boundary |
address |
array of JSON objects | N | Address(es) of the practitioner |
→ use |
string | N | "home", "work", "temp", "old", "billing" |
→ type |
string | N | "postal", "physical", "both" |
→ text |
string | N | Full text representation of the address |
→ line |
array of strings | N | Street name, number, direction & P.O. Box etc. |
→ city |
string | N | Name of city, town etc. |
→ district |
string | N | District name (aka county) |
→ state |
string | N | Sub-unit of country (abbreviations ok) |
→ postalCode |
string | N | Postal code for area |
→ country |
string | N | Country (e.g. can be ISO 3166 2 or 3 letter code) |
→ period |
JSON object | N | Time period when |
Each line of the PractitionerRole File is a minified JSON object that represents a set of roles/locations/specialties/services that a practitioner may perform at an organization for a period of time. The PractitionerRole resource represents the specific roles that practitioners perform at organizations where appointments are available. According to the FHIR R4 PractitionerRole definition, practitioner roles define the specific context in which practitioners provide services, including:
Each PractitionerRole includes at least:
| field name | type | required | description |
|---|---|---|---|
resourceType |
string | Y | fixed value of "PractitionerRole" |
id |
string | Y | unique identifier for this practitioner role (up to 64 alphanumeric characters and may include - and .) |
identifier |
array of JSON objects | N | Business identifiers that are specific to a role/location |
active |
boolean | N | Whether this practitioner role is in active use |
period |
JSON object | N | The period during which the person is authorized to act as a practitioner in these role(s) for the organization |
→ start |
timestamp as string | N | Start time with inclusive boundary |
→ end |
timestamp as string | N | End time with inclusive boundary, if not ongoing |
practitioner |
JSON object | N | Practitioner that is able to provide the defined services for the organization |
→ reference |
string | N | Reference to a Practitioner resource (e.g., "Practitioner/dr-smith") |
→ display |
string | N | Text alternative for the resource |
organization |
JSON object | N | Organization where the roles are available |
→ reference |
string | N | Reference to an Organization resource |
→ display |
string | N | Text alternative for the resource |
code |
array of JSON objects | N | Roles which this practitioner may perform |
→ coding |
array of JSON objects | N | Coded representation of the role |
→ → system |
string | N | The code system (e.g., "http://snomed.info/sct") |
→ → code |
string | N | The role code |
→ → display |
string | N | Human-readable description of the role |
specialty |
array of JSON objects | N | Specific specialty of the practitioner |
→ coding |
array of JSON objects | N | Coded representation of the specialty |
→ → system |
string | N | The code system (e.g., "http://snomed.info/sct") |
→ → code |
string | N | The specialty code |
→ → display |
string | N | Human-readable description of the specialty |
location |
array of JSON objects | N | The location(s) at which this practitioner provides care |
→ reference |
string | N | Reference to a Location resource (e.g., "Location/clinic-a") |
→ display |
string | N | Text alternative for the resource |
healthcareService |
array of JSON objects | N | The list of healthcare services that this worker provides for this role's Organization/Location(s) |
→ reference |
string | N | Reference to a HealthcareService resource |
→ display |
string | N | Text alternative for the resource |
telecom |
array of JSON objects | N | Contact details that are specific to the role/location/service |
→ system |
string | N | "phone", "email", or "url" |
→ value |
string | N | phone number, email address, or URL for this practitioner role |
availableTime |
array of JSON objects | N | Times the practitioner is available at this location/service (if not specified, then these times are the default for all services) |
→ daysOfWeek |
array of strings | N | Days of the week. Values: "mon", "tue", "wed", "thu", "fri", "sat", "sun" |
→ allDay |
boolean | N | Always available? e.g. 24 hour service |
→ availableStartTime |
string | N | Opening time of day (ignored if allDay = true) |
→ availableEndTime |
string | N | Closing time of day (ignored if allDay = true) |
notAvailable |
array of JSON objects | N | Not available during this period of time due to the provided reason |
→ description |
string | Y | Reason presented to the user explaining why time not available |
→ during |
JSON object | N | Service not available from this date/time |
→ → start |
timestamp as string | N | Starting time with inclusive boundary |
→ → end |
timestamp as string | N | End time with inclusive boundary |
Each identifier object includes a system and a value.
If a PractitionerRole is associated with organization-specific identifiers (such as role-specific employee numbers, provider numbers, or location-specific identifiers), publishers SHOULD include these. The system should be a URL that identifies the identifier system, preferably a page on the publisher's web site (e.g. {"system": "https://healthsystem.example.com/practitioner-role-directory", "value": "ROLE-123"})
National Provider Identifier (NPI): Publishers SHALL include the practitioner's NPI when available, using the system "http://hl7.org/fhir/sid/us-npi" and the 10-digit NPI number as the value (e.g. {"system": "http://hl7.org/fhir/sid/us-npi", "value": "1234567890"})
If a PractitionerRole participates in external registry programs that assign role-specific identifiers, publishers MAY include these identifiers using the appropriate system URL for the registry.
Additional identifiers: Any number of additional identifiers MAY be included. Each should populate system and value as appropriate, following FHIR identifier conventions.
PractitionerRole{
"resourceType": "PractitionerRole",
"id": "doc-smith-role",
"identifier": [{
"system": "https://healthsystem.example.com/practitioner-role-directory",
"value": "ROLE-12345"
}, {
"system": "http://hl7.org/fhir/sid/us-npi",
"value": "1234567890"
}],
"active": true,
"period": {
"start": "2020-01-01"
},
"practitioner": {
"reference": "Practitioner/doc-smith",
"display": "Dr. John Robert Smith"
},
"organization": {
"reference": "Organization/berkshire-family-medicine",
"display": "Berkshire Family Medicine"
},
"code": [{
"coding": [{
"system": "http://snomed.info/sct",
"code": "309343006",
"display": "Physician"
}]
}],
"specialty": [{
"coding": [{
"system": "http://snomed.info/sct",
"code": "394802001",
"display": "General medicine"
}]
}],
"location": [{
"reference": "Location/123",
"display": "Berkshire Family Medicine - Pittsfield"
}],
"telecom": [{
"system": "phone",
"value": "413-555-0123"
}, {
"system": "email",
"value": "appointments@berkshirefamilymedicine.example.com"
}],
"availableTime": [{
"daysOfWeek": ["mon", "tue", "wed", "thu", "fri"],
"availableStartTime": "09:00:00",
"availableEndTime": "17:00:00"
}]
}
| Field Name | Type | Required | Description |
|---|---|---|---|
resourceType |
string | Y | Fixed value of "Practitioner" |
id |
string | Y | Unique identifier for this practitioner (up to 64 alphanumeric characters and may include - and .) |
identifier |
array of JSON objects | N | An identifier for the person as this agent |
→ use |
string | N | "usual", "official", "temp", "secondary", "old" |
→ system |
string | N | The namespace for the identifier value (e.g., "http://hl7.org/fhir/sid/us-npi") |
→ value |
string | N | The value that is unique within the system |
active |
boolean | N | Whether this practitioner's record is in active use |
name |
array of JSON objects | N | The name(s) associated with the practitioner |
→ use |
string | N | "usual", "official", "temp", "nickname", "anonymous", "old", "maiden" |
→ text |
string | N | Full text representation of the name |
→ family |
string | N | Family name (surname) |
→ given |
array of strings | N | Given names (not always 'first'). Includes middle names |
→ prefix |
array of strings | N | Parts that come before the name (e.g., "Dr.", "Prof.") |
→ suffix |
array of strings | N | Parts that come after the name (e.g., "Jr.", "MD") |
→ period |
JSON object | N | Time period when name was/is in use |
→ → start |
timestamp as string | N | Start time with inclusive boundary |
→ → end |
timestamp as string | N | End time with inclusive boundary |
telecom |
array of JSON objects | N | Contact details for the practitioner (e.g., phone, email) |
→ system |
string | N | "phone", "fax", "email", "pager", "url", "sms", "other" |
→ value |
string | N | The actual contact point details |
→ use |
string | N | "home", "work", "temp", "old", "mobile" |
→ rank |
integer | N | Specify preferred order of use (1 = highest) |
→ period |
JSON object | N | Time period when the contact point was/is in use |
→ → start |
timestamp as string | N | Start time with inclusive boundary |
→ → end |
timestamp as string | N | End time with inclusive boundary |
address |
array of JSON objects | N | Address(es) of the practitioner |
→ use |
string | N | "home", "work", "temp", "old", "billing" |
→ type |
string | N | "postal", "physical", "both" |
→ text |
string | N | Full text representation of the address |
→ line |
array of strings | N | Street name, number, direction & P.O. Box etc. |
→ city |
string | N | Name of city, town etc. |
→ district |
string | N | District name (aka county) |
→ state |
string | N | Sub-unit of country (abbreviations ok) |
→ postalCode |
string | N | Postal code for area |
→ country |
string | N | Country (e.g. can be ISO 3166 2 or 3 letter code) |
→ period |
JSON object | N | Time period when |
Each line of the Schedule File is a minified JSON object that conveys information about a Schedule to which slots are attached. The Schedule represents a particular healthcare service (e.g., primary care appointments, specialist consultations, or procedures) offered at a specific location or by a specific practitioner role.
Each Schedule includes at least:
| Field Name | Type | Required | Description |
|---|---|---|---|
resourceType |
string | Yes | fixed value of "Schedule" |
id |
string | Yes | a unique identifier for this schedule (up to 64 alphanumeric characters and may include - and .) |
actor |
array of JSON objects | Yes | References to the primary resource(s) that the schedule is providing availability for. This array can contain multiple actors, commonly including both Location and PractitionerRole references to indicate appointments for a specific practitioner role at a specific location. |
→ reference |
string | Yes | Reference to a Location, PractitionerRole, or other resource. Use Location + / + the id value (e.g., "Location/123") for location references and PractitionerRole + / + the id value (e.g., "PractitionerRole/doc-smith-role") for practitioner role references. Multiple references can be included to represent schedules that are associated with both a specific practitioner role and a specific location. |
→ display |
string | No | Human-readable label for the referenced actor. For a Location, populate with Location.name. For a PractitionerRole, populate with PractitionerRole.practitioner.display. Including display helps clients render schedules without dereferencing actors. |
serviceType |
array of JSON objects | No | Each object is a standardized concept indicating what services are offered. The serviceType should use appropriate coding systems such as SNOMED CT or the HL7 service-type code system. For example, a general practice appointment schedule might include: <pre>[{ "system": "http://terminology.hl7.org/CodeSystem/service-type", "code": "124", "display": "General Practice" }]</pre> For immunization services, you might use: <pre>[{ "system": "http://terminology.hl7.org/CodeSystem/service-type", "code": "57", "display": "Immunization" }]</pre> Additional serviceTypes may be included if this schedule offers multiple services; or additional codings may be included to convey more nuanced information about the services offered. Multiple codings can express different levels of specificity following the FHIR convention for "codeable concepts" – see here for details. |
extension |
array of JSON objects | No | see details below |
Schedules can reference multiple actors in the actor array to provide more specific context about the healthcare service. Common patterns include:
When multiple actors are specified, all referenced resources apply to the schedule and its associated slots.
Each Schedule object may optionally include extension JSON objects in the Schedule's extension array to provide additional context about the healthcare services offered. Common extensions might include:
Specialty extension: Used to indicate the medical specialty associated with this schedule. This helps clients categorize and filter schedules by practitioner role specialty or service area.
| field name | type | description |
|---|---|---|
url |
string | fixed value of "http://fhir-registry.smarthealthit.org/StructureDefinition/specialty" |
valueCoding |
JSON object | A coded value representing the medical specialty |
→ system |
string | The code system (e.g., "http://snomed.info/sct" for SNOMED CT) |
→ code |
string | The specialty code |
→ display |
string | Human-readable description of the specialty |
Service Category extensions: Used to provide additional categorization of healthcare services beyond the standard serviceType codes. These can help clients filter and display services more effectively.
PractitionerRole extensions: Used to indicate specific practitioner role qualifications or specializations required for appointments on this schedule (e.g., board certifications, special training, location-specific credentials).
Extensions should follow FHIR extension conventions and use appropriate extension URLs. Implementers may define custom extensions as needed for their specific use cases, following FHIR extension guidelines.
Schedule{
"resourceType": "Schedule",
"id": "456",
"serviceType": [
{
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/service-type",
"code": "124",
"display": "General Practice"
}
]
}
],
"actor": [
{
"reference": "Location/123",
"display": "Berkshire Family Medicine - Pittsfield"
},
{
"reference": "PractitionerRole/doc-smith-role",
"display": "Dr. John Robert Smith"
}
],
"extension": [
{
"url": "http://fhir-registry.smarthealthit.org/StructureDefinition/specialty",
"valueCoding": {
"system": "http://snomed.info/sct",
"code": "394802001",
"display": "General medicine"
}
}
]
}
This example demonstrates a Schedule with multiple actors, indicating that general practice appointments are available for practitioner role "doc-smith-role" at location "123". This pattern is commonly used when a specific practitioner role provides services at a specific location.
Each line of the Slot File is a minified JSON object that conveys information about an appointment slot. Publishers are encouraged to represent slots with fine-grained timing details (e.g. representing appointments at specific times of the day), but MAY represent slots with coarse grained timing (e.g., "between 9 a.m. and 5 p.m." or "between noon and five p.m.").
Note: When publishing a Slot with "status": "free", Publishers should ensure that the Slot is in fact available for booking, given current business rules. For example, if a provider requires certain prerequisites to be met before an appointment can be booked (such as referrals, prior authorization, or specific patient eligibility criteria), then the provider SHOULD NOT advertise the slot as available unless those requirements are satisfied.
Each Slot has:
| field name | type | required | description |
|---|---|---|---|
resourceType |
string | Y | fixed value of "Slot" |
id |
string | Y | a unique identifier for this slot (up to 64 alphanumeric characters and may include - and .) |
schedule |
JSON object | Y | has a single field indicating the Schedule this slot belongs to |
→ reference |
string | Y | the schedule for this slot formed as Schedule + / + the id value of an entry in a Schedule File (e.g., "Schedule/123"). |
status |
string | Y | "free", "busy", "busy-tentative", or "busy-unavailable". The "busy-tentative" status indicates a slot is temporarily held (see Find → Hold → Book Pattern). |
start |
timestamp as string | Y | the start time of this slot. Together start and end SHOULD identify a narrow window of time for the appointment, but MAY be as broad as the clinic's operating hours for the day, if the publisher does not support fine-grained scheduling. Timestamp SHALL be expressed with an accurate offset suffix, which SHOULD reflect the local timezone offset of the Location this slot belongs to (e.g., -05:00 suffix for UTC-5) or use UTC (i.e., Z suffix). For example, to represent a start time of 10:45AM in America/New_York on 2021-04-21, this could be returned as either 2021-04-21T10:45:00.000-04:00 or 2021-04-21T14:45:00.000Z. |
end |
timestamp as string | Y | the end time of this slot. See notes about offset suffix for start. |
extension |
array of JSON objects | N | see details below |
Each Slot object may optionally include one or both of the following extension JSON objects in the Slot's extension array.
"Booking link" extension: used to convey a web link into the Provider Booking Portal (see below) where the user can begin booking this slot.
| field name | type | description |
|---|---|---|
url |
string | fixed value of "http://fhir-registry.smarthealthit.org/StructureDefinition/booking-deep-link" |
valueUrl |
string | URL that's a deep link into the Provider Booking Portal |
"Booking phone" extension: used to convey a phone number the user can call to book this slot.
| field name | type | description |
|---|---|---|
url |
string | fixed value of "http://fhir-registry.smarthealthit.org/StructureDefinition/booking-phone" |
valueString |
string | Phone number the user can call to book this slot. |
Slot{
"resourceType": "Slot",
"id": "789",
"schedule": {
"reference": "Schedule/456"
},
"status": "free",
"start": "2021-03-10T15:00:00-05",
"end": "2021-03-10T15:20:00-05",
"extension": [{
"url": "http://fhir-registry.smarthealthit.org/StructureDefinition/booking-deep-link",
"valueUrl": "https://ehr-portal.example.org/bookings?slot=opaque-slot-handle-89172489"
}]
}
The Health Service resource is used to describe a single healthcare service or category of services that are provided by an organization at a location. The location of the services could be virtual, as with telemedicine services. This profile provides a scheduling-optimized view of healthcare service offerings, enabling discovery and booking of appointments when no specific practitioner is required or specified.
| Field Name | Type | Required | Description |
|---|---|---|---|
id |
id | Yes | Logical identifier for the health service |
active |
boolean | Yes | Whether the service is currently active and available for scheduling |
providedBy |
Reference(Organization) | No | Organization that provides this service |
category |
CodeableConcept | No | Broad categorization of the service (e.g., Primary Care, Allied Health, Mental Health) |
type |
CodeableConcept | Yes | Specific type of service offered (e.g., Physical Therapy, Vaccination, Emergency Services) |
specialty |
CodeableConcept | Yes | Clinical specialty required to perform the service (e.g., Cardiology, General Practice) |
location |
Reference(Location) | Yes | Physical or virtual location(s) where service is provided |
name |
string | Yes | Human-readable name of the service for display purposes |
comment |
string | No | Additional description or instructions relevant to scheduling (e.g., "Walk-in available", "Referral required") |
telecom |
ContactPoint | No | Contact information for the service (phone, email, website) |
serviceProvisionCode |
CodeableConcept | No | Conditions under which service is available (e.g., free, fees apply, by referral only) |
appointmentRequired |
boolean | No | Whether an appointment is required for this service (false indicates walk-in accepted) |
availableTime |
BackboneElement | No | General times the service is available at the location |
availableTime.daysOfWeek |
code | No | Days of week service is available (mon, tue, wed, thu, fri, sat, sun) |
availableTime.allDay |
boolean | No | Service available all day during specified days |
availableTime.availableStartTime |
time | No | Opening time of day (ignored if allDay = true) |
availableTime.availableEndTime |
time | No | Closing time of day (ignored if allDay = true) |
notAvailable |
BackboneElement | No | Exception periods when the service is not available |
notAvailable.description |
string | Yes* | Reason service is not available (*required if notAvailable is present) |
notAvailable.during |
Period | No | Specific time period when service is unavailable |
{
"resourceType": "HealthcareService",
"id": "online-primary-care",
"active": true,
"providedBy": {
"reference": "Organization/acme-health",
"display": "ACME Health System"
},
"category": [{
"coding": [{
"system": "http://terminology.hl7.org/CodeSystem/service-category",
"code": "17",
"display": "General Practice"
}]
}],
"type": [{
"coding": [{
"system": "http://snomed.info/sct",
"code": "308335008",
"display": "Patient encounter procedure"
}],
"text": "Primary Care Visit"
}],
"specialty": [{
"coding": [{
"system": "http://snomed.info/sct",
"code": "394814009",
"display": "General practice"
}]
}],
"location": [{
"reference": "Location/main-clinic",
"display": "Main Street Clinic"
}],
"name": "Primary Care Appointments - Online Booking",
"comment": "Book your primary care appointment online. Appointments available with next available provider. For urgent needs, please call our office or visit urgent care.",
"telecom": [{
"system": "phone",
"value": "(555) 123-4567",
"use": "work"
}, {
"system": "email",
"value": "appointments@acmehealth.org",
"use": "work"
}, {
"system": "url",
"value": "https://appointments.acmehealth.org",
"use": "work"
}],
"serviceProvisionCode": [{
"coding": [{
"system": "http://terminology.hl7.org/CodeSystem/service-provision-conditions",
"code": "cost",
"display": "Fees apply"
}]
}],
"appointmentRequired": true,
"availableTime": [{
"daysOfWeek": ["mon", "tue", "wed", "thu", "fri"],
"availableStartTime": "08:00:00",
"availableEndTime": "17:00:00"
}],
"availabilityExceptions": "Closed on major holidays. Online booking available 24/7 for future appointments."
}
The Booking Portal is responsible for handling incoming deep links.
Each Slot exposed by the Slot Publisher can include an extension indicating the Booking Deep Link, a URL that the Slot Discovery Client can redirect a user to. The Slot Discovery Client can attach the following URL parameters to a Booking Deep Link:
source: a correlation handle indicating the identity of the Slot Discovery Client, for use by the Provider Booking Portal in tracking the source of incoming referrals.booking-referral: a correlation handle for this specific booking referral. This parameter can optionally be retained by the Provider Booking Portal throughout the booking process, which can subsequently help the Slot Discovery Client to identify booked slots. (Details for this lookup are out of scope for this specification.)For the Slot example above, a client can construct the following URL to provide a deep link for a user to book a slot:
sourcebooking-referralIn this case, if the source value is source-abc and the booking-referral is 34d1a803-cd6c-4420-9cf5-c5edcc533538, then the fully constructed deep link URL would be:
https://ehr-portal.example.org/bookings?slot=opaque-slot-handle-89172489&source=source-abc&booking-referral=34d1a803-cd6c-4420-9cf5-c5edcc533538
(Note: this construction is not as simple as just appending &source=... to the booking-deep-link, because the booking-deep-link may or may not already include URL parameters. The Slot Discovery Client must take care to parse the booking-deep-link and append parameters, e.g., including a ? prefix if not already present.)
This specification adheres to general guidance in FHIR scheduling to implement a Find, Hold, Book pattern for appointment booking. This pattern provides a robust workflow that minimizes booking conflicts while maintaining the scalability benefits of static slot publication.
The recommended appointment booking workflow follows these stages:
This specification recognizes that Slot Publishers and Provider Booking Portals are typically the same organizational entity serving different functional roles from a unified scheduling system, or at least both have direct access to the underlying source of truth. This unified model resolves potential consistency challenges:
"status": "busy-tentative") in the static filesProvider Booking Portals SHOULD initiate slot holds automatically when:
Holds provide a critical user experience improvement by reducing booking failures due to concurrent access while the user is actively engaged in the booking process.
This approach maintains clean separation of concerns:
The Provider Booking Portal is responsible for initiating holds within the scheduling system's source of truth, with the Slot Publisher reflecting these state changes in subsequent publications at its discretion based on publication frequency and caching strategies.
Systems that re-publish data from other Slot Publishers are referred to as Slot Aggregators. If you are a Slot Aggregator, you may wish to use some additional extensions and FHIR features to supply useful provenance information or describe ways that your aggregated data may not exactly match the definitions in the SMART Scheduling Links specification. The practices and approaches in this section are recommendations; none are required for a valid implementation.
Slot Aggregators usually need to assign new id values to resources in order to make sure they are unique across the aggregated dataset. The id of the original resource SHOULD be preserved in the identifier list, alongside the identifiers from the original resource. The specific system and value used for the identifier is left up to the implementer.
For example, given this Location resource from an underlying source:
{
"resourceType": "Location",
"id": "123",
"identifier": [
{
"system": "https://healthsystem.example.com/facility-directory",
"value": "FAC-PITT-001"
}
],
"name": "Berkshire Family Medicine - Pittsfield",
// additional Location fields here...
}
```js
A _Slot Aggregator_ might publish a Location like:
```js
{
"resourceType": "Location",
"id": "456",
"identifier": [
{
"system": "https://healthsystem.example.com/facility-directory",
"value": "FAC-PITT-001"
},
{
"system": "https://berkshirefamilymedicine.example.org/",
"value": "123"
}
],
"name": "Berkshire Family Medicine - Pittsfield",
// additional Location fields here...
}
In this example, https://berkshirefamilymedicine.example.org/ is an arbitrary string that defines “Flynn’s Pharmacy” as the identifier system. Slot Aggregators should only use a URL that is not under their control in cases where the URL is predictable and might reasonably be chosen by other Slot Aggregators. When this is not the case, Slot Aggregators SHOULD choose a URL under their control. For example, an aggregator at usdr.example.org might choose a URL like https://usdr.example.org/fhir/identifiers/flynns.
When the source system is a SMART Scheduling Links implementation, a Slot Aggregator SHOULD use FHIR’s Resource.meta.source field to describe it. The value is a URI that SHOULD include the URL of the source system, and MAY add the resource id.
For example, given the above example resource at https://api.flynnspharmacy.example.org/fhir/smart-scheduling/$bulk-publish, a Slot Aggregator might publish a location like:
{
"resourceType": "Location",
"id": "456",
"meta": {
"source": "https://api.flynnspharmacy.example.org/fhir/smart-scheduling/Location/123"
},
"identifier": [
{
"system": "https://cdc.gov/vaccines/programs/vtrcks",
"value": "CV1654321"
},
{
"system": "https://flynnspharmacy.example.org/",
"value": "123"
}
],
"name": "Flynn's Pharmacy in Pittsfield, MA",
// additional Location fields here...
}
### Indicate Data “Freshness”
Aggregators may request or receive information from publishers at different times, and understanding how recently data about a Location, Schedule, or Slot was retrieved can help end users gauge the accuracy of items in an aggregated dataset. _Slot Aggregators_ SHOULD use the `lastSourceSync` extension on the `meta` field of any resource to indicate the last time at which the data was known to be accurate:
```js
{
"resourceType": "Schedule",
"id": "123",
"meta": {
"extension": [{
"url": "http://hl7.org/fhir/StructureDefinition/lastSourceSync",
"valueDateTime": "2021-04-19T20:35:05.000Z"
}]
}
// additional Schedule fields here...
}
Because source systems may experience errors or may not conform to the SMART Scheduling Links specification, Slot Aggregators need additional tools to describe unusual situations that are not relevant to first-party Slot Publishers. Specifically, Slot Aggregators may describe Schedules where:
Use the optional "Has Availability" extension to convey that a Schedule has non-zero or unknown future availability, without conveying details about when or how much. When used on a Schedule, that Schedule MAY have no associated Slots. Slot Publishers SHALL NOT use this capacity in place of publishing granular Slots; it is defined to support Slot Aggregators (i.e. systems that re-publish Slot data from other APIs).
| field name | type | description |
|---|---|---|
url |
string | Fixed value of "http://fhir-registry.smarthealthit.org/StructureDefinition/has-availability" |
valueCode |
string | One of: <ul><li>"some": The Schedule has non-zero future availability.</li><li>"none": The Schedule has no future availability.</li><li>"unknown": The schedule has unknown future availability (e.g. because there is no source of data for this schedule or because the source system had errors or was unparseable).</li></ul> |
Example usage on a Schedule:
{
"resourceType": "Schedule",
"id": "456",
"serviceType": [
{
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/service-type",
"code": "124",
"display": "General Practice"
}
]
}
],
"extension": [
{
"url": "http://fhir-registry.smarthealthit.org/StructureDefinition/has-availability",
"valueCode": "some"
}
],
"actor": [
{
"reference": "Location/123"
}
]
}