i99dash docs
Concepts

Error model

Two failure shapes, seven error classes, one decision flow. Understand the SDK's error contract once and stop second-guessing.

The SDK has two kinds of failure: thrown errors and error envelopes. They surface different things and want different handling. Once you know which is which, every error in the SDK follows the same pattern.

The two shapes

                   ┌──────────────────────────────┐
                   │  bridge call (e.g. callApi)  │
                   └──────────────┬───────────────┘

                  ┌───────────────┴───────────────┐
                  │                               │
        ┌─────────▼──────────┐         ┌──────────▼─────────┐
        │ THROWN             │         │ RETURNED IN ENVELOPE
        │ • bridge crashed   │         │ • backend said no   │
        │ • timeout          │         │ • path not allowed  │
        │ • malformed reply  │         │ • 4xx / 5xx         │
        │ • not in host      │         │ • offline / timeout │
        │                    │         │   (host's, not SDK's)
        │ try / catch this   │         │ if (!r.success) ... │
        └────────────────────┘         └─────────────────────┘

Thrown — programmer or environment issues

The SDK throws when something is wrong with the runtime itself:

  • The bridge isn't there (NotInsideHostError).
  • The bridge call timed out (BridgeTimeoutError).
  • The host returned a malformed payload (InvalidResponseError).
  • The bridge transport itself crashed (BridgeTransportError).
  • The car-status surface isn't supported on this host (CarStatusUnavailableError, CarStatusQuotaExceededError).
  • Privileged template not in your catalog (UnknownTemplateError).

These are exceptional: you typically don't recover, you log and render a fallback.

Returned — operational failures

callApi() and admin invoke() return { success: false, error: ... } when the operation failed but the bridge worked. This is data:

const r = await client.callApi({ path: '/api/v1/x', method: 'GET' });
if (!r.success) {
  // r.error.code: 'disallowed_path' | 'http_4xx' | 'http_5xx' | ...
}

Don't try/catch around if (!r.success) — that conflates two different failure modes and loses information.

The seven SDK error classes

ClassWhenWhat to do
NotInsideHostErrorNo bridge on window. SSR, Storybook, Node.Catch and render a fallback UI. Most common in test setups.
BridgeTimeoutErrorHost bridge didn't respond in time. Default 10 s.Retry once with backoff, or surface "device unresponsive". error.operation says which call timed out.
BridgeTransportErrorBridge layer crashed mid-call. Host bug.Log, report. Usually transient — retry with backoff. Original cause is in error.cause.
InvalidResponseErrorHost returned a payload that doesn't match the SDK's schema.Log + report. Means host/SDK version drift. Don't retry — same payload will fail again.
CarStatusUnavailableErrorBridge isn't a CarStatusBridge (older host, test stub).Catch on client.car.getStatus() etc.; render a fallback.
CarStatusQuotaExceededErrorMore than 10 concurrent onStatusChange subs from this mini-app.Find the leak. Almost always a missing useEffect cleanup.
UnknownTemplateError (admin-sdk)templateId not in your catalog snapshot.Bug in your code or a stale catalog. Refresh the catalog or check the template name.

All seven extend SDKError and carry a stable code field:

Classcode
NotInsideHostError'NOT_INSIDE_HOST'
BridgeTimeoutError'BRIDGE_TIMEOUT'
BridgeTransportError'BRIDGE_TRANSPORT'
InvalidResponseError'INVALID_RESPONSE'
CarStatusUnavailableError'CAR_STATUS_UNAVAILABLE'
CarStatusQuotaExceededError'CAR_STATUS_QUOTA_EXCEEDED'
UnknownTemplateError (admin-sdk)'UNKNOWN_TEMPLATE'

The decision flow

                  ┌──────────────────┐
                  │ caught an error? │
                  └────────┬─────────┘

            ┌──────────────┴───────────────┐
            │                              │
        ┌───▼────┐                  ┌──────▼──────┐
        │ thrown │                  │ envelope    │
        │ (try/  │                  │ (r.success  │
        │ catch) │                  │  === false) │
        └───┬────┘                  └──────┬──────┘
            │                              │
       switch (e.code) {              switch (r.error.code) {
         NOT_INSIDE_HOST:               disallowed_path:
           ▶ render fallback              ▶ log; config bug
         BRIDGE_TIMEOUT:                http_4xx:
           ▶ retry / banner               ▶ surface message
         CAR_STATUS_UNAVAILABLE:        http_5xx:
           ▶ hide widget                  ▶ retry / banner
         CAR_STATUS_QUOTA_EXCEEDED:     network_error:
           ▶ check for leak               ▶ "no signal"
         INVALID_RESPONSE:              timeout:
           ▶ log + report                 ▶ same as network
         BRIDGE_TRANSPORT:              <backend code>:
           ▶ log + retry                  ▶ render per-code
       }                              }

Switch on code, not instanceof

// Wrong — coupling to class names
if (e instanceof BridgeTimeoutError) { ... }
else if (e instanceof BridgeTransportError) { ... }

// Right — switch on the stable code
import { SDKError } from '@i99dash/sdk';
if (e instanceof SDKError) {
  switch (e.code) {
    case 'BRIDGE_TIMEOUT':
      return retryWithBackoff();
    case 'BRIDGE_TRANSPORT':
      return reportTransient(e.cause);
    case 'NOT_INSIDE_HOST':
      return renderFallback();
    // ...
  }
}
throw e;

code is part of the public API. New codes only get added in minor releases; existing codes never change meaning. Class names work too, but they're harder to switch on cleanly when you have many cases.

cause carries the underlying error

Every wrapped error sets cause per the ES2022 Error spec:

catch (e) {
  if (e instanceof BridgeTransportError) {
    Sentry.captureException(e, { extra: { cause: e.cause } });
  }
}

Sentry, modern browser DevTools, and most logging libraries walk the .cause chain automatically. Don't redact it — the original cause is the actionable part.

When to retry

Class / codeRetry?How
BRIDGE_TIMEOUTMaybe — once with backoff. Persistent timeout = host hung.Single 500-1000 ms delay then retry. Two failures → surface.
BRIDGE_TRANSPORTYes — usually transient.Exponential backoff (200 → 400 → 800 ms).
INVALID_RESPONSENo.The host will return the same broken payload. Log and stop.
NOT_INSIDE_HOSTNo.Permanent on this runtime.
CAR_STATUS_UNAVAILABLENo.Older host or test stub. Render fallback.
CAR_STATUS_QUOTA_EXCEEDEDNo.Find the leak.
envelope http_5xxYes.Up to backend's idempotency contract.
envelope network_errorYes — typically with longer backoff.Show progress.
envelope disallowed_pathNo.Config bug; retry won't fix it.

On this page