Resources

DrChrono Recall Inside a 500/Hour Budget

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

DrChrono's API is one of the more approachable EHR integrations to build against. It's REST, it's JSON, the resources are named the way you'd guess, and the documentation is public without a sales call. After a week inside a SOAP API or a FHIR server with an idiosyncratic search implementation, it feels like a holiday.

Then you read the rate limit, and the architecture of your recall system gets decided for you. DrChrono caps API access at 500 calls an hour, and that ceiling shapes how you sync appointments, how you rank a call list, and how you spend calls on the day you actually contact someone.

500 calls per hour

DrChrono's documented default is 500 API calls per hour per application, with requests over the limit returning HTTP 429. Two further throttles sit underneath it and are not configurable: ten calls in one second, and 290 requests in any rolling ten-minute window.

That second number is the one that actually decides your job design. Five hundred an hour reads like a budget you can spend whenever you like, but 290 per ten minutes means the hour cannot be front-loaded. A recall sweep has to be paced across the whole window rather than fired off at 2am and left to run.

Sit with the number for a moment, because it's smaller than it sounds. A recall campaign over a 3,000-patient list, fetching each patient individually, is six hours of pure reads before a single message goes out. A nightly sync that walks every appointment one at a time will not finish before the clinic opens.

Everything below follows from that constraint, and it's why a DrChrono recall system ends up structured differently from one built against an EHR with generous limits. You spend calls on change, not on state.

Sync once, then sync deltas

The move is to hold a local mirror and never re-read what hasn't changed.

DrChrono appointments carry created_at, updated_at, and extended_updated_at timestamps. Those fields are what turn an unaffordable full sync into an affordable incremental one: after the initial backfill, you only ask for records modified since your last successful run.

Two things make this reliable rather than merely clever. Store the high-water mark from the server's timestamps, not from your own clock, so a slow run doesn't skip records. And overlap the window slightly, asking for changes since a minute or two before your last mark, because near-simultaneous writes at a boundary are the classic way to lose a record silently.

Page size matters too. Fetching 200 records in one call and fetching 200 records in 200 calls cost the same in data and differ by 199 in budget. Request the largest page the endpoint allows.

The status field, and why recall needs it

DrChrono's appointment status field takes a documented set of values: an empty string, Arrived, Checked In, In Room, Cancelled, Complete, Confirmed, In Session, No Show, Not Confirmed, and Rescheduled.

Two of those are unusual and both are useful. The empty string is a real, meaningful value: an appointment nobody has touched yet. It isn't the same as Not Confirmed, which is a deliberate statement that the patient hasn't confirmed. Treating blank and Not Confirmed as one bucket is a common bug, and it produces confirmation reminders for appointments the front desk hasn't finished entering.

Rescheduled is rarer still. Most EHRs model a reschedule as a cancel plus a create and leave you to infer the relationship. DrChrono names it directly. For recall that distinction matters: a patient who rescheduled is engaged and shouldn't be swept into a reactivation campaign, while a patient whose appointment was cancelled and never rebooked is exactly who the campaign is for.

There is also status_transitions, which carries the history rather than just the current value. When you need to know when something became a no-show, not merely that it is one, that field is where to look.

Finding open time

Availability doesn't live on the appointments endpoint. DrChrono models it through /api/appointment_templates, which defines blocks of time containing available slots. Querying that endpoint with the available parameter returns the unscheduled blocks.

Related but distinct is /api/appointment_profiles, which sets default values for a visit's color, duration, and reason. Profiles describe the shape of a visit type; templates say when that shape can be booked. Conflating the two is a half-day of confusion the first time you hit it.

A few appointment fields shape scheduling logic worth knowing before you write a booking flow. allow_overlapping means a booked calendar isn't necessarily a full one. appt_is_break marks blocks that aren't patient time at all and have to be excluded from any availability calculation. is_walk_in flags visits that never went through scheduling, which matters when you're measuring whether recall actually produced booked appointments: walk-ins will inflate the count if you don't filter them out.

Spending the budget where it counts

With 500 calls an hour, a recall run should look like this. Reads come from the mirror, not from the API. The mirror refreshes on a delta sync that costs a handful of calls, and the calls you do spend go on two things: the sync itself, and a check on each patient's current status immediately before you contact them.

That check is worth its cost. Between the last sync and the moment a message goes out, a patient may have booked. Telling someone you haven't seen them in a while when they have a visit on the calendar next Tuesday is one of the most damaging things a recall system can do, and confirming against it costs one call.

Leave headroom, too. If scheduled jobs consume all 500, there's nothing left for the interactive path, a staff member clicking a button in your UI and getting a 429 instead of a result. Cap background jobs well below the ceiling so a person is never the one who runs out of budget.

Handling the 429 you will eventually get

Plan for it anyway, because someone will run a manual backfill on a Tuesday afternoon while the nightly job is still catching up, and the ceiling will get hit despite the pacing above.

Fail closed on outbound messages. When a 429 interrupts a recall run mid-list, the tempting move is to retry hard and push through to the end. The safer move is to stop sending and pick back up later, because the pre-contact status check described above is exactly what gets skipped under pressure, and an unverified message is worse than a late one.

Design the job so a crash mid-run costs minutes, not a restart. Track progress patient by patient rather than as a single pass, so a failure at record 1,400 of 3,000 resumes at 1,400 instead of re-contacting everyone already reached.

And keep queues separate. A background sync and a staff member's button click competing for the same 500 calls means the sync wins by default, because it's already running. Reserve a share of the budget for interactive requests so a person waiting on a click is never stuck behind a batch job.

Honest limits

Recupra's DrChrono integration connects over OAuth and does cover appointment scheduling: it cancels, confirms, and books directly back into DrChrono. What it doesn't get from DrChrono is a free-slot endpoint: DrChrono doesn't publish one, so availability is derived from your configured office hours minus booked appointments and blockers, the same way a front-desk scheduler would work it out by hand. Patient tags and patient groups aren't part of what DrChrono exposes either.

The field names, status values, and endpoints described above come from DrChrono's public API documentation. Rate limits are stated as a default, which implies they're negotiable: if a use case genuinely needs more than 500 an hour, that's a conversation to have with DrChrono before engineering elaborate workarounds around the number.

Sources

  1. DrChrono API documentation
  2. DrChrono: API bulk endpoints

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

Is the 500 calls per hour limit per application or per practice?

DrChrono documents it as an application-level default. Confirm with DrChrono for your specific setup, especially if one application serves multiple practices.

How do I find available appointment slots in DrChrono?

Query /api/appointment_templates with the available parameter. Availability isn't exposed on the appointments endpoint itself.

Does DrChrono support webhooks?

DrChrono has offered webhook functionality in the past; check current documentation for which events are covered. Where available, a webhook is the best answer to a rate limit, since it removes polling from the picture entirely.

What's the difference between a blank status and Not Confirmed?

Blank means the appointment hasn't been touched yet. Not Confirmed is an explicit statement that the patient hasn't confirmed. Treat them differently in any confirmation workflow, or reminders will go out for appointments the front desk hasn't finished entering.

Recall that respects DrChrono's rate limit.

Connect DrChrono and Recupra runs the delta sync and the pre-contact status check inside the 500-call budget, then calls the patients who are actually still open.