Skip to main content
Capture and process audio in the browser and generate structured medical documentation through Eka Care’s voice transcription service.
Two packages, one protocol. There are two ways to integrate in TypeScript:
  • med-scribe-alliance-ts-sdk — the open-source MedScribe Alliance Protocol SDK. A direct ScribeClient over the protocol: discovery, recording, chunked upload, session lifecycle, output retrieval. This is the recommended integration and the main guide below.
  • @eka-care/ekascribe-ts-sdk — an Eka Care convenience wrapper built on top of the same protocol. Adds a prebuilt recording Widget, session/document utilities (history, details, documents), and Eka env/clientId handling. Use it only if you want those extras — see Eka Care wrapper (advanced) at the end.
Both speak the same backend. Start with the Alliance SDK; reach for the wrapper if you need the widget or EMR session utilities.

Prerequisites

  • Node 14+
  • npm or yarn
  • Microphone access via browser permissions
  • Stable network connectivity
  • An access token from Eka Care

Installation

Peer dependencies (installed automatically):
  • @ricky0123/vad-web — Voice Activity Detection
  • @breezystack/lamejs — MP3 encoding
  • zod — Schema validation
npm package →

Integration Guide (Step-by-Step)

Step 1: Create the Client

The baseUrl is required — every API call (session creation, upload, status polling) goes through it. If you leave it out, the SDK throws allianceConfig.baseUrl is required at runtime. To use Eka Care’s hosted scribe service, use:
Production APIs require a secure (HTTPS) origin. They will not work from http:// or http://localhost — an insecure origin fails the CORS preflight (the authorization header is rejected).Recommended: Use ngrok to give your local server a public HTTPS URL and test against the production baseUrl directly.Alternatively, point at the staging baseUrl (https://api.dev.eka.care/voice/v1) which works from plain localhost.

Step 2: Initialize (Discovery)

init() fetches the discovery document from the server. This tells the SDK what the server supports (models, languages, upload methods, audio formats, etc.).
startRecording() calls init() automatically if not already initialized. You can skip this step if you go directly to recording.

Step 3: Register Callbacks

Register callbacks before starting a recording. These are how you receive events from the SDK.

Step 4: Start Recording

Creates a session, starts the microphone, and begins chunked upload in one call.
Use clinical_notes_template for testing, or contact Eka Care to create a custom template for your use case.

Pause / Resume

Step 5: End Recording

Stops the microphone, flushes the last audio chunk, waits for all uploads to complete, and tells the server the session has ended (triggers server-side processing).

Step 6: Poll for Results

After ending the recording, poll the server until processing is complete.

Step 7: Clean Up

Flow Diagram


Important Notes

  • baseUrl is the root for all API calls. Session creation, audio upload, status polling — everything uses this URL. Make sure it’s correct and accessible.
  • accessToken must be a valid Bearer token. All API requests include Authorization: Bearer <token>. If it expires, register onTokenRequired to auto-refresh.
  • Register callbacks before startRecording(). Events fire immediately once recording starts — if callbacks aren’t registered, you’ll miss upload progress and errors.
  • endRecording() triggers server processing. Once you call it, the server begins processing the uploaded audio. Use cancelSession() instead if you don’t want processing to happen.
  • cancelSession() does NOT trigger processing. It stops the recorder locally, cleans up state, and tells the server the session is cancelled. No endSession call is made to the backend.
  • All async methods return SDKResult<T>, never throw. Always check result.success before accessing result.data. Errors are in result.error.
  • The SDK validates inputs against the discovery document. If the server doesn’t support an upload type, language, or model you requested, you’ll get a ValidationError before the API call is made.
  • SharedWorker is optional. If you provide workerScriptUrl, the SDK offloads MP3 compression and upload to a SharedWorker. If the worker fails to load, it silently falls back to main-thread processing.
  • Microphone permission is requested on startRecording(). The browser will prompt the user for mic access. If denied, you’ll get an error via onError callback.
  • reset() is a full teardown. It destroys the transport, clears discovery cache, removes all callbacks, and sets the client back to uninitialized state. You’ll need to call init() (or startRecording()) again after reset.
  • Polling supports AbortSignal. Pass signal in poll options to cancel polling early (e.g. when the user navigates away).

Other Operations

Cancel a Session

Stops the recorder locally without triggering server-side processing, then tells the server the session is cancelled.

Update a Session (Patch)

Update session properties after creation.

Two-Step Flow (Create Session + Record Separately)

Get Status for a Specific Template

Retry Failed Uploads

Update Auth Token


Configuration

Recording Options

API Reference

Lifecycle

Recording

Session

Discovery

Auth

Callbacks

Register with client.registerCallback(name, handler), remove with client.removeCallback(name, handler).

Payload Shapes

Request / Response Types

Session

Recording

Error Handling

All public async methods return SDKResult<T> — errors are returned, not thrown:

Error Classes

SharedWorker Support

The SDK offloads MP3 compression and upload to a SharedWorker for better main-thread performance. The worker is bundled separately as dist/worker.bundle.js.

Setup

Serving the Worker

The worker file must be served as a static asset: Copy to your public directory:
Or use a CDN blob URL (avoids same-origin restrictions):
Or set a global override:
If the SharedWorker fails to initialize, the SDK silently falls back to main-thread compression and upload.

