Errors

BuddyError hierarchy — catch with instanceof, no string matching.

Every error thrown by the SDK extends BuddyError. Subclass with instanceof — no string code matching.

Hierarchy

BuddyError (extends Error)
 ├─ AuthenticationError    HTTP 401 / 403
 ├─ APIError               Other non-2xx API responses
 ├─ NetworkError           Fetch failed (offline, DNS, etc.)
 ├─ TimeoutError           Request exceeded the 30s timeout
 ├─ InvalidRequestError    Invalid SDK usage / malformed request
 └─ SessionError           Session-lifecycle error (e.g. speak before connect)

Fields on BuddyError

FieldTypeNotes
messagestringHuman-readable reason
statusCodenumber \| undefinedHTTP status when applicable
detailsRecord<string, unknown>?Raw response body or extra context
namestringClass name — e.g. 'AuthenticationError'

Catching

import {
  BuddyError,
  AuthenticationError,
  APIError,
  NetworkError,
  TimeoutError,
  InvalidRequestError,
  SessionError
} from '@juspay/breeze-buddy-client-sdk';

try {
  const session = await client.startSession({ templateId, payload });
} catch (err) {
  if (err instanceof AuthenticationError) {
    await refreshTokenAndRetry();
    return;
  }
  if (err instanceof NetworkError || err instanceof TimeoutError) {
    return showRetryBanner();
  }
  if (err instanceof APIError) {
    console.error(err.statusCode, err.details);
    return showApiErrorUI(err);
  }
  if (err instanceof BuddyError) {
    // Catch-all for any other SDK error
    console.error(err.message);
    return showGenericError(err.message);
  }
  // Unknown — re-throw
  throw err;
}

When each error is thrown

AuthenticationError

Thrown on HTTP 401 / 403 from the API. Your JWT is invalid, expired, or doesn’t have the requested reseller_id / merchant_id in its allow-list.

// Likely cause: token expired
if (err instanceof AuthenticationError) await refreshToken();

APIError

Thrown on any other non-2xx response (400, 404, 500, etc.). Check err.statusCode and err.details.

if (err instanceof APIError) {
  if (err.statusCode === 404) return 'Template not found';
  if (err.statusCode === 429) return 'Rate limited — retry later';
  return `API error: ${err.message}`;
}

NetworkError

Thrown when fetch() itself fails — offline, DNS failure, CORS, etc. Not an HTTP error (the request never got a response).

TimeoutError

Thrown when an HTTP request exceeds the 30 second timeout.

InvalidRequestError

Thrown by SDK-side validation — before hitting the network. Examples:

  • session.assistantSpeak('') — empty text rejected
  • Future: unknown execution mode, malformed options

SessionError

Thrown for session-lifecycle violations on the client side. Examples:

  • session.assistantSpeak(text) called before the session is 'connected'
  • session.sendMessage(...) called on a disconnected session
  • Session closes while an await session.assistantSpeak(...) is still pending

Pattern: retry with auth refresh

async function startWithRefresh(client: BuddyClient, opts: StartSessionOptions) {
  try {
    return await client.startSession(opts);
  } catch (err) {
    if (err instanceof AuthenticationError) {
      await refreshToken();
      // Rebuild client with new token, retry once
      const fresh = new BuddyClient({ ...clientOptions, auth: { token: newToken } });
      return fresh.startSession(opts);
    }
    throw err;
  }
}

Pattern: retry network failures

async function withRetry<T>(fn: () => Promise<T>, attempts = 3): Promise<T> {
  for (let i = 0; i < attempts; i++) {
    try {
      return await fn();
    } catch (err) {
      const retriable = err instanceof NetworkError || err instanceof TimeoutError;
      if (!retriable || i === attempts - 1) throw err;
      await new Promise((r) => setTimeout(r, 2 ** i * 500));
    }
  }
  throw new Error('unreachable');
}

const session = await withRetry(() => client.startSession({ templateId, payload }));

Don't retry AuthenticationError

Retrying with the same token is pointless — you'll just hammer a 401 loop. Refresh the token first, then retry.
Was this helpful?