Skip to main content
The Sanas Browser SDK lets a web application send a live microphone stream through Sanas’s audio processing (accent translation, speech enhancement, language translation) and receive the processed audio back as a 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

  1. Overview
  2. Installation
  3. Authentication
  4. Quick start
  5. Remote Inferencing mode (accent translation, speech enhancement)
  6. Language Translation mode
  7. The processor lifecycle
  8. API reference
  9. Error handling
  10. Advanced options
  11. 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 MediaStream in (typically from navigator.mediaDevices.getUserMedia).
  • Emits a processed MediaStream out. 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 conversationId so the server can share context between them. Callbacks stream per-utterance transcripts and language-detection hints back.
Availability of RI and LT is controlled per group in the Sanas Developer Console. The SDK checks entitlement before creating a processor and throws a distinct error if a feature isn’t enabled for your group.

Installation

The package is self-contained — no peer dependencies. Works with any modern bundler (Vite, webpack, Next.js, Rollup, Parcel). Browser support: Any browser that supports MediaStream, RTCPeerConnection, and WebSocket — Chrome 90+, Firefox 90+, Safari 15+, Edge 90+.

Authentication

The SDK never sees your Sanas API key. Instead, it accepts a tokenProvider 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:
  1. Authenticates the browser request using your app’s own session mechanism (cookies, JWT, etc. — that’s your call).
  2. Calls Sanas’s OAuth token endpoint with your stored API key.
  3. Returns the token payload to the browser as JSON.
Here’s a minimal Node.js example (no dependencies beyond Node 18+):
Run it with 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.
Common errors:
  • 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 shared conversationId. 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").
Give both processors the same 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).
How the stream evolves for one utterance. 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:
Trailing punctuation typically arrives in its own event a second after the last word — plan your UI to accept it gracefully rather than assuming the utterance is done as soon as 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 with onTranscript events for the same utterance.
  • lang_in / lang_out — the concrete input and output languages for this utterance.
  • is_finaltrue when the server has locked in the routing for this utterance. In wildcard-mode edge cases the server may emit tentative routes with is_final: false before it commits; a subsequent event with the same utterance_idx and is_final: true supersedes the earlier one.
Fires for every utterance in both normal and wildcard mode. In wildcard mode, set 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:
Usage attribution correctly rotates across language changes — the SDK bills each language segment separately without losing history when you switch back. Common error:
  • 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 via onStateChange:
  • Initializing — the SDK is negotiating the media session. outputStream exists and is silent.
  • Ready — the media session is up. outputStream is now delivering processed audio.
  • Failed — a fatal error occurred. Read reason for a short human-readable explanation. The processor cannot be recovered; destroy it and create a new one.
  • Disconnected — the consumer called destroyAudioProcessor. Terminal, expected.
The 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

This is exactly the shape returned by Sanas’s OAuth login endpoint — your backend can forward the response body verbatim.

AudioProcessorParams

A discriminated union:

ProcessorCallbacks

The two JSON callbacks fire only for LT processors. The payload is a raw JSON string — parse with 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 of SdkError with a machine-readable code and a human-readable message.

InitErrorCode

CreateProcessorErrorCode

Runtime state changes

Once a processor is created, transient failures surface through onStateChange(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:
Rates outside the supported set throw 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 in InitParams or per processor:

Swapping input mid-call

Users unplug headsets or switch devices. Update in place instead of tearing down:
The active call stays up; the outgoing track is swapped.

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

Set logLevel: "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 **initialize
Your 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.