npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@healthcloudai/hc-ehr-steadymd

v0.2.4

Published

Healthcheck SteadyMD EHR connector SDK with TypeScript

Readme

Healthcheck SteadyMD EHR Connector

SDK client for the Healthcheck SteadyMD EHR integration.

HCSteadyMDClient uses a configured and authenticated HCLoginClient for tenant, environment, and auth headers. Methods return the shared Healthcheck API envelope.


Installation

npm install @healthcloudai/hc-ehr-steadymd \
  @healthcloudai/hc-login-connector \
  @healthcloudai/hc-http

Import

import { HCSteadyMDClient } from "@healthcloudai/hc-ehr-steadymd";
import { HCLoginClient } from "@healthcloudai/hc-login-connector";
import { FetchClient } from "@healthcloudai/hc-http";

import type {
  APIResponse,
  SteadyMDPatient,
  SteadyMDEpisode,
  SteadyMDConsult,
  SteadyMDIntake,
} from "@healthcloudai/hc-ehr-steadymd";

Setup

const httpClient = new FetchClient();
const loginClient = new HCLoginClient(httpClient);

loginClient.configure("healthcheck", "dev");
await loginClient.login("[email protected]", "ExamplePassword123!");

const steadymdClient = new HCSteadyMDClient(httpClient, loginClient);

API Key

If the environment requires an API key, configure it once:

steadymdClient.setApiKey("x-api-key", process.env.HEALTHCLOUD_API_KEY ?? "");

After it is set, the API key header is included automatically with every request.


Response Format

Most methods return:

type APIResponse<T> = {
  Data: T | null;
  IsOK: boolean;
  ErrorMessage: string | null;
};

getLabResultPdf is the exception — it returns a raw Promise<ArrayBuffer>.

Methods that accept payloads wrap them in { Data: payload } internally. Pass the raw object to the method, not a wrapped version:

// Correct
await steadymdClient.createConsult({ programGuid, consultType, usState });

// Wrong — do NOT wrap in { Data: ... }
await steadymdClient.createConsult({ Data: { programGuid, consultType, usState } });

Order of Execution

The methods below generally follow the documented flow from the "Healthcheck SteadyMD + Impilo APIs" Postman collection. Steps must be run in order because later steps depend on GUIDs produced by earlier ones.

Deviation from the Postman order: run Lab Order (steps 10–11) before Intake (steps 7–8). createLabOrder() returns a usable LabRequisitions[].Guid synchronously, while the documented step-6→7 path (createEpisodeLabRequisition → poll getIntake) relies on an async vendor link that does not reliably populate. Running Lab Order first lets updateIntake() use a real, immediately-available labRequisitionGuid instead of depending on that unreliable async link. See Lab Requisition GUID is created asynchronously below for the full explanation.

| Step | Area | Action | |------|------|--------| | 1 | Patient | getMyPatient() — confirm the patient record exists | | 2–3 | Program | listPrograms()getProgram()getProgramConsultOptions()getProgramLabs()listProgramClinicians()getProgramAppointments() | | 2–3 | Pharmacy / Clinician | listPharmacies() / getClinician() — lookup only | | 4 | Episode | createEpisode()getEpisode() | | 5 | Consult | createConsult()getConsult()getConsultAvailability()scheduleConsultAppointment()getConsultPatientUI()sendConsultMessage()createConsultIssue() | | 6 | Episode | updateEpisode() / patchEpisode()createEpisodeLabRequisition()getEpisodeSignedDocuments() | | 10–11 | Lab Order | createLabOrder()getLabOrder()getLabOrderPdf()listLabOrderResults()createLabOrderResult()getLabOrderResult()getLabResultPdf()run before Intake; capture labRequisitionGuid from createLabOrder()'s response (see deviation note above) | | 7–8 | Intake | createIntake() (draft) → getIntake()updateIntake(..., { LabRequisitionGuid: <guid captured from createLabOrder> }) (final) → addAllergyIntolerance() | | 9 | Identity | claimIdentityVerification() (optional KYC) | | 12–13 | Consult | updateConsultStatus() | | 14 | Message | createMessage() (episode-level) → getMessage() |


Known Vendor Limitations

Lab Requisition GUID is created asynchronously — do not read it from createEpisodeLabRequisition

POST /episode/{guid}/lab/requisition returns {"Guid": null, ...}. The vendor creates the requisition asynchronously and is supposed to link it to the episode's intake some time later (GET /intake/{guid}Data.LabRequisitionGuid).

This async link does not reliably populateData.LabRequisitionGuid from getIntake can remain null indefinitely after calling createEpisodeLabRequisition. Since updateIntake requires a non-null LabRequisitionGuid to finalize an intake (IsDraft: false), relying on the documented flow can leave updateIntake permanently blocked.

Workaround — get the guid from createLabOrder instead:

createLabOrder() (steps 10–11) embeds the newly created requisition directly in its response as Data.LabRequisitions[0].Guid, populated synchronously — no polling required. Run the Lab Order section before Intake (see Order of Execution), capture labRequisitionGuid = response.Data.LabRequisitions[0].Guid from createLabOrder, and feed that directly into updateIntake:

// 1. Create a lab order — its response carries a usable requisition guid immediately:
const labOrderResponse = await steadymdClient.createLabOrder({ EpisodeGuid: episodeGuid, /* ... */ });
const labRequisitionGuid = labOrderResponse.Data?.LabRequisitions?.[0]?.Guid;

// 2. Use it directly to finalize the intake — no need to poll getIntake:
await steadymdClient.updateIntake(intakeGuid, {
  EpisodeGuid: episodeGuid,
  LabRequisitionGuid: labRequisitionGuid,
  IsDraft: false,
  // ...
});

This does not replace createEpisodeLabRequisition in the documented flow — it is a practical alternative for unblocking updateIntake when the async link does not populate in a reasonable time. createEpisodeLabRequisition itself still returns {"Guid": null} as documented, and getEpisodeLabRequisition / polling getIntake remain the vendor-documented way to retrieve that requisition's guid if/when the link completes.

createEpisodeLabRequisition — payload must be FLAT (labOrderCode/labOrderName at the top level), not the documented Tests[] array

The SDK type SteadyMDLabRequisitionCreateRequest (and the Postman collection) describe this endpoint's body as { Laboratory, Tests: [{ LabOrderCode, LabOrderName }], Icd10Codes } — an array of tests, PascalCase keys, matching the convention used by every other non-Consult endpoint.

The live dev backend rejects that shape. Both the documented PascalCase Tests: [{ LabOrderCode, LabOrderName }] and a lowercase-keyed Tests: [{ labOrderCode, labOrderName }] variant 400 with:

SteadyMD POST → 400: {"labOrderCode":["This field is required."],"labOrderName":["This field is required."]}

