Resources
Appointment Scheduling API for Cerner and Oracle Health
By Ian Spektor, CTO, Puppeteer AI · · 6 min read
When a patient cancels at a Cerner site, the freed slot only earns revenue if someone books it before the day arrives. Recupra's waitlist automation searches for the open slot, offers it to the best-matched patient, and books whoever accepts. This post is the technical companion for your IT or integration team: how Cerner's scheduling API differs from other FHIR servers, and what a cancellation workflow needs from it.
Cerner, now Oracle Health, speaks FHIR, which makes it look familiar to anyone who has integrated with Canvas or Medplum. The resources have the same names. The parameters don't behave the same way, and a slot search that works verbatim against another FHIR server will return nothing here.
This is a walkthrough of the differences that actually cost time.
The slot search wants more than you'd expect
Finding open time is a GET /Slot search. It needs at least one of three filters, -location, schedule.actor or service-type, plus a start range whenever any of them is present. Each filter may appear only once, though service-type accepts a comma-separated list.
GET /Slot?service-type={system}|{code}&-location={id}&start=ge{date}T00:00:00.000Z&start=lt{date}T23:59:59.000Z
- The location filter carries a leading hyphen.
-location, notlocation. It's an Oracle-defined search parameter whose name starts with a hyphen, not a FHIR modifier (those are colon suffixes such as:exact), and it's easy to normalize away in a client library that tidies parameter names. - The type value is a fully qualified system, not a bare code. The system is
https://fhir.cerner.com/followed by the EHR source id, the literal path segmentcodeSetand a code set number, with the appointment type code after a pipe:
https://fhir.cerner.com/{ehr_source_id}/codeSet/14249|24477854
14249 is the Scheduling Appointment Type Synonyms code set, and Oracle documents that code sets are consistent across customers while the values inside them vary. 24477854 is the Established Patient type in Oracle's public sandbox, so it is an example: the code set number is a constant, the appointment type code is something you look up per health system. Canadian and Australian tenants use fhir.ca.cerner.com and fhir.au.cerner.com.
Timestamps are explicit and zoned too. The range uses ge with a full T00:00:00.000Z and lt with T23:59:59.000Z, repeated on the same start key. Both bounds are required.
Booking binds to the slot you found
Once you have a slot, creating the appointment is a POST /Appointment, and the status you send decides which fields are required. Only proposed and booked are accepted.
- A booked appointment takes its time from the slot. Top-level
startandendare not among the supported body fields, and Oracle warns that unsupported fields are ignored or can cause errors. A proposed appointment instead carriesrequestedPeriodas a single period with both start and end, a singleserviceType, and the patient plus at least one location as participants. - The slot is referenced explicitly. The appointment carries
slot: [{ reference }], pointing at the slot resource you retrieved. This is what ties the booking to real availability rather than asserting a time and hoping the schedule agrees.
The rest of a booked body is short: status: "booked", exactly one slot reference, a participant array holding exactly one patient as an actor reference at status: "accepted", and optionally a reasonCode. The service type comes from the slot, so a booked appointment does not need one.
That slot binding is the meaningful design difference. On Healthie you assemble an appointment from a date and a set of ids. Here you must first obtain a slot object, then book against it. It's more round trips, and considerably fewer ways to create an appointment that doesn't correspond to anything on the calendar.
What a cancellation workflow looks like here
The slot binding shapes the whole flow, and mostly in your favor.
When an appointment is cancelled, the underlying slot becomes available again. Your workflow re-runs the slot search for that practitioner, location, and service type, and the freed time reappears as a real slot object with its own reference. You then book a waiting patient against that reference.
Compare that to an EHR where you assert a time and hope. Here, if the slot search doesn't return the freed time, it genuinely isn't bookable, whether because of a provider block, an overlapping appointment, or a schedule that was never configured to offer that service type at that hour. You find out before you contact a patient rather than after they accept.
The cost is round trips. Every offer needs a fresh slot search, because a slot reference retrieved five minutes ago may already be taken. Booking against a stale reference fails, which is correct behavior and still something your code has to handle gracefully rather than surfacing as an error to whoever is on the phone.
Build the flow as search-then-book, close together, and treat a failed booking as search again rather than as a fault.
Reading a patient's appointments
Straightforward, with the same timestamp discipline:
GET /Appointment?patient={id}&date=ge{start}T00:00:00.000Z&date=lt{end}T23:59:59.000Z&_count={n}
_count controls page size. Set it deliberately, because the default won't be what you assume, and a patient with a long history will page.
Note that patient here takes a bare id rather than a Patient/{id} reference, while schedule.actor takes a reference and Oracle's own examples write -location both ways. That inconsistency is worth a comment in your own code, because you will get it backwards at least once.
Everything is tenant-scoped
The tenant id isn't a header or a token claim. It's baked into the FHIR base URL, and each health system also has its own EHR source id inside the code set systems you send as search parameters.
Two consequences follow. Configuration is per tenant, including appointment type codes that look like constants but aren't; only the code set numbers are shared. And anything you build for one health system needs its identifiers re-derived, rather than copied, when it moves to the next.
Treat the code set system and the appointment type codes as per-tenant configuration. Hard-coding the assembled string is the shortcut that works in one environment and fails silently in the next.
What you need on your side
- Tenant credentials, the tenant's FHIR base URL, and its EHR source id. The base URL addresses every call; the source id goes into the code set systems for slot searches.
- Code values for the appointment types you schedule. Look them up per tenant. The code set number carries over, the values inside it don't.
- Location, practitioner or service type ids. Any one of them, plus a start range, scopes a slot search.
- An HTTP client that leaves parameter names alone. One that also supports repeating a key with different prefixes. Both requirements are unusual enough to break naive clients built against other FHIR servers.
What access actually costs
The technical work above is the cheap part, and it's worth knowing that before you scope a Cerner project.
Oracle publishes its prices, which not every large vendor does. Its developer page lists the paid services as optional value-added extras that are not required to build and deploy an app, charged as annual fees: Oracle PartnerNetwork membership, and a dedicated Millennium environment for development and testing at US$5,000 a year. The PartnerNetwork page adds a validation fee of US$8,500 per validation event. None of it is metered per API call.
Annual fees are a different planning problem from usage pricing. A Cerner integration doesn't get much cheaper by starting small, so the sensible move is to have more than one health system lined up before paying for an environment, rather than treating it as the cost of an experiment. The figures are also knowable in advance, which puts Oracle ahead of several vendors in this series who won't name a price until you're deep in a sales conversation. Check the current numbers on Oracle's pages before you budget.
The other cost isn't billed to you at all: the health system's own approval process to let a new vendor into their environment. That's measured in months, it varies by organization, and no amount of budget shortens it.
Honest limits
- Slot searches are narrow by construction. Each filter can appear only once, so a sweep across many locations or practitioners is many queries, not one.
- Nothing here is a waitlist. Cerner's scheduling surface exposes availability and bookings; the list of patients wanting an earlier slot lives in your own system.
- Availability reflects schedule configuration. Time that isn't modeled as a bookable slot for that service type won't appear, however empty the calendar looks.
- Access is the long pole. Getting production API access to a health system's Cerner instance is an organizational process with published annual fees, not a signup. Plan the integration timeline around that, not around the code.
The same guide for other EHRs: filling cancelled appointments in Canvas Medical, filling cancelled appointments in athenahealth, and waitlist automation in Medplum.
Sources
- Oracle Health: developer API program and pricing
- Oracle PartnerNetwork: Integrated with Oracle Health platform track, fees
- Oracle Health Millennium: proprietary code sets and code set system format
- Oracle Health FHIR R4: Get a list of slots
- Oracle Health FHIR R4: Create an appointment
- Oracle Health FHIR R4: Get a list of appointments
About the author

Ian Spektor is the CTO of Puppeteer AI, the company behind Recupra. He leads engineering and technology across the company, building the AI agents, integrations and infrastructure that let healthcare teams reach their patients at scale, securely and in line with HIPAA. More from Ian · LinkedIn