API reference

Connect an EHR, a scheduling system or a lab interface to Lumagen. One base URL, two ways to authenticate, JSON in and out.

Base URL https://lumagen.ai/api/v1Version v1OpenAPI document

Authentication

Every request carries a bearer token. Two kinds work, and they reach exactly the same routes.

API keys

An admin creates a key under Admin, API and integrations, picks its scopes and, for a health system with several sites, the sites it may reach. The key is shown once and hashed at rest. Send it as a bearer token.

Authorization: Bearer lmg_live_...

A key beginning lmg_test_ reaches a sandbox copy of your organization seeded with synthetic patients. Nothing written with a test key touches your real data, so a first request is impossible to regret.

OAuth 2.0 client credentials

For an integration service that should hold a client id and secret rather than a key. A client is created against an existing key and inherits its scopes, sites, environment and revocation. Exchange the credentials for an access token that lasts one hour, then send the token as a bearer.

POST /api/v1/oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials&client_id=lmg_client_...&client_secret=lmg_cs_...

Scopes

A credential carries the scopes it was created with. A request to a route whose scope the credential lacks is refused with 403 missing_scope.

  • patients:read
  • patients:write
  • appointments:read
  • appointments:write
  • pedigrees:read
  • eligibility:read
  • results:read
  • results:write
  • recontact:read
  • guidelines:read
  • locations:read
  • locations:write
  • calls:read

Revoking a key takes effect on the next request, and revokes any client created against it.

Requests and writes

Bodies are JSON. Responses are JSON with a data field, or an error field and a message you can show to a person.

Idempotency-Key

Every write (POST and PATCH) requires an Idempotency-Key header, any string unique to the attempt. A retry with the same key replays the first response instead of writing twice. The same key with a different body is refused with 409.

Idempotency-Key: 6f1c2d5e-appointment-84213

Validation

A write body is checked against the fields listed for the resource. A field the resource does not have is refused rather than dropped, and the response names every field that did not validate.

Rate limits

Each credential may make 600 requests per minute. Past that the response is 429 with a Retry-After header and X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset. Limits are per credential, not per organization, so one script cannot starve another.

Sites

A credential scoped to particular sites reads and writes only records at those sites. A write naming another site is refused with 403 location_out_of_scope.

Every write an integration makes is recorded in your organization's audit log against the credential that made it.

Errors

Errors carry an error code for your program and a message for a person.

StatusCodeMeaning
400invalid_requestA field did not validate; the issues field lists each one.
400idempotency_key_requiredA write arrived without an Idempotency-Key header.
400invalid_jsonThe body is not valid JSON.
401unauthorizedNo credential, an unknown one, or a revoked one.
403missing_scopeThe credential lacks the route's scope.
403location_out_of_scopeThe credential is scoped to other sites.
403identity_not_permittedThe organization cannot hold identifiers: a paid plan and a current BAA are required.
404not_foundNo such record in this organization.
409idempotency_key_reusedThe Idempotency-Key was used with a different body.
429rate_limitedToo many requests from this credential this minute.

Resources

Every route sits under the base URL. Path parameters are UUIDs from earlier responses.

GET/patientsScope patients:read

List patients

Newest first. Filter by record number to find one.

Query parameters
  • mrnstringoptionalExact record number.
  • limitintegeroptionalHow many rows to return.
Response 200 data[]
  • iduuid
  • name_givenstringGiven name.
  • name_familystringFamily name.
  • date_of_birthdateYYYY-MM-DD.
  • sexfemale | male | intersex | unknownSex assigned at birth.
  • external_patient_refstringYour record number (MRN).
  • monitoring_statusactive | deceased | departed | withdrawnWhether the patient is still monitored on the registry.
  • location_iduuidThe site the patient belongs to.
  • created_atdate-time
POST/patientsScope patients:write

Create a patient

Identifiers need a paid plan and a current BAA; a test key writes to the sandbox.

Write. Needs an Idempotency-Key.

Request body
  • name_givenstringoptionalGiven name.
  • name_familystringoptionalFamily name.
  • date_of_birthdateoptionalYYYY-MM-DD.
  • sexfemale | male | intersex | unknownoptionalSex assigned at birth.
  • external_patient_refstringoptionalYour record number (MRN).
  • phonestringoptionalE.164 where possible.
  • emailemailoptionalContact email.
  • preferred_languagestringoptionalBCP 47 tag, such as en or es.
  • location_iduuidoptionalThe site the patient belongs to.
Response 201
  • iduuid
PATCH/patients/{id}Scope patients:write

Update a patient

Send only the fields to change. Null clears a field.

Write. Needs an Idempotency-Key.

Request body
  • name_givenstringoptionalGiven name.
  • name_familystringoptionalFamily name.
  • date_of_birthdateoptionalYYYY-MM-DD.
  • sexfemale | male | intersex | unknownoptionalSex assigned at birth.
  • external_patient_refstringoptionalYour record number (MRN).
  • phonestringoptionalE.164 where possible.
  • emailemailoptionalContact email.
  • preferred_languagestringoptionalBCP 47 tag, such as en or es.
  • location_iduuidoptionalThe site the patient belongs to.
  • monitoring_statusactive | deceased | departed | withdrawnoptional
