theClinicDiary.com
Display

How this page looks

Text size
My portal

For developers

The clinic API

One key per clinic, made by the clinic under Settings → API keys, and every answer scoped to that clinic. Read patients, appointments, practitioners, therapies, locations and invoices; create and update patients and record attendance; find times, hold and book, move and cancel, book classes; and receive webhooks. The same document as JSON: /api/agent/v1/openapi.json.

Authentication

Send the key as a bearer token on every request. A key is shown once when made and can be revoked at any time; each key is limited to 120 requests a minute.

curl https://www.theclinicdiary.com/api/agent/v1/clinic \
  -H "Authorization: Bearer tcd_…"

Answers and errors

JSON in, JSON out. A refused request answers 400 with an error, and where the agent might read it out, a spoken line; 401 is a bad key, 403 a capability the key may not use, 404 a thing that is not this clinic's. Dates are ISO 8601; times carry an offset; money is pence.

Reading

The administrative record. Nothing clinical is ever returned: no notes, documents, outcomes or contraindications.

GET/api/agent/v1/patientslist_patients

Search patients

The administrative record of every active patient, or those matching a search. Paged.

Parameters

searchquery · stringPart of a name, mobile, email or postcode.
page*query · integer
pageSize*query · integer

Answer

patients*object[]
id*uuid
title*string | null
firstName*string
lastName*string
dateOfBirth*string | null
gender*string | null
mobilePhone*string | null
homePhone*string | null
email*string | null
addressLine1*string | null
addressLine2*string | null
cityCounty*string | null
postcode*string | null
gpName*string | null
gpPractice*string | null
consentText*boolean
consentEmail*boolean
createdAt*string | null
updatedAt*string | null
page*integer
hasMore*boolean
GET/api/agent/v1/patients/{patientId}get_patient

A patient

One patient's administrative record and upcoming appointments. Viewing is on the patient's audit trail.

Path

patientId*path · uuid

Answer

patient*objectThe administrative record. Nothing clinical is ever returned.
id*uuid
title*string | null
firstName*string
lastName*string
dateOfBirth*string | null
gender*string | null
mobilePhone*string | null
homePhone*string | null
email*string | null
addressLine1*string | null
addressLine2*string | null
cityCounty*string | null
postcode*string | null
gpName*string | null
gpPractice*string | null
consentText*boolean
consentEmail*boolean
createdAt*string | null
updatedAt*string | null
upcoming*object[]
id*uuid
startISO*date-time
endISO*date-time
spoken*string
status*string
therapy*string | null
therapyId*uuid
practitioner*string | null
practitionerId*uuid
GET/api/agent/v1/appointmentslist_appointments

Appointments by date

Appointments in a date range, optionally for one practitioner or patient.

Parameters

from*query · stringFirst day, inclusive.
to*query · stringLast day, inclusive. At most three months after from.
practitionerIdquery · uuid
patientIdquery · uuid
status*query · "active" | "cancelled" | "all"

Answer

appointments*object[]
id*uuid
patientId*uuid
patientName*string | null
practitionerId*uuid
practitioner*string | null
therapyId*uuid
therapy*string | null
startISO*date-time
endISO*date-time
status*string
didNotAttend*boolean
checkedInISO*string | null
bookedOnline*boolean
isTelehealth*boolean
cancelledReason*string | null
GET/api/agent/v1/appointments/{appointmentId}get_appointment

An appointment

One appointment.

Path

appointmentId*path · uuid

Answer

id*uuid
patientId*uuid
patientName*string | null
practitionerId*uuid
practitioner*string | null
therapyId*uuid
therapy*string | null
startISO*date-time
endISO*date-time
status*string
didNotAttend*boolean
checkedInISO*string | null
bookedOnline*boolean
isTelehealth*boolean
cancelledReason*string | null
GET/api/agent/v1/practitionerslist_practitioners

Practitioners

Active practitioners.

Answer

practitioners*object[]
id*uuid
name*string
GET/api/agent/v1/therapieslist_therapies

Therapies

Active therapies with lengths, prices, whether they are bookable online, and who offers them.

Answer

therapies*object[]
id*uuid
name*string
newPatientMinutes*integer | null
newPatientPrice*string | null
followUpMinutes*integer | null
followUpPrice*string | null
bookableOnline*boolean
practitionerIds*uuid[]
GET/api/agent/v1/locationslist_locations

Locations

The clinic's sites.

Answer

locations*object[]
id*uuid
name*string
address*string | null
postcode*string | null
phoneNumber*string | null
isPrimary*boolean
GET/api/agent/v1/invoiceslist_invoices

Invoices

Invoices and credit notes, filtered by patient, status or issue date. Paged.

