Luna Health API — API Reference
Introduction
Luna Health offers a modern REST API that allows you to integrate with our platform programmatically.
For Developers
- You have programmatic access to the same features available in the Luna Health platform
- Create your own custom data representations and reporting tools
- Our developer support team is available to help you integrate with our platform
API Overview
Our API adheres to REST principles and is designed to be developer-friendly. It uses standard HTTP methods and returns responses in JSON format.
Authentication
API access requires authentication using API keys. You need to include your API key in the Authorization header of all requests using the Bearer token format:
Authorization: Bearer YOUR_API_KEY_HERESecurity Warning
Never share your API keys in publicly accessible areas such as GitHub, client-side code, etc. API keys should be stored securely server-side.
Generating API Keys
You can generate and manage API keys in your Luna Health account settings. Follow these steps:
1. Log in to your Luna Health account 2. Navigate to Settings > API Keys 3. Click "Generate New API Key" 4. Store your new API key securely as it will only be displayed once
Error Handling
All errors are returned in a consistent format with appropriate HTTP status codes.
Biomarkers
Endpoints for managing and analyzing biomarker data. Biomarkers are measurable indicators of biological states or conditions that provide insights into a patient's health status.
Patients
Endpoints for managing patient records in your organization — listing patients, looking up a patient by ID, accessing patient chart data and health records, registering new patients, and updating patient contact information. Staff and administrator accounts are never exposed.
- GETList patients
/patients - POSTCreate a patient
/patients - GETGet patient by id
/patient - PATCHUpdate a patient’s contact information
/patients/{id} - GETGet patient charts data
/patient-charts - GETList a patient's completed forms
/patient-forms - GETGet a completed patient form
/patient-form - GETDownload a completed patient form as PDF
/patient-form/pdf
Webhooks
Read-only endpoint for retrieving your organization's webhook configuration. The secret key is never returned, and webhook settings are changed from your Luna Health account settings rather than through the public API. Luna Health currently sends five lab-test events — lab_test.processed, lab_test.failed, lab_test.rejected, lab_test.split and lab_test.split_error — form.completed, and four appointment events — appointment.created, appointment.updated, appointment.cancelled and appointment.no_show — as signed POST requests to the URL you configure.
Events we send
lab_test.processed— a lab test finished processing and its biomarkers were extracted.dataalways carrieslab_test_id,patient_id,status,biomarker_countandtimestamp. It may also carryimageset_idandsubmission_id: they are sent only for lab tests digitized from an uploaded image set.lab_test.failed— a lab test could not be processed.dataalways carrieserrorandtimestamp. It may also carrylab_test_id,patient_id,imageset_idandsubmission_id:lab_test_idandpatient_idare omitted when the failure happens before the lab test or its patient is identified, and the image-set fields are sent only for digitized uploads.lab_test.rejected— a submitted image set was rejected during digitization.dataalways carriesimageset_id,rejection_commentandtimestamp. It may also carryrejection_reason_id,lab_test_id,patient_idanderror:rejection_reason_idis omitted when the digitization service gives no reason code, and when the rejected image set cannot be matched to a lab test,lab_test_idandpatient_idare omitted anderrorexplains why.lab_test.split— a submitted image set was split into separate lab tests.dataalways carriesimageset_id,patient_id,sub_file_idandtimestamp. It may also carrylab_test_id: it is omitted while the split file is not yet linked to a lab test.lab_test.split_error— an image set split could not be completed.dataalways carriesimageset_id,errorandtimestamp.form.completed— a patient completed a form, such as an intake, admission or consent form. The answers are not in the event: read them withGET /patient-form?id=usingform_id. Sent only whensend_form_updatesis on.dataalways carriesform_id,patient_idandcompleted_at.appointment.created— an appointment was booked, from the dashboard, your booking page, the virtual assistant orPOST /appointments. A new recurring series sends one event per appointment in the series.statusis PENDING when the clinic still has to approve a patient request. Sent only whensend_appointment_updatesis on, like everyappointmentevent.dataalways carriesappointment_id,patient_id,staff_ids,service_id,location_id,status,start_timeandend_time.appointment.updated— an appointment moved to a new time, was assigned to different providers, or both.changeslistsstart_timeand/orstaff.previous_start_timeandprevious_staff_idshold the values before the change, and are null when the channel that made the change did not report them.dataalways carriesappointment_id,patient_id,staff_ids,service_id,location_id,status,start_time,end_time,changes,previous_start_timeandprevious_staff_ids.appointment.cancelled— an appointment was cancelled, by the clinic, by the patient or throughPOST /appointments/{id}/cancel.dataalways carriesappointment_id,patient_id,staff_ids,service_id,location_id,status,start_timeandend_time.appointment.no_show— a pending or confirmed appointment was marked as a no-show, from the dashboard or throughPOST /appointments/{id}/no-show.dataalways carriesappointment_id,patient_id,staff_ids,service_id,location_id,status,start_timeandend_time.
A test event is also delivered when you use the test button in your account settings, so you can confirm an endpoint works before enabling it.
Flags returned by this endpoint control delivery: enabled turns the webhook on or off for the whole organization, send_lab_test_updates controls the lab_test events above, send_form_updates controls form.completed, and send_appointment_updates controls the appointment events. Each event family is off until you turn it on in your account settings. send_biomarker_updates is reserved for a future biomarker event family — no biomarker events are sent today.
Ignore any event type you do not recognize: new event types may be added, and they are always off for an existing webhook until you turn them on.
Each form completion is announced once, even if the patient submits twice. Still, treat delivery as at least once and use the identifiers in data (such as form_id, lab_test_id or appointment_id) to ignore any event you have already processed. Appointment events report changes made through any channel, including your own API calls, so match appointment_id against the changes your integration just made to avoid acting on them twice.
Payload shape
Every delivery is an HTTP POST with Content-Type: application/json and the same envelope. Only data varies by event:
{
"event": "lab_test.processed",
"timestamp": "2025-01-01T00:00:00.000Z",
"organization_id": "6543210fedcba9876543210f",
"data": {
"lab_test_id": "6543210fedcba9876543210a",
"patient_id": "6543210fedcba9876543210b",
"status": "COMPLETED",
"biomarker_count": 42,
"timestamp": "2025-01-01T00:00:00.000Z"
}
}A form.completed delivery carries only the form, the patient and when the form was completed:
{
"event": "form.completed",
"timestamp": "2025-01-01T00:00:05.000Z",
"organization_id": "6543210fedcba9876543210f",
"data": {
"form_id": "6543210fedcba9876543210c",
"patient_id": "6543210fedcba9876543210b",
"completed_at": "2025-01-01T00:00:00.000Z"
}
}An appointment.updated delivery for an appointment moved to another hour and another provider:
{
"event": "appointment.updated",
"timestamp": "2025-01-02T15:00:01.000Z",
"organization_id": "6543210fedcba9876543210f",
"data": {
"appointment_id": "6543210fedcba9876543210d",
"patient_id": "6543210fedcba9876543210b",
"staff_ids": ["6543210fedcba98765432101"],
"service_id": "6543210fedcba98765432102",
"location_id": null,
"status": "CONFIRMED",
"start_time": "2025-01-03T17:00:00.000Z",
"end_time": "2025-01-03T17:50:00.000Z",
"changes": ["start_time", "staff"],
"previous_start_time": "2025-01-03T16:00:00.000Z",
"previous_staff_ids": ["6543210fedcba98765432103"]
}
}Events carry identifiers rather than clinical values. Use the identifiers to read the records themselves from the Lab Tests and Biomarkers endpoints, a completed form's answers from GET /patient-form, and an appointment from GET /appointments.
Verifying a delivery
Each request carries an X-Luna-Signature header: the HMAC-SHA256 of the raw request body, keyed with your webhook secret and hex-encoded. Recompute the HMAC over the exact bytes you received and compare it with a constant-time comparison before trusting the payload. Reject any request whose signature does not match.
Delivery behaviour
Each event is attempted once, with a 10-second timeout, and is not retried automatically. Respond 2xx promptly and move slow work off the request so a delivery is not lost to a timeout. Because there are no retries, reconcile against the read endpoints if your integration needs a completeness guarantee.
Your endpoint must be a public HTTPS address. Plain http:// is rejected, as are internal, private and loopback addresses — including hostnames that resolve to one — because deliveries carry patient-linked identifiers.
Lab Tests
APIs for ordering, tracking, and retrieving laboratory test results. These endpoints provide access to comprehensive lab data, historical test results, and reference ranges for accurate interpretation.
Patient Tags
Endpoints for reading your organization's patient tags and for attaching or detaching them on a patient. Tags are labels only — no clinical or contact data can be written through them — and both the patient and the tag are scoped to your organization.
Appointments
Endpoints for listing and managing appointments. Retrieve scheduled, confirmed, completed, and canceled appointments for your organization with filtering by date range, status, and patient. To book, list providers (GET /staff) and services (GET /services) for their IDs, find open slots with GET /availability (one day) or GET /availability/range (up to 14 days), then create the appointment with POST /appointments.
- GETList appointments
/appointments - POSTCreate an appointment
/appointments - GETGet available appointment slots
/availability - GETGet available appointment slots for a date range
/availability/range - POSTCancel an appointment
/appointments/{id}/cancel - POSTMark an appointment as a no-show
/appointments/{id}/no-show - POSTReschedule an appointment
/appointments/{id}/reschedule - GETList services
/services - GETList providers
/staff
Payments
Endpoints for retrieving payment records (invoices) in your organization — listing payments with filtering by status, payment method, patient, and date range, and looking up a single payment by ID. Of the factura (CFDI) only its SAT UUID, serie, folio, status and stamp or cancellation times are exposed; the fiscal data of the receptor and the issuer, the XML/PDF files and Stripe identifiers never are.