This guide books an appointment the way an AI agent or a patient-facing app does, through Agenda’s booking operations: find the service, check what the clinic requires, find a slot, book it once the patient confirms, and confirm it to the patient. Then it moves the appointment and cancels it. How Agenda works explains the model, and Availability and booking every field used here.
You need an API key for your organization with agenda:bookings:write, which covers every booking operation, reads included, and agenda:waitlist:write to put patients on the waitlist (see Authentication). The examples read the key from ARBOL_API_KEY. Every id, token and patient detail in them is an example.
Book it
-
Find the service. Search for the name the patient used with
agenda.bookingOfferings.list:curl --get https://api.getarbol.com/v1/agenda/booking-offerings \ --data-urlencode "q=neurología" --data-urlencode "limit=3" \ --header "Authorization: Bearer $ARBOL_API_KEY"Each offering in
data.datacarries itsid, itsserviceId, its sites and what the service asks of the patient:service.bookingChecklistto check against what you know,service.patientInstructions, andservice.eligibility, such as{ "minAgeYears": 18, "maxAgeYears": null }. With an age rule, you’ll need the patient’s date of birth. -
Check the requirements.
agenda.bookingRequirements.getanswers the intake fields, document types and payers;payerQueryleaves out unrelated payers:curl --get https://api.getarbol.com/v1/agenda/booking-requirements \ --data-urlencode "payerQuery=Example Health Plan" \ --header "Authorization: Bearer $ARBOL_API_KEY"In
data.fields, built-in fields such asphoneandpayerareoff,optionalorrequired, anddocumentisoptionalorrequired;data.customFieldsadds the organization’s own;data.documentTypeslists the document codes, such asCC; and each payer indata.payershas abookingPolicy,openorstaff_only. Collect each required value you don’t have yet. If the payer’s policy isstaff_only, don’t book: put the patient on the waitlist. -
Find a slot.
agenda.nearestBookingSlots.listanswers the earliest times across every agenda that offers the service:curl --get https://api.getarbol.com/v1/agenda/nearest-booking-slots \ --data-urlencode "serviceId=svc_00000000-0000-4000-8000-000000000001" \ --data-urlencode "timezone=America/Bogota" --data-urlencode "limit=3" \ --header "Authorization: Bearer $ARBOL_API_KEY"{ "startsAt": "2026-11-05T09:00:00-05:00", "timezone": "America/Bogota", "slotToken": "EXAMPLE_SLOT_TOKEN_0001" }That’s the first slot in
data.data, abridged: each also has itsendsAt,offeringIdandresourceIds. Offer the times to the patient in the slot’stimezone. An empty list means there’s no opening as far ahead as the service can be booked: offer the waitlist. -
Book it once the patient confirms the time. Send the
slotTokenunchanged toagenda.bookings.create, with the patient and the intake the clinic requires:curl https://api.getarbol.com/v1/agenda/bookings \ --request POST \ --header "Authorization: Bearer $ARBOL_API_KEY" \ --header "Content-Type: application/json" \ --data '{ "slotToken": "EXAMPLE_SLOT_TOKEN_0001", "patient": { "name": "Example Patient", "documentNumber": "1234567890", "documentType": "CC" }, "patientBirthDate": "1990-01-01", "intake": { "phone": "+57 300 000 0000", "entity": "Example Health Plan" } }'The answer is the appointment; keep its
id, which rescheduling and cancelling take. Abridged, fromdata:{ "id": "eng_00000000-0000-4000-8000-000000000005", "startsAt": "2026-11-05T09:00:00-05:00", "guidance": { "attendedBy": ["Dr. Example"], "patientInstructions": "Bring your medical order and arrive 15 minutes early.", "location": { "name": "Example Clinic North", "address": "Calle 00 # 00-00", "city": "Bogotá" } }, "slip": { "url": "https://example.com/EXAMPLE-slip.pdf", "expiresAt": "2026-10-30T16:00:00Z" } } -
Confirm it to the patient. Tell them the time and what
guidancesays: the instructions, who attends, and the site with its address. Download the slip while its link works, within the hour, and send them the PDF file rather than the link:curl --fail --output appointment-slip.pdf "$SLIP_URL"Here
SLIP_URLholdsdata.slip.url; the link carries no credential.agenda.bookings.getreads the appointment again later, with its guidance and slip. Setting a status such asconfirmedis the clinic’s: staff do it withagenda.appointments.changeStatus, which accepts only people.
Handle the common errors
| Status | error.code |
What to do |
|---|---|---|
400 |
INVALID_INPUT, INVALID_APPOINTMENT_INTAKE |
Fix the fields in error.data.issues: each path points at one. |
403 |
FORBIDDEN |
The key lacks agenda:bookings:write. |
409 |
BIRTH_DATE_REQUIRED |
The service has an age rule: ask the patient’s date of birth and send patientBirthDate. |
409 |
AGE_NOT_ELIGIBLE |
The patient’s age on that day is outside the service’s age rule, so this service can’t be booked for them. |
409 |
PAYER_STAFF_ONLY |
Only the clinic’s team books this payer’s patients: put the patient on the waitlist. |
409 |
RESOURCE_CAPACITY_EXCEEDED, SERVICE_CAPACITY_EXCEEDED, SCHEDULE_CHANGED |
The time is no longer free: find another slot and confirm it with the patient. |
409 |
INVALID_SLOT_TOKEN |
Find a fresh slot, and send its token exactly as returned. |
502, 504 |
OUTCOME_UNKNOWN, UPSTREAM_TIMEOUT |
The appointment may exist: check before you repeat, as in Handle errors and retries, which also covers 429. |
Put the patient on the waitlist
With the patient’s consent, agenda.waitlist.join records the request for the clinic’s team, with the reason payer_review for a staff_only payer or manual when no time suits. It never books: tell the patient the clinic’s team will contact them.
curl https://api.getarbol.com/v1/agenda/waitlist/join \
--request POST \
--header "Authorization: Bearer $ARBOL_API_KEY" \
--header "Content-Type: application/json" \
--data '{
"reason": "payer_review",
"serviceId": "svc_00000000-0000-4000-8000-000000000001",
"patient": { "name": "Example Patient", "documentNumber": "1234567890", "documentType": "CC" },
"phone": "+57 300 000 0000", "payer": "Example Health Plan"
}'Reschedule
agenda.bookings.lookup takes the patient’s document and answers their appointments in data.appointments, newest first. Pick the one the patient means by its startsAt and status; found: false means no patient has that document.
curl https://api.getarbol.com/v1/agenda/bookings/lookup \
--request POST \
--header "Content-Type: application/json" \
--data '{ "documentNumber": "1234567890", "documentType": "CC" }' \
--header "Authorization: Bearer $ARBOL_API_KEY"Find a new time for the same service as in step 3, where another professional or room of the service is fine, and have the patient confirm it. Then send its slotToken to agenda.bookings.reschedule:
curl https://api.getarbol.com/v1/agenda/bookings/eng_00000000-0000-4000-8000-000000000005/reschedule \
--request POST \
--header "Authorization: Bearer $ARBOL_API_KEY" \
--header "Content-Type: application/json" \
--data '{ "slotToken": "EXAMPLE_SLOT_TOKEN_0002" }'It answers the appointment at its new time, with guidance and a slip to send again. A slot of another service is refused with SERVICE_CHANGED, and a closed appointment with APPOINTMENT_NOT_MOVABLE. Rescheduling isn’t idempotent: if the call times out, read the appointment with agenda.bookings.get and check its startsAt before you send it again.
Cancel
Once the patient confirms which appointment, send the reason they gave to agenda.bookings.cancel. The slot is freed and the reason is kept in the appointment’s history. Cancelling is idempotent, so repeating it is safe.
curl https://api.getarbol.com/v1/agenda/bookings/eng_00000000-0000-4000-8000-000000000005/cancel \
--request POST \
--header "Authorization: Bearer $ARBOL_API_KEY" \
--header "Content-Type: application/json" \
--data '{ "reason": "The patient can no longer attend." }'