Docs

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"
200 response
{
  "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:

RequirementHow to meet itError
firstName, lastName, dateOfBirth, and phoneNumber on the userCollect demographics400 error.appointments.missing_patient_demographics
An address with line1, city, state, and postalCodeSet the address400 error.appointments.missing_address or incomplete_address
A chat whose stream has sent ai_consultation.completeSee end states400 error.appointments.chat_not_ready
A two-letter US state code in location.stateSend the patient's state400 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

  1. List slots with GET /appointments/scheduled/available-slots/. state is required and limits slots to practitioners credentialed in that state. Bound the search with optional datetimeFrom and datetimeTo ISO 8601 timestamps.
  2. Show the returned slots to the patient with their timestamps unchanged.
  3. Book the chosen slot with POST /appointments/scheduled/, sending the same state.
  4. 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:

CodeMeaningHandling
error.appointments.provisioning_failedThe clinical service didn't accept the booking.Show your failure path.
error.appointments.waiting_room_unavailableThe 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 with status, and paginate with page (default 1) and size (default 20, maximum 100). The response has items, total, page, size, pages, hasNext, and hasPrev.
  • GET /appointments/{appointment_id}/ returns one appointment, or 404 error.appointments.not_found.
FieldContents
idAppointment ID
chatIdThe chat the appointment was booked from
statusOne of the status values below
durationMinutesAppointment length in minutes
location.stateThe US state sent at booking
startsAt, endsAtScheduled times; null for ASAP appointments
waitingRoomUrlJoin URL; null until the waiting room is ready
createdAtCreation time, or null
StatusMeaning
bookedThe appointment is booked.
cancelledThe appointment was cancelled.
fulfilledThe visit took place. Its result can be retrieved.
noshowThe appointment was recorded as a no-show.
unknownHandle 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"}}'
ErrorReturned byHandling
409 error.appointments.unavailable_timeRescheduleRefresh availability and ask the patient to choose again.
409 error.appointments.cannot_be_cancelled_or_rescheduledBothRefresh the appointment and show its current state.
404 error.appointments.patient_not_foundBothCheck the user mapping.

Get the visit result

GET /appointments/{appointment_id}/result/ returns what the clinician recorded when they finalized the visit note.

StatusMeaningHandling
200The appointment is fulfilled and its result is available.Read the result.
202The result isn't available yet. data can hold appointmentId and status.Retry with bounded backoff.
404The appointment is unavailable in the authenticated organization and user scope (error.appointments.not_found).Stop and check the stored user mapping.
409The 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.

FieldContents
appointmentIdThe appointment ID
prescriptions[].displayNameDisplay name of each prescription
prescriptions[].pharmacyThe pharmacy the clinician chose for that prescription (ncpdpId, name, address, phoneNumber), or null. It can differ from the patient's preferred pharmacy.
clinicianNoteArtifact 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"}'
FieldRequiredUse
latitudeYesWGS84 decimal degrees, -90 to 90
longitudeYesWGS84 decimal degrees, -180 to 180
searchTermNoMatches pharmacy names. Without it, results are ranked by proximity.
stateNoLimits 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.

On this page