Appearance
Handling errors
Every failure Talkline reports carries a machine-readable code, and the same code means the same thing whether it came from the control API, the signaling server or the SDK itself. Branch on the code. Never branch on the message, and never show either to a user: messages are written for your logs, and every word a user reads is yours to choose, in their language.
In the browser
Everything in @talkline/core that can fail returns a promise that rejects with a TalklineError:
ts
import { isTalklineError } from '@talkline/core';
try {
await client.call({ to: ['agent:7'], media: 'video' });
} catch (err) {
if (!isTalklineError(err)) throw err;
switch (err.code) {
case 'device_error':
showCameraHelp(); // your words: how to allow the camera
break;
case 'permission_denied':
showNotAllowed();
break;
default:
showCouldNotCall();
log.warn('talkline call failed', { code: err.code, detail: err.detail });
}
}| Property | Meaning |
|---|---|
code | The code to branch on. Every code. |
detail | What happened, for your logs and bug reports. Never render it. |
fromServer | True when a server said it; false when the SDK raised it itself. |
retryable | The server's hint that the same request may succeed later. |
info | Structured facts for some codes: minProtocolVersion for client_outdated, endsAt and remainingCredits for insufficient_credit. |
Failures that no promise was waiting for — the connection giving up, a credit warning in the middle of a call — arrive as the client's error event (useErrors() in React).
A code your build does not know is possible: a newer deployment may add codes. Treat one as a failure you cannot act on — show a generic message, log it — and never as a crash.
On your backend
The control API answers an error with an RFC 9457 problem document:
json
{
"type": "https://docs.talkline.example/errors/room-full",
"title": "Room is full",
"status": 409,
"code": "room_full",
"detail": "Room RM_9f3a… has 50 of 50 participants.",
"instance": "/v1/rooms/RM_9f3a…/participants"
}type is this site's page for the code. @talkline/server-sdk throws a TalklineApiError carrying it:
ts
import { TalklineApiError } from '@talkline/server-sdk';
try {
await talkline.rooms.create({ name: 'case-4821' });
} catch (err) {
if (err instanceof TalklineApiError && err.code === 'conflict') {
// a live room already has that name
} else {
throw err;
}
}A 429 (rate_limited) carries Retry-After; every response carries RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset. The limits are per API key: 600 reads a minute, 120 writes, and 60 POST /v1/calls.
Which failures to expect
Most codes mean a bug or a misconfiguration and are worth an alert. A few are part of normal life, and your UI should have words ready for them:
| Code | When | A UI typically says |
|---|---|---|
already_answered | Answering a call another device answered first | "Answered on another device" |
invite_expired | Answering a call that stopped ringing | "Missed call" |
device_error | The camera or microphone is blocked or missing | How to allow it, and an option to continue without |
room_full | Joining a full room | "This meeting is full" |
room_closed | Joining a room that has ended | "This meeting has ended" |
insufficient_credit | An identity's prepaid credit is spent, or about to be | Your own top-up flow |
media_failed | The connection could not carry media | "The call dropped" and a way to call again |
client_outdated | This build is too old for the deployment | "Please update the app" |