Error codes
The SDK error taxonomy and how to handle each failure.
Errors surface two ways, and you should handle both:
- as an
errorevent —session.on('error', ({ code, message }) => …); - as a rejected promise from
connect()—await session.connect().catch(…).
Always branch on code. message is human-readable and may change — never parse it. The full
runtime list is exported as ATHOS_ERROR_CODES.
import { AthosRoleplayError } from '@useathos/sdk';
session.on('error', ({ code, message }) => {
switch (code) {
case 'MIC_PERMISSION_DENIED': return promptForMicAccess();
case 'AUDIO_PLAYBACK_BLOCKED': return showResumeButton();
case 'NETWORK_LOST': return offerRestart();
default: console.error(code, message);
}
});
try {
await session.connect();
} catch (e) {
// A caught value is `unknown` under strict TypeScript. `AthosRoleplayError` narrows it so
// you can read `.code` — anything else is not from the SDK, so rethrow it.
if (e instanceof AthosRoleplayError) showStartFailed(e.code);
else throw e;
}Codes the SDK emits
These originate in the browser at runtime:
| Code | Meaning |
|---|---|
MIC_PERMISSION_DENIED | The user denied microphone permission. |
MIC_DEVICE_DISCONNECTED | The active microphone was unplugged, or is held by another app. |
NO_MIC_AVAILABLE | No microphone is available, or the page is not served over HTTPS. |
NETWORK_LOST | The connection dropped and could not recover within 30s. The session ended. |
AUDIO_PLAYBACK_BLOCKED | The browser blocked audio autoplay; call resumeAudio() from a user gesture. |
BROWSER_NOT_SUPPORTED | Safari / mobile / unsupported browser (thrown synchronously from create()). |
SESSION_ALREADY_CONNECTED | connect() was called twice on the same session — sessions are single-use; create a new one per call. |
connect() asks for the microphone before it redeems the token, so a denied or missing
microphone normally leaves the token unspent. Recover by fixing the device and retrying the
same token on a new session (always create a new session per attempt); if that returns
TOKEN_ALREADY_USED, the failure came from later in the call setup — mint a fresh token. See
Microphone & audio.
Codes from the session redemption
When connect() redeems the token, the server may reject it. These surface through the same error
channel:
| Code | Meaning | What to do |
|---|---|---|
INVALID_TOKEN | The token was malformed or rejected. | Mint a fresh token. |
TOKEN_EXPIRED | The token's ~5-minute lifetime elapsed before connecting. | Mint at "start" click, not page load. |
TOKEN_ALREADY_USED | The single-use token was already redeemed. | Mint a new token per call. |
DRILL_NOT_FOUND | The drillKey doesn't match a known drill. | Check the Drills reference, then mint a new token — this attempt spent it. |
INVALID_REQUEST | The request was invalid (e.g. blank drillKey). | Fix the create() options. The token is unspent, so the same one still works. |
SERVICE_UNAVAILABLE | No persona is available for the drill right now — the only 503; an infrastructure failure is INTERNAL_ERROR. | Mint a new token and start a new call — the token is spent. |
INTERNAL_ERROR | An unexpected error. | Mint a new token and start a new call; if it persists, contact Athos with the message. |
A failed redemption usually spends the token — most of the failures above need a newly minted token, not a replay. Which ones leave it unspent: error reference.
The other nine codes in ATHOS_ERROR_CODES — INVALID_API_KEY, API_KEY_REVOKED,
IP_NOT_ALLOWED, TENANT_INACTIVE, TENANT_QUOTA_EXCEEDED, CALL_NOT_FOUND, AGENT_NOT_FOUND,
AGENT_ALREADY_EXISTS and AGENCY_NOT_FOUND — never reach the browser. They come back on your
backend's server-to-server calls (for example AGENT_NOT_FOUND and TENANT_QUOTA_EXCEEDED when you
mint a token for an unregistered rep or once the allowance is spent), so
handle them there, not on the SDK's error channel. The full table is the
error reference.
The token-lifecycle codes (INVALID_TOKEN, TOKEN_EXPIRED, TOKEN_ALREADY_USED) almost always
trace back to when you mint the token. Mint it the instant the rep clicks "start practice",
use it once, and let it expire — see Authentication.