Resources

Fill Cancelled Appointments in Canvas Medical

By Federico Ruiz Cassarino, CEO and founder, Puppeteer AI · · 6 min read

Canvas Medical does something most EHRs don't: it treats an open slot as a real thing you can ask for.

That sounds minor. It's the difference between asking "when is this provider free next week" and reconstructing the answer yourself from a list of bookings and a guess about working hours. If you've built cancellation workflows on other systems, this is the part that will feel unfamiliar.

Slot is a resource, not a calculation

Canvas is FHIR. Open time is exposed as /Slot, and you query it directly: GET /Slot?schedule=Schedule/{id}&start={date}&end={date}.

Two optional parameters narrow it further. duration filters to slots long enough for the visit you're trying to book. appointment-type takes a system code and filters to slots that accept that type.

The response comes back as a FHIR bundle. The slots are in entry, each with a resource. Standard shape if you've worked with FHIR, mildly surprising if you haven't, since an empty week returns a valid bundle with no entries rather than an error.

What you get from this is that availability is Canvas's answer, not yours. You aren't inferring free time by subtracting appointments from a schedule and hoping you got the provider's blocked time right. When a cancellation frees an hour, /Slot reflects it.

You need a Schedule id before you can ask

The catch is in that first parameter. /Slot is scoped to a schedule, and a schedule id isn't something you know in advance.

You get it from /Schedule, and the query is by actor: GET /Schedule?actor=Location/{location_id}&actor=Practitioner/{practitioner_id}.

Two actors, both required in practice, because a practitioner working across two locations has a schedule per location. The parameter repeats rather than taking a list, a small detail that trips up HTTP clients that collapse duplicate query keys.

So availability is always two hops: resolve the schedule for this practitioner at this location, then ask that schedule for open slots. Cache the schedule ids. They're stable, and re-resolving them on every availability check doubles your request count for no benefit.

Booking is a POST, and the body is more interesting than it looks

To fill the slot you create an appointment with POST /Appointment.

The body is a FHIR Appointment with status: "booked", a start, an end, and a participant array. Each participant is an actor reference (Practitioner/{id} and Patient/{id}) with a status of "accepted".

Location doesn't go in the participant array. It goes in supportingInformation, as a reference to Location/{id}.

Telehealth is where it gets genuinely unusual. There's no video_url field. Instead the appointment carries a contained array holding an inline FHIR Endpoint resource: an id you choose, a status of "active", a connectionType code of "https", a payloadType of "video", and the meeting URL in address.

Then supportingInformation references it by fragment, #appointment-meeting-endpoint-0, with the leading hash that marks a contained resource.

This is correct FHIR and it is nobody's first guess. If you're porting a booking flow from a system with a flat video-link field, this is the piece that silently doesn't carry over: the appointment books fine, and the patient has no way to join.

Everything comes back as a bundle

FHIR responses are wrapped. A slot search doesn't return an array of slots. It returns a Bundle, and the slots sit under entry, each one nested inside its own resource key. Same for schedules, same for appointments.

Two consequences worth planning for.

The first is that "no results" and "error" look nothing alike, which is good, but "no results" also looks a lot like success, which is where bugs hide. A /Slot query against a schedule id that doesn't exist returns an empty bundle, not a 404. So does a genuinely full week. If your code treats empty as "nothing open," a mistyped schedule id reads as a fully booked provider forever, and nothing in the logs objects.

The second is paging. Bundles page, and _count controls the page size on searches that support it. For a single provider over a week this rarely bites. For a date range spanning a month across a busy schedule, taking only the first page means quietly ignoring most of the availability you asked for.

The contrast with GraphQL-based EHRs is sharp here. On Healthie you name the fields you want and get exactly those back, but availability is scoped per provider per appointment type with no slot resource underneath. Canvas gives you the slot as a real object and hands you the whole resource whether you wanted all of it or not. Neither is better in the abstract; they fail differently, and the failures are what you actually build around.

What a cancellation workflow actually looks like

Because slots are queryable, the loop is simpler than on EHRs that don't expose them:

  • Learn that an appointment was cancelled. The trigger, and the only part that has to be timely.
  • Confirm the time is genuinely open. Query /Slot for the affected schedule and date range.
  • Rank the patients who could take it. By overdue-ness, by fit, by who has already been contacted this week.
  • Book whoever accepts. POST /Appointment against the slot you just confirmed.

That second step is worth doing rather than assuming. A cancellation frees the time only if nothing else claims it: a double-booking, a provider block, an administrative hold. Asking /Slot rather than inferring from the cancellation means you never offer a slot that isn't really there, which is the failure that costs you trust with patients the fastest.

Ranking is the part Canvas doesn't help with, and it's the same everywhere: the EHR knows what's open, not who wants it.

What you need on your side

  • A Canvas instance and API credentials. Issued for your own organization.
  • The location and practitioner ids. The pairs you will be resolving schedules for, and somewhere to cache the resulting schedule ids.
  • Appointment type system codes. If you intend to filter slots by type. These are codes, not names, and the mapping has to be maintained.
  • The ranked list of patients. Which lives outside Canvas.

Honest limits

  • /Slot is scoped per schedule, and a schedule is per practitioner per location. A practice-wide availability view means one query per combination, which grows quickly.
  • Canvas tells you a slot is open. It has no concept of who is waiting for one, and its appointment status valueset doesn't include a waitlist state at all. That list is yours to build and yours to keep.
  • Slot data is only as good as the provider's schedule configuration. Time that isn't modeled as available won't appear, no matter how empty the calendar looks in the UI.
  • Contained resources are easy to get subtly wrong. A malformed contained entry can be accepted without error while producing an appointment that behaves oddly downstream.

Sources

  1. Canvas Medical API: Appointment

About the author

Federico Ruiz Cassarino is the CEO and founder of Puppeteer AI, the company behind Recupra. He works with clinic operators on the scheduling and outreach problems that decide how much of a practice's booked capacity turns into revenue. Connect on LinkedIn.

FAQ

Frequently asked questions

Does Canvas have a waitlist?

Not as appointment state. Canvas implements the FHIR appointment status valueset without the waitlist value, so there is no way to mark a patient as waiting on the appointment itself.

Do I have to query /Schedule every time?

No, and you shouldn't. Schedule ids are stable per practitioner-location pair. Resolve them once, cache them, refresh when staffing changes.

Why doesn't my telehealth link show up?

Most likely the contained Endpoint is missing, or the supportingInformation reference doesn't match its fragment id. The appointment will book successfully either way, which is what makes this one hard to catch.

Can I filter slots by appointment length?

Yes, via duration on the /Slot query. Combine it with appointment-type when you need slots that accept a specific visit type rather than just slots of the right size.

Fill the cancelled slot while it is still worth filling.

Connect Canvas Medical and Recupra reads the open slot, ranks who can take it, and calls them until it's booked.