Parameters

patientIdquery · uuid
statusquery · "unpaid" | "paid" | "part_paid" | "void" | "refunded" | "draft"
fromquery · stringIssued on or after.
toquery · stringIssued on or before.
page*query · integer
pageSize*query · integer

Answer

invoices*object[]
id*uuid
patientId*uuid
type*string
status*string
invoiceNumber*integer | null
source*string
subtotalPence*integer
discountPence*integer
taxPence*integer
totalPence*integer
paymentMethod*string | null
issuedISO*date-time
paidISO*string | null
page*integer
hasMore*boolean
GET/api/agent/v1/invoices/{invoiceId}get_invoice

An invoice

One invoice with its lines.

Path

invoiceId*path · uuid

Answer

invoice*object
id*uuid
patientId*uuid
type*string
status*string
invoiceNumber*integer | null
source*string
subtotalPence*integer
discountPence*integer
taxPence*integer
totalPence*integer
paymentMethod*string | null
issuedISO*date-time
paidISO*string | null
lines*object[]
id*uuid
description*string
quantity*number
unitPricePence*integer
totalPence*integer

Writing

Writes go through the same code the desk uses, so consent records, task rules and webhooks fire the same way.

POST/api/agent/v1/patientscreate_patient

Create a patient

A new patient record with the opening consent position recorded. Refuses an apparent duplicate unless told otherwise.

Body

titlestring | null
firstName*string
lastName*string
dateOfBirthstring | null
genderstring | null
mobilePhonestring | null
homePhonestring | null
emailstring | null
addressLine1string | null
addressLine2string | null
cityCountystring | null
postcodestring | null
gpNamestring | null
gpPracticestring | null
consentTextbooleanService texts (reminders). Defaults to true.
consentEmailbooleanService emails. Defaults to true.
allowDuplicatebooleanCreate even if a patient with the same name and contact exists.

Answer

patient*objectThe administrative record. Nothing clinical is ever returned.
id*uuid
title*string | null
firstName*string
lastName*string
dateOfBirth*string | null
gender*string | null
mobilePhone*string | null
homePhone*string | null
email*string | null
addressLine1*string | null
addressLine2*string | null
cityCounty*string | null
postcode*string | null
gpName*string | null
gpPractice*string | null
consentText*boolean
consentEmail*boolean
createdAt*string | null
updatedAt*string | null
upcoming*object[]
id*uuid
startISO*date-time
endISO*date-time
spoken*string
status*string
therapy*string | null
therapyId*uuid
practitioner*string | null
practitionerId*uuid
PATCH/api/agent/v1/patients/{patientId}update_patient

Update a patient

Change administrative details. Only the fields sent are changed.

Path

patientId*path · uuid

Body

titlestring | null
firstNamestring
lastNamestring
dateOfBirthstring | null
genderstring | null
mobilePhonestring | null
homePhonestring | null
emailstring | null
addressLine1string | null
addressLine2string | null
cityCountystring | null
postcodestring | null
gpNamestring | null
gpPracticestring | null

Answer

patient*objectThe administrative record. Nothing clinical is ever returned.
id*uuid
title*string | null
firstName*string
lastName*string
dateOfBirth*string | null
gender*string | null
mobilePhone*string | null
homePhone*string | null
email*string | null
addressLine1*string | null
addressLine2*string | null
cityCounty*string | null
postcode*string | null
gpName*string | null
gpPractice*string | null
consentText*boolean
consentEmail*boolean
createdAt*string | null
updatedAt*string | null
upcoming*object[]
id*uuid
startISO*date-time
endISO*date-time
spoken*string
status*string
therapy*string | null
therapyId*uuid
practitioner*string | null
practitionerId*uuid
POST/api/agent/v1/appointments/{appointmentId}/attendancerecord_attendance

Record attendance

Mark an appointment attended or did-not-attend once it has started. Fires the same follow-ups the desk's tick does.

Path

appointmentId*path · uuid

Body

status*"attended" | "did_not_attend"

Answer

id*uuid
patientId*uuid
patientName*string | null
practitionerId*uuid
practitioner*string | null
therapyId*uuid
therapy*string | null
startISO*date-time
endISO*date-time
status*string
didNotAttend*boolean
checkedInISO*string | null
bookedOnline*boolean
isTelehealth*boolean
cancelledReason*string | null

Booking, changing and handing over

The endpoints the AI call agent uses. They honour the clinic's online-booking rules: bookable therapies, notice period, payment modes and the fields a new patient must give. A change is read back first and carried out on a second call with confirm: true.

POST/api/agent/v1/calleridentify_caller

Identify the caller