Electron / IPC Mode

For Electron apps where network requests must go through the main process:
IPC mode always uses main-thread compression (SharedWorker can’t access the IPC bridge).

Eka Care Wrapper (Advanced)

You only need this section if you want the wrapper-only features: the prebuilt recording Widget, session/document utilities (history, details, documents), or Eka env/clientId handling. For everything else, the Alliance ScribeClient above is the recommended path.
@eka-care/ekascribe-ts-sdk is an Eka Care convenience wrapper over the same protocol. It exposes a singleton instance instead of new ScribeClient(), and the recording flow mirrors the Alliance API with slightly different method names and return shapes.
npm package →

Alliance → wrapper method map

The wrapper uses a singleton patterngetEkaScribeInstance() always returns the same instance for a given env + clientId combination.allianceConfig.baseUrl is required — omitting it throws [EkaScribe] allianceConfig.baseUrl is required at runtime. To use Eka Care’s hosted scribe service, use:
Production APIs require a secure (HTTPS) origin — they will not work from http://localhost. Recommended: use ngrok to tunnel your local server over HTTPS and test against production directly. Alternatively, point at the development baseUrl (https://api.dev.eka.care/voice/v1) which works from plain localhost.
  • Calling getEkaScribeInstance() again with the same config returns the same instance.
  • If env or clientId changes, the old instance is automatically reset.
  • If only access_token changes, the token is updated without resetting.
  • One active recording at a time. Call endRecording() or cancelSession() before starting a new one.
Next: register callbacks and run a recording with the wrapper methods — see the accordion below.
A full session with the wrapper, end to end. The steps mirror the Alliance flow but use the wrapper’s method names and return shapes (startRecordingV2, resetInstance, error_code/txn_id instead of SDKResult).Step 1 — Register callbacks (before recording). The wrapper’s onTokenRequired returns the token instead of calling event.resolve():
Step 2 — Start recording with startRecordingV2(). Returns TStartRecordingResponse — check error_code, read the session id from txn_id:
Use clinical_notes_template for testing, or contact Eka Care to create a custom template for your use case. Call ekascribe.sessions.getConfig() (see the Session & document utilities accordion) to list the templates enabled for your account.
Step 3 — Pause / Resume (optional, during recording):
Step 4 — End recording with endRecording(). On audio_upload_failed, retry with retryUploadRecording():
Step 5 — Poll for results with getSessionStatus() (same shape as the Alliance SDK — returns SDKResult):
Step 6 — Cancel instead of end (optional) — stops recording without triggering processing:
Step 7 — Clean upresetInstance() tears down the singleton (clears state, destroys the widget, removes callbacks). Call getEkaScribeInstance() again afterwards:

Full example

The wrapper uses a SharedWorker for background audio uploads. Modern bundlers handle this automatically.Vite — works out of the box. Webpack 5 — works out of the box (new URL(..., import.meta.url) is natively supported).Next.js — ensure the SDK is used only on the client:
Browser (script tag):
SharedWorker URL — pass sharedWorkerUrl in config. Resolve it with the wrapper’s helper:
The wrapper provides an optional pre-built recording UI injected via Shadow DOM — you write zero UI code.Step 1: Enable the widget in config with session defaults and callbacks:
Step 2: Call startForPatient() per patient — the widget appears and the user drives it (pause, resume, stop). Results arrive via callbacks.
The widget handles startRecordingV2(), pauseRecording(), resumeRecording(), endRecording(), and getSessionStatus() internally.Widget state flow:
Wrapper-only helpers for fetching session history, details, and documents.getSessionHistory(request) — fetch previous sessions.
getSessionDetails(request) — detailed info including documents, context, and presigned URLs.
Presigned URLs are temporary — check presigned_url_expires_at (epoch) before using. Call getDocument(documentId) for a fresh URL if expired.
getDocument(documentId) — fetch a single document by ID (and a fresh presigned URL).
patchSessionStatus(request, sessionId?) — update session properties.
Config — call ekascribe.sessions.getConfig() to fetch your account config: supported languages, output formats, consultation modes, and my_templates (the valid template_id values to pass when starting a recording). clinical_notes_template is a ready-to-use testing template; contact Eka Care for a custom one.
Response: TGetConfigV2Response
Discoveryekascribe.sessions.getDiscoveryDocument() mirrors the Alliance discovery method.
Upload a pre-recorded audio file instead of live recording, for non-real-time flows.
  1. Create session via ekascribe.sessions.createSession()
  2. Upload audio via processPreRecordedAudio()
  3. End session via ekascribe.sessions.endSession()
Manually update the access token — propagates to all internal transports and the worker.
If you have onTokenRequired registered, the wrapper handles 401s automatically. You only need updateAuthTokens() for proactive token rotation (e.g., before expiry).
The wrapper’s TStartRecordingResponse / TEndRecordingResponse carry an error_code field (unlike the Alliance SDKResult error classes).
These methods are from older wrapper versions. They still work but are not recommended for new integrations.

Refer to the MedScribe Alliance Protocol, the MedScribe Alliance TS SDK, and the EkaScribe TS SDK repositories for implementation details.