@healthcloudai/hc-healthrecord-connector
v1.2.2
Published
Healthcheck Healthrecord SDK with TypeScript
Maintainers
Readme
Healthcheck Health Record Connector
Patient-facing SDK connector for Healthcheck EHR Services.
HCHealthRecordClient uses a configured and authenticated HCLoginClient to resolve:
- the current patient session
- the tenant header
- the authorization header
- the target EHR Services environment
The connector supports patient profile data, allergies, conditions, family history, procedures, medications, immunizations, lab results, care plans, vitals, and social history.
Installation
npm install @healthcloudai/hc-healthrecord-connector \
@healthcloudai/hc-login-connector \
@healthcloudai/hc-httpImport
import { HCHealthRecordClient } from "@healthcloudai/hc-healthrecord-connector";
import { HCLoginClient } from "@healthcloudai/hc-login-connector";
import { FetchClient, HCServiceError } from "@healthcloudai/hc-http";
import type {
APIResponse,
Patient,
PatientData,
Allergy,
Problem,
FamilyHistory,
Procedure,
Medication,
Immunization,
LabResult,
CarePlan,
VitalSign,
ImportVitalsRequest,
VitalReadingUpdate,
SocialHistory,
SocialHistoryAnswer,
SocialHistoryClearBody
} from "@healthcloudai/hc-healthrecord-connector";Setup
Create the shared HTTP client, configure and authenticate the login client, then pass both instances to HCHealthRecordClient.
const httpClient = new FetchClient();
const loginClient = new HCLoginClient(httpClient);
loginClient.configure("healthcheck", "dev");
await loginClient.login("<PATIENT_EMAIL>", "<PASSWORD>");
const healthRecords = new HCHealthRecordClient(httpClient, loginClient);HCHealthRecordClient does not have its own configure(...) method. It reads the environment from loginClient.getEnvironment() and maps it to the EHR Services host internally.
Supported environment mapping in the current client code:
| Login environment | EHR Services host |
| --- | --- |
| dev | https://dev-api-ehrservices.health.cloud |
| uat | https://uat-api-ehrservices.health.cloud |
| prod | https://api-ehrservices.health.cloud |
If the login client returns an unsupported environment, the constructor throws ConfigError.
Optional API Key Header
If the deployment requires an API key header, set it once on the Health Record client.
healthRecords.setApiKey("x-api-key", "<API_KEY>");The header name and value are trimmed. Empty header names or values throw ConfigError.
When set, the API key header is added to authenticated Health Record requests together with the login client's auth headers.
Authentication
All patient record methods use loginClient.getAuthHeader() internally. The login client must already have a valid ID token.
Authenticated requests include:
{
Authorization: "Bearer <ID_TOKEN>",
"X-Tenant-ID": "<TENANT_ID>"
}The SDK consumer does not pass auth headers into individual methods.
getHealth() is a raw service health call and does not use the authenticated request helper.
Request Envelopes
POST and PUT methods accept resource data directly at the SDK surface. The connector wraps most values in the backend envelope internally.
SDK call:
await healthRecords.createAllergy({
Allergen: "Peanuts",
Reaction: "Anaphylaxis",
Severity: "severe",
IsActive: true
});Request body sent by the connector:
{
"Data": {
"Allergen": "Peanuts",
"Reaction": "Anaphylaxis",
"Severity": "severe",
"IsActive": true
}
}GET and DELETE methods do not send request bodies.
Response Envelopes
Most methods return:
Promise<APIResponse<T>>export interface APIResponse<T> {
Data: T | null;
IsOK: boolean;
ErrorMessage: string | null;
}The connector validates that successful responses match this shape.
getHealth() is the exception. It returns:
Promise<HealthResponse>Error Handling
The client uses the shared @healthcloudai/hc-http error model and its own execute(...) wrapper.
Typical runtime behavior:
| Situation | Error behavior |
| --- | --- |
| Unsupported EHR environment | Throws ConfigError in the constructor. |
| Empty API key header name or value | Throws ConfigError from setApiKey(...). |
| Missing required SDK input before a request | Throws ValidationError with code INVALID_INPUT. |
| Shared HTTP layer throws APIError | Re-thrown unchanged. |
| Request throws a non-APIError runtime error | Wrapped in APIError with code UNKNOWN_ERROR. |
| Response is null or undefined | Throws APIError with code EMPTY_RESPONSE. |
| Response is not an APIResponse envelope | Throws APIError with code INVALID_RESPONSE. |
| Backend returns isOK: false | Throws HCServiceError with code BACKEND_FAILURE; original response is preserved in details. |
Example:
try {
await healthRecords.getAllergy("<ALLERGY_ID>");
} catch (error) {
if (error instanceof HCServiceError) {
console.log(error.backendMessage);
console.log(error.details);
}
}Common pre-request validation failures in the current client:
| Method | Validation |
| --- | --- |
| searchPatients(query) | query must be non-empty. |
| createPatient(patient) | FirstName, LastName, and BirthDate are required. |
| updateCurrentPatient(patient) | patient is required. |
| createAllergy(allergy) | Allergen is required. |
| createCondition(condition) | SnomedCode, ICDCode, or Description is required. |
| createFamilyHistory(familyHistory) | Name is required; do not pass both BornDate and Age. |
| createProcedure(procedure) | ProcedureCode or Description is required. |
| createMedication(medication) | Name is required. |
| createImmunization(immunization) | Vaccine or CvxCode is required. |
| createLabResult(labResult) | At least one analyte is required. |
| createCarePlan(carePlan) | Title is required. |
| getVitalSnapshot(dateRecorded) | dateRecorded is required. |
| recordVitalsSnapshot(vitals) | dateRecorded is required. |
| replaceVitalsSnapshot(vitals) | dateRecorded is required. |
| syncVitals(request) | Records must contain at least one vital record. |
| updateVitalsReading(observationId, reading) | observationId and reading.Value are required. |
| mergeSocialHistoryAnswers(answers) | At least one answer with Key is required. |
| upsertSocialHistoryAnswer(answer) | Key is required. |
| clearSocialHistory(request) | Keys must contain at least one non-empty key. |
ID path parameters are validated and URL-encoded. Empty IDs throw ValidationError.
Methods
Client
| Method | Returns |
| --- | --- |
| setApiKey(headerName, value) | void |
Health
| Method | Returns |
| --- | --- |
| getHealth() | Promise<HealthResponse> |
Patient
| Method | Returns |
| --- | --- |
| getCurrentPatient() | Promise<APIResponse<Patient>> |
| getPatientByVendor(vendorPatientId) | Promise<APIResponse<Patient>> |
| searchPatients(query) | Promise<APIResponse<Patient[]>> |
| createPatient(patient) | Promise<APIResponse<Patient>> |
| updateCurrentPatient(patient) | Promise<APIResponse<boolean>> |
| deactivateEHRPatient() | Promise<APIResponse<boolean>> |
Allergies
| Method | Returns |
| --- | --- |
| listAllergies() | Promise<APIResponse<Allergy[]>> |
| getAllergy(id) | Promise<APIResponse<Allergy>> |
| createAllergy(allergy) | Promise<APIResponse<string>> |
| updateAllergy(id, allergy) | Promise<APIResponse<string>> |
| deleteAllergy(id) | Promise<APIResponse<boolean>> |
Conditions
| Method | Returns |
| --- | --- |
| listConditions(options?) | Promise<APIResponse<Problem[]>> |
| getCondition(id) | Promise<APIResponse<Problem>> |
| createCondition(condition) | Promise<APIResponse<string>> |
| updateCondition(id, condition) | Promise<APIResponse<string>> |
| deleteCondition(id) | Promise<APIResponse<boolean>> |
Family History
| Method | Returns |
| --- | --- |
| listFamilyHistory() | Promise<APIResponse<FamilyHistory[]>> |
| getFamilyHistoryItem(id) | Promise<APIResponse<FamilyHistory>> |
| createFamilyHistory(familyHistory) | Promise<APIResponse<string>> |
| updateFamilyHistory(id, familyHistory) | Promise<APIResponse<string>> |
| deleteFamilyHistory(id) | Promise<APIResponse<boolean>> |
Procedures
| Method | Returns |
| --- | --- |
| listProcedures() | Promise<APIResponse<Procedure[]>> |
| getProcedure(id) | Promise<APIResponse<Procedure>> |
| createProcedure(procedure) | Promise<APIResponse<string>> |
| updateProcedure(id, procedure) | Promise<APIResponse<string>> |
| deleteProcedure(id) | Promise<APIResponse<boolean>> |
Medications
| Method | Returns |
| --- | --- |
| listMedications() | Promise<APIResponse<Medication[]>> |
| getMedication(id) | Promise<APIResponse<Medication>> |
| createMedication(medication) | Promise<APIResponse<string>> |
| updateMedication(id, medication) | Promise<APIResponse<string>> |
| deleteMedication(id) | Promise<APIResponse<boolean>> |
Immunizations
| Method | Returns |
| --- | --- |
| listImmunizations() | Promise<APIResponse<Immunization[]>> |
| getImmunization(id) | Promise<APIResponse<Immunization>> |
| createImmunization(immunization) | Promise<APIResponse<string>> |
| updateImmunization(id, immunization) | Promise<APIResponse<string>> |
| deleteImmunization(id) | Promise<APIResponse<boolean>> |
Lab Results
| Method | Returns |
| --- | --- |
| listLabResults() | Promise<APIResponse<LabResult[]>> |
| getLabResult(id) | Promise<APIResponse<LabResult>> |
| createLabResult(labResult) | Promise<APIResponse<string>> |
| updateLabResult(id, labResult) | Promise<APIResponse<string>> |
| deleteLabResult(id) | Promise<APIResponse<boolean>> |
Care Plans
| Method | Returns |
| --- | --- |
| listCarePlans() | Promise<APIResponse<CarePlan[]>> |
| getCarePlan(id) | Promise<APIResponse<CarePlan>> |
| createCarePlan(carePlan) | Promise<APIResponse<string>> |
| updateCarePlan(id, carePlan) | Promise<APIResponse<string>> |
| deleteCarePlan(id) | Promise<APIResponse<boolean>> |
Vitals
| Method | Returns |
| --- | --- |
| listVitals() | Promise<APIResponse<VitalSign[]>> |
| getVitalSnapshot(dateRecorded) | Promise<APIResponse<VitalSign>> |
| recordVitalsSnapshot(vitals) | Promise<APIResponse<VitalObservationIds>> |
| replaceVitalsSnapshot(vitals) | Promise<APIResponse<VitalObservationIds>> |
| syncVitals(request) | Promise<APIResponse<boolean>> |
| updateVitalsReading(observationId, reading) | Promise<APIResponse<boolean>> |
| deleteVitalsSnapshot(dateRecorded) | Promise<APIResponse<number>> |
| deleteVitalsReading(observationId) | Promise<APIResponse<boolean>> |
Social History
| Method | Returns |
| --- | --- |
| getSocialHistory() | Promise<APIResponse<SocialHistory>> |
| mergeSocialHistoryAnswers(answers) | Promise<APIResponse<string>> |
| upsertSocialHistoryAnswer(answer) | Promise<APIResponse<string>> |
| clearSocialHistory(request) | Promise<APIResponse<string>> |
Response Examples
The examples below show representative successful backend wire responses for every public method in HCHealthRecordClient. They preserve the casing returned by the service. Public SDK request models use PascalCase.
setApiKey(headerName, value)
undefinedgetHealth()
{
"status": "ok"
}getCurrentPatient()
{
"data": {
"id": "[email protected]",
"email": "[email protected]",
"firstName": "John",
"lastName": "Smith",
"phone": "555-0100",
"birthDate": "1990-01-01T00:00:00",
"sex": "male",
"fhirid": "<FHIR_ID>",
"tenantID": "healthcheck",
"address": {
"streetAndNumber": "1 Main St",
"city": "Springfield",
"state": "IL",
"postalCode": "62701",
"country": "US"
}
},
"isOK": true
}getPatientByVendor(vendorPatientId)
{
"data": {
"id": "[email protected]",
"email": "[email protected]",
"firstName": "John",
"lastName": "Smith",
"fhirid": "<FHIR_ID>",
"tenantID": "healthcheck"
},
"isOK": true
}searchPatients(query)
{
"data": [
{
"id": "[email protected]",
"email": "[email protected]",
"firstName": "John",
"lastName": "Smith",
"fhirid": "<FHIR_ID>",
"tenantID": "healthcheck"
}
],
"isOK": true
}createPatient(patient)
{
"data": {
"id": "[email protected]",
"email": "[email protected]",
"firstName": "John",
"lastName": "Smith",
"birthDate": "1990-01-01T00:00:00",
"fhirid": "<FHIR_ID>",
"tenantID": "healthcheck"
},
"isOK": true
}updateCurrentPatient(patient)
{
"data": true,
"isOK": true
}deactivateEHRPatient()
{
"data": true,
"isOK": true
}listAllergies()
{
"data": [
{
"id": "<ALLERGY_ID>",
"allergen": "Peanuts",
"reaction": "Anaphylaxis",
"severity": "severe",
"criticality": "high",
"onsetDate": "2020-01-15T00:00:00Z",
"recordedDate": "2026-05-28T23:09:42.364657Z",
"isActive": true
}
],
"isOK": true
}getAllergy(id)
{
"data": {
"id": "<ALLERGY_ID>",
"allergen": "Peanuts",
"reaction": "Anaphylaxis",
"severity": "severe",
"criticality": "high",
"onsetDate": "2020-01-15T00:00:00Z",
"recordedDate": "2026-05-28T23:09:42.364657Z",
"isActive": true
},
"isOK": true
}createAllergy(allergy)
{
"data": "<ALLERGY_ID>",
"isOK": true
}updateAllergy(id, allergy)
{
"data": "<ALLERGY_ID>",
"isOK": true
}deleteAllergy(id)
{
"data": true,
"isOK": true
}listConditions(options?)
{
"data": [
{
"id": "<CONDITION_ID>",
"snomedCode": "38341003",
"icdCode": "I10",
"description": "Essential hypertension",
"dateIdentified": "2024-01-10T00:00:00Z",
"recordedDate": "2026-05-19T21:13:13.290942Z",
"isActive": true,
"clinicalStatus": "active",
"verificationStatus": "confirmed",
"category": "problem-list-item"
}
],
"isOK": true
}getCondition(id)
{
"data": {
"id": "<CONDITION_ID>",
"snomedCode": "38341003",
"icdCode": "I10",
"description": "Essential hypertension",
"dateIdentified": "2024-01-10T00:00:00Z",
"recordedDate": "2026-05-19T21:13:13.290942Z",
"isActive": true,
"clinicalStatus": "active",
"verificationStatus": "confirmed",
"category": "problem-list-item"
},
"isOK": true
}createCondition(condition)
{
"data": "<CONDITION_ID>",
"isOK": true
}updateCondition(id, condition)
{
"data": "<CONDITION_ID>",
"isOK": true
}deleteCondition(id)
{
"data": true,
"isOK": true
}listFamilyHistory()
{
"data": [
{
"id": "<FAMILY_HISTORY_ID>",
"relationship": "Mother",
"relationshipCode": "MTH",
"name": "Sample Family Member",
"status": "partial",
"recordedDate": "2026-05-28T23:24:14.872165Z",
"conditions": [
{
"snomedCode": "44054006",
"description": "Type 2 diabetes mellitus",
"onsetAge": 50
}
]
}
],
"isOK": true
}getFamilyHistoryItem(id)
{
"data": {
"id": "<FAMILY_HISTORY_ID>",
"relationship": "Mother",
"relationshipCode": "MTH",
"name": "Sample Family Member",
"status": "partial",
"recordedDate": "2026-05-28T23:24:14.872165Z",
"conditions": [
{
"snomedCode": "44054006",
"description": "Type 2 diabetes mellitus",
"onsetAge": 50
}
]
},
"isOK": true
}createFamilyHistory(familyHistory)
{
"data": "<FAMILY_HISTORY_ID>",
"isOK": true
}updateFamilyHistory(id, familyHistory)
{
"data": "<FAMILY_HISTORY_ID>",
"isOK": true
}deleteFamilyHistory(id)
{
"data": true,
"isOK": true
}listProcedures()
{
"data": [
{
"id": "<PROCEDURE_ID>",
"procedureCode": "80146002",
"codeSystem": "http://snomed.info/sct",
"description": "Excision of appendix",
"status": "completed",
"date": "2024-08-12T00:00:00Z",
"recordedDate": "2024-08-12T00:00:00Z",
"category": "Surgical procedure"
}
],
"isOK": true
}getProcedure(id)
{
"data": {
"id": "<PROCEDURE_ID>",
"procedureCode": "80146002",
"codeSystem": "http://snomed.info/sct",
"description": "Excision of appendix",
"status": "completed",
"date": "2024-08-12T00:00:00Z",
"recordedDate": "2024-08-12T00:00:00Z",
"category": "Surgical procedure"
},
"isOK": true
}createProcedure(procedure)
{
"data": "<PROCEDURE_ID>",
"isOK": true
}updateProcedure(id, procedure)
{
"data": "<PROCEDURE_ID>",
"isOK": true
}deleteProcedure(id)
{
"data": true,
"isOK": true
}listMedications()
{
"data": [
{
"id": "<MEDICATION_ID>",
"name": "Lisinopril",
"code": "29046",
"codeSystem": "http://www.nlm.nih.gov/research/umls/rxnorm",
"dosage": "10mg",
"route": "oral",
"frequency": "daily",
"status": "active",
"startDate": "2025-01-15T00:00:00Z",
"recordedDate": "2026-05-19T21:13:15.833388Z",
"isActive": true
}
],
"isOK": true
}getMedication(id)
{
"data": {
"id": "<MEDICATION_ID>",
"name": "Lisinopril",
"code": "29046",
"codeSystem": "http://www.nlm.nih.gov/research/umls/rxnorm",
"dosage": "10mg",
"route": "oral",
"frequency": "daily",
"status": "active",
"startDate": "2025-01-15T00:00:00Z",
"recordedDate": "2026-05-19T21:13:15.833388Z",
"isActive": true
},
"isOK": true
}createMedication(medication)
{
"data": "<MEDICATION_ID>",
"isOK": true
}updateMedication(id, medication)
{
"data": "<MEDICATION_ID>",
"isOK": true
}deleteMedication(id)
{
"data": true,
"isOK": true
}listImmunizations()
{
"data": [
{
"id": "<IMMUNIZATION_ID>",
"vaccine": "Influenza vaccine",
"cvxCode": "150",
"status": "completed",
"dateAdministered": "2025-10-15T10:30:00Z",
"recordedDate": "2026-05-28T22:46:13.031818Z",
"site": "left arm",
"route": "intramuscular"
}
],
"isOK": true
}getImmunization(id)
{
"data": {
"id": "<IMMUNIZATION_ID>",
"vaccine": "Influenza vaccine",
"cvxCode": "150",
"status": "completed",
"dateAdministered": "2025-10-15T10:30:00Z",
"recordedDate": "2026-05-28T22:46:13.031818Z",
"site": "left arm",
"route": "intramuscular"
},
"isOK": true
}createImmunization(immunization)
{
"data": "<IMMUNIZATION_ID>",
"isOK": true
}updateImmunization(id, immunization)
{
"data": "<IMMUNIZATION_ID>",
"isOK": true
}deleteImmunization(id)
{
"data": true,
"isOK": true
}listLabResults()
{
"data": [
{
"id": "<LAB_RESULT_ID>",
"description": "Comprehensive Metabolic Panel",
"loincCode": "24323-8",
"status": "final",
"category": "laboratory",
"performedDate": "2026-05-28T08:00:00Z",
"issuedDate": "2026-05-28T23:00:41.095487Z",
"analytes": [
{
"id": "<ANALYTE_ID>",
"name": "Glucose",
"loincCode": "2345-7",
"description": "Glucose",
"value": "95.0",
"numericValue": 95,
"unit": "mg/dL",
"status": "final",
"performedDate": "2026-05-28T08:00:00Z"
}
]
}
],
"isOK": true
}getLabResult(id)
{
"data": {
"id": "<LAB_RESULT_ID>",
"description": "Comprehensive Metabolic Panel",
"loincCode": "24323-8",
"status": "final",
"category": "laboratory",
"performedDate": "2026-05-28T08:00:00Z",
"issuedDate": "2026-05-28T23:00:41.095487Z",
"analytes": [
{
"id": "<ANALYTE_ID>",
"name": "Glucose",
"loincCode": "2345-7",
"description": "Glucose",
"value": "95.0",
"numericValue": 95,
"unit": "mg/dL",
"status": "final",
"performedDate": "2026-05-28T08:00:00Z"
}
]
},
"isOK": true
}createLabResult(labResult)
{
"data": "<LAB_RESULT_ID>",
"isOK": true
}updateLabResult(id, labResult)
{
"data": "<LAB_RESULT_ID>",
"isOK": true
}deleteLabResult(id)
{
"data": true,
"isOK": true
}listCarePlans()
{
"data": [
{
"id": "<CARE_PLAN_ID>",
"title": "Hypertension management plan",
"description": "BP control through lifestyle + medication.",
"status": "active",
"intent": "plan",
"created": "2025-04-01T00:00:00Z",
"startDate": "2025-04-01T00:00:00Z",
"categories": ["assess-plan"],
"goals": ["Reduce systolic BP to < 130 mmHg"],
"goalDetails": [],
"activities": [
{
"description": "Daily 30-minute brisk walk",
"status": "in-progress",
"startDate": "2025-04-01T00:00:00Z"
}
],
"careTeam": []
}
],
"isOK": true
}getCarePlan(id)
{
"data": {
"id": "<CARE_PLAN_ID>",
"title": "Hypertension management plan",
"description": "BP control through lifestyle + medication.",
"status": "active",
"intent": "plan",
"created": "2025-04-01T00:00:00Z",
"startDate": "2025-04-01T00:00:00Z",
"categories": ["assess-plan"],
"goals": ["Reduce systolic BP to < 130 mmHg"],
"goalDetails": [],
"activities": [
{
"description": "Daily 30-minute brisk walk",
"status": "in-progress",
"startDate": "2025-04-01T00:00:00Z"
}
],
"careTeam": []
},
"isOK": true
}createCarePlan(carePlan)
{
"data": "<CARE_PLAN_ID>",
"isOK": true
}updateCarePlan(id, carePlan)
{
"data": "<CARE_PLAN_ID>",
"isOK": true
}deleteCarePlan(id)
{
"data": true,
"isOK": true
}listVitals()
{
"data": [
{
"observationIds": {
"HeartRate": "98dc2bde-9344-4bdb-85fc-b51e7e7d3ca0"
},
"dateRecorded": "2026-05-29T14:30:13.411Z",
"heartRate": 82
},
{
"observationIds": {
"BloodPressure:systolic": "aa117cdb-69e4-4e46-abf3-5142f541fdb1",
"HeartRate": "f9ca74fc-be37-4cf2-9181-72c093558db9"
},
"dateRecorded": "2026-05-29T00:20:25.029Z",
"heartRate": 72,
"systolicBP": 120
},
{
"observationIds": {
"HeartRate": "068a3614-6176-4842-baf1-cf868e021d25",
"BloodPressure:diastolic": "33e1d522-4b26-4a06-9754-c3afcbb32b8d",
"BloodPressure:systolic": "447102de-d08e-42dc-ada4-95e128b70dbe"
},
"dateRecorded": "2026-05-28T09:00:00Z",
"heartRate": 75,
"systolicBP": 120,
"diastolicBP": 78
},
{
"observationIds": {
"RespiratoryRate": "0145ad0a-9613-47a9-a22b-2c74076d89ee",
"Temperature": "ebe42dcd-fb43-47ed-a1ce-36093e11549d",
"HeartRate": "2857ca09-a01b-46b2-8f72-58727f05c2ad",
"BloodPressure:diastolic": "fd6dca6d-e3cb-4a29-b9c3-e71e47e9a78f",
"BloodPressure:systolic": "09806f45-e0aa-4c98-8337-64c73fead2e6"
},
"dateRecorded": "2026-05-28T08:00:00Z",
"temperature": 98.6,
"temperatureUnit": "[degF]",
"heartRate": 72,
"respiratoryRate": 14,
"systolicBP": 120,
"diastolicBP": 78
},
{
"observationIds": {
"BloodPressure:diastolic": "be355332-1d51-46c2-9fa2-a9d355740f20",
"BloodPressure:systolic": "a715fe0f-fbd5-4e04-9441-a2296b7bdab5"
},
"dateRecorded": "2026-05-27T19:46:17Z",
"systolicBP": 125,
"diastolicBP": 85
},
{
"observationIds": {
"BloodPressure:diastolic": "71f1a717-cb68-4586-b95c-5b98d9fd376b",
"BloodPressure:systolic": "5c8d14ea-1ffc-48c6-9a3c-5a536454a5d0"
},
"dateRecorded": "2026-05-27T19:35:27Z",
"systolicBP": 124,
"diastolicBP": 84
},
{
"observationIds": {
"BloodPressure:diastolic": "b7ab4257-b309-4456-b582-598a0827cb06",
"BloodPressure:systolic": "81ffa96e-ff59-4e60-97ae-b32a9a215cec"
},
"dateRecorded": "2026-05-27T19:34:20Z",
"systolicBP": 123,
"diastolicBP": 83
},
{
"observationIds": {
"BloodPressure:diastolic": "9f797af3-e104-4204-931e-5a90facdc260"
},
"dateRecorded": "2026-05-27T00:00:00Z",
"diastolicBP": 83
},
{
"observationIds": {
"BloodPressure:diastolic": "10c3022a-fd11-42dc-bc14-e69085579924",
"BloodPressure:systolic": "9efc3cb6-de99-4d01-b683-b2d813be40b4"
},
"dateRecorded": "2026-05-21T19:19:51Z",
"systolicBP": 128,
"diastolicBP": 98
},
{
"observationIds": {
"Temperature": "f51418f0-9aef-4497-b7b1-368a658ecb8b",
"HeartRate": "5d2d8201-3e1d-4c9f-b9d8-ab682c1dea27",
"BloodPressure:diastolic": "ac48bd8a-5735-479c-b55b-977f29227001",
"BloodPressure:systolic": "729ff154-6b85-4983-8c3c-9ef4e3b3d53d"
},
"dateRecorded": "2026-05-21T00:00:00Z",
"temperature": 36.6,
"temperatureUnit": "C",
"heartRate": 72,
"systolicBP": 120,
"diastolicBP": 80
},
{
"observationIds": {
"RespiratoryRate": "dfcd8cec-19dd-4297-b907-c8e146d8af69",
"Temperature": "31827ae1-5dad-4d5b-a99a-bdcc30ab3043",
"HeartRate": "13b82f34-995c-4353-a60f-f8cf48969283",
"BloodPressure:diastolic": "8c1ec75b-a211-4893-84a0-89b54a920c93",
"BloodPressure:systolic": "354a8b13-3147-4f0d-b77f-95bcebcfcde6"
},
"dateRecorded": "2026-05-20T00:37:36.326Z",
"temperature": 98.6,
"temperatureUnit": "[degF]",
"heartRate": 72,
"respiratoryRate": 14,
"systolicBP": 120,
"diastolicBP": 78
},
{
"observationIds": {
"Temperature": "bd441679-00ab-42fd-9088-66739d486d2b",
"HeartRate": "6e59a9fc-8423-4182-81b4-e2ab78684779",
"BloodPressure:diastolic": "c31eb955-c2fb-4578-a974-d775f097e017",
"BloodPressure:systolic": "f08fd035-5606-4234-add4-1b10b848b31b"
},
"dateRecorded": "2026-05-20T00:00:00Z",
"temperature": 36.6,
"temperatureUnit": "C",
"heartRate": 72,
"systolicBP": 120,
"diastolicBP": 80
},
{
"observationIds": {
"HeartRate": "b4f38401-4984-4c17-a86b-86d57fd6bcef",
"BloodPressure:diastolic": "73d6066d-c8af-488e-8c79-2eea49c323d9",
"BloodPressure:systolic": "785b3605-a5d0-4bd1-b9e8-38a7a66ca0da"
},
"dateRecorded": "2025-10-15T08:00:00Z",
"heartRate": 65,
"systolicBP": 118,
"diastolicBP": 76
}
],
"isOK": true
}getVitalSnapshot(dateRecorded)
{
"data": {
"observationIds": {
"HeartRate": "98dc2bde-9344-4bdb-85fc-b51e7e7d3ca0"
},
"dateRecorded": "2026-05-29T14:30:13.411Z",
"heartRate": 82
},
"isOK": true
}recordVitalsSnapshot(vitals)
{
"data": {
"BloodPressure:systolic": "<OBSERVATION_ID>",
"BloodPressure:diastolic": "<OBSERVATION_ID>",
"HeartRate": "<OBSERVATION_ID>",
"Temperature": "<OBSERVATION_ID>",
"RespiratoryRate": "<OBSERVATION_ID>"
},
"isOK": true
}replaceVitalsSnapshot(vitals)
{
"data": {
"BloodPressure:systolic": "<OBSERVATION_ID>",
"BloodPressure:diastolic": "<OBSERVATION_ID>",
"HeartRate": "<OBSERVATION_ID>",
"Temperature": "<OBSERVATION_ID>",
"RespiratoryRate": "<OBSERVATION_ID>"
},
"isOK": true
}syncVitals(request)
{
"data": true,
"isOK": true
}updateVitalsReading(observationId, reading)
{
"data": true,
"isOK": true
}deleteVitalsSnapshot(dateRecorded)
{
"data": 3,
"isOK": true
}deleteVitalsReading(observationId)
{
"data": true,
"isOK": true
}getSocialHistory()
{
"data": {
"id": "<SOCIAL_HISTORY_ID>",
"patientFHIRID": "<FHIR_ID>",
"questionnaireRef": "https://healthcheck.com/fhir/Questionnaire/social-history/healthcheck",
"status": "inprogress",
"recordedDate": "2026-05-28T23:24:41.813707Z",
"answers": [
{
"key": "SOCIALHISTORY.SMOKING.STATUS",
"question": "Smoking status",
"answer": "Never smoker",
"readableAnswer": "Never smoker"
}
]
},
"isOK": true
}mergeSocialHistoryAnswers(answers)
{
"data": "<SOCIAL_HISTORY_ID>",
"isOK": true
}upsertSocialHistoryAnswer(answer)
{
"data": "<SOCIAL_HISTORY_ID>",
"isOK": true
}clearSocialHistory(request)
{
"data": "<SOCIAL_HISTORY_ID>",
"isOK": true
}UPDATE Notes
These notes document update behavior present in the current HCHealthRecordClient code comments.
updateCurrentPatient(patient)
Partial field update.
- Only provided fields are updated.
- Omitted fields preserve existing values.
BirthDateis documented in code as immutable and vendor-managed.- Updates apply to both Athena and FHIR.
- Recommended flow: fetch existing data, merge locally, submit the intended partial update.
updateAllergy(id, allergy)
Bulk PUT semantics internally.
- Fetches all allergies from Athena.
- Modifies the specific allergy identified by FHIR ID.
- Preserves vendor IDs.
- PUTs the entire allergy list back to the vendor.
- Operation is not atomic; concurrent updates are unsafe and last writer wins.
- Allergen ID is immutable after creation.
- Changing the allergen after creation is not supported by the documented vendor constraints.
- Vendor sync may be skipped if the allergen is not found in the reference table.
- FHIR may be updated while vendor state remains unchanged.
updateCondition(id, condition)
FHIR-only update.
- No per-problem Athena update endpoint is exposed according to code comments.
- Vendor update is skipped.
- FHIR resource is updated.
- Athena record remains unchanged.
- Read sync may overwrite FHIR-only changes with vendor state.
- Do not rely on this method to update vendor-backed condition data.
Family History Update Behavior
Updating a family history record is not processed as a simple record-level update.
The backend performs a family-history synchronization workflow to ensure the patient's family-history dataset remains consistent.
For this reason, family history updates may behave differently from resources that are updated through a direct record-level write operation.
When updating a record, the route ID is always treated as the source of truth.
Procedure Update Behavior
Procedure updates operate on a single procedure record.
Unlike resources that require dataset-level synchronization, updating a procedure targets only the specified procedure entry.
When updating a procedure, the route ID is always treated as the source of truth.
Medication Update Behavior
Medication updates operate on a single medication record.
Updating a medication affects only the targeted medication entry and does not trigger dataset-level synchronization behavior.
When updating a medication, the route ID is always treated as the source of truth.
Immunization Update Behavior
Immunization updates operate on a single immunization record.
Updating an immunization affects only the targeted immunization entry and does not trigger dataset-level synchronization behavior.
When updating an immunization, the route ID is always treated as the source of truth.
CVX Codes
Immunizations can be created using either a vaccine name or a CVX code.
For maximum compatibility across vendor integrations, providing a valid CVX code is recommended whenever available.
Lab Result Update Behavior
Lab result updates replace the complete analyte collection associated with the lab result.
When updating a lab result, the supplied Analytes array becomes the complete source of truth.
Any analytes omitted from the update request will be removed from the resulting lab result.
Always send the full analyte collection that should exist after the update operation completes.
Care Plan Update Behavior
Care plan updates should be treated as full-state updates.
Collections such as:
CategoriesGoalsGoalDetailsActivitiesCareTeam
are rebuilt from the values supplied in the update request.
If a collection is omitted from the request, it may become empty after the update completes.
Recommended update workflow:
1. Retrieve the existing care plan.
2. Modify the fields that need to change.
3. Send the complete desired care plan state back to updateCarePlan().Example:
const existing = await client.getCarePlan(id);
await client.updateCarePlan(id, {
...existing.Data,
Title: "Updated Care Plan Title"
});This approach prevents accidental loss of collection data.
Vitals Method Behavior
listVitals()
Returns all vital snapshots as APIResponse<VitalSign[]>.
Each VitalSign represents one recorded date. Its ObservationIds map contains the observation IDs for the snapshot readings.
getVitalSnapshot(dateRecorded)
Returns one vital snapshot as APIResponse<VitalSign>.
The dateRecorded value must use ISO 8601 round-trip format because the backend route applies a datetime constraint.
The backend returns 404 when the requested date does not exist.
recordVitalsSnapshot(vitals)
Creates observation resources for each supplied vital sign and returns APIResponse<VitalObservationIds>.
The returned map is keyed by reading name, such as SystolicBP, DiastolicBP, Weight, and Temperature.
The backend treats both a null result and an empty result as failure.
replaceVitalsSnapshot(vitals)
Performs a full vital snapshot replacement and returns APIResponse<VitalObservationIds>.
Observation IDs are regenerated, so previously cached IDs become invalid.
Use updateVitalsReading(observationId, reading) for single-reading updates.
The backend treats a null result as failure and an empty map as success. This differs from recordVitalsSnapshot(vitals).
syncVitals(request)
Imports mobile/app synced vitals and returns APIResponse<boolean>.
The request is sent directly to the backend without a { Data: ... } envelope. Payload fields use PascalCase names such as Records, StartDate, and EndDate.
Repeated tests should use fresh Id or timestamp values because duplicate records may be skipped by TimescaleDB.
This is separate from recordVitalsSnapshot(vitals), which creates a snapshot from normalized SDK vitals.
updateVitalsReading(observationId, reading)
Updates a single vital sign observation and returns APIResponse<boolean>.
The observationId comes from VitalObservationIds. The backend returns 404 when the observation does not exist.
Use this method for individual reading changes instead of replacing the whole snapshot.
deleteVitalsSnapshot(dateRecorded)
Deletes all vital observations for a specific date and returns APIResponse<number>.
The returned value is the number of deleted observations.
The backend returns 200 even when the result is 0 and never returns 404. This differs from deleteVitalsReading(observationId).
deleteVitalsReading(observationId)
Deletes a single vital sign observation and returns APIResponse<boolean>.
The backend returns 404 when the observation does not exist.
Social History Update Behavior
Social history uses merge/upsert semantics rather than full replacement semantics.
When updating answers:
- Existing answers are matched by
Key. - Matching answers are updated.
- New answers are inserted.
- Answers not included in the request remain unchanged.
Example:
await healthRecords.mergeSocialHistoryAnswers([
{
Key: "SOCIALHISTORY.SMOKING.STATUS",
Answer: "Never"
}
]);This updates only the smoking status answer and preserves all other social history answers.
Clearing Answers
To remove answers, use:
clearSocialHistory(...)Only the supplied Keys are removed.
Unrelated answers remain unchanged.
Usage Examples
Read Current Patient
const response = await healthRecords.getCurrentPatient();
if (response.Data) {
console.log(response.Data.FirstName, response.Data.LastName);
}Create Allergy
const response = await healthRecords.createAllergy({
Allergen: "Peanuts",
Reaction: "Anaphylaxis",
Severity: "severe",
Criticality: "high",
IsActive: true
});
console.log(response.Data); // "<ALLERGY_ID>"Merge Social History Answers
await healthRecords.mergeSocialHistoryAnswers([
{
Key: "SOCIALHISTORY.SMOKING.STATUS",
Question: "Smoking status",
Answer: "Never smoker",
ReadableAnswer: "Never smoker"
}
]);Replace Vitals Snapshot
const response = await healthRecords.replaceVitalsSnapshot({
DateRecorded: "2026-05-28T08:00:00Z",
Temperature: 98.6,
TemperatureUnit: "[degF]",
HeartRate: 72,
RespiratoryRate: 14,
SystolicBP: 120,
DiastolicBP: 78
});
console.log(response.Data); // { "BloodPressure:systolic": "<ID>", ... }Get Vital Snapshot
const response = await healthRecords.getVitalSnapshot("2026-05-28T08:00:00Z");
console.log(response.Data);Import Vitals
const response = await healthRecords.syncVitals({
User: {
TenantID: "healthcheck",
Email: "[email protected]"
},
Records: [
{
Id: "apple-health-heart-rate-001",
Type: "HeartRate",
Value: 72,
Unit: "count/min",
StartDate: new Date("2026-05-28T08:00:00Z"),
EndDate: new Date("2026-05-28T08:01:00Z"),
SourceName: "Apple Health",
SourcePlatform: "ios"
}
],
SyncTimestamp: new Date("2026-05-28T08:05:00Z"),
DevicePlatform: "ios"
});
console.log(response.Data);Update One Vital Reading
await healthRecords.updateVitalsReading("<OBSERVATION_ID>", {
Value: 80,
Unit: "/min"
});Notes
- All patient record methods require an authenticated patient context.
setApiKey(...)is optional and only needed when the deployment requires an API key header.- The connector returns backend
APIResponse<T>objects; it does not unwrapdata. - Backend business failures with
isOK: falsethrowHCServiceErrorin the current client. - All field names in requests and responses use camelCase.
- Request and response examples in this README use placeholders for patient data, tenant IDs, tokens, resource IDs, and API keys.
- This README documents the public methods present in
src/client.ts; internalprotectedandprivatehelpers are not SDK surface.