Look the caller up by the number they are calling from, and confirm who they are from their name and date of birth. Never reveals details until confirmed.

Body

phonestringThe number the caller is ringing from, in any format.
firstNamestring
lastNamestring
dateOfBirthstringYYYY-MM-DD, as the caller states it.

Answer

known*booleanWhether the phone number matches a patient at this clinic.
greetAs*string | nullA first name to greet with, when the number is known. Not confirmation of identity.
confirmed*booleanTrue when the name and date of birth given match a patient.
patientId*uuid
firstName*string | null
lastName*string | null
upcoming*object[]
appointmentId*uuid
startISO*date-time
spoken*stringThe date and time in words, in the clinic's time zone.
therapy*string | null
practitioner*string | null
location*string | null
note*string | nullWhat to say next when identity could not be confirmed.
GET/api/agent/v1/clinicclinic_information

About the clinic

Name, sites, opening hours, what is offered with prices, booking and cancellation rules, and what a new patient must supply.

Answer

name*string
timezone*string
phone*string | null
locations*object[]
id*uuid
name*string
address*string | null
arrivalInfo*string | null
hours*object[]
day*string
open*string
close*string
openNow*boolean
hoursToday*object[]
open*string
close*string
therapies*object[]Only what the clinic offers for booking over the phone.
id*uuid
name*string
description*string | null
newPatientMinutes*integer
newPatientPrice*string | null
followUpMinutes*integer | null
followUpPrice*string | null
practitioners*object[]
id*uuid
name*string
classes*booleanWhether the clinic runs classes the agent may book.
bookingNoticeHours*integer
bookingWindowDays*integer
cancellationHours*integer
cancellationPolicy*string | null
paymentNewPatient*"none" | "deposit" | "full"
paymentExistingPatient*"none" | "deposit" | "full"
depositPrice*string | null
newPatientFields*object[]What a new patient must supply before they can be booked.
key*string
label*string
type*string
capabilities*object
information*boolean
book*boolean
amend*boolean
newPatients*boolean
classes*boolean
payment*boolean
handover*boolean
transfer*boolean
instructions*string | nullAnything the clinic wants said or done on every call.
callbackPromise*string | null
POST/api/agent/v1/availabilityfind_appointments

Free appointment times

Times free for a therapy over the next days, honouring the clinic's notice period, booking window and online-booking filters.

Body

therapyId*uuid
practitionerIduuidLeave out to search every practitioner who offers the therapy.
patientType*"new" | "existing" (default "new")
fromDatestringFirst day to look at; today when omitted.
days*integer (default 7)
partOfDay*"morning" | "afternoon" | "evening" | "any" (default "any")

Answer

therapy*string
durationMinutes*integer
days*object[]
date*string
spoken*string
slots*object[]
startISO*date-time
durationMinutes*integer
spoken*string
practitionerId*uuid
practitioner*string
locationId*uuid
location*string | null
nextAfter*string | nullWhen nothing suits, the first day after this range with anything free.
POST/api/agent/v1/holdshold_appointment

Hold a time

Hold a time for thirty minutes while the caller decides or pays. Book against the hold with /bookings.

Body

therapyId*uuid
practitionerId*uuid
startISO*date-time
patientType*"new" | "existing" (default "new")

Answer

holdToken*string
expiresISO*date-time
spoken*string
POST/api/agent/v1/bookingsbook_appointment

Book a held time

Book the held time for an existing patient or a new one. If a payment is required the caller is texted a link and the booking is confirmed when they pay; it lapses after thirty minutes.

Body

holdToken*string
therapyId*uuid
patientIduuidAn existing patient, confirmed through /caller.
newPatientobjectA new patient. Every field the clinic marks mandatory must be present.
firstName*string
lastName*string
mobile*string
email*string
dateOfBirthstring
addressLine1string
addressLine2string
cityCountystring
postcodestring
gpNamestring
gpPracticestring
fieldsobjectAnswers to the clinic's own mandatory fields, keyed as newPatientFields gives them.
notesstringAnything the caller asked to pass on about the visit.

Answer

status*"confirmed" | "awaiting_payment"
appointmentId*uuid
patientId*uuid
spoken*stringThe booking read back in words.
payment*object | null
amount*string
kind*"deposit" | "full"
sentTo*string
expiresISO*date-time
spoken*string
missingFields*string[]When the booking was refused for a new patient, the labels still needed.
POST/api/agent/v1/bookings/{appointmentId}/cancelcancel_appointment

Cancel an appointment

First call returns whether it is a late cancellation and any charge, to read out. Call again with confirm true to cancel.

Path

appointmentId*path · uuid

Body

patientId*uuid
confirm*booleanFalse returns the consequences to read out; true carries the change out. (default false)

