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 directScribeClientover 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 Ekaenv/clientIdhandling. Use it only if you want those extras — see Eka Care wrapper (advanced) at the end.
Prerequisites
- Node 14+
npmoryarn- Microphone access via browser permissions
- Stable network connectivity
- An access token from Eka Care
Installation
@ricky0123/vad-web— Voice Activity Detection@breezystack/lamejs— MP3 encodingzod— Schema validation
Integration Guide (Step-by-Step)
Step 1: Create the Client
ThebaseUrl 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:
| Environment | baseUrl |
|---|---|
| Production | https://api.eka.care/voice/v1 |
| Development | https://api.dev.eka.care/voice/v1 |
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()callsinit()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
baseUrlis the root for all API calls. Session creation, audio upload, status polling — everything uses this URL. Make sure it’s correct and accessible.accessTokenmust be a valid Bearer token. All API requests includeAuthorization: Bearer <token>. If it expires, registeronTokenRequiredto 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. UsecancelSession()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. NoendSessioncall is made to the backend.- All async methods return
SDKResult<T>, never throw. Always checkresult.successbefore accessingresult.data. Errors are inresult.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
ValidationErrorbefore 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 viaonErrorcallback. 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 callinit()(orstartRecording()) again after reset.- Polling supports
AbortSignal. Passsignalin 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
| Method | Returns | Description |
|---|---|---|
init() | SDKResult<void> | Fetch discovery document. Called automatically by startRecording. |
reset() | Promise<void> | Stop recording, clear all state and caches. |
Recording
| Method | Returns | Description |
|---|---|---|
startRecording(options) | SDKResult<CreateSessionResponse> | Create session + start mic + begin upload. |
startRecordingWithSession(session, options?) | SDKResult<void> | Attach recorder to an existing session. |
pauseRecording() | void | Pause VAD (mic stays open, no chunks created). |
resumeRecording() | void | Resume VAD processing. |
endRecording() | SDKResult<StopRecordingResult> | Stop mic, flush audio, wait for uploads, end session. |
isRecording() | boolean | Whether a recording is active. |
isRecordingPaused() | boolean | Whether the active recording is paused. |
retryFailedUploads() | SDKResult<RetryUploadResult> | Retry uploads that failed during the last recording. |
hasFailedUploads() | boolean | Whether there are retryable failed uploads. |
Session
| Method | Returns | Description |
|---|---|---|
createSession(request) | SDKResult<CreateSessionResponse> | Create a session without starting a recording. |
getSessionStatus(sessionId?, options?) | SDKResult<GetSessionStatusResponse> | Get status. Supports poll and templateId options. |
getCurrentSession() | CreateSessionResponse | null | Get the active session if any. |
updateSession(request, sessionId?) | SDKResult<PatchSessionResponse> | Patch session (patient details, status, etc.). |
cancelSession(sessionId?) | SDKResult<PatchSessionResponse> | Cancel session (stops recorder, no server processing). |
Discovery
| Method | Returns | Description |
|---|---|---|
getDiscoveryDocument() | DiscoveryDocument | null | Raw discovery document. |
getDiscoveryConfig() | SDKResult<ResolvedConfig> | Resolved config from discovery. |
refreshDiscovery() | SDKResult<ResolvedConfig> | Force-refresh discovery. |
Auth
| Method | Description |
|---|---|
setAccessToken(token) | Update Bearer token. Propagates to transport, recorder, and worker. |
Callbacks
Register withclient.registerCallback(name, handler), remove with client.removeCallback(name, handler).
| Callback | Payload | Description |
|---|---|---|
onRecordingStateChange | RecordingStateChangeEvent | Recording started, paused, resumed, or ended. |
onAudioEvent | AudioEvent | Speech detection, silence warnings, chunk ready. |
onUploadEvent | UploadEvent | Upload progress and failures. |
onSessionEvent | SessionEvent | Session created, ended, status updates. |
onError | ErrorEvent | VAD, worker, transport, or validation errors. |
onTokenRequired | TokenRequiredEvent | 401 received — call event.resolve(newToken) to retry. |
Payload Shapes
Request / Response Types
Session
Recording
Error Handling
All public async methods returnSDKResult<T> — errors are returned, not thrown:
Error Classes
| Error | HTTP | Description |
|---|---|---|
ScribeError | — | Base error class |
ValidationError | 400 | Invalid request or config |
AuthenticationError | 401 | Auth failed (after token refresh attempt) |
ForbiddenError | 403 | Access denied |
SessionNotFoundError | 404 | Session doesn’t exist |
SessionExpiredError | 410 | Session expired |
RateLimitError | 429 | Rate limit exceeded |
DiscoveryError | — | Discovery fetch/parse failed |
TransportError | — | Network / IPC failure |
WorkerError | — | SharedWorker failure |
UploadError | — | Audio upload failure |
SharedWorker Support
The SDK offloads MP3 compression and upload to a SharedWorker for better main-thread performance. The worker is bundled separately asdist/worker.bundle.js.
Setup
Serving the Worker
The worker file must be served as a static asset: Copy to your public directory:Electron / IPC Mode
For Electron apps where network requests must go through the main process: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.
Alliance → wrapper method map
| Operation | Alliance (ScribeClient) | Eka wrapper |
|---|---|---|
| Create client | new ScribeClient(config) | getEkaScribeInstance(config) (singleton) |
| Start recording | startRecording() → SDKResult | startRecordingV2() → TStartRecordingResponse (txn_id, error_code) |
| Token refresh | onTokenRequired → event.resolve(token) | onTokenRequired → return token |
| Retry uploads | retryFailedUploads() | retryUploadRecording() |
| Teardown | reset() | resetInstance() |
| Get output | getSessionStatus(id, { poll }) | getSessionStatus(id, { poll }) (same) |
Initialize the wrapper (getEkaScribeInstance)
Initialize the wrapper (getEkaScribeInstance)
The wrapper uses a singleton pattern —
Next: register callbacks and run a recording with the wrapper methods — see the accordion below.
getEkaScribeInstance() 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:| Environment | baseUrl |
|---|---|
| Production | https://api.eka.care/voice/v1 |
| Development | https://api.dev.eka.care/voice/v1 |
- Calling
getEkaScribeInstance()again with the same config returns the same instance. - If
envorclientIdchanges, the old instance is automatically reset. - If only
access_tokenchanges, the token is updated without resetting. - One active recording at a time. Call
endRecording()orcancelSession()before starting a new one.
Complete a recording (wrapper methods)
Complete a recording (wrapper methods)
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 (Step 2 — Start recording with Step 3 — Pause / Resume (optional, during recording):Step 4 — End recording with Step 5 — Poll for results with Step 6 — Cancel instead of end (optional) — stops recording without triggering processing:Step 7 — Clean up —
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():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.endRecording(). On audio_upload_failed, retry with retryUploadRecording():getSessionStatus() (same shape as the Alliance SDK — returns SDKResult):resetInstance() tears down the singleton (clears state, destroys the widget, removes callbacks). Call getEkaScribeInstance() again afterwards:Full example
Bundler setup (Vite / Webpack / Next.js)
Bundler setup (Vite / Webpack / Next.js)
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 (Browser (script tag):SharedWorker URL — pass
new URL(..., import.meta.url) is natively supported).Next.js — ensure the SDK is used only on the client:sharedWorkerUrl in config. Resolve it with the wrapper’s helper:Prebuilt Widget (zero-UI recording)
Prebuilt Widget (zero-UI recording)
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 The widget handles
startForPatient() per patient — the widget appears and the user drives it (pause, resume, stop). Results arrive via callbacks.startRecordingV2(), pauseRecording(), resumeRecording(), endRecording(), and getSessionStatus() internally.Widget state flow:Session & document utilities
Session & document utilities
Wrapper-only helpers for fetching session history, details, and documents.Config — call Response: Discovery —
getSessionHistory(request) — fetch previous sessions.getSessionDetails(request) — detailed info including documents, context, and presigned URLs.Presigned URLs are temporary — checkpresigned_url_expires_at(epoch) before using. CallgetDocument(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.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.TGetConfigV2Responseekascribe.sessions.getDiscoveryDocument() mirrors the Alliance discovery method.Pre-recorded audio upload
Pre-recorded audio upload
Upload a pre-recorded audio file instead of live recording, for non-real-time flows.
- Create session via
ekascribe.sessions.createSession() - Upload audio via
processPreRecordedAudio() - End session via
ekascribe.sessions.endSession()
Authentication (updateAuthTokens)
Authentication (updateAuthTokens)
Manually update the access token — propagates to all internal transports and the worker.
If you haveonTokenRequiredregistered, the wrapper handles 401s automatically. You only needupdateAuthTokens()for proactive token rotation (e.g., before expiry).
Wrapper error codes
Wrapper error codes
The wrapper’s
TStartRecordingResponse / TEndRecordingResponse carry an error_code field (unlike the Alliance SDKResult error classes).| Error Code | Description |
|---|---|
microphone | Microphone access error (permission denied or unavailable) |
txn_init_failed | Failed to initialize session |
txn_limit_exceeded | Maximum concurrent sessions exceeded |
internal_server_error | Unexpected server-side error |
end_recording_failed | Failed to end recording |
audio_upload_failed | Audio file upload to server failed |
txn_commit_failed | Commit call failed |
txn_status_mismatch | Invalid operation for current session state |
network_error | Network connectivity issue |
unknown_error | Unclassified error |
unauthorized | Authentication failed (invalid or expired token) |
forbidden | Insufficient permissions |
Deprecated wrapper methods
Deprecated wrapper methods
These methods are from older wrapper versions. They still work but are not recommended for new integrations.
| Deprecated Method | Use Instead |
|---|---|
initTransaction() + startRecording() | startRecordingV2() |
getTemplateOutput() | getSessionStatus() with polling |
getOutputTranscription() | getSessionStatus() with polling |
commitTransactionCall() | Handled automatically by endRecording() |
stopTransactionCall() | Handled automatically by endRecording() |
Refer to the MedScribe Alliance Protocol, the MedScribe Alliance TS SDK, and the EkaScribe TS SDK repositories for implementation details.

