Luna SaludAPI ReferenceLuna Salud

Create an appointment

POST/appointments

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

NameTypeRequiredDescription
patientIdstringYesID of an existing patient in your organization
staffIdstringYesID of the provider (staff member) who will see the patient
serviceIdstringYesID of the service; its duration sets the end time
startTimestring (date-time)YesStart time in ISO 8601 with a UTC offset, e.g. a start_time from GET /availability
locationIdstringNoID of the location where the appointment takes place
reasonForVisitstringNoReason for the visit, shown to staff
telemedicinebooleanNoBook 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.

Example request

{
  "patientId": "string",
  "staffId": "string",
  "serviceId": "string",
  "startTime": "2025-01-01T00:00:00Z",
  "locationId": "string",
  "reasonForVisit": "string",
  "telemedicine": true
}

Responses

StatusDescription
200Successful response
400Invalid input data
401Authorization not provided
403Insufficient access
500Internal server error

200 — Successful response

Response fields

NameTypeRequiredDescription
idstringYes—
statusstringYes—
typestringYes—
titlestringnullYes—
descriptionstringnullYes—
start_timestringYes—
end_timestringYes—
telemedicinebooleanYes—
video_call_typestringYesVideo 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_atstringYes—
updated_atstringYes—
patientobject | nullYes—
staffsarray<object>Yes—
serviceobject | nullYes—
locationobject | nullYes—
video_call_urlstringnullYesThe 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.

Example response

{
  "id": "string",
  "status": "NEW",
  "type": "string",
  "title": null,
  "description": null,
  "start_time": "string",
  "end_time": "string",
  "telemedicine": true,
  "video_call_type": "NONE",
  "created_at": "string",
  "updated_at": "string",
  "patient": {
    "id": "string",
    "first_name": null,
    "last_name": null,
    "email": null
  },
  "staffs": [
    {
      "id": "string",
      "first_name": null,
      "last_name": null
    }
  ],
  "service": {
    "id": "string",
    "name": "string"
  },
  "location": {
    "id": "string",
    "name": null
  },
  "video_call_url": null
}

400 — Invalid input data

Response fields

NameTypeRequiredDescription
messagestringYesThe error message
codestringYesThe error code
issuesarray<object>NoAn array of issues that were responsible for the error

Example response

{
  "code": "BAD_REQUEST",
  "message": "Invalid input data",
  "issues": []
}

401 — Authorization not provided

Response fields

NameTypeRequiredDescription
messagestringYesThe error message
codestringYesThe error code
issuesarray<object>NoAn array of issues that were responsible for the error

Example response

{
  "code": "UNAUTHORIZED",
  "message": "Authorization not provided",
  "issues": []
}

403 — Insufficient access

Response fields

NameTypeRequiredDescription
messagestringYesThe error message
codestringYesThe error code
issuesarray<object>NoAn array of issues that were responsible for the error

Example response

{
  "code": "FORBIDDEN",
  "message": "Insufficient access",
  "issues": []
}

500 — Internal server error

Response fields

NameTypeRequiredDescription
messagestringYesThe error message
codestringYesThe error code
issuesarray<object>NoAn array of issues that were responsible for the error

Example response

{
  "code": "INTERNAL_SERVER_ERROR",
  "message": "Internal server error",
  "issues": []
}