Scheduling quickstart
Show a provider's open times and book a patient into one.
This guide takes about 10 minutes in your sandbox. It picks up where the Quickstart ends. You need the values you saved there: NEX_TOKEN, NEX_SUBDOMAIN, NEX_LOCATION_ID, NEX_PATIENT_ID, NEX_PROVIDER_ID and NEX_OPERATORY_ID.
How scheduling works
Three things decide which times you can offer.
- Appointment types set what is being booked and how long it takes.
- Working hours set when a provider can be booked. You create them through the API, or NexHealth syncs them from the health record system where that is supported.
- Available slots are the working hours minus what is already on the schedule.
1. Create an appointment type
cat > appointment-type.json <<EOF
{
"location_id": $NEX_LOCATION_ID,
"appointment_type": {
"name": "New patient exam",
"minutes": 30,
"parent_type": "Location",
"parent_id": $NEX_LOCATION_ID
}
}
EOF
export NEX_APPT_TYPE_ID=$(curl -s --request POST \
--url "https://nexhealth.info/appointment_types?subdomain=$NEX_SUBDOMAIN" \
--header "Nex-Api-Version: v3.0.0" \
--header "Authorization: Bearer $NEX_TOKEN" \
--header "Content-Type: application/json" \
--data @appointment-type.json | jq -r '.data.id')
echo $NEX_APPT_TYPE_IDAppointment type names are unique. If you run this twice, change the name.
2. Set the provider's working hours
This gives the provider a Monday to Friday week, 9:00 to 17:00 in the location's time zone, for the appointment type you created.
cat > working-hours.json <<EOF
{
"working_hour": {
"provider_id": $NEX_PROVIDER_ID,
"operatory_id": $NEX_OPERATORY_ID,
"begin_time": "09:00",
"end_time": "17:00",
"days": ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"],
"appointment_type_ids": [$NEX_APPT_TYPE_ID],
"active": true
}
}
EOF
curl -s --request POST \
--url "https://nexhealth.info/working_hours?subdomain=$NEX_SUBDOMAIN&location_id=$NEX_LOCATION_ID" \
--header "Nex-Api-Version: v3.0.0" \
--header "Authorization: Bearer $NEX_TOKEN" \
--header "Content-Type: application/json" \
--data @working-hours.jsonSend operatory_id when the location books by operatory. The map_by_operatory attribute of the location tells you.
3. Get the open slots
Ask for a week of slots. Change start_date to today or a later date.
curl -s --globoff --request GET \
--url "https://nexhealth.info/available_slots?subdomain=$NEX_SUBDOMAIN&start_date=2026-12-01&days=7&lids[]=$NEX_LOCATION_ID&pids[]=$NEX_PROVIDER_ID&appointment_type_id=$NEX_APPT_TYPE_ID" \
--header "Nex-Api-Version: v3.0.0" \
--header "Authorization: Bearer $NEX_TOKEN" > slots.json
jq '.data[0].slots[0]' slots.json
export NEX_SLOT_START=$(jq -r '.data[0].slots[0].time' slots.json)
export NEX_SLOT_END=$(jq -r '.data[0].slots[0].end_time' slots.json)Each slot has a start and an end in the location's time zone.
{
"code": true,
"data": [
{
"lid": 1,
"pid": 83,
"operatory_id": 54,
"slots": [
{
"time": "2026-12-01T09:00:00.000-05:00",
"end_time": "2026-12-01T09:30:00.000-05:00",
"operatory_id": 54,
"provider_id": 83
}
],
"next_available_date": null
}
],
"count": 1
}If slots is empty, read next_available_date and then work through Troubleshooting available slots.
4. Book the slot
cat > appointment-request.json <<EOF
{
"appt": {
"patient_id": $NEX_PATIENT_ID,
"provider_id": $NEX_PROVIDER_ID,
"operatory_id": $NEX_OPERATORY_ID,
"appointment_type_id": $NEX_APPT_TYPE_ID,
"start_time": "$NEX_SLOT_START",
"end_time": "$NEX_SLOT_END"
}
}
EOF
curl -s --request POST \
--url "https://nexhealth.info/appointments?subdomain=$NEX_SUBDOMAIN&location_id=$NEX_LOCATION_ID¬ify_patient=false" \
--header "Nex-Api-Version: v3.0.0" \
--header "Authorization: Bearer $NEX_TOKEN" \
--header "Content-Type: application/json" \
--data @appointment-request.jsonA 201 response means NexHealth accepted the appointment. Ask for slots again and that time is gone.
5. Confirm the write
At a real practice the appointment is written into the health record system after the response. Subscribe to appointment_insertion and wait for appointment_insertion.complete before you tell the patient they are booked. See How a write reaches the health record system.
The same flow in one script
Each script creates the appointment type and working hours, reads the slots and books the first one. They need only your API key.
// Save as scheduling.mjs and run: NEX_API_KEY=your-key node scheduling.mjs
const BASE = "https://nexhealth.info";
async function call(method, path, opts = {}) {
const url = new URL(BASE + path);
for (const [key, value] of Object.entries(opts.query || {})) {
url.searchParams.append(key, value);
}
const res = await fetch(url, {
method,
headers: {
"Nex-Api-Version": "v3.0.0",
"Content-Type": "application/json",
Authorization: opts.token ? "Bearer " + opts.token : process.env.NEX_API_KEY,
},
body: opts.body ? JSON.stringify(opts.body) : undefined,
});
const json = await res.json();
if (!res.ok || json.code === false) {
throw new Error(method + " " + path + " failed: " + res.status + " " + JSON.stringify(json.error));
}
return json.data;
}
const { token } = await call("POST", "/authenticates");
const [institution] = await call("GET", "/institutions", { token });
const subdomain = institution.subdomain;
const locationId = institution.locations[0].id;
const scope = { subdomain, location_id: locationId, per_page: 5 };
const [patient] = await call("GET", "/patients", { token, query: scope });
const [provider] = await call("GET", "/providers", { token, query: scope });
const [operatory] = await call("GET", "/operatories", { token, query: scope });
// 1. Create an appointment type
const type = await call("POST", "/appointment_types", {
token,
query: { subdomain },
body: {
location_id: locationId,
appointment_type: {
name: "New patient exam " + Date.now(),
minutes: 30,
parent_type: "Location",
parent_id: locationId,
},
},
});
// 2. Set the provider's working hours
await call("POST", "/working_hours", {
token,
query: { subdomain, location_id: locationId },
body: {
working_hour: {
provider_id: provider.id,
operatory_id: operatory.id,
begin_time: "09:00",
end_time: "17:00",
days: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"],
appointment_type_ids: [type.id],
active: true,
},
},
});
// 3. Get the open slots for the next 7 days
const today = new Date().toISOString().slice(0, 10);
const results = await call("GET", "/available_slots", {
token,
query: {
subdomain,
start_date: today,
days: 7,
"lids[]": locationId,
"pids[]": provider.id,
appointment_type_id: type.id,
},
});
const slot = results[0].slots[0];
if (!slot) throw new Error("No open slots. Next available date: " + results[0].next_available_date);
// 4. Book the slot
const { appt } = await call("POST", "/appointments", {
token,
query: { subdomain, location_id: locationId, notify_patient: false },
body: {
appt: {
patient_id: patient.id,
provider_id: provider.id,
operatory_id: operatory.id,
appointment_type_id: type.id,
start_time: slot.time,
end_time: slot.end_time,
},
},
});
console.log("Booked appointment", appt.id, "at", slot.time);# Save as scheduling.py and run: NEX_API_KEY=your-key python scheduling.py
import os
import time
from datetime import date
import requests
BASE = "https://nexhealth.info"
def call(method, path, token=None, params=None, body=None):
res = requests.request(
method,
BASE + path,
params=params,
json=body,
headers={
"Nex-Api-Version": "v3.0.0",
"Authorization": "Bearer " + token if token else os.environ["NEX_API_KEY"],
},
)
payload = res.json()
if not res.ok or payload.get("code") is False:
raise RuntimeError(f"{method} {path} failed: {res.status_code} {payload.get('error')}")
return payload["data"]
token = call("POST", "/authenticates")["token"]
institution = call("GET", "/institutions", token)[0]
subdomain = institution["subdomain"]
location_id = institution["locations"][0]["id"]
scope = {"subdomain": subdomain, "location_id": location_id, "per_page": 5}
patient = call("GET", "/patients", token, scope)[0]
provider = call("GET", "/providers", token, scope)[0]
operatory = call("GET", "/operatories", token, scope)[0]
# 1. Create an appointment type
appt_type = call(
"POST",
"/appointment_types",
token,
{"subdomain": subdomain},
{
"location_id": location_id,
"appointment_type": {
"name": f"New patient exam {int(time.time())}",
"minutes": 30,
"parent_type": "Location",
"parent_id": location_id,
},
},
)
# 2. Set the provider's working hours
call(
"POST",
"/working_hours",
token,
{"subdomain": subdomain, "location_id": location_id},
{
"working_hour": {
"provider_id": provider["id"],
"operatory_id": operatory["id"],
"begin_time": "09:00",
"end_time": "17:00",
"days": ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"],
"appointment_type_ids": [appt_type["id"]],
"active": True,
}
},
)
# 3. Get the open slots for the next 7 days
results = call(
"GET",
"/available_slots",
token,
{
"subdomain": subdomain,
"start_date": date.today().isoformat(),
"days": 7,
"lids[]": location_id,
"pids[]": provider["id"],
"appointment_type_id": appt_type["id"],
},
)
slots = results[0]["slots"]
if not slots:
raise RuntimeError(f"No open slots. Next available date: {results[0].get('next_available_date')}")
slot = slots[0]
# 4. Book the slot
appt = call(
"POST",
"/appointments",
token,
{"subdomain": subdomain, "location_id": location_id, "notify_patient": "false"},
{
"appt": {
"patient_id": patient["id"],
"provider_id": provider["id"],
"operatory_id": operatory["id"],
"appointment_type_id": appt_type["id"],
"start_time": slot["time"],
"end_time": slot["end_time"],
}
},
)["appt"]
print("Booked appointment", appt["id"], "at", slot["time"])Go further
- Scheduling configuration guide covers lunch breaks, time off, more providers and working hours synced from the health record system.
- Troubleshooting available slots is a checklist for when no slots come back.
- Troubleshooting appointment writes covers what to do when a write fails.
Updated about 16 hours ago