Browser SDK
Run a live Athos roleplay call in your frontend with @useathos/sdk.
@useathos/sdk is a headless browser SDK. It exposes a small, domain-focused API — a roleplay
session with lifecycle events. Your code only ever works with Athos domain concepts (sessions, events, personas).
- Synchronous
create()so you register handlers before any network work starts. - A typed, discriminated event union and a stable error-code taxonomy.
- Microphone selection, mute, and automatic reconnect handling built in.
- Ships as ESM + CJS with full TypeScript types.
Install
npm install @useathos/sdkRequirements
-
Browsers: desktop Chromium-based browsers (Chrome, Edge, Brave, Opera, …) and desktop Firefox. Safari and all mobile browsers are rejected by
create()withBROWSER_NOT_SUPPORTED— see Browser support. -
HTTPS: the page must be served over HTTPS (or
localhost). On a plainhttp://origin the browser withholds the microphone, which the SDK reports asNO_MIC_AVAILABLE. -
Framework: none required — no React/Vue binding, no global state. Call
create()from a click handler or an effect after mount, never during render or server-side rendering (it throws there). Create one session per call. -
Size: about 5 kB gzipped, plus its voice-transport dependency (about 250 kB gzipped), installed automatically. Both are ESM + CJS.
-
Iframes: if the call runs inside an
<iframe>, the frame needsallow="microphone"or the browser denies the microphone (MIC_PERMISSION_DENIED). -
Content Security Policy: the SDK calls
https://app.useathos.ai(to redeem the token) and then opens a WebSocket + WebRTC connection to the voice host returned by that call — currently under*.livekit.cloud; do not hard-code a single hostname. If you set a CSP, allow:connect-src https://app.useathos.ai https://*.livekit.cloud wss://*.livekit.cloudThat covers the token redeem and the signalling (both
https://andwss://to the voice host). The audio itself travels over WebRTC, which CSP does not govern, and is played through an<audio>element the SDK creates — nomedia-srcentry is needed.
The five-line integration
import { AthosRoleplay } from '@useathos/sdk';
const token = await getAthosToken(); // 1. your backend mints a token
const session = AthosRoleplay.create({ token, drillKey: 'ma-full-sale' }); // 2. create (sync)
session.on('ready', ({ persona }) => console.log(`${persona.name} is ready`)); // 3. handlers
session.on('ended', ({ durationSec }) => console.log(`done in ${durationSec}s`));
await session.connect(); // 4. join the callgetAthosToken() is your endpoint — it calls POST /v1/session server-side and returns the
token. The browser never sees your API key. See minting tokens.
Always register handlers before connect(). create() does no network work, so nothing is
missed; connect() is what redeems the token and joins the call.
AthosRoleplay.create(options)
Creates a session synchronously. On an unsupported environment it throws BROWSER_NOT_SUPPORTED
straight away, so call it on the client only (see Browser support).
Prop
Type
The session object
create() returns a session with these methods:
| Method | Description |
|---|---|
connect() | Redeem the token and join the call. Returns a Promise<void>. |
disconnect() | End the call and leave cleanly. See How a call ends. |
on(event, cb) | Subscribe to an event. Returns an unsubscribe function. |
off(event, cb) | Unsubscribe. |
listMicrophones() | Enumerate available mics. See Microphone & audio. |
setMicrophone(deviceId) | Switch mic mid-call without dropping. |
mute() / unmute() | Mute / unmute the local mic. |
resumeAudio() | Resume playback after AUDIO_PLAYBACK_BLOCKED (call from a user gesture). |
How a call ends
The AI persona never hangs up. It has no way to decide the conversation is finished — that is deliberate, because whether a rep's call is over is your product's decision, not a model's. So a call ends in exactly three ways:
- You call
session.disconnect()— the normal ending. Wire it to an "End call" button. - The two-hour ceiling — a backstop for abandoned calls, not a normal ending. A full-sale roleplay usually runs a few minutes.
- The network drops and does not recover within 30 seconds (
NETWORK_LOST).
All three fire the ended event with the public callId, including the
disconnect() you called yourself. That event is how your UI learns the call is over, and the
callId is what ties it to the score that arrives later.
Ship the "End call" button. Without one, a rep who walks away leaves the call running until
the two-hour ceiling: you are billed for the full two hours and it counts against your monthly
usage allowance. disconnect() is the only way to end a call early.