Skip to main content

Error Handling

Building robust applications requires proper error handling. The TABStack SDK provides a comprehensive error class hierarchy and patterns for handling different error scenarios.

Error Class Hierarchy

The SDK includes specific error classes for different failure scenarios:

TABStackError (base class)
├── BadRequestError (400)
├── UnauthorizedError (401)
├── InvalidURLError (422)
├── ServerError (500)
├── ServiceUnavailableError (503)
└── APIError (generic, with status code)

Importing Error Classes

import {
TABStack,
TABStackError,
BadRequestError,
UnauthorizedError,
InvalidURLError,
ServerError,
ServiceUnavailableError,
APIError
} from '@tabstack/sdk';

Error Classes Reference

TABStackError

Base error class for all SDK errors. All other error classes extend this.

Properties:

  • message: string - Error message
  • name: string - Error class name
  • cause?: Error - Optional underlying error

BadRequestError (400)

Thrown when the request is malformed or invalid.

Common causes:

  • Missing required parameters
  • Invalid JSON schema format
  • Malformed request body

UnauthorizedError (401)

Thrown when authentication fails.

Common causes:

  • Invalid API key
  • Missing API key
  • Expired API key

InvalidURLError (422)

Thrown when the provided URL is invalid or inaccessible.

Common causes:

  • Malformed URL format
  • URL points to private/internal resources (localhost, 127.0.0.1, private IPs)
  • URL is unreachable

ServerError (500)

Thrown when the server encounters an internal error.

Common causes:

  • Failed to fetch the URL
  • Page content too large
  • Failed to generate/extract data
  • Server-side processing error

ServiceUnavailableError (503)

Thrown when the service is temporarily unavailable.

Common causes:

  • Server maintenance
  • Temporary overload
  • Rate limiting

APIError

Generic API error with HTTP status code. Used for errors that don't match specific error classes.

Properties:

  • statusCode: number - HTTP status code
  • message: string - Error message

Basic Error Handling

Simple Try-Catch

import { TABStack, TABStackError } from '@tabstack/sdk';

const tabs = new TABStack({
apiKey: process.env.TABSTACK_API_KEY!
});

try {
const result = await tabs.extract.markdown('https://example.com');
console.log(result.content);
} catch (error) {
if (error instanceof TABStackError) {
console.error('API error:', error.message);
} else {
console.error('Unexpected error:', error);
}
}

Handling Specific Error Types

import {
TABStack,
UnauthorizedError,
InvalidURLError,
ServerError,
TABStackError
} from '@tabstack/sdk';

const tabs = new TABStack({
apiKey: process.env.TABSTACK_API_KEY!
});

async function extractWithErrorHandling(url: string) {
try {
const result = await tabs.extract.markdown(url);
return result;
} catch (error) {
if (error instanceof UnauthorizedError) {
console.error('Authentication failed. Please check your API key.');
// Notify user to update API key
} else if (error instanceof InvalidURLError) {
console.error(`Invalid or inaccessible URL: ${url}`);
// Log and skip this URL
} else if (error instanceof ServerError) {
console.error('Server error occurred. Retrying in 5 seconds...');
// Implement retry logic
await new Promise(resolve => setTimeout(resolve, 5000));
return extractWithErrorHandling(url); // Retry
} else if (error instanceof TABStackError) {
console.error('API error:', error.message);
} else {
console.error('Unexpected error:', error);
throw error; // Re-throw unexpected errors
}
}
}

Advanced Error Handling Patterns

Retry Logic with Exponential Backoff

import { TABStack, ServerError, ServiceUnavailableError } from '@tabstack/sdk';

async function extractWithRetry(
tabs: TABStack,
url: string,
maxRetries: number = 3,
baseDelay: number = 1000
) {
let lastError: Error;

for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
const result = await tabs.extract.markdown(url);
return result;
} catch (error) {
lastError = error;

// Only retry on server errors
if (error instanceof ServerError || error instanceof ServiceUnavailableError) {
if (attempt < maxRetries - 1) {
// Exponential backoff: 1s, 2s, 4s, 8s, etc.
const delay = baseDelay * Math.pow(2, attempt);
console.log(`Attempt ${attempt + 1} failed. Retrying in ${delay}ms...`);
await new Promise(resolve => setTimeout(resolve, delay));
continue;
}
}

// Don't retry other errors
throw error;
}
}

