Skip to content

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 });
  }
}
PropertyMeaning
codeThe code to branch on. Every code.
detailWhat happened, for your logs and bug reports. Never render it.
fromServerTrue when a server said it; false when the SDK raised it itself.
retryableThe server's hint that the same request may succeed later.
infoStructured 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:

CodeWhenA UI typically says
already_answeredAnswering a call another device answered first"Answered on another device"
invite_expiredAnswering a call that stopped ringing"Missed call"
device_errorThe camera or microphone is blocked or missingHow to allow it, and an option to continue without
room_fullJoining a full room"This meeting is full"
room_closedJoining a room that has ended"This meeting has ended"
insufficient_creditAn identity's prepaid credit is spent, or about to beYour own top-up flow
media_failedThe connection could not carry media"The call dropped" and a way to call again
client_outdatedThis build is too old for the deployment"Please update the app"

Talkline documentation