Luna SaludAPI ReferenceLuna Salud

Luna Health API — API Reference

Introduction

Luna Health offers a modern REST API that allows you to integrate with our platform programmatically.

For Developers

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_HERE
Security 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.

25 endpoints documented.

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.

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. data always carries lab_test_id, patient_id, status, biomarker_count and timestamp. It may also carry imageset_id and submission_id: they are sent only for lab tests digitized from an uploaded image set.
  • lab_test.failed — a lab test could not be processed. data always carries error and timestamp. It may also carry lab_test_id, patient_id, imageset_id and submission_id: lab_test_id and patient_id are 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. data always carries imageset_id, rejection_comment and timestamp. It may also carry rejection_reason_id, lab_test_id, patient_id and error: rejection_reason_id is omitted when the digitization service gives no reason code, and when the rejected image set cannot be matched to a lab test, lab_test_id and patient_id are omitted and error explains why.
  • lab_test.split — a submitted image set was split into separate lab tests. data always carries imageset_id, patient_id, sub_file_id and timestamp. It may also carry lab_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. data always carries imageset_id, error and timestamp.
  • form.completed — a patient completed a form, such as an intake, admission or consent form. The answers are not in the event: read them with GET /patient-form?id= using form_id. Sent only when send_form_updates is on. data always carries form_id, patient_id and completed_at.
  • appointment.created — an appointment was booked, from the dashboard, your booking page, the virtual assistant or POST /appointments. A new recurring series sends one event per appointment in the series. status is PENDING when the clinic still has to approve a patient request. Sent only when send_appointment_updates is on, like every appointment event. data always carries appointment_id, patient_id, staff_ids, service_id, location_id, status, start_time and end_time.
  • appointment.updated — an appointment moved to a new time, was assigned to different providers, or both. changes lists start_time and/or staff. previous_start_time and previous_staff_ids hold the values before the change, and are null when the channel that made the change did not report them. data always carries appointment_id, patient_id, staff_ids, service_id, location_id, status, start_time, end_time, changes, previous_start_time and previous_staff_ids.
  • appointment.cancelled — an appointment was cancelled, by the clinic, by the patient or through POST /appointments/{id}/cancel. data always carries appointment_id, patient_id, staff_ids, service_id, location_id, status, start_time and end_time.
  • appointment.no_show — a pending or confirmed appointment was marked as a no-show, from the dashboard or through POST /appointments/{id}/no-show. data always carries appointment_id, patient_id, staff_ids, service_id, location_id, status, start_time and end_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.

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.