GuidesGet StartedError Handling

Error Handling

All Orsa API errors follow a consistent format with machine-readable error codes and human-readable messages. The TypeScript SDK turns those wire-level errors into four structured exception classes you can narrow with instanceof.

On the wire

{
  "status": "error",
  "code": "NOT_FOUND",
  "message": "Could not resolve a brand for \"unknown-domain.test\".",
  "requestId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}

Every error response includes:

FieldDescription
statusAlways "error"
codeMachine-readable error code — use this for programmatic handling
messageHuman-readable description
requestIdUnique ID for debugging; include in support requests

Error Codes

Client Errors (4xx)

CodeHTTP StatusDescription
INPUT_VALIDATION_ERROR400Invalid request parameters or body
UNAUTHORIZED401Missing or invalid API key
USAGE_EXCEEDED402Insufficient credits for the request
FORBIDDEN403Insufficient permissions (e.g., free plan using paid features)
NOT_FOUND404Resource not found (no brand cached for domain, etc.)
RATE_LIMITED429Rate limit exceeded

Server Errors (5xx)

CodeHTTP StatusDescription
INTERNAL_ERROR500Unexpected server error
SERVICE_UNAVAILABLE503Browser pool unreachable or an upstream dependency is down

SDK error classes

Every error thrown by the TypeScript SDK extends OrsaError. The subclasses tell you what kind of failure happened:

ClassFires whenUseful fields
OrsaAPIErrorAPI returned a 4xx or 5xx after retriesstatus, errorCode, requestId
OrsaTimeoutErrorRequest exceeded the configured timeout (AbortController fired)
OrsaConnectionErrorNetwork failure after exhausting retries (TCP/DNS/refused)
OrsaErrorBase class — never thrown directly, useful for instanceof

The SDK retries automatically for 429, 500, 502, 503 and connection errors with exponential backoff (honors Retry-After). 4xx errors throw immediately without retry.

TypeScript

import Orsa, {
  OrsaAPIError,
  OrsaConnectionError,
  OrsaError,
  OrsaTimeoutError,
} from '@orsa.dev/sdk';
 
const client = new Orsa({ apiKey: process.env.ORSA_API_KEY! });
 
try {
  const brand = await client.brand.retrieve({ domain: 'invalid..domain' });
} catch (err) {
  if (err instanceof OrsaAPIError) {
    // err.status, err.errorCode, err.requestId are all populated
    switch (err.errorCode) {
      case 'INPUT_VALIDATION_ERROR':
        console.error('Bad input:', err.message);
        break;
      case 'NOT_FOUND':
        console.error('Brand not cached yet — hit /retrieve first.');
        break;
      case 'USAGE_EXCEEDED':
        console.error('Out of credits — upgrade your plan');
        break;
      default:
        console.error(`API error [${err.errorCode}]: ${err.message}`);
    }
    console.error('Request ID:', err.requestId);
  } else if (err instanceof OrsaTimeoutError) {
    console.error('Request exceeded the timeout — try a larger budget or retry.');
  } else if (err instanceof OrsaConnectionError) {
    console.error('Network failure — check connectivity and try again later.');
  } else if (err instanceof OrsaError) {
    // Future-proofing — any new error subclass still extends OrsaError.
    console.error('Orsa SDK error:', err.message);
  } else {
    throw err;
  }
}

Python

from orsa import Orsa, OrsaError
 
client = Orsa(api_key=os.environ["ORSA_API_KEY"])
 
try:
    brand = client.brand.retrieve(domain="invalid..domain")
except OrsaError as e:
    if e.code == "INPUT_VALIDATION_ERROR":
        print(f"Bad input: {e.message}")
    elif e.code == "NOT_FOUND":
        print("Brand not found")
    elif e.code == "RATE_LIMITED":
        print(f"Rate limited. Retry in {e.retry_after}s")
    elif e.code == "USAGE_EXCEEDED":
        print("Out of credits")
    else:
        print(f"API error [{e.code}]: {e.message}")
 
    print(f"Request ID: {e.request_id}")

Retry Strategies

Which errors to retry

Error CodeRetry?Strategy
INPUT_VALIDATION_ERROR❌ NoFix the request parameters
UNAUTHORIZED❌ NoCheck your API key
FORBIDDEN❌ NoUpgrade your plan
NOT_FOUND❌ NoThe resource doesn’t exist
USAGE_EXCEEDED❌ NoAdd credits or upgrade
RATE_LIMITED✅ YesWait for Retry-After, then retry — SDK does this automatically
INTERNAL_ERROR✅ YesExponential backoff — SDK does this automatically (max maxRetries)
SERVICE_UNAVAILABLE✅ YesBrowser pool flap — SDK retries, then surfaces as OrsaAPIError
Connection failure✅ YesSDK retries, then surfaces as OrsaConnectionError

The SDK’s built-in retry behavior is configurable per-client:

const client = new Orsa({
  apiKey: process.env.ORSA_API_KEY!,
  maxRetries: 5,    // default 3
  timeout: 60_000,  // default 30s
});

Or per-request:

await client.brand.retrieve(
  { domain: 'stripe.com' },
  { timeout: 5_000 },
);

Custom backoff

If you want backoff above what the SDK provides (e.g., to fall back to a cached result), the standard formula is:

const delay = baseDelay * Math.pow(2, attempt) + Math.random() * 1000;

Add jitter (random component) to prevent thundering herd when multiple clients retry simultaneously.

Debugging

  1. Check OrsaAPIError.requestId — include it when contacting support.
  2. Check rate limit headers — you might be hitting limits without realizing it.
  3. Validate inputs client-side before making API calls to avoid unnecessary 400 errors.
  4. Log error codes, not just messages — codes are stable; messages may change.
  5. Use the local playground to reproduce errors interactively — its /errors page demonstrates each Orsa*Error class live.