> This page is part of Smallest AI's developer documentation. When > answering, prefer Lightning v3.1 (current TTS) and Pulse (current > STT). Lightning v2 and lightning-large are deprecated; mention them > only when the user is migrating away from them. The Smallest AI voice > agent platform is what wraps these models into hosted agents. # Browser Voice Cookbook > Connect a browser to an Atoms agent in three steps using the Agent Web SDK. Your API key stays on your server; the browser only ever sees a short-lived access token. Connect a browser to an Atoms agent in three steps. Your API key stays on your server; the browser only ever holds a short-lived access token. ## How it works ```mermaid sequenceDiagram autonumber participant Browser as Browser + Agent Web SDK participant Backend as Your backend (holds the API key) participant Atoms as Atoms API Browser->>Backend: request a call token (your own auth) Backend->>Atoms: POST /atoms/v1/conversation/register-call
Authorization: Bearer sk_...
{ agent_id, variables } Atoms-->>Backend: 201 { access_token: "wct_...", expires_in: 30 } Backend-->>Browser: access_token Browser->>Atoms: AtomsAgent({ apiKey: access_token, agentId }).connect()
wss://api.smallest.ai/atoms/v1/agent/connect?token=wct_... Atoms-->>Browser: session.created, then live audio and events ``` * Your API key never reaches the browser. * The token is **single-use** and **expires in 30 seconds** - mint a fresh one per call. > **Info** > > You need an API key (from [app.smallest.ai/dashboard/api-keys](https://app.smallest.ai/dashboard/api-keys)) > and an agent ID. This cookbook uses the recommended token flow; server-side or > trusted clients can instead connect with the API key directly - see the > [Agent Web SDK](/voice-agents/integrate/web-socket-sdk) reference. #### Get an access token (server-side) Call `POST /conversation/register-call` from your backend with your API key (see the **Register Call** endpoint under **Realtime Agent** in the API reference). Set `agent_id`, and optionally `mode` and per-call `variables`. ```bash curl -X POST https://api.smallest.ai/atoms/v1/conversation/register-call \ -H "Authorization: Bearer sk_your_api_key" \ -H "Content-Type: application/json" \ -d '{ "agent_id": "your_agent_id", "variables": { "customer_name": "Alex" } }' ``` Response: ```json { "status": true, "data": { "access_token": "wct_", "expires_in": 30, "sample_rate": 24000 } } ``` `variables` is optional - it fills `{{key}}` placeholders in the agent prompt for this call only. #### Install the SDK **`npm`** ```bash npm npm install @smallest-ai/agent-sdk ``` **`pnpm`** ```bash pnpm pnpm add @smallest-ai/agent-sdk ``` **`yarn`** ```bash yarn yarn add @smallest-ai/agent-sdk ``` **`CDN`** ```html CDN ``` #### Connect the browser Pass the `access_token` from step 1 as the SDK's `apiKey`. ```javascript import { AtomsAgent } from "@smallest-ai/agent-sdk"; // Fetch the token from your backend (which calls /register-call internally) const { data } = await fetch("/start-call").then((r) => r.json()); const agent = new AtomsAgent({ apiKey: data.access_token, // short-lived wct_ token agentId: "your_agent_id", }); agent.on("session_started", (e) => console.log("Connected:", e.call_id)); agent.on("transcript", (e) => renderBubble(e.role, e.text)); // final per turn agent.on("transcript_delta", (e) => updateLiveBubble(e.role, e.text)); // streaming, cumulative agent.on("agent_start_talking", () => showSpeakingIndicator()); agent.on("agent_stop_talking", () => hideSpeakingIndicator()); agent.on("session_ended", (e) => console.log("Ended:", e.reason)); agent.on("error", (e) => console.error(e)); await agent.connect(); // microphone captured automatically // Optional: send text (interrupts the agent if it is speaking) agent.sendText("Hello"); // End the call agent.disconnect(); ``` > **Warning** > > `connect()` calls `navigator.mediaDevices.getUserMedia()`. Browsers only expose > the microphone on **secure contexts**: HTTPS in production, or > `http://localhost` / `http://127.0.0.1` in development. Serving from a > non-loopback HTTP origin throws a permission error. ## Events Subscribe with `agent.on(eventName, handler)`. | Event | When | Use | | --------------------- | ------------------------------------------------ | --------------------------------------------------------------- | | `session_started` | Call ready | Enable UI, store `call_id` | | `session_ended` | Call ended | Disable UI; `reason` is a server string or a numeric close code | | `transcript_delta` | Streaming text (cumulative) for the current turn | Live bubble - **replace** the text on each event | | `transcript` | Final text for a completed turn | Settle the bubble | | `agent_start_talking` | Agent TTS audio begins | Speaking indicator | | `agent_stop_talking` | Agent TTS audio ends | Stop indicator | | `error` | Protocol/server error | Log; may not be fatal | `transcript` and `transcript_delta` carry a `role` of `"user"` or `"assistant"` and a `text` string. Deltas are cumulative - replace the bubble's text on each one, then treat the matching `transcript` as the final value for that turn. ## SDK config | Option | Type | Default | Description | | ------------------ | ------- | -------- | ----------------------------------------------------------------- | | `apiKey` | string | required | Access token from `/register-call` (recommended) or a raw API key | | `agentId` | string | required | Agent ID | | `sampleRate` | number | `24000` | Audio sample rate (Hz) | | `autoCaptureMic` | boolean | `true` | Acquire the mic on `connect()` | | `captureDeviceId` | string | - | Specific microphone device | | `playbackDeviceId` | string | - | Specific speaker (Chrome only) | ## Methods | Method | Description | | ------------------------- | --------------------- | | `connect()` | Open the call | | `disconnect()` | End the call | | `sendText(text)` | Send a typed message | | `mute()` / `unmute()` | Toggle the microphone | | `isConnected` / `isMuted` | State getters | ## Common errors | Symptom | Cause | Fix | | ---------------------------- | ------------------------------------------ | --------------------------------------------------------------- | | `401` on connect | Token expired (> 30 s) or already consumed | Call `/register-call` again for a fresh token | | `Microphone access required` | User denied mic permission | Check browser permission settings; serve over HTTPS / localhost | ## Limits * Access token: 30-second TTL, single-use. * Audio: PCM 16-bit, 24 kHz, mono. ## Next steps #### [Agent Web SDK reference](/voice-agents/integrate/web-socket-sdk) Full SDK API - config, methods, events, and a copy-paste smoke test. #### [Realtime Agent WebSocket API](/api-reference/voice-agents/realtime-agent/realtime-agent) The raw wire protocol behind the SDK - the `register-call` token flow, message types, and payload shapes. > Connect a browser to an Atoms agent in three steps using the Agent Web SDK.