MediaStream. It runs entirely in the browser and is transport-agnostic from your point of view: give it an input stream, receive an output stream.
This guide covers everything a developer needs to ship an integration. If you get stuck, jump to Troubleshooting.
Contents
- Overview
- Installation
- Authentication
- Quick start
- Remote Inferencing mode (accent translation, speech enhancement)
- Language Translation mode
- The processor lifecycle
- API reference
- Error handling
- Advanced options
- Troubleshooting
Overview
The SDK exposes a single top-level object —SanasSDK — that manages a session. From that session you create one or more AudioProcessor instances, each of which:
- Takes a
MediaStreamin (typically fromnavigator.mediaDevices.getUserMedia). - Emits a processed
MediaStreamout. You can attach the output to an<audio>element, feed it into WebRTC, or hand it to any other Web Audio API consumer. - Runs one of two modes:
- Remote Inferencing (RI) — a single Sanas model applied to the stream. Used for accent translation, speech enhancement, and other single-model features. You identify the model by name.
- Language Translation (LT) — one-direction live translation. Each processor handles a single direction (say English → Spanish). A real two-person conversation uses two processors — one per speaker’s direction — linked by a shared
conversationIdso the server can share context between them. Callbacks stream per-utterance transcripts and language-detection hints back.
Installation
MediaStream, RTCPeerConnection, and WebSocket — Chrome 90+, Firefox 90+, Safari 15+, Edge 90+.
Authentication
The SDK never sees your Sanas API key. Instead, it accepts atokenProvider callback that asks your backend for a short-lived access token, and your backend uses its stored API key to mint one.
Never embed a Sanas API key in browser code. Anyone can view your bundle, extract the key, and use it against your usage budget.
Backend responsibilities
Your backend needs a single endpoint (name it whatever you want) that:- Authenticates the browser request using your app’s own session mechanism (cookies, JWT, etc. — that’s your call).
- Calls Sanas’s OAuth token endpoint with your stored API key.
- Returns the token payload to the browser as JSON.
SANAS_API_KEY=... node server.js.
Browser side
tokenProvider is called once at initialize() and again any time the SDK needs a fresh token. Make it idempotent and quick — the SDK will time it out after 30 seconds.
Origin allowlisting
Every group in the Sanas Developer Console has an allowed origins list. All API calls the SDK makes from the browser include the page’s origin; requests from a non-allowlisted origin are rejected. Add every origin your app runs on (production, staging, localhost during development) under Project settings → Allowed origins.Quick start
Remote Inferencing mode
For accent translation, speech enhancement, and similar single-model features. The model you use is provisioned in your Developer Console; the SDK verifies your group is entitled to the model before starting the call.CreateProcessorErrorCode.FeatureNotEntitled— Your group doesn’t have Remote Inferencing enabled at all. Contact your Sanas administrator.CreateProcessorErrorCode.ModelNotEntitled— Your group has RI but not this specific model. Verify the exact name against the console.
Language Translation mode
Each LT processor handles one direction of translation — one input language, one output language. A processor delivers translated audio out and streams two kinds of side-channel data via callbacks:onTranscript— a per-utterance transcript with partial and complete words for the detected input language.onLanguageRoute— a per-utterance language-detection result telling you which input language the server detected on that utterance (and which output language it will render).
Two-person conversations use two processors
For a real conversation between two people who speak different languages, run two processors on the same page, one per direction, linked by a sharedconversationId. For example, an English-speaking agent and a Spanish-speaking customer on the same call:
- Processor A: agent’s mic (
languageIn: "en-US") → Spanish audio played to the customer (languageOut: "es-ES"). - Processor B: customer’s mic (
languageIn: "es-ES") → English audio played to the agent (languageOut: "en-US").
conversationId (any UUID you generate per call) and the server shares translation context between them — proper nouns, jargon, and prior context stay consistent across both directions.
conversationId is optional. Omit it if you’re only running a single direction (e.g. a monologue translation) and don’t need context sharing.
Single-direction usage
If you only need to translate one direction (for example, translating a broadcast into another language), a single processor is enough:onTranscript payload
Delivered as a JSON string; parse before use. Fires multiple times per utterance as the server refines its hypothesis. Each event carries a mix of complete (newly-finalized words) and partial (still-in-flight words).
partial is the current best guess and is replaced wholesale each event as the server refines it. complete is a delta of newly-finalized words in this event only — it is not the running total of everything finalized so far. To get the full utterance you must accumulate complete per utterance_idx on your side. end / start timestamps are reserved for future use and can be treated as 0.
For example, an utterance like “Hi, testing of language translation.” arrives across several events. The complete deltas are [], [], ["Hi", ",", " testing", " of"], [" language"], [" translation"], ["."] — concatenating those deltas rebuilds the full sentence.
Minimal accumulator:
partial empties.
utterance_idx starts at 0 and increments per utterance. It correlates with onLanguageRoute events so you can label transcripts with the language the server detected.
onLanguageRoute payload
Fires once at the start of each utterance, before the transcript events for that utterance begin arriving. Tells you the input language the server detected on that utterance and the output language it’s rendering into.
utterance_idx— correlates withonTranscriptevents for the same utterance.lang_in/lang_out— the concrete input and output languages for this utterance.is_final—truewhen the server has locked in the routing for this utterance. In wildcard-mode edge cases the server may emit tentative routes withis_final: falsebefore it commits; a subsequent event with the sameutterance_idxandis_final: truesupersedes the earlier one.
languageIn or languageOut to "*" when creating the processor and the server detects the actual language for you; the value you get here is the concrete language the server chose. In normal mode (fixed languageIn / languageOut), the values echo back what the server confirmed for that utterance.
Changing languages mid-call
Rather than tearing down and recreating the processor (which drops the call), swap languages in place:CreateProcessorErrorCode.FeatureNotEntitled— Language Translation isn’t enabled for your group. Contact your Sanas administrator.
The processor lifecycle
Every processor moves through a small state machine, observable viaonStateChange:
- Initializing — the SDK is negotiating the media session.
outputStreamexists and is silent. - Ready — the media session is up.
outputStreamis now delivering processed audio. - Failed — a fatal error occurred. Read
reasonfor a short human-readable explanation. The processor cannot be recovered; destroy it and create a new one. - Disconnected — the consumer called
destroyAudioProcessor. Terminal, expected.
outputStream reference is stable for the life of the processor — you can attach it to an <audio> element or WebRTC track before the processor reaches Ready, and it will start emitting audio automatically once the media path opens.
API reference
SanasSDK
initialize(params)
Establish a session with your token provider and validate your group’s entitlements. Must be called before createAudioProcessor. Calling initialize twice throws InitErrorCode.AlreadyInitialized.
createAudioProcessor(params, callbacks?)
Create a processor for a single audio stream. You can hold multiple active processors on one SDK instance (each processes an independent stream).
Wire callbacks at construction — they are guaranteed to fire even for the initial Initializing → Failed transition on setup errors.
destroyAudioProcessor(processor)
Gracefully tear down a single processor. The output stream ends. Emits a final usage snapshot before returning so that usage accounting is complete.
shutdown()
Destroy all live processors, stop background reporting, clear all session state. Call this on page unload or when your app no longer needs Sanas. After shutdown, you can call initialize again to start a new session.
InitParams
TokenResponse
AudioProcessorParams
A discriminated union:
ProcessorCallbacks
JSON.parse on your side.
IAudioProcessor
updateInputStream swaps the mic mid-call (device change, headset unplug) without dropping the session.
updateLanguageConfig throws if called on a model-mode processor.
ProcessorState
Error handling
All SDK errors are instances ofSdkError with a machine-readable code and a human-readable message.
InitErrorCode
CreateProcessorErrorCode
Runtime state changes
Once a processor is created, transient failures surface throughonStateChange(Failed, reason), not by throwing. Wire the callback at construction so you never miss an early failure:
Advanced options
Sample rate
Default is 16000 Hz. Supported: 8000 / 16000 / 48000. Set per processor:UnsupportedSampleRate.
Jitter buffer
For bumpy networks, increase the receive-side buffer to smooth playback at the cost of extra latency. Default is chosen by the SDK; override globally inInitParams or per processor:
Swapping input mid-call
Users unplug headsets or switch devices. Update in place instead of tearing down:Multiple concurrent processors
You can run more than one processor per SDK instance — e.g. a mic in RI mode and a system-audio stream in LT mode. Each is fully independent.Logging
SetlogLevel: "debug" in InitParams to see the SDK’s internal logs in DevTools. Default is warn. Use silent in production if you want no console output.
Troubleshooting
**“Origin not allowed” during **initializeYour page’s origin (protocol + host + port) isn’t in the group’s allowed list. Add it under Project settings → Allowed origins. Note that
http://localhost:5173 and http://localhost:3000 are different origins — add every dev port you use.
Processor reaches Failed immediately with “Server rejected the call (auth failure)“The access token expired between
tokenProvider returning it and the media session opening, or your backend returned a stale token.
Processor reaches Failed mid-call with “Media connection lost”Network dropped. The SDK does not auto-reconnect a lost media session. Destroy the processor and create a new one. Metrics appear delayed
The SDK batches usage reports on a 1-minute cadence. If you tear down a processor, the final snapshot is captured before the underlying session closes.