Skip to main content

Error Handling

Handle errors gracefully when integrating with the ASCEND API.

Error Types

HTTP Errors

import axios, { AxiosError } from 'axios';

try {
const result = await client.submitAction(action);
} catch (error) {
if (axios.isAxiosError(error)) {
const axiosError = error as AxiosError<{ detail: string }>;

switch (axiosError.response?.status) {
case 400:
console.error('Bad Request:', axiosError.response.data.detail);
break;
case 401:
console.error('Authentication failed - check your API key');
break;
case 403:
console.error('Access forbidden - check permissions');
break;
case 404:
console.error('Resource not found');
break;
case 429:
console.error('Rate limited - wait before retrying');
break;
case 500:
console.error('Server error:', axiosError.response.data.detail);
break;
default:
console.error('HTTP error:', axiosError.message);
}
} else {
console.error('Unexpected error:', error);
}
}

Connection Errors

try {
await client.testConnection();
} catch (error) {
if (axios.isAxiosError(error)) {
if (error.code === 'ECONNREFUSED') {
console.error('Cannot connect to API server');
} else if (error.code === 'ETIMEDOUT') {
console.error('Connection timed out');
} else if (error.code === 'ENOTFOUND') {
console.error('DNS lookup failed - check API URL');
}
}
}

Authorization Errors

class AuthorizationError extends Error {
constructor(public reason: string, public actionId?: number) {
super(`Authorization denied: ${reason}`);
this.name = 'AuthorizationError';
}
}

class TimeoutError extends Error {
constructor(public actionId: number, public timeoutMs: number) {
super(`Authorization timed out after ${timeoutMs}ms`);
this.name = 'TimeoutError';
}
}

// Usage in AuthorizedAgent
async executeIfAuthorized<T>(/* ... */): Promise<T> {
// ... submit and wait ...

if (finalResult.decision === 'denied') {
throw new AuthorizationError(
finalResult.reason || 'No reason provided',
result.id
);
}

if (finalResult.decision === 'timeout') {
throw new TimeoutError(result.id, timeoutMs);
}

// ... execute ...
}

Retry Logic

async function withRetry<T>(
fn: () => Promise<T>,
options: {
maxRetries?: number;
baseDelayMs?: number;
maxDelayMs?: number;
retryOn?: number[];
} = {}
): Promise<T> {
const {
maxRetries = 3,
baseDelayMs = 1000,
maxDelayMs = 30000,
retryOn = [429, 500, 502, 503, 504],
} = options;

let lastError: Error | undefined;

for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
return await fn();
} catch (error) {
lastError = error as Error;

if (!axios.isAxiosError(error)) {
throw error;
}

const status = error.response?.status;

// Don't retry client errors (except rate limiting)
if (status && !retryOn.includes(status)) {
throw error;
}

if (attempt === maxRetries) {
break;
}

// Exponential backoff with jitter
const delay = Math.min(
baseDelayMs * Math.pow(2, attempt) + Math.random() * 1000,
maxDelayMs
);

console.log(`Retry ${attempt + 1}/${maxRetries} after ${delay}ms...`);
await new Promise(resolve => setTimeout(resolve, delay));
}
}

throw lastError;
}

// Usage
const result = await withRetry(
() => client.submitAction(action),
{ maxRetries: 3 }
);

Comprehensive Error Handler

interface ErrorResult {
success: false;
error: {
type: 'auth' | 'network' | 'validation' | 'server' | 'unknown';
message: string;
code?: string;
retryable: boolean;
};
}

interface SuccessResult<T> {
success: true;
data: T;
}

type Result<T> = SuccessResult<T> | ErrorResult;

async function safeApiCall<T>(fn: () => Promise<T>): Promise<Result<T>> {
try {
const data = await fn();
return { success: true, data };
} catch (error) {
if (axios.isAxiosError(error)) {
const status = error.response?.status;
const detail = error.response?.data?.detail;

if (status === 401 || status === 403) {
return {
success: false,
error: {
type: 'auth',
message: detail || 'Authentication failed',
code: `HTTP_${status}`,
retryable: false,
},
};
}

if (status === 400 || status === 422) {
return {
success: false,
error: {
type: 'validation',
message: detail || 'Invalid request',
code: `HTTP_${status}`,
retryable: false,
},
};
}

if (status === 429 || (status && status >= 500)) {
return {
success: false,
error: {
type: 'server',
message: detail || 'Server error',
code: `HTTP_${status}`,
retryable: true,
},
};
}

if (error.code === 'ECONNREFUSED' || error.code === 'ETIMEDOUT') {
return {
success: false,
error: {
type: 'network',
message: 'Network connection failed',
code: error.code,
retryable: true,
},
};
}
}

return {
success: false,
error: {
type: 'unknown',
message: String(error),
retryable: false,
},
};
}
}

// Usage
const result = await safeApiCall(() => client.submitAction(action));

if (result.success) {
console.log('Action submitted:', result.data.id);
} else {
console.error(`Error (${result.error.type}): ${result.error.message}`);
if (result.error.retryable) {
console.log('This error can be retried');
}
}

HTTP Status Codes

StatusMeaningRetryable
400Bad Request - Invalid parametersNo
401Unauthorized - Invalid API keyNo
403Forbidden - Insufficient permissionsNo
404Not Found - Resource doesn't existNo
422Validation Error - Invalid dataNo
429Too Many Requests - Rate limitedYes (with backoff)
500Internal Server ErrorYes
502Bad GatewayYes
503Service UnavailableYes
504Gateway TimeoutYes

Best Practices

  1. Always wrap API calls in try-catch blocks
  2. Implement retry logic for transient errors (429, 5xx)
  3. Use exponential backoff to avoid overwhelming the server
  4. Log errors with context for debugging
  5. Handle authorization denials gracefully in your application
  6. Set appropriate timeouts to prevent hanging requests

Source Code Reference

Error handling patterns based on:

  • /ow-ai-backend/integration-examples/python_sdk_example.py:177-193
  • HTTP error codes from FastAPI routes in /ow-ai-backend/routes/

Next Steps