Response 200
  • iduuid
  • name_givenstringGiven name.
  • name_familystringFamily name.
  • date_of_birthdateYYYY-MM-DD.
  • sexfemale | male | intersex | unknownSex assigned at birth.
  • external_patient_refstringYour record number (MRN).
  • monitoring_statusactive | deceased | departed | withdrawnWhether the patient is still monitored on the registry.
  • location_iduuidThe site the patient belongs to.
  • created_atdate-time
GET/appointmentsScope appointments:read

List appointments

Soonest first.

Query parameters
  • fromdate-timeoptionalStarting at or after this instant.
  • todate-timeoptionalStarting before this instant.
  • limitintegeroptionalHow many rows to return.
Response 200 data[]
  • iduuid
  • case_iduuid
  • location_iduuid
  • starts_atdate-time
  • visit_typestringYour visit type, as text.
  • providerstringThe clinician, as text.
  • sourcestringHow the appointment arrived: feed, api, or console.
  • external_refstringYour identifier for the appointment.
  • statusscheduled | completed | cancelled | no_show
POST/appointmentsScope appointments:write

Book an appointment on a case

Write. Needs an Idempotency-Key.

Request body
  • case_iduuidrequired
  • location_iduuidrequired
  • starts_atdate-timerequired
  • visit_typestringoptional
  • providerstringoptional
  • external_refstringoptionalYour identifier for the appointment.
Response 201
  • iduuid
PATCH/appointments/{id}Scope appointments:write

Reschedule, reassign or close an appointment

Write. Needs an Idempotency-Key.

Request body
  • starts_atdate-timeoptional
  • location_iduuidoptional
  • visit_typestringoptional
  • providerstringoptional
  • external_refstringoptional
  • statusscheduled | completed | cancelled | no_showoptional
Response 200
  • iduuid
  • case_iduuid
  • location_iduuid
  • starts_atdate-time
  • visit_typestringYour visit type, as text.
  • providerstringThe clinician, as text.
  • sourcestringHow the appointment arrived: feed, api, or console.
  • external_refstringYour identifier for the appointment.
  • statusscheduled | completed | cancelled | no_show
GET/pedigreesScope pedigrees:read

List cases with a pedigree

Most recently changed first. Fetch one by id for the family itself.

Query parameters
  • patient_iduuidoptionalOnly this patient's cases.
  • limitintegeroptionalHow many rows to return.
Response 200 data[]
  • iduuid
  • case_codestringThe human-readable case code.
  • stateintake_pending | intake_complete | in_review | decided | ordered | result_received | released | on_registry | not_tested
  • patient_iduuid
  • location_iduuid
  • peopleintegerHow many people are on the pedigree.
  • updated_atdate-time
GET/pedigrees/{caseId}Scope pedigrees:read

Read a pedigree

Every person with their relation to the proband derived from the graph, their diagnoses and current results, and the edges between them. Reading only: the pedigree is edited in Lumagen, where its invariants are enforced.

Response 200
  • caseobject
  • personsobject[]
  • edgesobject[]
GET/eligibility/{caseId}Scope eligibility:read

Read a case's testing criteria

Evaluated on request against the current pedigree and the current guideline versions. Decision support: the clinician decides.

Response 200
  • case_iduuid
  • evaluated_atdate-time
  • guidelinesobject[]
  • recommend_testingbooleanTrue when at least one indicated criterion is met.
  • metobject[]
  • near_missobject[]Criteria the source words as consider or clinical judgment.
  • not_evaluableobject[]Criteria this engine cannot decide from a pedigree, named so nobody reads a false negative.
GET/resultsScope results:read

List results

Newest first. Findings are read on the pedigree, per person.

Query parameters
  • case_iduuidoptionalOnly this case.
  • releasedbooleanoptionaltrue for released results only.
  • limitintegeroptionalHow many rows to return.
Response 200 data[]
  • iduuid
  • case_iduuid
  • patient_iduuid
  • sourceehr_feed | lab_api | manual_upload | api
  • lab_namestring
  • lab_accessionstring
  • reported_atdate
  • verified_atdate-timeWhen a clinician verified the summary.
  • released_atdate-timeWhen it was released to the patient's record.
  • created_atdate-time
POST/resultsScope results:write

Deliver a lab result

Matched to a patient by order id, record number, or name and date of birth. A result that matches nobody is held for a clinician to link, never dropped.

Write. Needs an Idempotency-Key.

Request body
  • lab_accessionstringrequiredThe lab's accession number.
  • lab_namestringoptionalThe lab.
  • reported_atdateoptionalYYYY-MM-DD.
  • mrnstringoptionalYour record number for the patient, the surest way to match.
  • patient_namestringoptionalFull name as the lab has it, used to match when there is no record number.
  • date_of_birthdateoptionalYYYY-MM-DD.
  • order_iduuidoptionalA Lumagen order id, when Lumagen placed the order.
  • findingsobject[]requiredOne row per gene reported.
  • rawobjectoptionalThe lab's own payload, kept as sent.