Answer

done*boolean
late*boolean
charge*string | nullThe late-cancellation charge that applies, in words, or null.
policy*string | null
spoken*string
POST/api/agent/v1/bookings/{appointmentId}/reschedulemove_appointment

Move an appointment

Move an appointment to a free time found with /availability for the same practitioner. First call reads back; confirm true moves it.

Path

appointmentId*path · uuid

Body

patientId*uuid
confirm*booleanFalse returns the consequences to read out; true carries the change out. (default false)
startISO*date-time

Answer

done*boolean
late*boolean
policy*string | null
from*string
to*string
spoken*string
POST/api/agent/v1/classes/availabilityfind_classes

Classes with places

Upcoming classes with places left and their prices.

Body

fromDatestring
days*integer (default 14)
courseIduuid

Answer

sessions*object[]
sessionId*uuid
courseId*uuid
course*string
description*string | null
startISO*date-time
spoken*string
practitioner*string | null
placesLeft*integer
price*string | null
wholeCoursePrice*string | null
remainingInCourse*integer
POST/api/agent/v1/classes/enrolbook_class

Book a class place

Book a confirmed patient onto a class, or onto this and every later class in the course. A class with a fee is paid by text link, as an appointment is.

Body

sessionId*uuid
patientId*uuid
scope*"session" | "remaining"This class only, or this and every later class in the course. (default "session")

Answer

status*"confirmed" | "awaiting_payment"
enrolmentId*uuid
classes*integer
spoken*string
payment*object | null
amount*string
sentTo*string
expiresISO*date-time
spoken*string
POST/api/agent/v1/handoverhand_over_to_clinic

Hand over to a person

When the caller wants a person or the call is beyond the agent. Transfers to the clinic if it has a number and somebody answers; otherwise takes a message for the clinic.

Body

reason*"caller_asked" | "too_complex" | "identity_failed" | "complaint" | "clinical" | "other"
wantsPerson*booleanTrue when the caller wants to speak to somebody now; the clinic's transfer number is tried first if it has one. (default false)
callerNamestring
callerPhonestring
patientIduuid
messagestringThe message to leave, in the caller's words.
summarystringWhat the call was about so far.

Answer

action*"transferring" | "message_taken" | "take_message"
spoken*stringWhat to tell the caller.

Webhooks

Register an https URL and be told when something happens, with a signed payload and retries.

GET/api/agent/v1/webhookslist_webhooks

Webhooks

The clinic's webhooks and the events available.

Answer

webhooks*object[]
id*uuid
url*string
events*string[]
description*string | null
isActive*boolean
lastDeliveredISO*string | null
lastFailedISO*string | null
failures*integer
createdISO*date-time
events*string[]Every event name that can be subscribed to.
POST/api/agent/v1/webhookscreate_webhook

Create a webhook

Register an https URL for events. The signing secret is returned once.

Body

url*string
events*string[]Event names; empty subscribes to everything. (default [])
descriptionstring

Answer

id*uuid
url*string
events*string[]
secret*stringShown once. Sign-check every delivery with it.
DELETE/api/agent/v1/webhooks/{webhookId}delete_webhook

Delete a webhook

Stops deliveries at once.

Path

webhookId*path · uuid

Answer

deleted*true

Receiving webhooks

Each delivery is a POST with a JSON body { id, event, occurredAt, clinicId, data } and three headers: x-clinicdiary-event, x-clinicdiary-delivery, and x-clinicdiary-signature as t=<unix seconds>,v1=<hex>, where v1 is HMAC-SHA256 of `${t}.${body}` with the secret shown when the webhook was made. Answer 2xx within ten seconds; anything else is retried after 1, 5 and 30 minutes, then 2 and 12 hours, then given up. Deliveries and their outcomes are listed under Settings → API keys.

Events

  • appointment.bookedAn appointment is booked
  • appointment.movedAn appointment is moved
  • appointment.cancelledAn appointment is cancelled
  • appointment.attendanceAttendance is recorded (attended or did not attend)
  • patient.createdA patient is created
  • invoice.paidAn invoice is paid

Verifying a signature (Node)

const [t, v1] = req.headers['x-clinicdiary-signature']
  .split(',').map((p) => p.split('=')[1]);
const expected = crypto.createHmac('sha256', SECRET)
  .update(`${t}.${rawBody}`).digest('hex');
const fresh = Math.abs(Date.now() / 1000 - Number(t)) < 300;
const ok = fresh && crypto.timingSafeEqual(
  Buffer.from(expected, 'hex'), Buffer.from(v1, 'hex'));

The AI call agent uses the booking group above with a key of its own; that key cannot reach the reading, writing or webhook endpoints.