Install @trelis/converse from npm. To hear Converse before integrating, open the playground.
Browser quickstart
1. Install the SDK
npm install @trelis/converse
2. Create a persistent key
Sign in to API & Billing and create a key. Keys begin with ck_ and are shown once. Store the key as a server-side secret; never put it in browser JavaScript.
3. Add a credential route to your backend
Your authenticated backend exchanges its persistent key for a short-lived credential bound to one browser session:
import { randomUUID } from 'node:crypto';
app.post('/voice/session', requireUser, async (_req, res) => {
const upstream = await fetch('https://converse.trelis.com/api/v1/session-keys', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.CONVERSE_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ session_id: randomUUID() }),
});
res.status(upstream.status).json(await upstream.json());
});
Protect this route with your own user authentication. The browser should receive only the returned scoped credential.
4. Connect from the browser
import { ConverseClient } from '@trelis/converse';
const response = await fetch('/voice/session', {
method: 'POST', credentials: 'same-origin',
});
if (!response.ok) throw new Error(`Voice credential failed: ${response.status}`);
const credential = await response.json();
const client = new ConverseClient({
url: 'wss://converse.trelis.com/ws',
sessionId: credential.session_id,
apiKey: credential.api_key,
mode: { kind: 'converse' },
});
client.addEventListener('utterance', ({ detail }) => {
console.log('Assistant:', detail.text);
});
client.addEventListener('error', ({ detail }) => {
console.error(detail.detail || detail.error);
});
startButton.addEventListener('click', async () => {
await client.unlockAudio(); // keep this inside the user gesture
await client.connect();
await client.startMic();
});
stopButton.addEventListener('click', () => client.close());
Prepare the scoped credential before enabling the Start button. If it has sat unused for more than 10 minutes, mint a fresh one so the complete two-hour session window remains available.
Authentication
| Credential | Use | Where it belongs |
|---|---|---|
Persistent ck_… key | Create scoped browser credentials; connect trusted Python or WebSocket clients. | Backend or trusted service only. |
| Scoped session key | Authenticate connections for one bound session_id until the key expires. | May be returned to the browser. |
POST /api/v1/session-keys
Exchange a persistent key for a browser-safe session credential.
POST https://converse.trelis.com/api/v1/session-keys
Authorization: Bearer ck_your_key
Content-Type: application/json
{"session_id":"your-unique-session-id"}
HTTP/1.1 201 Created
{
"api_key": "short-lived-session-key",
"session_id": "your-unique-session-id",
"expires_in": 7800
}
| Status | Meaning |
|---|---|
201 | Credential created. |
400 | Invalid or missing session_id. |
401 | Persistent key missing, unknown or revoked. |
415 | Request is not JSON. |
The scoped key expires after 130 minutes and works only with the returned session_id. It may be reused for sequential connections or reconnects carrying that ID until expiry; expiry also ends a connection still using it. Revoking the persistent key prevents new exchanges but does not invalidate scoped keys already issued.
session_id is 1–64 characters. The first character must be a letter, number, underscore or hyphen; later characters may also contain a period. In regex form: [A-Za-z0-9_-][A-Za-z0-9._-]{0,63}.
Browser SDK reference
ConverseClient(options)
| Option | Type / default | Description |
|---|---|---|
url | string, required | Use wss://converse.trelis.com/ws. |
sessionId | string, generated | Must match a scoped key's bound session ID. |
apiKey | string | Scoped browser key or, in trusted environments only, a persistent key. |
mode | {kind:'converse'} | Conversation configuration; see Mode options below. |
user | string | Optional stable identifier for your user. It is metadata, not authentication. |
timezone | string | Optional IANA timezone such as Europe/Dublin. |
player | StreamingPlayer | Optional caller-supplied playback implementation. |
playAcknowledgements | boolean, true | Automatically play short assistant acknowledgements. |
autoReconnect | boolean, true | Reconnect after abnormal transport loss. A reconnect starts a new conversation. |
reconnectBaseMs | number, 500 | Initial reconnect delay. |
reconnectMaxMs | number, 5000 | Maximum reconnect delay. |
maxReconnectAttempts | number, 12 | Attempts before a terminal error. |
rawAssist | boolean, false | Support diagnostic for comparing processed and unprocessed microphone audio. Leave disabled unless Converse support asks you to enable it. |
Converse mode
mode: {
kind: 'converse',
voice: 'optional-voice-key',
instructions: 'Optional application instructions',
greeting: 'Hello!', // string, false, or omit for the default
web_search: false,
flow: false,
tools: [],
temperature: 0.7,
}
web_search defaults to false. Converse sessions may combine it with client tools; search joins the same automatic tool-selection loop as a managed lookup.
Methods
| Method | Returns | Description |
|---|---|---|
unlockAudio() | Promise | Unlocks browser playback. Call inside the Start button's user gesture. |
connect(options?) | Promise<ConverseClient> | Connects and resolves after ready. Options: temperature, noGreeting. |
startMic({workletUrl?, sdkAec?}) | Promise<object> | Starts SDK-owned capture. sdkAec is 'auto', true or false; keep the default unless Converse asks you to override it. |
stopMic() | Promise | Releases SDK-owned microphone resources without closing the session. |
setMicEnabled(enabled) | void | Temporarily gates SDK-owned microphone tracks. |
reset() | Promise | Clears playback and starts a fresh conversation on the same connection. |
setVoice(key) | void | Changes the voice beginning with the next assistant reply. |
close() | void | Stops capture and playback and closes the socket. |
closeAndWait(timeoutMs?) | Promise | Closes and waits for the WebSocket close handshake. |
pushMicFrame(frame) | void | Advanced: sends a caller-owned Float32 mono 16 kHz frame on an already-live session. |
appendAudio(frame) | Promise | Advanced: connects if needed, then sends one caller-owned Float32 frame. |
sendRawFrame(frame) | void | Support diagnostic: sends a synchronized unprocessed frame when rawAssist is enabled. |
sendAmbienceState(active) | void | Records whether your client-side ambience layer is active; it does not alter server audio. |
Properties: client.mode is the active mode configuration; client.responding is true while an assistant reply is active.
One-shot helpers
sendFeedback({url, sessionId, rating?, text?, device?, browser?, apiKey?})sendClientError({url, sessionId?, detail, context?, apiKey?})
Both return promises and use a standalone WebSocket, so they can be called after the conversation socket has closed.
Advanced named exports are StreamingPlayer, MicCapture, EchoCanceller, needsSdkAec, audio conversion helpers, tagged-uplink helpers, and sample-rate/channel constants. Most applications should use ConverseClient rather than assembling these pieces.
Microphone, playback and echo cancellation
startMic() requests mono audio with echo cancellation enabled and noise suppression and automatic gain control disabled. The SDK chooses the available echo-cancellation path and feeds 16 kHz audio to Converse. Interruption detection remains server-side.
If you use pushMicFrame(), the SDK no longer owns capture. Supply echo-cancelled Float32 mono audio at 16 kHz. If assistant audio is audible through speakers and echo is not removed, the service may transcribe or react to its own voice.
Events
Server JSON events are shared by the browser and Python SDKs. In the browser, read fields from event.detail; in Python, read them from SessionEvent.data. Binary assistant audio and browser transport lifecycle events use the SDK-specific forms below.
| Event | Source | Data | Meaning |
|---|---|---|---|
ready | Server | voice, name, voices | The session is accepted and ready. |
turn | Server | welcome? | An assistant reply has started. |
asr | Server | text | Final transcript of the user's turn. |
utterance | Server | text, corrected?, barge_seq? | Assistant text. A corrected event replaces the earlier text for that interruption sequence. |
done | Server | — | The current assistant reply has finished sending. |
interrupted | Server | barge_seq, clear? | The user took the floor and the assistant reply stopped. |
canceled | Server | — | An eager reply was retracted; discard its uncommitted playback. |
playback_pause | Server | pause_seq | Hold playback while Converse distinguishes an acknowledgement from an interruption. |
playback_resume | Server | pause_seq | Resume the matching held playback. |
ack | Server | frames | The next binary frames are a short acknowledgement outside a normal reply. |
tool_call | Server | id, name, args | Run a declared client tool and return its result. |
tool_cancel | Server | id | Cancel the matching tool call if possible. |
voice | Server | voice, name | A requested voice change was accepted. |
error | Server or SDK | detail or error | The service or SDK rejected or lost the operation. |
audio | SDK from binary frame | Browser: detail.samples and detail.sr. Python: event.audio; rate is OUTPUT_SR. | One Float32 mono assistant-audio frame. |
listening | Browser SDK only | — | Microphone frames are reaching the service after capture warm-up. |
reconnecting | Browser SDK only | — | The SDK is recovering an abnormal transport loss. |
reconnected | Browser SDK only | — | A new session is live; previous conversation context was not retained. |
session_end | Browser SDK only | code, reason | A clean server close ended the session. The Python event iterator simply ends. |