Error Hierarchy
All errors thrown by the Limrun SDK inherit from the base LimrunError class. The SDK provides specific error types for different failure scenarios.
LimrunError
└─ APIError
├─ BadRequestError (400)
├─ AuthenticationError (401)
├─ PermissionDeniedError (403)
├─ NotFoundError (404)
├─ ConflictError (409)
├─ UnprocessableEntityError (422)
├─ RateLimitError (429)
├─ InternalServerError (5xx)
├─ APIUserAbortError
└─ APIConnectionError
└─ APIConnectionTimeoutError
Base Errors
LimrunError
Base class for all Limrun SDK errors. Extends the standard JavaScript Error class.
Type Definition:
class LimrunError extends Error {}
Example:
import { LimrunError } from 'limrun';
try {
const instance = await client.androidInstances.retrieve('inst_123');
} catch (error) {
if (error instanceof LimrunError) {
console.error('Limrun SDK error:', error.message);
} else {
console.error('Unexpected error:', error);
}
}
APIError
Base class for all API-related errors. Contains HTTP status, headers, and response body information.
Properties:
HTTP status code for the response that caused the error
HTTP headers for the response that caused the error
JSON body of the response that caused the error
Human-readable error message
Example:
import { APIError } from 'limrun';
try {
const instance = await client.androidInstances.retrieve('inst_invalid');
} catch (error) {
if (error instanceof APIError) {
console.error(`API Error: ${error.status} - ${error.message}`);
console.error('Response body:', error.error);
console.error('Response headers:', error.headers);
}
}
HTTP Status Errors
BadRequestError
Thrown when the API returns a 400 Bad Request status.
HTTP Status: 400
Common Causes:
- Invalid request parameters
- Malformed request body
- Missing required fields
Example:
import { BadRequestError } from 'limrun';
try {
const instance = await client.androidInstances.create({
// Missing required fields
});
} catch (error) {
if (error instanceof BadRequestError) {
console.error('Invalid request:', error.message);
console.error('Details:', error.error);
}
}
AuthenticationError
Thrown when the API returns a 401 Unauthorized status.
HTTP Status: 401
Common Causes:
- Missing API key
- Invalid API key
- Expired authentication token
Example:
import { AuthenticationError } from 'limrun';
try {
const instances = await client.androidInstances.list();
} catch (error) {
if (error instanceof AuthenticationError) {
console.error('Authentication failed. Please check your API key.');
}
}
PermissionDeniedError
Thrown when the API returns a 403 Forbidden status.
HTTP Status: 403
Common Causes:
- Insufficient permissions for the requested operation
- Resource access denied
- Account limitations
Example:
import { PermissionDeniedError } from 'limrun';
try {
await client.androidInstances.delete('inst_123');
} catch (error) {
if (error instanceof PermissionDeniedError) {
console.error('Permission denied:', error.message);
}
}
NotFoundError
Thrown when the API returns a 404 Not Found status.
HTTP Status: 404
Common Causes:
- Requested resource does not exist
- Invalid resource ID
- Resource has been deleted
Example:
import { NotFoundError } from 'limrun';
try {
const instance = await client.androidInstances.retrieve('inst_nonexistent');
} catch (error) {
if (error instanceof NotFoundError) {
console.error('Instance not found');
}
}
ConflictError
Thrown when the API returns a 409 Conflict status.
HTTP Status: 409
Common Causes:
- Resource already exists
- Concurrent modification conflict
- State conflict
Example:
import { ConflictError } from 'limrun';
try {
const instance = await client.androidInstances.create({
metadata: { id: 'existing-id' },
});
} catch (error) {
if (error instanceof ConflictError) {
console.error('Resource conflict:', error.message);
}
}
UnprocessableEntityError
Thrown when the API returns a 422 Unprocessable Entity status.
HTTP Status: 422
Common Causes:
- Request is well-formed but semantically incorrect
- Validation errors
- Business logic constraints violated
Example:
import { UnprocessableEntityError } from 'limrun';
try {
const instance = await client.androidInstances.create({
androidVersion: 'invalid-version',
});
} catch (error) {
if (error instanceof UnprocessableEntityError) {
console.error('Validation error:', error.message);
console.error('Details:', error.error);
}
}
RateLimitError
Thrown when the API returns a 429 Too Many Requests status.
HTTP Status: 429
Common Causes:
- Too many requests in a short time period
- API rate limit exceeded
Example:
import { RateLimitError } from 'limrun';
try {
for (let i = 0; i < 1000; i++) {
await client.androidInstances.list();
}
} catch (error) {
if (error instanceof RateLimitError) {
console.error('Rate limit exceeded. Please slow down.');
// Implement exponential backoff
}
}
InternalServerError
Thrown when the API returns a 5xx server error status.
HTTP Status: 500 or higher
Common Causes:
- Server-side error
- Service unavailable
- Temporary outage
Example:
import { InternalServerError } from 'limrun';
try {
const instance = await client.androidInstances.create({
androidVersion: '13',
});
} catch (error) {
if (error instanceof InternalServerError) {
console.error('Server error:', error.message);
// Retry logic here
}
}
Connection Errors
APIConnectionError
Thrown when a connection to the API cannot be established.
Properties:
Error message (defaults to “Connection error.”)
The underlying error that caused the connection failure
Common Causes:
- Network is unavailable
- DNS resolution failed
- Server is unreachable
- Firewall blocking connection
Example:
import { APIConnectionError } from 'limrun';
const client = new Limrun({
baseURL: 'https://invalid-domain.example.com',
});
try {
await client.androidInstances.list();
} catch (error) {
if (error instanceof APIConnectionError) {
console.error('Connection failed:', error.message);
if (error.cause) {
console.error('Underlying error:', error.cause);
}
}
}
APIConnectionTimeoutError
Thrown when a request times out.
Properties:
Error message (defaults to “Request timed out.”)
Common Causes:
- Request took longer than configured timeout
- Slow network connection
- Server not responding
Example:
import { APIConnectionTimeoutError } from 'limrun';
const client = new Limrun({
timeout: 5000, // 5 seconds
});
try {
// Long-running operation
await client.androidInstances.create({
androidVersion: '13',
});
} catch (error) {
if (error instanceof APIConnectionTimeoutError) {
console.error('Request timed out');
// Increase timeout or retry
}
}
APIUserAbortError
Thrown when a request is aborted by the user.
Properties:
Error message (defaults to “Request was aborted.”)
Example:
import { APIUserAbortError } from 'limrun';
const controller = new AbortController();
const client = new Limrun({
fetchOptions: {
signal: controller.signal,
},
});
// Start a long-running request
const promise = client.androidInstances.create({
androidVersion: '13',
});
// Abort after 2 seconds
setTimeout(() => controller.abort(), 2000);
try {
await promise;
} catch (error) {
if (error instanceof APIUserAbortError) {
console.log('Request was cancelled by user');
}
}
Error Handling Best Practices
Specific Error Handling
Handle specific error types to provide better user feedback:
import {
NotFoundError,
AuthenticationError,
RateLimitError,
InternalServerError,
APIConnectionError,
} from 'limrun';
try {
const instance = await client.androidInstances.retrieve('inst_123');
} catch (error) {
if (error instanceof NotFoundError) {
console.error('Instance not found');
} else if (error instanceof AuthenticationError) {
console.error('Please check your API key');
} else if (error instanceof RateLimitError) {
console.error('Rate limit exceeded, waiting before retry...');
await new Promise(resolve => setTimeout(resolve, 60000));
} else if (error instanceof InternalServerError) {
console.error('Server error, retrying...');
} else if (error instanceof APIConnectionError) {
console.error('Connection failed, check your network');
} else {
console.error('Unexpected error:', error);
}
}
Retry Logic
Implement retry logic for transient errors:
import { InternalServerError, APIConnectionTimeoutError } from 'limrun';
async function retryableRequest<T>(
fn: () => Promise<T>,
maxRetries = 3
): Promise<T> {
let lastError;
for (let i = 0; i < maxRetries; i++) {
try {
return await fn();
} catch (error) {
lastError = error;
// Retry on server errors and timeouts
if (
error instanceof InternalServerError ||
error instanceof APIConnectionTimeoutError
) {
const delay = Math.pow(2, i) * 1000; // Exponential backoff
console.log(`Retry ${i + 1}/${maxRetries} after ${delay}ms`);
await new Promise(resolve => setTimeout(resolve, delay));
continue;
}
// Don't retry other errors
throw error;
}
}
throw lastError;
}
// Usage
const instance = await retryableRequest(() =>
client.androidInstances.retrieve('inst_123')
);
Accessing Error Details
Extract detailed information from errors:
import { APIError } from 'limrun';
try {
await client.androidInstances.create({ /* ... */ });
} catch (error) {
if (error instanceof APIError) {
console.error('Status:', error.status);
console.error('Message:', error.message);
console.error('Error body:', JSON.stringify(error.error, null, 2));
// Check specific headers
const rateLimitRemaining = error.headers?.get('x-ratelimit-remaining');
if (rateLimitRemaining) {
console.log(`Rate limit remaining: ${rateLimitRemaining}`);
}
}
}
Type Guards
Use TypeScript type guards for better type safety:
import { APIError, NotFoundError, LimrunError } from 'limrun';
function isLimrunError(error: unknown): error is LimrunError {
return error instanceof LimrunError;
}
function isAPIError(error: unknown): error is APIError {
return error instanceof APIError;
}
try {
await client.androidInstances.retrieve('inst_123');
} catch (error) {
if (isAPIError(error)) {
// TypeScript knows error has status, headers, etc.
console.log('API Error:', error.status, error.message);
} else if (isLimrunError(error)) {
// TypeScript knows error is a LimrunError
console.log('SDK Error:', error.message);
} else {
// Unknown error
console.error('Unexpected error:', error);
}
}