Response 201
  • outcomeattached | held
  • result_iduuid
  • case_iduuid
  • unmatched_iduuid
  • candidate_countinteger
GET/recontactScope recontact:read

List recontact items

Patients the registry has flagged since their result: a reclassified variant, a family-history change, a guideline change. Open items unless a status is given.

Query parameters
  • statusopen | actioned | dismissedoptionalDefaults to open.
  • limitintegeroptionalHow many rows to return.
Response 200 data[]
  • iduuid
  • case_iduuid
  • case_codestring
  • reasonreclassification | management | testing_criteria
  • statusopen | actioned | dismissed
  • created_atdate-time
  • dispositioned_atdate-time
GET/guidelinesScope guidelines:read

List the guideline versions in force and their criteria

What the eligibility endpoint evaluates against, by id, so a change in version is visible before it changes an answer.

Response 200 data[]
  • keystringNCCN-BOPP or NCCN-CEEG.
  • namestring
  • versionstring
  • version_datedate
  • criteriaobject[]
GET/locationsScope locations:read

List sites

Response 200 data[]
  • iduuid
  • namestring
  • timezonestringIANA zone, such as America/Phoenix.
  • addressstring
  • phonestring
  • created_atdate-time
POST/locationsScope locations:write

Add a site

Write. Needs an Idempotency-Key.

Request body
  • namestringrequired
  • timezonestringoptionalIANA zone.
  • addressstringoptional
  • phonestringoptional
Response 201
  • iduuid
  • namestring
  • timezonestringIANA zone, such as America/Phoenix.
  • addressstring
  • phonestring
  • created_atdate-time
GET/callsScope calls:read

List intake conversations

Every chat, browser voice session or phone call a patient had with the intake agent, with whether it reached the coverage minimum. Transcripts are not exposed.

Query parameters
  • case_iduuidoptionalOnly this case.
  • limitintegeroptionalHow many rows to return.
Response 200 data[]
  • iduuid
  • case_iduuid
  • channelchat | web_voice | phone
  • statusstarted | ready_for_review | abandoned
  • attempt_numberinteger
  • started_atdate-time
  • ended_atdate-time
  • duration_sintegerSeconds.
  • minimum_metbooleanWhether the conversation reached the coverage minimum.
POST/fhirScope patients:write

Deliver a FHIR Bundle

Patient, Appointment and DiagnosticReport resources, mapped onto Lumagen's own records. Needs patients:write and appointments:write. Fields Lumagen does not hold are named in the response as ignored-field issues.

Write. Needs an Idempotency-Key.

Request body
  • resourceTypeBundlerequired
  • typestringoptional
  • entryobject[]required
Response 200
  • createdobject[]
  • issueobject[]
POST/oauth/token

Exchange client credentials for an access token

Form-encoded or JSON: grant_type=client_credentials, client_id, client_secret, and an optional space-separated scope. Basic authentication with the client id and secret also works. Tokens last one hour.

Request body
  • grant_typeclient_credentialsrequired
  • client_idstringrequired
  • client_secretstringrequired
  • scopestringoptionalSpace-separated, a subset of the key's scopes.
Response 200
  • access_tokenstring
  • token_typebearer
  • expires_inintegerSeconds.
  • scopestring

Webhooks

Register an endpoint under Admin, API and integrations and choose the events it receives. Every attempt is listed there with its status, so a missed event has an answer without a support ticket.

Events

  • pedigree.completed
  • eligibility.changed
  • result.released
  • recontact.created
  • call.failed

Each delivery is a POST with a JSON body: event, created_at, and data describing the record.

Verifying a delivery

Each delivery carries an x-lumagen-signature header of the form t=<unix seconds>,v1=<hex>. Compute HMAC-SHA256 over "<t>.<raw body>" with the endpoint's signing secret, compare it to v1 in constant time, and reject a timestamp more than 300 seconds from now.

import { createHmac, timingSafeEqual } from "node:crypto";

export function verify(rawBody: string, header: string, secret: string): boolean {
  const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
  const expected = createHmac("sha256", secret).update(`${parts.t}.${rawBody}`).digest("hex");
  const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) <= 300;
  return fresh && timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}
// header: request.headers["x-lumagen-signature"]

A delivery that fails is retried 5 times with increasing delays, then marked failed. Answer with any 2xx status within ten seconds.

FHIR

POST /fhir accepts a Bundle of Patient, Appointment and DiagnosticReport resources. Lumagen maps them onto its own patient, appointment and result records and keeps nothing else; every field it did not store is named in the response as an issue with code ignored-field, and the drop is recorded. A Patient identifier of type MR becomes the record number, and an Appointment participant referencing that patient books the appointment on the case.

Needs patients:write and appointments:write.