@cariva/medical-sdk
v1.0.4
Published
Unified Cariva Medical SDK for Node.js and browsers - ASR, ICD Code, and more (single install, auto platform detection)
Readme
@cariva/medical-sdk
Turn a recorded consultation into a structured medical document. One install, same API on Node.js and in the browser — the right build is selected automatically.
Install · Quick start · Authentication ·
Modes · Options · process() ·
Result shapes · Errors · Audio ·
Diagnosis Code (ICD) · Migrating
Install
npm install @cariva/medical-sdk<script src="https://unpkg.com/@cariva/[email protected]/dist/browser/index.umd.js"></script>
<script>
const asr = new CarivaMedicalSDK.ASR({
key: 'YOUR_KEY',
secret: 'YOUR_SECRET',
mode: 'doc-ipd-soap',
});
const icd = new CarivaMedicalSDK.ICD({
key: 'YOUR_KEY',
secret: 'YOUR_SECRET',
});
</script>The UMD build exposes window.CarivaMedicalSDK (.ASR, .ICD). Pin an exact version in
production.
Quick start
import { ASR } from '@cariva/medical-sdk';
const asr = new ASR({
key: 'YOUR_KEY',
secret: 'YOUR_SECRET',
mode: 'doc-ipd-soap',
lang: 'th',
});
// audio: a File from <input>, a Blob from MediaRecorder, or file bytes on Node
const result = await asr.process(audio, {
onProgress: p => console.log(`upload ${p.percentage}%`),
onProcessing: step => console.log(step),
});
console.log(result.data.assessment); // typed as SOAPDataOne process() call runs everything — compress, upload, poll — and resolves only when the note is
ready. There is no task to track and nothing to poll yourself.
result.data is typed from the mode you passed — no type argument, no cast. When the mode is only
known at runtime, switch on result.mode for the same narrowing:
const asr = new ASR({ key, secret, mode }); // `mode: ASRMode`, picked at runtime
const result = await asr.process(audio);
switch (result.mode) {
case 'doc-ipd-soap':
render(result.data.assessment); // SOAPData
break;
case 'radiology':
render(result.data.impression); // RadiologyData
break;
}On Node, wrap the bytes in a Blob:
import { readFile } from 'node:fs/promises';
const audio = new Blob([await readFile('./consultation.wav')], { type: 'audio/wav' });Authentication
Pass either key + secret or accessToken. Mixing the two, or passing neither, is a type
error in TypeScript and a ValidationError at runtime.
new ASR({ key, secret, mode }); // API key + secret (HMAC)
new ASR({ accessToken, mode }); // bearer tokenCredentials are issued per application — ask your Cariva contact if you do not have a pair yet.
Keep secrets out of client-side bundles — in the browser, prefer a short-lived accessToken issued
by your own backend.
Modes
The mode decides which service runs and what result.data contains. Pick one when constructing.
| Mode | Produces | Result type |
| ---------------------------------------- | ----------------------------------- | ----------------------------------------------- |
| doc-ipd-soap | IPD SOAP note | SOAPData |
| doc-opd-clinicalrecord | OPD clinical record | OPDClinicalRecordData |
| nur-opd-nursenote-start / -end | Nursing note — start / end of shift | NurseNoteStartOfShiftData / …EndOfShiftData |
| nur-opd-nursenote-faie-start / -end | Nursing note (FAIE) | NurseNoteFaieStartData / …FaieEndData |
| nur-opd-nursenote-adpie-start / -end | Nursing note (ADPIE) | NurseNoteAdpieStartData / …AdpieEndData |
| center-form | Center form | CenterFormData |
| radiology | Radiology report | RadiologyData |
| medical-mom | Minutes of a medical meeting | MedicalMOMData |
soap and medclerk also appear in autocomplete — legacy aliases of doc-ipd-soap and
doc-opd-clinicalrecord. They still work; prefer the full names.
Options
Everything except mode, the credentials, and timeout can also be passed per call to process(),
where it overrides the constructor value for that run.
| Option | Type | Default | Per call | Notes |
| --------------- | ------------------------- | --------------- | :------: | -------------------------------------------------- |
| mode | ASRMode | — (required) | | See Modes |
| key secret | string | — | | HMAC credentials |
| accessToken | string | — | | Bearer token, instead of key + secret |
| lang | string | service default | ✓ | 'th', 'en', … |
| specialty | ASRSpecialty | — | ✓ | Biases the model — see Specialties |
| prefix | string | — | ✓ | Forwarded to the service with the task |
| suggestion | boolean | — | ✓ | Forwarded to the service with the task |
| newLineString | string \| null | null | ✓ | Replaces \n in the returned text |
| taskPayload | Record<string, unknown> | — | ✓ | Extra fields for the create-task request |
| timeout | number (ms) | 300000 | | Request timeout and polling limit (5 min) |
process(audio, options?)
Resolves with the completed result, or throws — see Errors. audio is a Blob or
File. Beyond the options above it takes two callbacks:
| Callback | Fires | Argument |
| -------------- | ------------- | ------------------------------------------------------ |
| onProgress | During upload | { percentage, loaded, total } |
| onProcessing | On each step | 'Preparing audio...', 'Uploading audio file...', … |
The result:
{
mode: ASRMode; // the mode that produced this result — switch on it
data: …; // that mode's payload — see Result shapes
status: 'COMPLETED'; // anything else threw instead of resolving
taskId: string;
error?: string;
progress?: number;
createdAt?: string;
updatedAt?: string;
completedAt?: string;
}data is not optional and status is not a union: process() resolves only on a completed task,
so there is nothing to guard against at the call site. A completed task that arrives without a
payload throws MISSING_RESULT_DATA instead of resolving with an empty result.
To name the result type — passing it between functions, storing it in state — use ASRResult<Mode>:
function render(result: ASRResult<'doc-ipd-soap'>) {
result.data.assessment;
}Result shapes
Fields marked ? may be absent.
// doc-ipd-soap
interface SOAPData {
fastTranscription: string;
subjective?: string;
objective?: string;
assessment?: string;
plan?: string;
}
// doc-opd-clinicalrecord
interface OPDClinicalRecordData {
fastTranscription: string;
rawNote: string;
chiefComplaint: string;
presentIllness: string;
diagnosis: string;
pastMedicalHistory: string;
familyHistory: string;
personalHistory?: string;
currentMedication?: string;
physicalExamination: {
generalAppearance: string;
heent: string;
cardiovascular: string;
respiratory: string;
gastrointestinal: string;
neurological: string;
vitalSign: string;
other: string;
};
investigation: string;
treatment: string;
plan: string;
recommendation?: string;
}// nur-opd-nursenote-start (NurseNoteFaieStartData has the same shape)
interface NurseNoteStartOfShiftData {
fastTranscription: string;
rawNote: string;
focus: string;
assessment: string;
intervention: string;
vitalSigns: VitalSign;
}
// nur-opd-nursenote-end — vitalSigns is a plain string here
interface NurseNoteEndOfShiftData {
fastTranscription: string;
rawNote: string;
evaluation: string;
vitalSigns: string;
}
// nur-opd-nursenote-faie-end — vitalSigns is a VitalSign object here
interface NurseNoteFaieEndData {
fastTranscription: string;
rawNote: string;
evaluation: string;
vitalSigns: VitalSign;
}
// nur-opd-nursenote-adpie-start
interface NurseNoteAdpieStartData {
fastTranscription: string;
rawNote: string;
assessment: {
objective: string;
subjective: string;
};
diagnosis: string;
planning: string;
intervention: string;
}
// nur-opd-nursenote-adpie-end
interface NurseNoteAdpieEndData {
fastTranscription: string;
rawNote: string;
evaluation: string;
}
// shared by the modes above
interface VitalSign {
heartRate: string;
respiratoryRate: string;
bloodPressure: string;
bodyTemperature: string;
oxygenSaturation: string;
}// radiology — fields are empty strings until the task completes
interface RadiologyData {
modality: string;
requestDetails: string;
technique: string;
findings: string;
impression: string;
}// medical-mom — a ward round, case conference, or handover written up as
// meeting minutes rather than as a patient record
interface MedicalMOMData {
fastTranscription: string;
rawNote: string;
minutesOfMeeting: string;
}// center-form
interface CenterFormData {
[key: string]: unknown;
}Need the text without knowing the shape? extractTranscript(result) returns the best available
field, or '' — see Chaining.
Rating a result
Binary, like a thumbs up or down. Call it after process(); it rates the task that just finished.
await asr.rate(5);
await asr.rate(1, 'transcript cut off', ['RESULT_MISMATCH', 'PART_OF_THE_RESULT_IS_MISSING']);| Parameter | Type | Notes |
| ---------- | ------------------- | --------------------------------------------- |
| score | 1 \| 5 | 5 positive, 1 negative — no scale between |
| comment? | string | Free-text feedback |
| reasons? | ASRRatingReason[] | Why it was downvoted |
| ASRRatingReason | Meaning |
| ------------------------------- | ----------------- |
| SLOW_PROCESSING | Took too long |
| RESULT_MISMATCH | Incorrect content |
| PART_OF_THE_RESULT_IS_MISSING | Missing details |
| EXTRA_CONTENT_IN_RESULT | Extra content |
Instance API
await asr.cancel(); // stop the in-flight task and reset
await asr.create(); // cancel + reset, ready for the next recording
asr.taskId; // current task id ('' before the first task)
asr.status; // last known task status
asr.isProcessing; // true while a run is in flightCancelling makes the in-flight process() reject with ABORTED, so keep it inside the same try
as the call.
Errors
Everything thrown extends CarivaError, which carries code, status, and details — details
holds the API's error payload, or the original exception when the SDK wrapped one.
| Class | Raised when |
| ----------------- | ------------------------------------------------------------- |
| ValidationError | Missing or conflicting credentials, missing mode, bad input |
| ProcessingError | The audio could not be prepared, or the upload failed |
| CarivaError | Everything else — API errors, timeouts, failed tasks |
Which call throws what:
| Call | Throws |
| ---------------- | ---------------------------------------------------------------------------------- |
| new ASR(...) | ValidationError for bad config — construct it where you can catch it |
| process(audio) | Any of the codes below |
| rate(score, …) | NO_TASK_ID before a task has completed, ValidationError for a score not 1 or 5 |
| cancel() | Never — a server-side cancel that fails is a console warning |
import { CarivaError, ProcessingError, ValidationError } from '@cariva/medical-sdk';
try {
await asr.process(audio);
} catch (err) {
if (err instanceof ValidationError) {
// bad config or input — a bug on your side, retrying will not help
} else if (err instanceof ProcessingError) {
// the audio never made it to the service
} else if (err instanceof CarivaError) {
console.log(err.code, err.status); // 'TRANSCRIPTION_FAILED', 500, …
}
}Branch on err.code when you need more than the class — it says which step failed:
| Code | Raised when | Worth retrying |
| ------------------------- | ----------------------------------------------------------------------- | ------------------------------ |
| VALIDATION_ERROR | Bad credentials, missing mode, bad argument | No — fix the call |
| AUDIO_PROCESSING_FAILED | The recording could not be prepared for upload | No — the same file fails again |
| asr/create-task-error | The service refused the task — quota, permission, or a rejected payload | Read err.status first |
| TRANSCRIPTION_FAILED | The task ran and failed; err.message carries the service's reason | Rarely — usually the recording |
| PROCESSING_TIMED_OUT | Still unfinished after timeout (5 minutes by default) | Yes, or raise timeout |
| ABORTED | cancel() ended the run | You asked for it |
| MISSING_RESULT_DATA | The task completed with no payload | Yes |
| PROCESS_FAILED | Anything the pipeline did not classify, e.g. the upload failing | Yes |
The codes the SDK raises autocomplete on err.code; codes the API returns come through unchanged,
so treat the list as the common cases rather than a closed set.
Specialties
Pass one as specialty to bias the model toward that domain. The list is autocomplete, not a
whitelist — a value the service adds before the SDK knows about it is still accepted.
| Key | Label |
| ------------------ | -------------------------------- |
| gen-practitioner | General Practitioner (GP) |
| gen-med | General Medicine |
| med-cardio | Cardiology |
| med-pulmonary | Pulmonology |
| med-gastro | Gastroenterology (GI) |
| med-neuro | Neurology |
| med-nephro | Nephrology |
| med-onco | Oncology |
| med-endo | Endocrinology |
| med-skin | Dermatology (Skin) |
| med-allergy | Allergy and Immunology |
| surgery | General Surgery |
| orthopedic | Orthopedic |
| gen-ped | General Pediatrics |
| ophthalmology | Ophthalmology (Eye) |
| otolaryngology | Otolaryngology (ENT) |
| ob-gyn | Obstetrics & Gynecology (OB-GYN) |
| emergency | Emergency (ER) |
| other | Other specialists |
Audio handling
Recordings are normalised to mono 16 kHz MP3 at 48 kbps before upload — the format the recogniser
consumes, and roughly a 29x reduction from a stereo 44.1 kHz WAV. Hand process() whatever you
recorded; there is nothing to configure and no encoder to install. If any step fails the SDK falls
back to a larger format and ultimately to the original file, so pre-processing never blocks a
transcription.
A recording is uploaded untouched when there is nothing to gain from encoding it: when it is already mono at or below 16 kHz, or already recorded at a lower bitrate than the encoder would produce — which covers most phone voice memos.
ffmpeg on a Node server (optional)
npm install @cariva/medical-sdk is the whole installation. In the browser there is nothing more to
think about. On a Node server, one optional addition is worth knowing about.
The encoder is pure JavaScript, so WAV compresses with no setup at all. Decoding an already-compressed recording — M4A, MP3, OGG, WebM — needs ffmpeg, which this SDK does not install. Without one, those formats upload exactly as they were recorded: the transcript is identical, the upload is just larger. Nothing errors and nothing warns.
Add ffmpeg when your server receives compressed audio that is not already mono at ≤16 kHz or ≤48
kbps — a phone M4A or a MediaRecorder WebM usually is not, so expect roughly 2–3x less bandwidth
per upload. Install it in the image, not as a package:
RUN apk add --no-cache ffmpeg # Alpine
RUN apt-get update && apt-get install -y ffmpeg # Debian/UbuntuThe SDK finds it on PATH by itself — no configuration, no code change. Verify with
ffmpeg -version.
If you cannot change the image (serverless, for instance),
ffmpeg-staticin your own dependencies works too, and the SDK picks it up the same way. Be aware that it downloads a 45 MB binary from an install script, and npm 12, pnpm 10+, and Bun all block those by default — so you will have to allow that build in your own project.
Diagnosis Code (ICD)
Turns a clinical note into ICD codes and looks codes up by keyword. The same ICD class handles
ICD-10, ICD-9, and combined ICD-9+ICD-10 — select the system with the optional modes array on
every call. When modes is omitted the default is ["icd10"], keeping backward-compatibility with
the legacy ICD10 class.
import { ICD } from '@cariva/medical-sdk';
const icd = new ICD({ key: 'YOUR_KEY', secret: 'YOUR_SECRET', baseUrl: 'https://…' });
// ICD-10
const { primary } = await icd.getIcdOpdCode('runny nose and cough');
console.log(primary?.code, primary?.description); // J00 Acute nasopharyngitis
const { primary: p } = await icd.getIcdOpdCode(['icd10'], 'runny nose and cough');
// ICD-9
const { principal, secondary } = await icd.getIcdOpdCode(['icd9'], 'appendectomy');
console.log(principal?.code, principal?.orStatus); // 47.0 true
// Combined ICD-10 + ICD-9
const result = await icd.getIcdOpdCode(['icd10', 'icd9'], 'appendectomy');
console.log(result.icd10.primary?.code); // K35.9
console.log(result.icd9.principal?.orStatus); // trueThe constructor takes the same key / secret or accessToken credentials as ASR. baseUrl is
required (the ICD service has its own host, separate from ASR).
new ICD({ key, secret, baseUrl }); // API key + secret (HMAC)
new ICD({ accessToken, baseUrl }); // bearer token
new ICD({ key, secret, baseUrl, timeout: 60_000 }); // custom timeout (ms, default 300 000)Modes
The first argument of every method is an optional modes array that selects the coding
system(s). When omitted it defaults to ["icd10"]. Order does not matter — ["icd9","icd10"]
is identical to ["icd10","icd9"].
| modes | Systems | Combined endpoint |
| ---------------------- | --------------------------------- | ----------------- |
| omitted or ["icd10"] | ICD-10 diagnosis codes | — |
| ["icd9"] | ICD-9 procedure / diagnosis codes | — |
| ["icd10","icd9"] | Both in one async task | ✓ (coding only) |
Search and suggest-block accept only a single system (
["icd10"]or["icd9"]). Passing["icd10","icd9"]to those methods throws aValidationError.
Which call do you need?
| You have | Call | You get back |
| ----------------------------- | ------------------------------------------------------------------- | ------------------------------------- |
| Outpatient note, as free text | getIcdOpdCode(modes?, note, options?) | see Coding results |
| Inpatient record, in sections | getIcdIpdCode(modes?, records, options?) | see Coding results |
| A keyword to look up | searchIcdOpd(modes?, query, options?) / searchIcdIpd | flat list of matches |
| A code, and want related ones | suggestBlockIcdOpd(modes?, code, options?) / suggestBlockIcdIpd | flat list of related codes |
Outpatient and inpatient are permissioned and billed separately — use the correct pair for the encounter type.
Both coding methods are asynchronous tasks: the SDK creates the task, polls until it completes,
and resolves with the result. The default timeout is 5 minutes (300 000 ms); pass a custom
timeout to the constructor to change it.
Types
The return type is inferred from modes — no cast required. When modes is omitted the return
type is always the ICD-10 variant.
// modes → inferred return type
icd.getIcdOpdCode(note); // Promise<ICD10OpdCodeResult> (modes omitted)
icd.getIcdOpdCode(['icd10'], note); // Promise<ICD10OpdCodeResult>
icd.getIcdOpdCode(['icd9'], note); // Promise<ICD9CodeResult>
icd.getIcdOpdCode(['icd10', 'icd9'], note); // Promise<ICD9Icd10OpdCodeResult>
icd.searchIcdOpd(query); // Promise<ICD10SearchEntry[]> (modes omitted)
icd.searchIcdOpd(['icd10'], query); // Promise<ICD10SearchEntry[]>
icd.searchIcdOpd(['icd9'], query); // Promise<ICD9Entry[]>The generic utility types let you name the result in your own functions:
import type { ICDOpdCodeResult } from '@cariva/medical-sdk';
function render(r: ICDOpdCodeResult<['icd10']>) {
r.primary?.code; // typed as ICD10OpdCodeResult
}ICD-10 entry types
interface ICD10Entry {
code: string; // 'J00'
description: string; // 'Acute nasopharyngitis [common cold]'
}
interface ICD10SearchEntry extends ICD10Entry {
display?: string;
}ICD-9 entry type
interface ICD9Entry {
code: string;
description: string;
orStatus?: boolean;
drgStatus?: boolean;
}Coding result types per mode
| modes | OPD result | IPD result |
| ------------------ | ------------------------ | ------------------------ |
| ["icd10"] | ICD10OpdCodeResult | ICD10IpdCodeResult |
| ["icd9"] | ICD9CodeResult | ICD9CodeResult |
| ["icd10","icd9"] | ICD9Icd10OpdCodeResult | ICD9Icd10IpdCodeResult |
Coding results
modes: ["icd10"] — outpatient
interface ICD10OpdCodeResult {
primary: ICD10Entry | null;
secondary: ICD10Entry[];
externalCause: ICD10Entry[];
}modes: ["icd10"] — inpatient
interface ICD10IpdCodeResult {
principal: ICD10Entry | null;
comorbidity: ICD10Entry[];
complication: ICD10Entry[];
otherDiagnosis: ICD10Entry[];
externalCause: ICD10Entry[];
}modes: ["icd9"] — outpatient or inpatient
interface ICD9CodeResult {
principal: ICD9Entry | null;
secondary: ICD9Entry[];
}modes: ["icd10","icd9"] — outpatient
interface ICD9Icd10OpdCodeResult {
icd9: {
principal: ICD9Entry | null;
secondary: ICD9Entry[];
};
icd10: {
primary: ICD10Entry | null;
secondary: ICD10Entry[];
externalCause: ICD10Entry[];
};
}modes: ["icd10","icd9"] — inpatient
interface ICD9Icd10IpdCodeResult {
icd9: {
principal: ICD9Entry | null;
secondary: ICD9Entry[];
};
icd10: {
principal: ICD10Entry | null;
comorbidity: ICD10Entry[];
complication: ICD10Entry[];
otherDiagnosis: ICD10Entry[];
externalCause: ICD10Entry[];
};
}Coding an encounter
OPD: getIcdOpdCode(modes, note, options?)
// ICD-10 only
const { primary, secondary, externalCause } = await icd.getIcdOpdCode(
['icd10'],
'Runny nose, cough and mild fever for two days.',
{ patientProfile: { age: 35, sex: 'female' } }
);
console.log(primary); // { code: 'J00', description: 'Acute nasopharyngitis [common cold]' }
// ICD-9 only
const { principal, secondary } = await icd.getIcdOpdCode(['icd9'], 'appendectomy');
console.log(principal); // { code: '47.0', description: 'Appendectomy', orStatus: true, drgStatus: false }
// Combined ICD-10 + ICD-9
const combined = await icd.getIcdOpdCode(['icd10', 'icd9'], 'appendectomy');
console.log(combined.icd10.primary?.code); // 'K35.9'
console.log(combined.icd9.principal?.orStatus); // trueIPD: getIcdIpdCode(modes, records, options?)
Input is the record split into named sections. Combined mode is supported here too:
const records = [
{ name: 'Chief complaint', content: 'Fever and cough for 3 days' },
{
name: 'Assessment',
content: 'Community acquired pneumonia. Known type 2 diabetes. Fall at home.',
},
];
// ICD-10
const { principal, comorbidity, complication, otherDiagnosis, externalCause } =
await icd.getIcdIpdCode(['icd10'], records);
console.log(principal); // { code: 'J18.9', description: 'Pneumonia, unspecified' }
console.log(comorbidity); // [{ code: 'E11.9', description: 'Type 2 diabetes mellitus without complications' }]
console.log(complication); // [{ code: 'J96.00', description: 'Acute respiratory failure, unspecified whether with hypoxia or hypercapnia' }]
console.log(otherDiagnosis); // [{ code: 'E87.6', description: 'Hypokalaemia' }]
console.log(externalCause); // [{ code: 'W19.9', description: 'Unspecified fall, unspecified place' }]
// ICD-9
const { principal: icd9Principal, secondary } = await icd.getIcdIpdCode(['icd9'], records);
console.log(icd9Principal); // { code: '96.71', description: 'Continuous invasive mechanical ventilation for less than 96 consecutive hours', orStatus: false, drgStatus: true }
console.log(secondary); // []
// Combined
const both = await icd.getIcdIpdCode(['icd10', 'icd9'], records);
console.log(both.icd10.principal?.code); // 'J18.9'
console.log(both.icd10.comorbidity); // [{ code: 'E11.9', description: 'Type 2 diabetes mellitus without complications' }]
console.log(both.icd10.complication); // [{ code: 'J96.00', description: 'Acute respiratory failure, unspecified whether with hypoxia or hypercapnia' }]
console.log(both.icd10.otherDiagnosis); // [{ code: 'E87.6', description: 'Hypokalaemia' }]
console.log(both.icd10.externalCause); // [{ code: 'W19.9', description: 'Unspecified fall, unspecified place' }]
console.log(both.icd9.principal?.drgStatus); // true / false
console.log(both.icd9.secondary); // []Options — getIcdOpdCode, getIcdIpdCode
| Option | Type | Notes |
| ---------------- | ----------------------------- | --------------------------------------------------------------------------------------- |
| patientProfile | { age?, sex?, isPregnant? } | age ≤ 0 is ignored. With isPregnant: true, sex is required and cannot be 'male' |
| signal | AbortSignal | Cancels the in-flight task |
Searching by keyword
searchIcdOpd and searchIcdIpd perform a catalogue lookup — they are not coding a note, so there
is no primary in the result. Pass ["icd10"] or ["icd9"]; combined is not supported.
// ICD-10 OPD search
const matches = await icd.searchIcdOpd(['icd10'], 'pneumonia', { searchBy: 'text' });
// [{ code: 'J18', description: 'Pneumonia', display: 'J18 Pneumonia' }]
// ICD-9 IPD search
const icd9matches = await icd.searchIcdIpd(['icd9'], '47');
// [{ code: '47.0', description: 'Appendectomy', orStatus: true, drgStatus: false }]Options — searchIcdOpd, searchIcdIpd
| Option | Type | Notes |
| ---------- | --------------------------- | ------------------- |
| searchBy | 'code' \| 'text' \| 'all' | Default 'all' |
| signal | AbortSignal | Cancels the request |
Suggesting related codes
suggestBlockIcdOpd and suggestBlockIcdIpd take a code you already have and return codes commonly
seen alongside it. Pass ["icd10"] or ["icd9"]; combined is not supported.
// ICD-10 OPD — hypertension → codes seen alongside it
const related = await icd.suggestBlockIcdOpd(['icd10'], 'I10');
// [{ code: 'I15.8', description: 'Other secondary hypertension' }]
// ICD-9 IPD — codes alongside an appendectomy
const icd9related = await icd.suggestBlockIcdIpd(['icd9'], '47.0');
// [{ code: '47.1', description: 'Incidental appendectomy', orStatus: true, drgStatus: false }]Options — suggestBlockIcdOpd, suggestBlockIcdIpd
| Option | Type | Notes |
| -------- | ------------- | ------------------- |
| signal | AbortSignal | Cancels the request |
ICD errors
Failures throw the same CarivaError family as ASR — see Errors. Blank input, an empty
structuredRecords list, an invalid patientProfile, an empty modes array, an unknown system
name, or a combined modes on a search/suggest call all throw ValidationError before any request
goes out.
| Call | Error code |
| ------------------------------------ | ------------------------------- |
| getIcdOpdCode(['icd10'], …) | icd10/opd-code-error |
| getIcdIpdCode(['icd10'], …) | icd10/ipd-code-error |
| getIcdOpdCode(['icd9'], …) | icd9/opd-code-error |
| getIcdIpdCode(['icd9'], …) | icd9/ipd-code-error |
| getIcdOpdCode(['icd10','icd9'], …) | icd9-icd10/opd-code-error |
| getIcdIpdCode(['icd10','icd9'], …) | icd9-icd10/ipd-code-error |
| searchIcdOpd(['icd10'], …) | icd10/search-opd-error |
| searchIcdIpd(['icd10'], …) | icd10/search-ipd-error |
| searchIcdOpd(['icd9'], …) | icd9/search-opd-error |
| searchIcdIpd(['icd9'], …) | icd9/search-ipd-error |
| suggestBlockIcdOpd(['icd10'], …) | icd10/suggest-block-opd-error |
| suggestBlockIcdIpd(['icd10'], …) | icd10/suggest-block-ipd-error |
| suggestBlockIcdOpd(['icd9'], …) | icd9/suggest-block-opd-error |
| suggestBlockIcdIpd(['icd9'], …) | icd9/suggest-block-ipd-error |
Chaining: ASR result → Diagnosis Code (ICD)
import { ASR, ICD, extractTranscript } from '@cariva/medical-sdk';
const asr = new ASR({ key, secret, mode: 'doc-ipd-soap' });
const icd = new ICD({ key, secret, baseUrl: 'https://…' });
const asrResult = await asr.process(audio);
// ICD-10 from the transcription
const { primary } = await icd.getIcdOpdCode(['icd10'], extractTranscript(asrResult));
// ICD-9 from the transcription
const { principal } = await icd.getIcdOpdCode(['icd9'], extractTranscript(asrResult));
// Combined ICD-10 + ICD-9 from a structured IPD record
const both = await icd.getIcdIpdCode(
['icd10', 'icd9'],
[{ name: 'Assessment', content: asrResult.data.assessment ?? '' }]
);Requirements
- Node.js ≥ 22 — global
fetch,File,Blob, stable WebCrypto - Browsers — ES2020+ with the Web Audio API
- Bundlers — Vite, webpack, esbuild, Rollup, Next.js, or anything that reads
exports
Migrating from the legacy SDKs
This package replaces @cariva/asr-sdk-browser and @cariva/asr-sdk-node with one package that
runs on both.
npm uninstall @cariva/asr-sdk-browser @cariva/asr-sdk-node
npm install @cariva/medical-sdk- import { ASR } from '@cariva/asr-sdk-browser';
+ import { ASR } from '@cariva/medical-sdk';
const asr = new ASR({
key, secret,
- version: '1',
mode: 'doc-ipd-soap',
});
- const result = await asr.process('/path/to/audio.wav');
+ const bytes = await readFile('/path/to/audio.wav');
+ const result = await asr.process(new Blob([bytes], { type: 'audio/wav' }));
- await asr.cancelASRTask();
+ await asr.cancel();
- await asr.rateASRTask(5, 'good');
+ await asr.rate(5, 'good');| What | Legacy asr-sdk-* | medical-sdk 1.0 |
| ---------------- | ---------------------------------------- | --------------------------------- |
| Package | one per platform | one for both |
| Config version | required | removed |
| Audio on Node | file path string, system lame binary | Blob / File, no binary needed |
| UMD global | window.CarivaASRBrowser | window.CarivaMedicalSDK |
| CDN path | /dist/index.umd.js | /dist/browser/index.umd.js |
Renamed:
| Legacy | Now |
| -------------------- | ---------------------------- |
| cancelASRTask() | cancel() |
| rateASRTask() | rate() |
| clearTask() | (removed — use cancel()) |
| ASRError | CarivaError |
| ASRValidationError | ValidationError |
| ASRProcessingError | ProcessingError |
| Mode | ASRMode |
| MedicalSpecialty | ASRSpecialty |
| TaskStatus | ASRTaskStatus |
| TaskStatusResponse | ASRResult |
| CreateTaskResponse | ASRCreateTaskResponse |
| AsrResponseMap | ASRResponseMap |
| MedClerkData | OPDClinicalRecordData |
Migrating from ICD10 to ICD
The old ICD10 class has been replaced by a unified ICD class that handles ICD-10, ICD-9, and
combined ICD-9+ICD-10 through an optional modes array.
Minimal migration — just swap the class and add baseUrl; all calls work without passing
modes because it defaults to ["icd10"]:
- import { ICD10 } from '@cariva/medical-sdk';
+ import { ICD } from '@cariva/medical-sdk';
- const icd = new ICD10({ key, secret });
+ const icd = new ICD({ key, secret, baseUrl: 'https://…' });
// No other changes needed — modes defaults to ["icd10"]
- const { primary } = await icd.getOpdCode(note);
+ const { primary } = await icd.getIcdOpdCode(note);Explicit modes — recommended when you want to be clear about which system is used or when you need ICD-9 or combined:
- const { primary } = await icd.getOpdCode(note);
+ const { primary } = await icd.getIcdOpdCode(['icd10'], note);
- const { primary } = await icd.getIpdCode(records);
+ const { principal } = await icd.getIcdIpdCode(['icd10'], records);
- const hits = await icd.searchOpd(query, { searchBy: 'text' });
+ const hits = await icd.searchIcdOpd(['icd10'], query, { searchBy: 'text' });
- const related = await icd.suggestBlockOpd(code);
+ const related = await icd.suggestBlockIcdOpd(['icd10'], code);Authentication, the patientProfile option, and every payload shape for ICD-10 are unchanged. The
new ICD-9 fields orStatus and drgStatus only appear when modes includes 'icd9'.
License
Apache-2.0