throw new Error(`Failed after ${maxRetries} attempts: ${lastError.message}`);
}

// Usage
const tabs = new TABStack({ apiKey: process.env.TABSTACK_API_KEY! });

try {
const result = await extractWithRetry(tabs, 'https://example.com');
console.log('Success:', result.content);
} catch (error) {
console.error('Failed:', error.message);
}

Batch Processing with Error Tracking

import { TABStack, TABStackError } from '@tabstack/sdk';

interface BatchResult<T> {
url: string;
success: boolean;
data?: T;
error?: string;
}

async function batchExtractWithErrorHandling<T>(
tabs: TABStack,
urls: string[],
schema: object
): Promise<BatchResult<T>[]> {
const results: BatchResult<T>[] = [];

for (const url of urls) {
try {
const result = await tabs.extract.json<T>(url, schema);
results.push({
url,
success: true,
data: result.data
});
console.log(`✅ Success: ${url}`);
} catch (error) {
const errorMessage = error instanceof TABStackError
? error.message
: 'Unknown error';

results.push({
url,
success: false,
error: errorMessage
});
console.error(`❌ Failed: ${url} - ${errorMessage}`);
}

// Respectful delay between requests
await new Promise(resolve => setTimeout(resolve, 500));
}

return results;
}

// Usage
const tabs = new TABStack({ apiKey: process.env.TABSTACK_API_KEY! });
const urls = [
'https://example.com/page1',
'https://example.com/page2',
'https://example.com/page3'
];

const schema = {
type: 'object',
properties: {
title: { type: 'string' }
}
};

const results = await batchExtractWithErrorHandling(tabs, urls, schema);

// Summary
const successful = results.filter(r => r.success);
const failed = results.filter(r => !r.success);

console.log(`\nBatch Complete: ${successful.length} succeeded, ${failed.length} failed`);

if (failed.length > 0) {
console.log('\nFailed URLs:');
failed.forEach(r => console.log(` - ${r.url}: ${r.error}`));
}

Error Logging and Monitoring

import { TABStack, TABStackError, APIError } from '@tabstack/sdk';

interface ErrorLog {
timestamp: Date;
operation: string;
url?: string;
errorType: string;
message: string;
statusCode?: number;
stack?: string;
}

class ErrorTracker {
private errors: ErrorLog[] = [];

log(operation: string, error: Error, url?: string) {
const errorLog: ErrorLog = {
timestamp: new Date(),
operation,
url,
errorType: error.constructor.name,
message: error.message,
stack: error.stack
};

if (error instanceof APIError) {
errorLog.statusCode = error.statusCode;
}

this.errors.push(errorLog);

// Also log to console
console.error(`[${errorLog.timestamp.toISOString()}] ${operation} failed:`, error.message);
}

getErrors() {
return this.errors;
}

getSummary() {
const byType = this.errors.reduce((acc, error) => {
acc[error.errorType] = (acc[error.errorType] || 0) + 1;
return acc;
}, {} as Record<string, number>);

return {
total: this.errors.length,
byType
};
}

exportToJSON() {
return JSON.stringify(this.errors, null, 2);
}
}

// Usage
const tabs = new TABStack({ apiKey: process.env.TABSTACK_API_KEY! });
const errorTracker = new ErrorTracker();

async function monitoredExtract(url: string) {
try {
return await tabs.extract.markdown(url);
} catch (error) {
errorTracker.log('extract.markdown', error, url);
throw error;
}
}

// Run multiple operations
const urls = ['https://example.com/page1', 'https://example.com/page2'];

for (const url of urls) {
try {
await monitoredExtract(url);
} catch (error) {
// Error already logged, continue processing
}
}

// Print summary
console.log('\nError Summary:', errorTracker.getSummary());

// Export logs if needed
// fs.writeFileSync('error-log.json', errorTracker.exportToJSON());

Automate Error Handling

Automate operations stream events and require different error handling:

import { TABStack, TABStackError } from '@tabstack/sdk';

const tabs = new TABStack({ apiKey: process.env.TABSTACK_API_KEY! });

