Converse

Developer guide

Browser SDK

The recommended path for web applications. The SDK owns microphone capture, browser echo cancellation, playback, reconnects and structured events.

← Choose another integration

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

CredentialUseWhere it belongs
Persistent ck_… keyCreate scoped browser credentials; connect trusted Python or WebSocket clients.Backend or trusted service only.
Scoped session keyAuthenticate 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
}
StatusMeaning
201Credential created.
400Invalid or missing session_id.
401Persistent key missing, unknown or revoked.
415Request 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)

OptionType / defaultDescription
urlstring, requiredUse wss://converse.trelis.com/ws.
sessionIdstring, generatedMust match a scoped key's bound session ID.
apiKeystringScoped browser key or, in trusted environments only, a persistent key.
mode{kind:'converse'}Conversation configuration; see Mode options below.
userstringOptional stable identifier for your user. It is metadata, not authentication.
timezonestringOptional IANA timezone such as Europe/Dublin.
playerStreamingPlayerOptional caller-supplied playback implementation.
playAcknowledgementsboolean, trueAutomatically play short assistant acknowledgements.
autoReconnectboolean, trueReconnect after abnormal transport loss. A reconnect starts a new conversation.
reconnectBaseMsnumber, 500Initial reconnect delay.
reconnectMaxMsnumber, 5000Maximum reconnect delay.
maxReconnectAttemptsnumber, 12Attempts before a terminal error.
rawAssistboolean, falseSupport 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

MethodReturnsDescription
unlockAudio()PromiseUnlocks 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()PromiseReleases SDK-owned microphone resources without closing the session.
setMicEnabled(enabled)voidTemporarily gates SDK-owned microphone tracks.
reset()PromiseClears playback and starts a fresh conversation on the same connection.
setVoice(key)voidChanges the voice beginning with the next assistant reply.
close()voidStops capture and playback and closes the socket.
closeAndWait(timeoutMs?)PromiseCloses and waits for the WebSocket close handshake.
pushMicFrame(frame)voidAdvanced: sends a caller-owned Float32 mono 16 kHz frame on an already-live session.
appendAudio(frame)PromiseAdvanced: connects if needed, then sends one caller-owned Float32 frame.
sendRawFrame(frame)voidSupport diagnostic: sends a synchronized unprocessed frame when rawAssist is enabled.
sendAmbienceState(active)voidRecords 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.

Python and raw WebSocket integrations do not include a device media stack. For speakerphone use, add platform or telephony echo cancellation. Otherwise use headphones or disable the microphone during playback; disabling it prevents users from interrupting the assistant.

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.

EventSourceDataMeaning
readyServervoice, name, voicesThe session is accepted and ready.
turnServerwelcome?An assistant reply has started.
asrServertextFinal transcript of the user's turn.
utteranceServertext, corrected?, barge_seq?Assistant text. A corrected event replaces the earlier text for that interruption sequence.
doneServerThe current assistant reply has finished sending.
interruptedServerbarge_seq, clear?The user took the floor and the assistant reply stopped.
canceledServerAn eager reply was retracted; discard its uncommitted playback.
playback_pauseServerpause_seqHold playback while Converse distinguishes an acknowledgement from an interruption.
playback_resumeServerpause_seqResume the matching held playback.
ackServerframesThe next binary frames are a short acknowledgement outside a normal reply.
tool_callServerid, name, argsRun a declared client tool and return its result.
tool_cancelServeridCancel the matching tool call if possible.
voiceServervoice, nameA requested voice change was accepted.
errorServer or SDKdetail or errorThe service or SDK rejected or lost the operation.
audioSDK from binary frameBrowser: detail.samples and detail.sr. Python: event.audio; rate is OUTPUT_SR.One Float32 mono assistant-audio frame.
listeningBrowser SDK onlyMicrophone frames are reaching the service after capture warm-up.
reconnectingBrowser SDK onlyThe SDK is recovering an abnormal transport loss.
reconnectedBrowser SDK onlyA new session is live; previous conversation context was not retained.
session_endBrowser SDK onlycode, reasonA clean server close ended the session. The Python event iterator simply ends.