Appointments
Book scheduled or ASAP visits after a consultation, track and change them, read visit results, and save a pharmacy.
After a consultation, book the patient a visit at a scheduled time or in the next available ASAP queue. Start by listing scheduled slots for the patient's US state:
curl "$DOCTRONIC_API_URL/appointments/scheduled/available-slots/?state=NY" \
-H "Authorization: Bearer $DOCTRONIC_API_TOKEN" \
-H "X-Doctronic-User-ID: $DOCTRONIC_USER_ID"{
"code": "success",
"message": "Request successful",
"data": {
"slots": ["2026-08-20T15:00:00Z", "2026-08-20T15:30:00Z"]
}
}Before you book
Booking checks four things. Meet each one first, or the request fails with the error shown:
| Requirement | How to meet it | Error |
|---|---|---|
firstName, lastName, dateOfBirth, and phoneNumber on the user | Collect demographics | 400 error.appointments.missing_patient_demographics |
An address with line1, city, state, and postalCode | Set the address | 400 error.appointments.missing_address or incomplete_address |
A chat whose stream has sent ai_consultation.complete | See end states | 400 error.appointments.chat_not_ready |
A two-letter US state code in location.state | Send the patient's state | 400 error.validation_error |
The chatId must belong to the user in X-Doctronic-User-ID, or booking returns
404 error.appointments.chat_not_found. When no practitioner is available in the state, booking
and rescheduling return 400 error.appointments.no_practitioners_available.
Set the address
PUT /address/ creates or replaces the
patient's address. Every field is optional in the request, but slot listing and booking need the
four fields above. state must be a valid US state code. The response is the success envelope with
data: null.
# Reuse the same key if you retry this request.
IDEMPOTENCY_KEY="$(uuidgen)"
curl -X PUT "$DOCTRONIC_API_URL/address/" \
-H "Authorization: Bearer $DOCTRONIC_API_TOKEN" \
-H "X-Doctronic-User-ID: $DOCTRONIC_USER_ID" \
-H "Idempotency-Key: $IDEMPOTENCY_KEY" \
-H "Content-Type: application/json" \
-d '{"line1": "100 Example Street", "city": "New York", "state": "NY", "postalCode": "10001"}'GET /address/ returns line1, line2,
city, state, and postalCode, with null for any field never supplied. With no address on
file, it returns 404 error.address.not_found.
Book a scheduled appointment
- List slots with
GET /appointments/scheduled/available-slots/.stateis required and limits slots to practitioners credentialed in that state. Bound the search with optionaldatetimeFromanddatetimeToISO 8601 timestamps. - Show the returned
slotsto the patient with their timestamps unchanged. - Book the chosen slot with
POST /appointments/scheduled/, sending the same state. - Store the returned
data.id.
# Reuse the same key if you retry this request.
IDEMPOTENCY_KEY="$(uuidgen)"
curl -X POST "$DOCTRONIC_API_URL/appointments/scheduled/" \
-H "Authorization: Bearer $DOCTRONIC_API_TOKEN" \
-H "X-Doctronic-User-ID: $DOCTRONIC_USER_ID" \
-H "Idempotency-Key: $IDEMPOTENCY_KEY" \
-H "Content-Type: application/json" \
-d '{"chatId": "'"$DOCTRONIC_CHAT_ID"'", "startsAt": "2026-08-20T15:00:00Z", "location": {"state": "NY"}}'const baseUrl = process.env.DOCTRONIC_API_URL ?? 'https://staging-partners.doctronic.ai/api/v1';
// Create idempotencyKey once per intended booking and reuse it when retrying.
export async function bookScheduled(
userId: string,
chatId: string,
startsAt: string,
state: string,
idempotencyKey: string,
) {
const response = await fetch(`${baseUrl}/appointments/scheduled/`, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.DOCTRONIC_API_TOKEN}`,
'X-Doctronic-User-ID': userId,
'Idempotency-Key': idempotencyKey,
'Content-Type': 'application/json',
},
body: JSON.stringify({ chatId, startsAt, location: { state } }),
});
const body = await response.json().catch(() => ({}));
if (response.status === 201) return { booked: true, appointmentId: body.data.id };
if (body.code === 'error.appointments.slot_unavailable') return { booked: false, refreshSlots: true };
throw new Error(`Scheduled booking failed: HTTP ${response.status} ${body.code ?? ''}`);
}A 201 returns the appointment. startsAt must be a slot from the
availability response, or the API returns 400 error.appointments.invalid_slot. A slot can be
taken between listing and booking: on 409 error.appointments.slot_unavailable, refresh
availability and ask the patient to choose again. Never substitute a time the patient didn't choose.
Book an ASAP appointment
POST /appointments/asap/
places the patient in a shared waiting room immediately. A practitioner credentialed in the
patient's state joins when one is available.
# Reuse the same key if you retry this request.
IDEMPOTENCY_KEY="$(uuidgen)"
curl -X POST "$DOCTRONIC_API_URL/appointments/asap/" \
-H "Authorization: Bearer $DOCTRONIC_API_TOKEN" \
-H "X-Doctronic-User-ID: $DOCTRONIC_USER_ID" \
-H "Idempotency-Key: $IDEMPOTENCY_KEY" \
-H "Content-Type: application/json" \
-d '{"chatId": "'"$DOCTRONIC_CHAT_ID"'", "location": {"state": "NY"}}'A 201 returns the appointment plus waitingRoomUrl, the HTTPS URL the patient opens to join. The
URL expires one hour after it is issued and can change when it is reissued, so read the current
value from
GET /appointments/{appointment_id}/
or the appointment.waiting_room_ready webhook each time you show it.
Two 502 errors mean different things:
| Code | Meaning | Handling |
|---|---|---|
error.appointments.provisioning_failed | The clinical service didn't accept the booking. | Show your failure path. |
error.appointments.waiting_room_unavailable | The appointment is booked, but its waiting-room URL isn't available yet. data holds appointmentId and status. | Retrieve that appointment before deciding what to show or retrying. |
Track appointments
Show appointment state from the API rather than from local assumptions.
GET /appointments/lists the patient's appointments. Filter withstatus, and paginate withpage(default1) andsize(default20, maximum100). The response hasitems,total,page,size,pages,hasNext, andhasPrev.GET /appointments/{appointment_id}/returns one appointment, or404 error.appointments.not_found.
| Field | Contents |
|---|---|
id | Appointment ID |
chatId | The chat the appointment was booked from |
status | One of the status values below |
durationMinutes | Appointment length in minutes |
location.state | The US state sent at booking |
startsAt, endsAt | Scheduled times; null for ASAP appointments |
waitingRoomUrl | Join URL; null until the waiting room is ready |
createdAt | Creation time, or null |
| Status | Meaning |
|---|---|
booked | The appointment is booked. |
cancelled | The appointment was cancelled. |
fulfilled | The visit took place. Its result can be retrieved. |
noshow | The appointment was recorded as a no-show. |
unknown | Handle it without assuming an internal workflow state. |
Cancel or reschedule
Cancel with
POST /appointments/{appointment_id}/cancel/.
It takes no body and returns data: null.
# Reuse the same key if you retry this request.
IDEMPOTENCY_KEY="$(uuidgen)"
curl -X POST "$DOCTRONIC_API_URL/appointments/dc_01K1EXAMPLE00000000000000/cancel/" \
-H "Authorization: Bearer $DOCTRONIC_API_TOKEN" \
-H "X-Doctronic-User-ID: $DOCTRONIC_USER_ID" \
-H "Idempotency-Key: $IDEMPOTENCY_KEY"Move a booked appointment with
POST /appointments/{appointment_id}/reschedule/,
sending the new startsAt and location.state. It returns data: null.
# Reuse the same key if you retry this request.
IDEMPOTENCY_KEY="$(uuidgen)"
curl -X POST "$DOCTRONIC_API_URL/appointments/dc_01K1EXAMPLE00000000000000/reschedule/" \
-H "Authorization: Bearer $DOCTRONIC_API_TOKEN" \
-H "X-Doctronic-User-ID: $DOCTRONIC_USER_ID" \
-H "Idempotency-Key: $IDEMPOTENCY_KEY" \
-H "Content-Type: application/json" \
-d '{"startsAt": "2026-08-21T16:00:00Z", "location": {"state": "NY"}}'| Error | Returned by | Handling |
|---|---|---|
409 error.appointments.unavailable_time | Reschedule | Refresh availability and ask the patient to choose again. |
409 error.appointments.cannot_be_cancelled_or_rescheduled | Both | Refresh the appointment and show its current state. |
404 error.appointments.patient_not_found | Both | Check the user mapping. |
Get the visit result
GET /appointments/{appointment_id}/result/
returns what the clinician recorded when they finalized the visit note.
| Status | Meaning | Handling |
|---|---|---|
200 | The appointment is fulfilled and its result is available. | Read the result. |
202 | The result isn't available yet. data can hold appointmentId and status. | Retry with bounded backoff. |
404 | The appointment is unavailable in the authenticated organization and user scope (error.appointments.not_found). | Stop and check the stored user mapping. |
409 | The appointment was cancelled or was a no-show, so no result will exist (error.appointments.result_unavailable). | Stop polling. |
In the SDKs, appointments.result() returns null (None in Python) on 202, and throws
NotFoundError on 404 and ConflictError on 409.
| Field | Contents |
|---|---|
appointmentId | The appointment ID |
prescriptions[].displayName | Display name of each prescription |
prescriptions[].pharmacy | The pharmacy the clinician chose for that prescription (ncpdpId, name, address, phoneNumber), or null. It can differ from the patient's preferred pharmacy. |
clinicianNote | Artifact ID of the clinician-finalized note once it is available, otherwise null |
Retrieve the clinician note like any other consultation document.
The artifact.ready webhook signals when its content is ready.
Pharmacy
Save the patient's preferred pharmacy so prescriptions from a visit can be routed to it.
Search with POST /pharmacy/search/,
which takes the API token alone:
curl -X POST "$DOCTRONIC_API_URL/pharmacy/search/" \
-H "Authorization: Bearer $DOCTRONIC_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"latitude": 40.7608, "longitude": -111.891, "searchTerm": "Main Street", "state": "UT"}'| Field | Required | Use |
|---|---|---|
latitude | Yes | WGS84 decimal degrees, -90 to 90 |
longitude | Yes | WGS84 decimal degrees, -180 to 180 |
searchTerm | No | Matches pharmacy names. Without it, results are ranked by proximity. |
state | No | Limits results to one US state |
Each result in data.pharmacies has name, ncpdpId, address, phoneNumber, and
distanceMiles.
Save the patient's choice with PUT /pharmacy/,
sending the seven-digit ncpdpId from the search. The call saves or replaces the preferred
pharmacy and returns data: null.
# Reuse the same key if you retry this request.
IDEMPOTENCY_KEY="$(uuidgen)"
curl -X PUT "$DOCTRONIC_API_URL/pharmacy/" \
-H "Authorization: Bearer $DOCTRONIC_API_TOKEN" \
-H "X-Doctronic-User-ID: $DOCTRONIC_USER_ID" \
-H "Idempotency-Key: $IDEMPOTENCY_KEY" \
-H "Content-Type: application/json" \
-d '{"ncpdpId": "1234567"}'Saving needs the pharmacy demographics on
the user. An unknown ncpdpId returns 404 error.pharmacy.not_found.
Every write on this page accepts an Idempotency-Key; reuse it when you retry. The SDKs send one
on every write and reuse it on their own retries. See
idempotent requests, and the
error code reference for every appointment and pharmacy code.
Limits
- ASAP appointments have no guaranteed wait time.
- The API has no operation that reads back the saved pharmacy.