async function automateWithErrorHandling(task: string, options: any) {
try {
for await (const event of tabs.automate.execute(task, options)) {
// Handle error events within the stream
if (event.type === 'error') {
const error = event.data.get('error');
console.error('Automation error:', error);

// Decide whether to continue or abort
throw new Error(`Automation failed: ${error}`);
}

if (event.type === 'task:aborted') {
const reason = event.data.get('reason');
console.warn('Task aborted:', reason);
throw new Error(`Task aborted: ${reason}`);
}

if (event.type === 'task:validation_error') {
const error = event.data.get('error');
console.error('Validation failed:', error);
// May retry or abort based on your needs
}

if (event.type === 'task:completed') {
const result = event.data.get('finalAnswer');
console.log('Success:', result);
return result;
}
}
} catch (error) {
if (error instanceof TABStackError) {
console.error('API error during automation:', error.message);
} else {
console.error('Unexpected error:', error);
}
throw error;
}
}

// Usage
try {
await automateWithErrorHandling(
'Extract the top 5 articles',
{ url: 'https://news.example.com' }
);
} catch (error) {
console.error('Automation failed completely:', error.message);
}

Best Practices

1. Always Use Try-Catch

Never make API calls without error handling:

// ❌ Bad: No error handling
const result = await tabs.extract.markdown(url);

// ✅ Good: Proper error handling
try {
const result = await tabs.extract.markdown(url);
} catch (error) {
// Handle error
}

2. Handle Specific Errors First

Check for specific error types before generic ones:

// ✅ Good: Specific to generic
try {
// API call
} catch (error) {
if (error instanceof UnauthorizedError) {
// Handle auth error
} else if (error instanceof InvalidURLError) {
// Handle URL error
} else if (error instanceof TABStackError) {
// Handle other API errors
} else {
// Handle unexpected errors
}
}

3. Implement Retry Logic for Transient Errors

Retry on server errors, but not on client errors:

// ✅ Good: Retry server errors only
if (error instanceof ServerError || error instanceof ServiceUnavailableError) {
// Implement retry with backoff
} else if (error instanceof UnauthorizedError || error instanceof BadRequestError) {
// Don't retry - fix the issue first
throw error;
}

4. Log Errors for Debugging

Always log errors with context:

// ✅ Good: Contextual logging
try {
const result = await tabs.extract.json(url, schema);
} catch (error) {
console.error(`Failed to extract from ${url}:`, {
errorType: error.constructor.name,
message: error.message,
url,
timestamp: new Date().toISOString()
});
}

5. Provide User-Friendly Messages

Transform technical errors into actionable messages:

// ✅ Good: User-friendly messages
try {
const result = await tabs.extract.markdown(url);
} catch (error) {
if (error instanceof UnauthorizedError) {
return 'Your API key is invalid. Please check your credentials and try again.';
} else if (error instanceof InvalidURLError) {
return 'The URL you provided cannot be accessed. Please verify it is correct and publicly accessible.';
} else {
return 'An unexpected error occurred. Please try again later or contact support.';
}
}

6. Clean Up Resources on Error

Ensure resources are cleaned up even when errors occur:

// ✅ Good: Use finally for cleanup
let logFile;
try {
logFile = openLog();
const result = await tabs.extract.markdown(url);
writeLog(logFile, result);
} catch (error) {
console.error('Error:', error);
throw error;
} finally {
if (logFile) {
closeLog(logFile);
}
}

Common Error Scenarios

Invalid API Key

// Error: UnauthorizedError
// Solution: Verify API key is correct and active
try {
const result = await tabs.extract.markdown(url);
} catch (error) {
if (error instanceof UnauthorizedError) {
console.error('Please set a valid API key in TABSTACK_API_KEY environment variable');
process.exit(1);
}
}

URL Cannot Be Accessed

// Error: InvalidURLError
// Solution: Check URL is valid and publicly accessible
try {
const result = await tabs.extract.markdown('https://localhost:3000');
} catch (error) {
if (error instanceof InvalidURLError) {
console.error('Cannot access private URLs like localhost');
}
}

Schema Mismatch

// Error: ServerError (failed to generate JSON)
// Solution: Adjust schema to match page content
try {
const result = await tabs.extract.json(url, incompatibleSchema);
} catch (error) {
if (error instanceof ServerError && error.message.includes('failed to generate')) {
console.error('Schema does not match page content. Try generating a schema first.');
}
}

Next Steps