Books an appointment for an existing patient in your organization with one provider and one service, in person by default or online with telemedicine true. The end time is set from the service's duration. The start time must be in the future, inside the provider's availability, and free; use GET /availability to find open slots. Retrying an identical request returns the appointment already created instead of a duplicate. The appointment is created as CONFIRMED and this endpoint sends the patient nothing: no confirmation, no reminders, no calendar invitation. Luna messages this patient later only when you ask for it explicitly — notifyPatient true on POST /appointments/{id}/reschedule, /cancel or /no-show, each documented on its endpoint — or when your clinic's staff edit the appointment in the Luna dashboard, where your clinic's appointment-change and reminder settings apply as for any appointment. The provider is notified in the Luna dashboard. For an online appointment the video meeting is created on the provider's connected platform (Zoom, then Google Meet, then Luna Health video) and the patient's join link is returned as video_call_url for your system to deliver; it is null when the meeting could not be created yet. Online appointments cannot be rescheduled through the API yet. Requires an API key with the write scope.
Code samples
Node.js
const fetch = require('node-fetch');
async function callApi() {
const apiKey = 'YOUR_API_KEY';
const url = `https://account.lunahealth.app/api/appointments`;
const requestBody = {
// Add your request body data here
};
try {
const response = await fetch(url, {
method: 'POST',
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(requestBody)
});
if (!response.ok) {
throw new Error(`API error: ${response.status}`);
}
const data = await response.json();
console.log(data);
return data;
} catch (error) {
console.error('Error calling API:', error);
throw error;
}
}
callApi();
Python
import requests
def call_api():
api_key = 'YOUR_API_KEY'
url = 'https://account.lunahealth.app/api/appointments'
headers = {
'Authorization': f'Bearer {api_key}',
'Content-Type': 'application/json'
}
params = {
}
# Add your request body data here
request_body = {}
try:
response = requests.post(
url,
headers=headers,
params=params,
json=request_body
)
response.raise_for_status()
data = response.json()
print(data)
return data
except requests.exceptions.RequestException as e:
print(f'Error calling API: {e}')
raise
call_api()
Request body
Required
Request fields
Name
Type
Required
Description
patientId
string
Yes
ID of an existing patient in your organization
staffId
string
Yes
ID of the provider (staff member) who will see the patient
serviceId
string
Yes
ID of the service; its duration sets the end time
startTime
string (date-time)
Yes
Start time in ISO 8601 with a UTC offset, e.g. a start_time from GET /availability
locationId
string
No
ID of the location where the appointment takes place
reasonForVisit
string
No
Reason for the visit, shown to staff
telemedicine
boolean
No
Book an online appointment. The video platform is the provider's connected one (Zoom, then Google Meet, then Luna Health video) and the patient's join link is returned as video_call_url. Defaults to false, an in-person appointment.
Video platform of an online appointment: LUNAHEALTH, ZOOM or GOOGLE_MEET. NONE for in-person appointments, and for online ones where the clinic sends its own link
created_at
string
Yes
—
updated_at
string
Yes
—
patient
object | null
Yes
—
staffs
array<object>
Yes
—
service
object | null
Yes
—
location
object | null
Yes
—
video_call_url
stringnull
Yes
The patient's join link for an online appointment, for your system to deliver to the patient. Treat it as a credential for that patient: it opens their video room. null for in-person appointments, when the clinic sends its own link (video_call_type NONE), and when the meeting could not be created yet — the clinic's dashboard then creates it when the appointment is next opened.