Skip to content

Book an appointment

Book an Agenda appointment for a patient with curl, from finding the service and a slot to confirming it, then reschedule and cancel it.

Updated

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

  1. 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.data carries its id, its serviceId, its sites and what the service asks of the patient: service.bookingChecklist to check against what you know, service.patientInstructions, and service.eligibility, such as { "minAgeYears": 18, "maxAgeYears": null }. With an age rule, you’ll need the patient’s date of birth.

  2. Check the requirements. agenda.bookingRequirements.get answers the intake fields, document types and payers; payerQuery leaves 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 as phone and payer are off, optional or required, and document is optional or required; data.customFields adds the organization’s own; data.documentTypes lists the document codes, such as CC; and each payer in data.payers has a bookingPolicy, open or staff_only. Collect each required value you don’t have yet. If the payer’s policy is staff_only, don’t book: put the patient on the waitlist.

  3. Find a slot. agenda.nearestBookingSlots.list answers 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 its endsAt, offeringId and resourceIds. Offer the times to the patient in the slot’s timezone. An empty list means there’s no opening as far ahead as the service can be booked: offer the waitlist.

  4. Book it once the patient confirms the time. Send the slotToken unchanged to agenda.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, from data:

    {
      "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" }
    }
  5. Confirm it to the patient. Tell them the time and what guidance says: 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_URL holds data.slip.url; the link carries no credential. agenda.bookings.get reads the appointment again later, with its guidance and slip. Setting a status such as confirmed is the clinic’s: staff do it with agenda.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." }'

Type to search…

↑↓ navigate↵ selectEsc close