Errors

Every failure in @cstar.help/js throws a CStarError subclass. Catch by class. Strings rotate; class identity doesn't. Every error carries a request ID; paste it into a support ticket and we find the call in seconds.

Catch by class

Match on the typed subclass and recover differently per case. instanceof works across every subpath import (/auth, /library, /community, /quickhelp, /proactive) because the class identity is registered on a Symbol.for(...) slot of globalThis. Bundle splits don't break the check.

catch-by-class.js
import {
  CStarError,
  CStarValidationError,
  CStarRateLimitError,
  CStarAuthenticationError
} from '@cstar.help/js';

try {
  await cstar.tickets.create(formData);
} catch (e) {
  if (e instanceof CStarValidationError) {
    // e.param holds the offending field name
    return showFieldError(e.param, e.message);
  }
  if (e instanceof CStarRateLimitError) {
    return retryAfter(e.retryAfter ?? 60); // seconds; undefined without Retry-After
  }
  if (e instanceof CStarAuthenticationError) {
    // Bad/expired key. Bounce the user to settings.
    return redirectToKeyRotation();
  }
  if (e instanceof CStarError) {
    // Catch-all for any other typed cStar error.
    log.error('cStar call failed', {
      code: e.code,
      requestId: e.requestId,
      docUrl: e.docUrl,
      statusCode: e.statusCode
    });
  }
  throw e;
}

Every error carries

The base class fields land on every subclass. Most of what you need to file a support ticket is already there.

The subclasses

Eight typed subclasses, dispatched by HTTP status and error type. A turned-off feature is the one exception to the status rule: it has its own class.

CStarAuthenticationError 401

Missing or invalid API key. Bad Authorization header.

CStarPermissionError 403

Wrong key type for the operation, a key for another team, your role lacks the permission, or the post's comments are closed. Not a turned-off feature: see the last class.

CStarNotFoundError 404

The resource ID doesn't exist (or your key can't see it).

CStarValidationError 400 / 422

Bad payload. e.param holds the field name. Also an Idempotency-Key reused with a different body (idempotency_key_reused, 422).

CStarConflictError 409

A unique-key conflict, or an Idempotency-Key still in use: its first request is still running, or it was used on another endpoint. Also the team's webhook limit (webhook_limit_reached): waiting won't help, delete webhooks you no longer use first.

CStarRateLimitError 429

e.retryAfter tells you how many seconds to wait, or is undefined when the response had no Retry-After.

CStarServerError 5xx

Our problem. Retry once with backoff; ping support with the request ID if it persists.

CStarFeatureDisabledError 403

The team turned off the feature this call needs (feature_disabled), such as Community. e.feature names it and e.enableUrl links to the setting. It is not a CStarPermissionError, so catch it by its own class.

Request IDs

Every error carries requestId. So does every successful response, via cstar.lastMeta.requestId. Both pull from the same source: the x-cstar-request-id header that every cStar response emits.

logging.js
try {
  const ticket = await cstar.tickets.create(payload);
  log.info('ticket created', {
    ticketId: ticket.id,
    requestId: cstar.lastMeta.requestId
  });
} catch (e) {
  // Always log the request ID. Always.
  log.error('ticket.create failed', {
    code: e.code,
    requestId: e.requestId,
    docUrl: e.docUrl
  });
  throw e;
}

Cross-subpath identity

The error classes live on globalThis[Symbol.for('@cstar.help/js/errors/v1')]. That means an error thrown by LibraryClient (imported from @cstar.help/js/library) and caught with CStarError imported from the root entry (different bundles, different module copies) still resolves to the same class identity at runtime. instanceof works across every subpath.

Each subpath also re-exports the canonical error classes for ergonomics, so you can import { CStarError } from '@cstar.help/js/library' if you prefer.

Next up