Skip to main content

The recommended way to book against a free slot is the POST /fhir/Appointment/$book operation. It takes { slotId, participant[], reasonCode?, comment? }, atomically verifies the slot is free, checks each participant for schedule conflicts, creates the Appointment with status: "booked" (with start/end taken from the slot), and flips the Slot to busy.

curl -X POST https://api.esus.health/fhir/Appointment/\$book \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/fhir+json" \
  -d '{
    "slotId": "c1a2e3d4-5b6f-7890-abcd-ef1234567890",
    "participant": [
      {
        "actor": { "reference": "Patient/3fa85f64-5717-4562-b3fc-2c963f66afa6", "display": "John Doe" },
        "required": "required",
        "status": "accepted"
      },
      {
        "actor": { "reference": "Practitioner/a7b1c2d3-e4f5-6789-abcd-ef0123456789", "display": "Dr. Sarah Johnson" },
        "required": "required",
        "status": "accepted"
      }
    ],
    "reasonCode": [{
      "coding": [{ "system": "http://snomed.info/sct", "code": "185349003", "display": "Encounter for check up" }]
    }],
    "comment": "Annual wellness checkup"
  }'

If the slot is not free, $book returns ValidationError (HTTP 400). If any participant has an overlapping appointment, it also returns 400 for double booking.

Create an Appointment manually

You can also create the Appointment directly with a POST /fhir/Appointment. The slot[] and participant[].actor references use the standard FHIR reference shape.

Heads up: a manual POST /fhir/Appointment that carries slot[] does NOT flip the Slot to busy — only $book does. If you use manual create, your application is responsible for updating the Slot’s status separately. For bookings against a slot, prefer $book.

curl -X POST https://api.esus.health/fhir/Appointment \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/fhir+json" \
  -d '{
    "status": "booked",
    "serviceType": [{
      "coding": [{
        "system": "http://terminology.hl7.org/CodeSystem/service-type",
        "code": "124",
        "display": "General Practice"
      }]
    }],
    "appointmentType": {
      "coding": [{
        "system": "http://terminology.hl7.org/CodeSystem/v2-0276",
        "code": "NORMAL",
        "display": "Normal appointment"
      }]
    },
    "slot": [
      { "reference": "Slot/c1a2e3d4-5b6f-7890-abcd-ef1234567890" }
    ],
    "participant": [
      {
        "actor": {
          "reference": "Patient/3fa85f64-5717-4562-b3fc-2c963f66afa6",
          "display": "John Doe"
        },
        "required": "required",
        "status": "accepted"
      },
      {
        "actor": {
          "reference": "Practitioner/a7b1c2d3-e4f5-6789-abcd-ef0123456789",
          "display": "Dr. Sarah Johnson"
        },
        "required": "required",
        "status": "accepted"
      }
    ],
    "reasonCode": [{
      "coding": [{
        "system": "http://snomed.info/sct",
        "code": "185349003",
        "display": "Encounter for check up"
      }]
    }],
    "description": "Annual wellness checkup",
    "minutesDuration": 30
  }'

Appointment Status Flow

proposed → pending → booked → arrived → fulfilled
                           ↘ cancelled
                           ↘ noshow

The valid transitions are fixed on the server: proposed → {pending, cancelled}, pending → {booked, cancelled}, booked → {arrived, cancelled, noshow}, arrived → {fulfilled, cancelled}. Any other transition returns ValidationError (HTTP 400).

StatusMeaning
proposedAppointment suggested but not confirmed
pendingAwaiting confirmation
bookedConfirmed
arrivedPatient has arrived
fulfilledAppointment completed
cancelledAppointment cancelled
noshowPatient did not attend
checked-inEnum literal, but NOT reachable via a status update (no transition leads to it)
waitlistOnly valid at creation; no outgoing transition (see Waitlist Management)
entered-in-errorRecorded in error

Register Patient Arrival

Use PATCH to update only the status (PATCH accepts a partial Appointment body — see Getting Started → CRUD):

curl -X PATCH https://api.esus.health/fhir/Appointment/d4e5f6a7-b8c9-0123-def4-567890abcdef \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/fhir+json" \
  -d '{ "status": "arrived" }'

Cancel an Appointment

curl -X PATCH https://api.esus.health/fhir/Appointment/d4e5f6a7-b8c9-0123-def4-567890abcdef \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/fhir+json" \
  -d '{
    "status": "cancelled",
    "cancelationReason": {
      "coding": [{
        "system": "http://terminology.hl7.org/CodeSystem/appointment-cancellation-reason",
        "code": "PATIENT",
        "display": "Patient cancelled"
      }]
    }
  }'