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:
| Field | Description |
|---|---|
status | Always "error" |
code | Machine-readable error code — use this for programmatic handling |
message | Human-readable description |
requestId | Unique ID for debugging; include in support requests |
Error Codes
Client Errors (4xx)
| Code | HTTP Status | Description |
|---|---|---|
INPUT_VALIDATION_ERROR | 400 | Invalid request parameters or body |
UNAUTHORIZED | 401 | Missing or invalid API key |
USAGE_EXCEEDED | 402 | Insufficient credits for the request |
FORBIDDEN | 403 | Insufficient permissions (e.g., free plan using paid features) |
NOT_FOUND | 404 | Resource not found (no brand cached for domain, etc.) |
RATE_LIMITED | 429 | Rate limit exceeded |
Server Errors (5xx)
| Code | HTTP Status | Description |
|---|---|---|
INTERNAL_ERROR | 500 | Unexpected server error |
SERVICE_UNAVAILABLE | 503 | Browser 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:
| Class | Fires when | Useful fields |
|---|---|---|
OrsaAPIError | API returned a 4xx or 5xx after retries | status, errorCode, requestId |
OrsaTimeoutError | Request exceeded the configured timeout (AbortController fired) | — |
OrsaConnectionError | Network failure after exhausting retries (TCP/DNS/refused) | — |
OrsaError | Base 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 Code | Retry? | Strategy |
|---|---|---|
INPUT_VALIDATION_ERROR | ❌ No | Fix the request parameters |
UNAUTHORIZED | ❌ No | Check your API key |
FORBIDDEN | ❌ No | Upgrade your plan |
NOT_FOUND | ❌ No | The resource doesn’t exist |
USAGE_EXCEEDED | ❌ No | Add credits or upgrade |
RATE_LIMITED | ✅ Yes | Wait for Retry-After, then retry — SDK does this automatically |
INTERNAL_ERROR | ✅ Yes | Exponential backoff — SDK does this automatically (max maxRetries) |
SERVICE_UNAVAILABLE | ✅ Yes | Browser pool flap — SDK retries, then surfaces as OrsaAPIError |
| Connection failure | ✅ Yes | SDK 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
- Check
OrsaAPIError.requestId— include it when contacting support. - Check rate limit headers — you might be hitting limits without realizing it.
- Validate inputs client-side before making API calls to avoid unnecessary 400 errors.
- Log error codes, not just messages — codes are stable; messages may change.
- Use the local playground to reproduce errors interactively — its
/errorspage demonstrates eachOrsa*Errorclass live.