Note that the error reports labOrderCode/labOrderName as top-level required fields — not nested under "Tests"/"tests" — which reveals the endpoint actually validates a flat, single-test payload. Sending the fields directly at the root, lowercase, alongside Laboratory, succeeds (IsOK: true):

// Cast around the SDK type — it does not match what this endpoint accepts:
const payload = {
  Laboratory: "LABCORP",
  labOrderCode: "001453",
  labOrderName: "Hemoglobin A1c",
  Icd10Codes: ["E11.9"],
} as unknown as SteadyMDLabRequisitionCreateRequest;

await steadymdClient.createEpisodeLabRequisition(episodeGuid, payload);

This is a vendor-side schema/documentation mismatch — only this endpoint's request body behaves this way; createLabOrder's LabRequisitions[] array correctly accepts the documented PascalCase LabOrderCode/LabOrderName shape.

getProgramConsultOptions — only options with IsScheduled: true AND InteractionMode: "video_chat" support the full scheduled flow

getProgramConsultOptions can return several ConsultType/InteractionMode combinations (e.g. phone_callback / phone_call, scheduled_video_visit / video_chat, async types, etc.) — only options with IsScheduled: true and InteractionMode: "video_chat" support the full scheduled flow (availability, appointment scheduling, and patient UI).

Picking an arbitrary option (e.g. Data[0]) can select a non-schedulable, non-video option and cascade into failures in getConsultAvailability, scheduleConsultAppointment, and getConsultPatientUI (see below — all three have hard requirements tied to the chosen option's IsScheduled/InteractionMode). Select deliberately:

const { Data: options } = await steadymdClient.getProgramConsultOptions(programGuid);
const option = options?.find((o) => o.IsScheduled === true && o.InteractionMode === "video_chat") ?? options?.[0];

getConsultAvailability — returns 400 for consults not in a schedulable VisitStatus

GET /consult/{guid}/schedule/availability returns 400 when the target consult's VisitStatus is not waiting_to_schedule or scheduled:

SteadyMD GET → 400: {"status": "Cannot get availability for consult that's not in waiting_to_schedule or scheduled status"}

This is closely related to — but distinct from — "non-schedulable consult type": even a scheduled_video_visit consult (created with IsScheduled: true) returns this 400 once its VisitStatus moves out of waiting_to_schedule/scheduled (e.g. after being canceled or resolved). Check getConsult's VisitStatus before calling this, in addition to checking getProgramConsultOptions's IsScheduled flag.

scheduleConsultAppointmentstartsAt must be an exact slot from getConsultAvailability

POST /consult/{guid}/schedule/appointment rejects arbitrary timestamps. Always use a slot from GET /consult/{guid}/schedule/availability. Only call this for consult types where IsScheduled: true (see above). If Data is an empty array, show the user "No times available" and do not call scheduleConsultAppointment.

const { Data: slots } = await steadymdClient.getConsultAvailability(consultGuid);
if (!slots || slots.length === 0 || !slots[0]?.Availability?.length) {
  // Show "No times available"
  return;
}
const startsAt = slots[0].Availability[0];
await steadymdClient.scheduleConsultAppointment(consultGuid, { clinicianGuid, startsAt });

If getConsultAvailability returns an empty Availability array (no open slots configured for the program/clinician), treat this as an expected condition and show the user "No times available" rather than treating it as a failure.

getConsultPatientUI — 400 unless the consult's InteractionMode is video_chat

POST /consult/{guid}/patient-ui returns 400 for consults whose InteractionMode is not video_chat:

SteadyMD POST → 400: {"status": "Cannot return video chat url for consult that's not a video chat consult"}

Only call this for consults created from a getProgramConsultOptions option with InteractionMode: "video_chat" (see above — typically the scheduled_video_visit option). For any other interaction mode, skip the call rather than treating the 400 as an error.

sendConsultMessage — blocked patient-wide until the previous consult is in RESOLVED_STATUS

POST /consult/{guid}/message returns 400 when the previous consult is not resolved:

SteadyMD POST → 400: {"status": "You cannot create a message consult if the previous consult is not in a RESOLVED_STATUS"}

Root cause: this "previous consult" check is evaluated across the whole patient, not scoped to the current episode. listConsults({ episodeGuid }) can return results whose EmrUri values belong to entirely different episodes — the vendor ignores the episodeGuid filter (the parameter is echoed back correctly in the Next pagination URL, yet the result set is unfiltered). If any of the patient's historical consults — regardless of episode — sits in rejected (a non-resolved) status, it counts as the "previous consult" and blocks the new one.

Net effect: a new consult can be blocked from sending a message because some historical consult — possibly unrelated to the current episode — is sitting in rejected status. This cannot be fixed from the client side; it requires either (a) a vendor-side bulk cleanup/resolution of the patient's historical consults, or (b) a vendor fix to scope the RESOLVED_STATUS check (and the listConsults episodeGuid filter) to the episode rather than the whole patient. Treat a 400 with this exact message as a data/environment condition, not an application bug — log it and continue rather than failing the flow.

Note: updateConsultStatus (see below) can succeed despite being a known limitation (HTML 500 / 502) — this vendor-side issue is intermittent / environment-state-dependent rather than a hard, permanent blocker. Don't assume the error always reproduces; always handle both outcomes.

updateConsultStatus — full-integration consults can intermittently throw HTML 500 (502 from our filter)

For full-integration consults, SteadyMD can return an HTML 500 on status changes, surfaced as a 502 Bad Gateway by our filter. This is an intermittent vendor-side condition rather than a hard block (see note above). When updateConsultStatus throws, fall back to getConsult and display the last known status:

try {
  await steadymdClient.updateConsultStatus(consultGuid, { status: "canceled" });
} catch {
  const { Data } = await steadymdClient.getConsult(consultGuid);
  console.log("Latest status:", Data?.VisitStatus);
}

getLabOrderPdf — returns 404 until lab results are processed

GET /lab-order/{guid}/pdf returns 404 while results are pending. Catch a 404 and display "Pending lab results" instead of surfacing an error to the user.

try {
  const pdf = await steadymdClient.getLabOrderPdf(labOrderGuid);
  // use pdf.Data?.File (base64 or URL)
} catch (err: any) {
  if (err?.statusCode === 404) {
    // Show "Pending lab results"
  }
}

What needs to be resolved (vendor-side) for the full documented flow to work end-to-end

These are blockers that cannot be fixed from this SDK — they require vendor (SteadyMD) action, either in their environment or in their API/docs:

  1. Async lab requisition → intake link does not reliably populate (see Lab Requisition GUID is created asynchronously). Until the vendor's async linkage reliably populates getIntake().Data.LabRequisitionGuid within a reasonable time, the documented createEpisodeLabRequisition → poll getIntakeupdateIntake flow cannot be relied on end-to-end; the createLabOrder-based workaround above is the only reliable path to finalize updateIntake.
  2. createEpisodeLabRequisition request schema mismatch (see entry above) — the vendor needs to either fix the endpoint to accept the documented Tests: [{ LabOrderCode, LabOrderName }] shape, or correct the Postman collection / docs to describe the flat labOrderCode/labOrderName shape it actually validates.
  3. Patients can accumulate historical consults stuck in rejected status, which block sendConsultMessage patient-wide for every new consult (see entry above). A bulk cleanup/resolution pass on the patient's consult history — or a vendor fix scoping the RESOLVED_STATUS check to the episode — is required before this method can be used reliably against a fresh consult.
  4. listConsults ignores the episodeGuid query filter (discovered while investigating #3 above) — the parameter is accepted and echoed in pagination URLs but not applied to the result set. Any client code that relies on listConsults({ episodeGuid }) to scope results to one episode will silently receive unrelated results; this needs a vendor-side fix before episode-scoped consult listing can be trusted.
  5. No schedulable time slots configuredgetConsultAvailability can return Availability: [] even for a freshly created scheduled_video_visit consult, so scheduleConsultAppointment cannot be used end-to-end until open slots are configured for at least one program/clinician.

Important Notes

  • The login client must be configured and authenticated before calling any SteadyMD method.
  • Do not pass raw backend routes to SDK methods. Use the public methods only.
  • Casing: Consult request body fields are camelCase (programGuid, consultType, usState, text, startsAt, status, level). All other SteadyMD request types are PascalCase (EpisodeGuid, Laboratory, EpisodeGuid, etc.) — except createEpisodeLabRequisition, whose Tests[] items the live backend validates as flat, lowercase, top-level labOrderCode/labOrderName fields rather than the documented PascalCase array shape (a vendor schema/doc mismatch specific to that one endpoint — see Known Vendor Limitations).
  • Response field names are always PascalCase (EpisodeGuid, Guid, VisitStatus, etc.).
  • Response examples below are sanitized. Real GUIDs, names, and URLs are replaced with placeholders.

Patient Methods

getMyPatient() — GET

Returns the SteadyMD patient record for the authenticated Healthcheck user. The patientGuid is resolved server-side from the user's RecordMapping (STEADY_MD_ID) — clients never pass it.

const response = await steadymdClient.getMyPatient();

Returns Promise<APIResponse<SteadyMDPatient>>.

Example response:

{
  "Data": {
    "Guid": "11111111-1111-4111-8111-111111111111",
    "FirstName": "John",
    "LastName": "Smith",
    "Email": "[email protected]",
    "Phone": "+15555550123",
    "Address1": "123 Example St",
    "Address2": null,
    "City": "Oak Park",
    "UsState": "CA",
    "ZipCode": "91377",
    "BirthDate": "1978-10-10",
    "Gender": "male",
    "MaritalStatus": "UNK"
  },
  "ErrorMessage": null,
  "IsOK": true
}

Program Methods

listPrograms(options?) — GET

Lists programs. Optional filters: clinicianGuid, snomed, complaintsSnomed, programPopulation, limit, offset.

const response = await steadymdClient.listPrograms({ limit: 10 });

Returns Promise<APIResponse<SteadyMDProgramListResponse>>.

Example response:

{
  "Data": {
    "Count": 1,
    "Next": null,
    "Previous": null,
    "Results": [
      {
        "Guid": "66666666-6666-4666-8666-666666666666",
        "Name": "Safe Health Sandbox",
        "Description": "",
        "IdentityVerificationType": "sync_see_id",
        "ConsultTypes": [
          "async_messaging",
          "chart_review",
          "phone_callback",
          "scheduled_video_visit",
          "video_visit"
        ]
      }
    ]
  },
  "ErrorMessage": null,
  "IsOK": true
}

getProgram(programGuid) — GET

Returns one program by GUID.

const response = await steadymdClient.getProgram("66666666-6666-4666-8666-666666666666");

Returns Promise<APIResponse<SteadyMDProgram>>.

Example response:

{
  "Data": {
    "Guid": "66666666-6666-4666-8666-666666666666",
    "Name": "Safe Health Sandbox",
    "Description": "",
    "IdentityVerificationType": "sync_see_id",
    "ConsultTypes": [
      "async_messaging",
      "chart_review",
      "phone_callback",
      "scheduled_video_visit",
      "video_visit"
    ]
  },
  "ErrorMessage": null,
  "IsOK": true
}

getProgramAppointments(programGuid, consultType, usState, clinicianGuid?) — GET

Returns available appointment slots for a program, consult type, and US state. All three required params must be provided or SteadyMD returns 400.

const response = await steadymdClient.getProgramAppointments(
  "66666666-6666-4666-8666-666666666666",
  "scheduled_video_visit",
  "GA"
);

Returns Promise<APIResponse<SteadyMDAvailableAppointment[]>>.

Example response:

{
  "Data": [
    {
      "DurationMinutes": 30,
      "Availability": []
    }
  ],
  "ErrorMessage": null,
  "IsOK": true
}

Availability may be empty if no slots exist for this consult type or state.

listProgramClinicians(programGuid, options?) — GET

Lists clinicians for a program. Optional filters: usState, complaintsSnomed, programPopulation, limit, offset.

const response = await steadymdClient.listProgramClinicians(
  "66666666-6666-4666-8666-666666666666",
  { usState: "GA", limit: 10 }
);

Returns Promise<APIResponse<SteadyMDClinicianListResponse>>.

Example response:

{
  "Data": {
    "Count": 3,
    "Next": null,
    "Previous": null,
    "Results": [
      {
        "Guid": "88888888-8888-4888-8888-888888888888",
        "FirstName": "Jane",
        "LastName": "Clinician",
        "ClinicianSuffixes": "MD, NP",
        "Email": "[email protected]",
        "Headshot": "https://example.com/headshots/jane.jpg",
        "ClinicianType": "Medical Doctor",
        "ClinicianTypes": [
          { "ClinicianType": "Medical Doctor", "Suffix": "MD" }
        ],
        "ClinicianSpecialties": ["Internal Medicine"],
        "NpiNumber": "1234567890"
      }
    ]
  },
  "ErrorMessage": null,
  "IsOK": true
}

getProgramConsultOptions(programGuid, usState, consultType?) — GET

Returns consult options for a program and state. Use the returned ConsultType and UsState to drive createConsult.

Don't pick an option blindly (e.g. Data[0]). Several ConsultType/InteractionMode combinations can be returned, but only the option with IsScheduled: true AND InteractionMode: "video_chat" (typically scheduled_video_visit) supports the full scheduled + video flow. Picking a different option (e.g. phone_callback / IsScheduled: false) cascades into 400s from getConsultAvailability, scheduleConsultAppointment, and getConsultPatientUI — each of which has its own hard requirement on these flags (see their entries below and in Known Vendor Limitations). Select deliberately:

const option = response.Data?.find(
  (o) => o.IsScheduled === true && o.InteractionMode === "video_chat"
) ?? response.Data?.[0];
const response = await steadymdClient.getProgramConsultOptions(
  "66666666-6666-4666-8666-666666666666",
  "GA",
  "video_visit"
);

Returns Promise<APIResponse<SteadyMDConsultOption[]>>.

Example response:

{
  "Data": [
    {
      "ProgramGuid": "66666666-6666-4666-8666-666666666666",
      "ConsultType": "video_visit",
      "InteractionMode": "video_chat",
      "UsState": "GA",
      "ExternalLinks": [],
      "Metadata": {},
      "PreferredClinician": null,
      "IsScheduled": false
    }
  ],
  "ErrorMessage": null,
  "IsOK": true
}

getProgramLabs(programGuid, options?) — GET

Returns lab tests available for a program. Optional filters: laboratory, labOrderCode, labOrderName, complaintsSnomed, programPopulation.

const response = await steadymdClient.getProgramLabs("66666666-6666-4666-8666-666666666666");

Returns Promise<APIResponse<SteadyMDProgramLabTest[]>>.


Clinician Methods

getClinician(clinicianGuid) — GET

Returns one clinician by GUID.

const response = await steadymdClient.getClinician("88888888-8888-4888-8888-888888888888");

Returns Promise<APIResponse<SteadyMDClinician>>.

Example response:

{
  "Data": {
    "Guid": "88888888-8888-4888-8888-888888888888",
    "FirstName": "Jane",
    "LastName": "Clinician",
    "ClinicianSuffixes": "MD",
    "Email": "[email protected]",
    "Headshot": "https://example.com/headshots/jane.jpg",
    "ClinicianType": "Medical Doctor",
    "ClinicianTypes": [{ "ClinicianType": "Medical Doctor", "Suffix": "MD" }],
    "ClinicianSpecialties": ["Internal Medicine"],
    "Supervisor": null,
    "Bio": null,
    "LanguagesSpoken": [{ "Language": "English", "Hl7Code": "en" }],
    "Qualifications": [],
    "NpiNumber": "1234567890",
    "NpiData": {}
  },
  "ErrorMessage": null,
  "IsOK": true
}

Pharmacy Methods

listPharmacies(options?) — GET

Searches pharmacies. Optional filters: name, address, city, state, zipCode, phoneOrFax, specialty1, specialty2.

const response = await steadymdClient.listPharmacies({ city: "Atlanta", state: "GA" });

Returns Promise<APIResponse<SteadyMDPharmacyResponse[]>>.

Example response:

{
  "Data": [
    {
      "StoreName": "CVS/pharmacy #4178",
      "Address1": "895 Ralph Abernathy Blvd SW",
      "Address2": null,
      "City": "Atlanta",
      "State": "GA",
      "ZipCode": "30310",
      "PrimaryPhone": "4047551511",
      "PrimaryFax": "4047555065",
      "PharmacySpecialties": ["Retail", "EPCS"]
    }
  ],
  "ErrorMessage": null,
  "IsOK": true
}

Episode Methods

createEpisode(data) — POST

Creates an episode. All fields are optional.

const response = await steadymdClient.createEpisode({
  Pharmacy: {
    NcpdpId: "1234567",
    NpiNumber: "1234567890",
    StoreName: "CVS Pharmacy",
    Address1: "123 Main St",
    City: "Miami",
    State: "FL",
    ZipCode: "33101",
    PhoneNumber: "+15551234567",
    PrimaryFax: "+15557654321",
    IsDefault: true,
    IsPreferred: true
  },
  Metadata: { source: "mobile-app" }
});

Returns Promise<APIResponse<SteadyMDEpisode>>.

Example response:

{
  "Data": {
    "EpisodeGuid": "22222222-2222-4222-8222-222222222222",
    "EpisodeUri": "steadymd://episode/22222222-2222-4222-8222-222222222222",
    "PatientGuid": "11111111-1111-4111-8111-111111111111",
    "ParentsOrGuardians": [],
    "Diagnosis": [],
    "Prescriptions": [],
    "RequestedPharmacy": {
      "NcpdpId": "1234567",
      "NpiNumber": "1234567890",
      "StoreName": "CVS Pharmacy",
      "Address1": "123 Main St",
      "City": "Miami",
      "State": "FL",
      "ZipCode": "33101",
      "IsDefault": true,
      "IsPreferred": true
    },
    "PrescribedPharmacies": [],
    "Notes": "",
    "TreatmentPlan": "",
    "LabOrders": [],
    "Observations": [],
    "Metadata": {},
    "TransitionCareLocation": ""
  },
  "ErrorMessage": null,
  "IsOK": true
}

getEpisode(episodeGuid) — GET

Returns one episode by GUID.

const response = await steadymdClient.getEpisode("22222222-2222-4222-8222-222222222222");

Returns Promise<APIResponse<SteadyMDEpisode>>.

updateEpisode(episodeGuid, data) — PUT

Fully updates an episode. Replaces existing fields with whatever is sent.

const response = await steadymdClient.updateEpisode(
  "22222222-2222-4222-8222-222222222222",
  { ParentsOrGuardians: [] }
);

Returns Promise<APIResponse<SteadyMDEpisode>>.

patchEpisode(episodeGuid, data) — PATCH

Partially updates an episode. Only the fields provided are changed.

const response = await steadymdClient.patchEpisode(
  "22222222-2222-4222-8222-222222222222",
  { Metadata: { priority: "standard" } }
);

Returns Promise<APIResponse<SteadyMDEpisode>>.

getEpisodeSignedDocuments(episodeGuid, options?) — GET

Returns signed documents for an episode. Optional filters: encounterGuid, labOrderGuid, intakesGuid.

const response = await steadymdClient.getEpisodeSignedDocuments(
  "22222222-2222-4222-8222-222222222222"
);

Returns Promise<APIResponse<SteadyMDSignedDocument[]>>.

Example response:

{
  "Data": [],
  "ErrorMessage": null,
  "IsOK": true
}

createEpisodeLabRequisition(episodeGuid, data) — POST

Creates a lab requisition for an episode.

VENDOR ASYNC — The vendor creates the requisition asynchronously. The response always returns {"Guid": null}. Do not read the GUID from this response. The documented path is to poll getIntake until Data.LabRequisitionGuid is populated, then use that GUID in updateIntake — but that link does not reliably populate (see Known Vendor Limitations for a createLabOrder-based workaround that reliably unblocks updateIntake).

VENDOR SHAPE QUIRK — The SDK type SteadyMDLabRequisitionCreateRequest declares Tests: SteadyMDLabRequisitionShortRequest[] (an array of { LabOrderCode, LabOrderName }, PascalCase) — matching every other non-Consult endpoint's convention, and what the Postman collection shows too. The live dev backend rejects that shape with a 400: {"labOrderCode":["This field is required."],"labOrderName":["This field is required."]}. The error reports those as top-level required fields (not nested under Tests), proving the endpoint actually validates a flat, single-test, lowercase-keyed body. Use the shape below — confirmed working against the live dev backend — and cast around the SDK type, since the type itself doesn't (yet) reflect what the vendor accepts. This is a vendor schema/doc mismatch specific to this one endpoint; see Known Vendor Limitations for more detail.

// NOTE: this is the WORKING shape (confirmed live) — not what
// SteadyMDLabRequisitionCreateRequest currently declares. Cast around the
// type until the SDK type is corrected to match the vendor's real schema.
const payload = {
  Laboratory: "LABCORP",
  labOrderCode: "001453",
  labOrderName: "Hemoglobin A1c",
  Icd10Codes: ["E11.9"]
} as unknown as SteadyMDLabRequisitionCreateRequest;

const response = await steadymdClient.createEpisodeLabRequisition(
  "22222222-2222-4222-8222-222222222222",
  payload
);
// response.Data.Guid === null  ← expected (vendor links the requisition asynchronously), not an error

Returns Promise<APIResponse<SteadyMDLabRequisition>>.

getEpisodeLabRequisition(episodeGuid, labRequisitionGuid) — GET

Returns one lab requisition by GUID. Use the GUID obtained by polling getIntake, not from createEpisodeLabRequisition.

const response = await steadymdClient.getEpisodeLabRequisition(
  "22222222-2222-4222-8222-222222222222",
  "33333333-3333-4333-8333-333333333333"
);

Returns Promise<APIResponse<SteadyMDLabRequisition>>.


Consult Methods

Casing: All consult request body fields are camelCase (programGuid, consultType, usState, text, startsAt, status, level). The SteadyMD API is case-sensitive and will reject PascalCase field names on these endpoints.

listConsults(options?) — GET

Lists consults. Optional filters: episodeGuid, programGuid, visitStatus, limit, offset.

VENDOR FILTER BUG — episodeGuid is accepted but not applied. listConsults({ episodeGuid }) can return results belonging to entirely different episodes (their EmrUri values won't match the requested episodeGuid). The parameter is echoed correctly in the response's Next pagination URL — the vendor accepts it but does not filter by it server-side. Do not assume listConsults({ episodeGuid }) returns only that episode's consults; if you need episode-scoped results, filter Results client-side by EmrUri/EpisodeGuid. See sendConsultMessage's RESOLVED_STATUS block for related context.

const response = await steadymdClient.listConsults({
  episodeGuid: "22222222-2222-4222-8222-222222222222",
  limit: 10
});

Returns Promise<APIResponse<SteadyMDConsultListResponse>>.

Example response:

{
  "Data": {
    "Count": 1,
    "Next": null,
    "Previous": null,
    "Results": [
      {
        "Guid": "55555555-5555-4555-8555-555555555555",
        "EmrUri": "steadymd://episode/22222222-2222-4222-8222-222222222222",
        "UsState": "GA",
        "Clinician": null,
        "ClinicianGuid": null,
        "ConsultType": "phone_callback",
        "ConsultDefinitionGuid": "77777777-7777-4777-8777-777777777777",
        "EpisodeGuid": "22222222-2222-4222-8222-222222222222",
        "ProgramName": "Safe Health Sandbox",
        "ProgramGuid": "66666666-6666-4666-8666-666666666666",
        "VisitStatus": "received",
        "CreatedAt": "2026-06-01T23:55:51.022672Z",
        "AppointmentTimezone": null,
        "AppointmentStartsAt": null,
        "AppointmentEndsAt": null,
        "AppointmentStartsAtLocalTime": null,
        "AppointmentEndsAtLocalTime": null,
        "AppointmentScheduledStartsAt": null,
        "AppointmentScheduledEndsAt": null,
        "IsExternal": false,
        "IsPediatric": false,
        "IsHighPriority": false,
        "IsScheduled": false,
        "ExternalLinks": [],
        "Reason": null,
        "ReasonSlug": null,
        "PreferredClinician": null,
        "PreferredClinicianGuid": null,
        "ReasonForVisit": null,
        "VisitStatusSetAt": "2026-06-01T23:56:24.539213Z",
        "ProgramDisplayName": null,
        "Metadata": {}
      }
    ]
  },
  "ErrorMessage": null,
  "IsOK": true
}

createConsult(data) — POST

Creates a consult. programGuid, consultType, and usState are required (camelCase). Use values from getProgramConsultOptions.

SteadyMD allows only one active consult per episode and returns an error if one already exists.

const response = await steadymdClient.createConsult({
  programGuid: "66666666-6666-4666-8666-666666666666",
  consultType: "phone_callback",
  emrUri: "steadymd://episode/22222222-2222-4222-8222-222222222222",
  usState: "GA"
});

Returns Promise<APIResponse<SteadyMDConsult>>.

getConsult(consultGuid) — GET

Returns one consult by GUID.

const response = await steadymdClient.getConsult("55555555-5555-4555-8555-555555555555");

Returns Promise<APIResponse<SteadyMDConsult>>.

getConsultAvailability(consultGuid) — GET

Returns available appointment slots for a consult.

Only valid for schedulable consult types (IsScheduled: true AND InteractionMode: "video_chat" from getProgramConsultOptions — see that method's note). Returns 400 — {"status": "Cannot get availability for consult that's not in waiting_to_schedule or scheduled status"} — both for non-schedulable consult types and for consults whose VisitStatus has moved out of waiting_to_schedule/scheduled (e.g. canceled/resolved). Check both getProgramConsultOptions and the consult's current VisitStatus (via getConsult) before calling this. If Data is empty or all Availability arrays are empty, show the user "No times available" — do not call scheduleConsultAppointment. This can happen even for a freshly created scheduled_video_visit consult when no open slots are configured for the program/clinician. See Known Vendor Limitations.

const response = await steadymdClient.getConsultAvailability("55555555-5555-4555-8555-555555555555");

Returns Promise<APIResponse<SteadyMDAvailableAppointment[]>>.

Example response:

{
  "Data": [
    {
      "DurationMinutes": 30,
      "Availability": [
        "2026-06-10T14:00:00Z",
        "2026-06-10T14:30:00Z"
      ]
    }
  ],
  "ErrorMessage": null,
  "IsOK": true
}

scheduleConsultAppointment(consultGuid, data) — POST

Schedules an appointment for a consult. clinicianGuid and startsAt are required (camelCase).

startsAt must be an exact slot from getConsultAvailability. The vendor rejects arbitrary timestamps. See Known Vendor Limitations.

const { Data: slots } = await steadymdClient.getConsultAvailability(consultGuid);
if (!slots?.[0]?.Availability?.length) {
  // Show "No times available"
  return;
}
const response = await steadymdClient.scheduleConsultAppointment(
  "55555555-5555-4555-8555-555555555555",
  {
    clinicianGuid: "88888888-8888-4888-8888-888888888888",
    startsAt: slots[0].Availability[0]
  }
);

Returns Promise<APIResponse<SteadyMDConsult>>.

getConsultPatientUI(consultGuid, data) — POST

Creates or retrieves a patient-facing UI link for a consult. Used to embed the patient video/messaging interface.

Only valid for video_chat consults. Returns 400 — {"status": "Cannot return video chat url for consult that's not a video chat consult"} — for any consult whose InteractionMode is not "video_chat". Select a getProgramConsultOptions option with InteractionMode: "video_chat" (typically scheduled_video_visit) before calling this; for other interaction modes, skip the call instead of treating the 400 as an error. See Known Vendor Limitations.

const response = await steadymdClient.getConsultPatientUI(
  "55555555-5555-4555-8555-555555555555",
  {
    element: "video_visit",
    callbackUrl: "https://example.com/consult-complete"
  }
);

Returns Promise<APIResponse<SteadyMDConsultPatientUI>>.

Example response:

{
  "Data": {
    "Url": "https://patient.steadymd.com/r/SomeDeepLink"
  },
  "ErrorMessage": null,
  "IsOK": true
}

sendConsultMessage(consultGuid, data) — POST

Sends a message within a consult thread. text is required (camelCase).

Blocked patient-wide by historical consult data. Returns 400 ("You cannot create a message consult if the previous consult is not in a RESOLVED_STATUS") whenever any prior consult for the patient — not just the current episode — is not RESOLVED_STATUS. If the patient has historical consults stuck in rejected, this blocks every new consult. This is a vendor data/scoping issue, not something the client can fix — catch the 400, log the explanation, and continue. See Known Vendor Limitations for the full explanation (including the related listConsults episodeGuid filter bug).

const response = await steadymdClient.sendConsultMessage(
  "55555555-5555-4555-8555-555555555555",
  { text: "Hello, doctor.", recipient: "clinician" }
);

Returns Promise<APIResponse<SteadyMDMessage>>.

updateConsultStatus(consultGuid, data) — POST

Updates a consult's status. status is required (camelCase). Common values: "canceled".

Intermittent vendor issue for full-integration consults: SteadyMD can return an HTML 500 (surfaced as 502 by our filter) on status changes, or succeed — behavior is inconsistent. Treat it as flaky rather than a hard block: when it throws, fall back to getConsult and display the current status. See Known Vendor Limitations.

try {
  const response = await steadymdClient.updateConsultStatus(
    "55555555-5555-4555-8555-555555555555",
    { status: "canceled" }
  );
} catch {
  const { Data } = await steadymdClient.getConsult("55555555-5555-4555-8555-555555555555");
  console.log("Latest consult status:", Data?.VisitStatus);
}

Returns Promise<APIResponse<SteadyMDConsult>>.

createConsultIssue(consultGuid, data) — POST

Creates an issue for a consult. level is required (camelCase).

const response = await steadymdClient.createConsultIssue(
  "55555555-5555-4555-8555-555555555555",
  {
    level: "warning",
    description: "Patient reports new symptom",
    links: []
  }
);

Returns Promise<APIResponse<SteadyMDConsultIssue>>.

Example response:

{
  "Data": {
    "Guid": "99999999-9999-4999-8999-999999999999",
    "ConsultGuid": "55555555-5555-4555-8555-555555555555",
    "ChartAddendumGuid": null,
    "Level": "warning",
    "Status": "unresolved",
    "Description": "Patient reports new symptom",
    "Links": [],
    "Exception": null
  },
  "ErrorMessage": null,
  "IsOK": true
}

Intake Methods

listIntakes(options?) — GET

Lists intakes. Optional filters: episodeGuid, limit, offset.

const response = await steadymdClient.listIntakes({
  episodeGuid: "22222222-2222-4222-8222-222222222222"
});

Returns Promise<APIResponse<SteadyMDIntakeListResponse>>.

Example response:

{
  "Data": {
    "Count": 1,
    "Next": null,
    "Previous": null,
    "Results": [
      {
        "EpisodeGuid": "22222222-2222-4222-8222-222222222222",
        "IntakeGuid": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
        "Observations": [
          { "Name": "height", "Value": 70, "Unit": "in" },
          { "Name": "weight", "Value": 175, "Unit": "lb" }
        ],
        "Questionnaire": [],
        "Files": [],
        "LabRequisitionGuid": null,
        "IsDraft": true
      }
    ]
  },
  "ErrorMessage": null,
  "IsOK": true
}

createIntake(data) — POST

Creates an intake (typically as a draft). EpisodeGuid is required (PascalCase).

const response = await steadymdClient.createIntake({
  EpisodeGuid: "22222222-2222-4222-8222-222222222222",
  IsDraft: true,
  Observations: [
    { Name: "height", Value: 70, Unit: "in" },
    { Name: "weight", Value: 175, Unit: "lb" }
  ],
  Questionnaire: [
    {
      Question: "Do you have any allergies?",
      Answer: ["None"],
      Type: "text",
      AnswerType: "valueString",
      AnswerOption: []
    }
  ]
});

Returns Promise<APIResponse<SteadyMDIntake>>.

getIntake(intakeGuid) — GET

Returns one intake by GUID. The documented flow polls this after createEpisodeLabRequisition until Data.LabRequisitionGuid is non-null, then uses that GUID in updateIntake.

This link does not reliably populateLabRequisitionGuid can remain null indefinitely. Don't block your flow on it; instead capture labRequisitionGuid from createLabOrder's response (synchronous, reliable) and pass that to updateIntake. See Known Vendor Limitations for the full explanation and the working alternative flow. This is also why the Lab Order section should run before Intake — see Order of Execution.

const { Data } = await steadymdClient.getIntake("aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa");
console.log("Vendor-linked LabRequisitionGuid:", Data?.LabRequisitionGuid ?? "(not yet linked)");
// Don't poll waiting for this — use the guid captured from createLabOrder instead (see below).

Returns Promise<APIResponse<SteadyMDIntake>>.

updateIntake(intakeGuid, data) — PUT

Finalizes an intake. EpisodeGuid is required. LabRequisitionGuid is required when finalizing (IsDraft: false) — the backend returns 400 ("labRequisitionGuid is required") without it.

Don't poll getIntake for this guid — the vendor's async link to the intake does not reliably populate. Instead, capture labRequisitionGuid directly from createLabOrder's response (Data.LabRequisitions[0].Guid, populated synchronously) and pass that here. This requires running the Lab Order section before Intake — see Order of Execution and Known Vendor Limitations for the full explanation.

const response = await steadymdClient.updateIntake(
  "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
  {
    EpisodeGuid: "22222222-2222-4222-8222-222222222222",
    LabRequisitionGuid: labRequisitionGuid, // captured from createLabOrder, not from polling getIntake
    IsDraft: false,
    Observations: [],
    Questionnaire: []
  }
);

Returns Promise<APIResponse<SteadyMDIntake>>.

addAllergyIntolerance(intakeGuid, data) — POST

Adds allergy intolerance entries to an intake. At least one entry is required. Pass the array directly — do not wrap in { Data: ... }.

const response = await steadymdClient.addAllergyIntolerance(
  "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
  [
    {
      Code: {
        Coding: [
          {
            System: "https://med.steadymd.com/not-specified",
            Code: "not_specified",
            Display: "Not specified"
          }
        ]
      }
    }
  ]
);

Returns Promise<APIResponse<{ status: string }>>.

Example response:

{
  "Data": { "status": "okay" },
  "ErrorMessage": null,
  "IsOK": true
}

Identity Methods

claimIdentityVerification(data) — POST

Claims an identity verification event on an episode. EpisodeGuid and ClaimType are required (PascalCase). Use ClaimType: "external" for standard external verification.

const response = await steadymdClient.claimIdentityVerification({
  EpisodeGuid: "22222222-2222-4222-8222-222222222222",
  ClaimType: "external",
  ClaimedByName: "John Smith"
});

Returns Promise<APIResponse<SteadyMDIdentityClaim>>.

Example response:

{
  "Data": {
    "ClaimType": "external",
    "ClaimedByName": "John Smith",
    "EpisodeGuid": "22222222-2222-4222-8222-222222222222",
    "PatientConsentGuid": null,
    "Metadata": null
  },
  "ErrorMessage": null,
  "IsOK": true
}

Lab Order Methods

listLabOrders(options?) — GET

Lists lab orders. Optional filters: episodeGuid, limit, offset.

const response = await steadymdClient.listLabOrders({
  episodeGuid: "22222222-2222-4222-8222-222222222222"
});

Returns Promise<APIResponse<SteadyMDLabOrderListResponse>>.

Example response:

{
  "Data": {
    "Count": 1,
    "Next": null,
    "Previous": null,
    "Results": [
      {
        "EmrUri": "steadymd://episode/22222222-2222-4222-8222-222222222222/lab/order/bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb",
        "Guid": "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb",
        "EpisodeGuid": "22222222-2222-4222-8222-222222222222",
        "PatientGuid": "11111111-1111-4111-8111-111111111111",
        "LabOrderId": null,
        "Laboratory": "LABCORP",
        "PaymentResponsibility": "patient",
        "SubmissionMethod": "SELF_SUBMISSION",
        "LabAccountId": null,
        "LabReferenceId": null,
        "LabRequisitions": [
          {
            "Guid": "33333333-3333-4333-8333-333333333333",
            "LabOrderCode": "001453",
            "LabOrderName": "Hemoglobin A1c",
            "LabResultCodes": []
          }
        ],
        "OrderingClinician": null,
        "DiagnosticResultId": null,
        "LabResults": []
      }
    ]
  },
  "ErrorMessage": null,
  "IsOK": true
}

createLabOrder(data) — POST

Creates a lab order. EpisodeGuid and Laboratory are required (PascalCase). Unlike createEpisodeLabRequisition, this endpoint correctly accepts the documented PascalCase LabRequisitions: [{ LabOrderCode, LabOrderName }] array shape.

Useful side effect — a synchronous labRequisitionGuid source. The response embeds the newly created requisition at Data.LabRequisitions[0].Guid, populated immediately (unlike createEpisodeLabRequisition, which always returns {Guid: null} and links asynchronously — a link that does not reliably populate). Capture it here and pass it straight to updateIntake, skipping the unreliable getIntake polling entirely:

const labRequisitionGuid = response.Data?.LabRequisitions?.[0]?.Guid;

This is why Lab Order should run before Intake — see Order of Execution and Known Vendor Limitations.

const response = await steadymdClient.createLabOrder({
  EpisodeGuid: "22222222-2222-4222-8222-222222222222",
  Laboratory: "LABCORP",
  PaymentResponsibility: "patient",
  SubmissionMethod: "SELF_SUBMISSION",
  LabRequisitions: [
    { LabOrderCode: "001453", LabOrderName: "Hemoglobin A1c" }
  ]
});
const labRequisitionGuid = response.Data?.LabRequisitions?.[0]?.Guid;

Returns Promise<APIResponse<SteadyMDLabOrder>>.

Example response:

{
  "Data": {
    "EmrUri": "steadymd://episode/22222222-2222-4222-8222-222222222222/lab/order/bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb",
    "Guid": "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb",
    "EpisodeGuid": "22222222-2222-4222-8222-222222222222",
    "PatientGuid": "11111111-1111-4111-8111-111111111111",
    "LabOrderId": null,
    "Laboratory": "LABCORP",
    "PaymentResponsibility": "patient",
    "SubmissionMethod": "SELF_SUBMISSION",
    "LabAccountId": null,
    "LabReferenceId": null,
    "LabRequisitions": [
      {
        "Guid": "33333333-3333-4333-8333-333333333333",
        "LabOrderCode": "001453",
        "LabOrderName": "Hemoglobin A1c",
        "LabResultCodes": []
      }
    ],
    "OrderingClinician": null,
    "DiagnosticResultId": null,
    "LabResults": []
  },
  "ErrorMessage": null,
  "IsOK": true
}

getLabOrder(labOrderGuid) — GET

Returns one lab order by GUID.

const response = await steadymdClient.getLabOrder("bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb");

Returns Promise<APIResponse<SteadyMDLabOrder>>.

getLabOrderPdf(labOrderGuid) — GET

Returns the lab order PDF attachment.

Returns 404 until lab results are received and processed by the vendor. Catch a 404 and display "Pending lab results" instead of surfacing it as an error.

try {
  const response = await steadymdClient.getLabOrderPdf("bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb");
  // response.Data contains { Guid, Kind, File, Checksum, CreatedAt }
} catch (err: any) {
  if (err?.statusCode === 404) {
    // Show "Pending lab results"
  }
}

Returns Promise<APIResponse<SteadyMDFileAttachment>>.

listLabOrderResults(labOrderGuid, options?) — GET

Lists results for a lab order. Optional filters: diagnosticResultId, includeReferenceRanges, labOrderId, requestResultId, limit, offset.

const response = await steadymdClient.listLabOrderResults(
  "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb",
  { includeReferenceRanges: true }
);

Returns Promise<APIResponse<SteadyMDLabResultListResponse>>.

Example response (no results yet):

{
  "Data": {
    "Count": 0,
    "Next": null,
    "Previous": null,
    "Results": []
  },
  "ErrorMessage": null,
  "IsOK": true
}

createLabOrderResult(labOrderGuid, data) — POST

Creates a lab result for a lab order. The combination of ExternalId + Version must be unique per lab order — the vendor returns 400 on duplicates. Use a unique value on each call (e.g. a timestamp or UUID).

const response = await steadymdClient.createLabOrderResult(
  "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb",
  {
    ExternalId: `RESULT_${Date.now()}`,
    Version: "1",
    Status: "final",
    Observations: []
  }
);

Returns Promise<APIResponse<SteadyMDLabResult>>.

getLabOrderResult(labOrderGuid, labResultGuid) — GET

Returns one lab result by GUID.

const response = await steadymdClient.getLabOrderResult(
  "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb",
  "cccccccc-cccc-4ccc-8ccc-cccccccccccc"
);

Returns Promise<APIResponse<SteadyMDLabResult>>.

getLabResultPdf(labOrderGuid, labResultGuid) — GET (raw binary)

Returns the raw PDF bytes for a lab result. Unlike other methods, this returns a raw ArrayBuffer, not an APIResponse.

const buffer = await steadymdClient.getLabResultPdf(
  "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb",
  "cccccccc-cccc-4ccc-8ccc-cccccccccccc"
);
console.log(`Received ${buffer.byteLength} bytes`);

Returns Promise<ArrayBuffer>.


Message Methods

These are episode-level messages (standalone, not tied to a consult thread). For consult-level messages, use sendConsultMessage.

listMessages(options?) — GET

Lists episode messages. Optional filters: episodeGuid, limit, offset.

const response = await steadymdClient.listMessages({
  episodeGuid: "22222222-2222-4222-8222-222222222222"
});

Returns Promise<APIResponse<SteadyMDMessageListResponse>>.

Example response:

{
  "Data": {
    "Count": 1,
    "Next": null,
    "Previous": null,
    "Results": [
      {
        "EpisodeGuid": "22222222-2222-4222-8222-222222222222",
        "IsPleasantry": false,
        "Guid": "77777777-7777-4777-8777-777777777777",
        "Recipient": "to_clinician",
        "Text": "Following up on my visit",
        "ClinicianGuid": null,
        "SentAt": "2026-06-01T23:56:26.281412Z",
        "CreatedBy": 654
      }
    ]
  },
  "ErrorMessage": null,
  "IsOK": true
}

createMessage(data) — POST

Creates an episode-level message. EpisodeGuid and Text are required (PascalCase).

const response = await steadymdClient.createMessage({
  EpisodeGuid: "22222222-2222-4222-8222-222222222222",
  Text: "Following up on my visit",
  IsSentDuringVideoChat: false
});

Returns Promise<APIResponse<SteadyMDMessage>>.

Example response:

{
  "Data": {
    "EpisodeGuid": "22222222-2222-4222-8222-222222222222",
    "IsPleasantry": false,
    "Guid": "77777777-7777-4777-8777-777777777777",
    "Recipient": "to_clinician",
    "Text": "Following up on my visit",
    "ClinicianGuid": null,
    "SentAt": "2026-06-01T23:56:26.281412Z",
    "CreatedBy": 654
  },
  "ErrorMessage": null,
  "IsOK": true
}

getMessage(messageGuid) — GET

Returns one episode message by GUID.

const response = await steadymdClient.getMessage("77777777-7777-4777-8777-777777777777");

Returns Promise<APIResponse<SteadyMDMessage>>.


Method Summary

| Area | Method | HTTP | Required Params | |------|--------|------|-----------------| | Patient | getMyPatient() | GET | — | | Program | listPrograms(options?) | GET | — | | Program | getProgram(programGuid) | GET | programGuid | | Program | getProgramAppointments(programGuid, consultType, usState, clinicianGuid?) | GET | programGuid, consultType, usState | | Program | listProgramClinicians(programGuid, options?) | GET | programGuid | | Program | getProgramConsultOptions(programGuid, usState, consultType?) | GET | programGuid, usState | | Program | getProgramLabs(programGuid, options?) | GET | programGuid | | Clinician | getClinician(clinicianGuid) | GET | clinicianGuid | | Pharmacy | listPharmacies(options?) | GET | — | | Episode | createEpisode(data) | POST | — | | Episode | getEpisode(episodeGuid) | GET | episodeGuid | | Episode | updateEpisode(episodeGuid, data) | PUT | episodeGuid | | Episode | patchEpisode(episodeGuid, data) | PATCH | episodeGuid | | Episode | getEpisodeSignedDocuments(episodeGuid, options?) | GET | episodeGuid | | Episode | createEpisodeLabRequisition(episodeGuid, data) ⚠️ async | POST | episodeGuid | | Episode | getEpisodeLabRequisition(episodeGuid, labRequisitionGuid) | GET | episodeGuid, labRequisitionGuid | | Consult | listConsults(options?) | GET | — | | Consult | createConsult(data) | POST | programGuid, consultType, usState | | Consult | getConsult(consultGuid) | GET | consultGuid | | Consult | getConsultAvailability(consultGuid) ⚠️ schedulable only | GET | consultGuid | | Consult | scheduleConsultAppointment(consultGuid, data) ⚠️ exact slot | POST | consultGuid, clinicianGuid, startsAt | | Consult | getConsultPatientUI(consultGuid, data) | POST | consultGuid | | Consult | sendConsultMessage(consultGuid, data) ⚠️ blocked | POST | consultGuid, text | | Consult | updateConsultStatus(consultGuid, data) ⚠️ 502 | POST | consultGuid, status | | Consult | createConsultIssue(consultGuid, data) | POST | consultGuid, level | | Intake | listIntakes(options?) | GET | — | | Intake | createIntake(data) | POST | EpisodeGuid | | Intake | getIntake(intakeGuid) | GET | intakeGuid | | Intake | updateIntake(intakeGuid, data) | PUT | intakeGuid, LabRequisitionGuid | | Intake | addAllergyIntolerance(intakeGuid, data[]) | POST | intakeGuid | | Identity | claimIdentityVerification(data) | POST | EpisodeGuid, ClaimType | | Lab Order | listLabOrders(options?) | GET | — | | Lab Order | createLabOrder(data) | POST | EpisodeGuid, Laboratory | | Lab Order | getLabOrder(labOrderGuid) | GET | labOrderGuid | | Lab Order | getLabOrderPdf(labOrderGuid) ⚠️ 404 until ready | GET | labOrderGuid | | Lab Order | listLabOrderResults(labOrderGuid, options?) | GET | labOrderGuid | | Lab Order | createLabOrderResult(labOrderGuid, data) | POST | labOrderGuid | | Lab Order | getLabOrderResult(labOrderGuid, labResultGuid) | GET | labOrderGuid, labResultGuid | | Lab Order | getLabResultPdf(labOrderGuid, labResultGuid) | GET raw binary | labOrderGuid, labResultGuid | | Message | listMessages(options?) | GET | — | | Message | createMessage(data) | POST | EpisodeGuid, Text | | Message | getMessage(messageGuid) | GET | messageGuid |

⚠️ = see Known Vendor Limitations before using.