--- title: Error Handling | Tabstack description: Build robust TypeScript applications with the Tabstack SDK's comprehensive error class hierarchy and handling patterns. --- 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) ├── APIError (with status code) │ ├── AuthenticationError (401) │ ├── PermissionDeniedError (403) │ ├── NotFoundError (404) │ ├── ConflictError (409) │ ├── UnprocessableEntityError (422) │ ├── RateLimitError (429) │ ├── InternalServerError (500+) │ └── BadRequestError (400) ├── APIConnectionError └── APIConnectionTimeoutError ``` ## Importing Error Classes - [TypeScript](#tab-panel-72) - [JavaScript](#tab-panel-73) ``` import Tabstack, { TabstackError, APIError, AuthenticationError, PermissionDeniedError, NotFoundError, ConflictError, UnprocessableEntityError, RateLimitError, InternalServerError, BadRequestError, APIConnectionError, APIConnectionTimeoutError } from '@tabstack/sdk'; ``` ``` const Tabstack = require('@tabstack/sdk').default; const { TabstackError, APIError, AuthenticationError, PermissionDeniedError, NotFoundError, ConflictError, UnprocessableEntityError, RateLimitError, InternalServerError, BadRequestError, APIConnectionError, APIConnectionTimeoutError } = require('@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 ### APIError Generic API error with HTTP status code. Used for errors that don’t match specific error classes. **Properties:** - `status: number` - HTTP status code - `message: string` - Error message ### AuthenticationError (401) Thrown when authentication fails. **Common causes:** - Invalid API key - Missing API key - Expired API key ### PermissionDeniedError (403) Thrown when you don’t have permission to access a resource. **Common causes:** - Insufficient permissions - Resource access denied ### NotFoundError (404) Thrown when a requested resource is not found. **Common causes:** - Invalid endpoint - Resource doesn’t exist ### ConflictError (409) Thrown when there’s a conflict with the current state of a resource. **Common causes:** - Duplicate resource creation - Version conflicts ### UnprocessableEntityError (422) Thrown when the provided data 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 - Invalid JSON schema format ### RateLimitError (429) Thrown when you’ve exceeded the rate limit. **Common causes:** - Too many requests in a short period - Quota exceeded ### InternalServerError (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 ### BadRequestError (400) Thrown when the request is malformed or invalid. **Common causes:** - Missing required parameters - Invalid JSON schema format - Malformed request body ### APIConnectionError Thrown when the SDK cannot connect to the API. **Common causes:** - Network connectivity issues - DNS resolution failures - Firewall blocking requests ### APIConnectionTimeoutError Thrown when the connection times out. **Common causes:** - Slow network connection - Server not responding - Request took too long ## Basic Error Handling ### Simple Try-Catch - [TypeScript](#tab-panel-74) - [JavaScript](#tab-panel-75) ``` import Tabstack, { TabstackError } from '@tabstack/sdk'; const client = new Tabstack({ apiKey: process.env.TABSTACK_API_KEY! }); try { const result = await client.extract.markdown({ url: '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); } } ``` ``` const Tabstack = require('@tabstack/sdk').default; const { TabstackError } = require('@tabstack/sdk'); const client = new Tabstack({ apiKey: process.env.TABSTACK_API_KEY }); try { const result = await client.extract.markdown({ url: '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 - [TypeScript](#tab-panel-76) - [JavaScript](#tab-panel-77) ``` import Tabstack, { AuthenticationError, UnprocessableEntityError, InternalServerError, RateLimitError, TabstackError } from '@tabstack/sdk'; const client = new Tabstack({ apiKey: process.env.TABSTACK_API_KEY! }); async function extractWithErrorHandling(url: string) { try { const result = await client.extract.markdown({ url }); return result; } catch (error) { if (error instanceof AuthenticationError) { console.error('Authentication failed. Please check your API key.'); } else if (error instanceof UnprocessableEntityError) { console.error(`Invalid or inaccessible URL: ${url}`); } else if (error instanceof RateLimitError) { console.error('Rate limit exceeded. Waiting before retry...'); await new Promise(resolve => setTimeout(resolve, 60000)); return extractWithErrorHandling(url); } else if (error instanceof InternalServerError) { console.error('Server error occurred. Retrying in 5 seconds...'); await new Promise(resolve => setTimeout(resolve, 5000)); return extractWithErrorHandling(url); } else if (error instanceof TabstackError) { console.error('API error:', error.message); } else { console.error('Unexpected error:', error); throw error; } } } ``` ``` const Tabstack = require('@tabstack/sdk').default; const { AuthenticationError, UnprocessableEntityError, InternalServerError, RateLimitError, TabstackError } = require('@tabstack/sdk'); const client = new Tabstack({ apiKey: process.env.TABSTACK_API_KEY }); async function extractWithErrorHandling(url) { try { const result = await client.extract.markdown({ url }); return result; } catch (error) { if (error instanceof AuthenticationError) { console.error('Authentication failed. Please check your API key.'); } else if (error instanceof UnprocessableEntityError) { console.error(`Invalid or inaccessible URL: ${url}`); } else if (error instanceof RateLimitError) { console.error('Rate limit exceeded. Waiting before retry...'); await new Promise(resolve => setTimeout(resolve, 60000)); return extractWithErrorHandling(url); } else if (error instanceof InternalServerError) { console.error('Server error occurred. Retrying in 5 seconds...'); await new Promise(resolve => setTimeout(resolve, 5000)); return extractWithErrorHandling(url); } else if (error instanceof TabstackError) { console.error('API error:', error.message); } else { console.error('Unexpected error:', error); throw error; } } } ``` ## Advanced Error Handling Patterns ### Retry Logic with Exponential Backoff - [TypeScript](#tab-panel-78) - [JavaScript](#tab-panel-79) ``` import Tabstack, { InternalServerError, RateLimitError } from '@tabstack/sdk'; async function extractWithRetry( client: Tabstack, url: string, maxRetries: number = 3, baseDelay: number = 1000 ) { let lastError: Error; for (let attempt = 0; attempt < maxRetries; attempt++) { try { const result = await client.extract.markdown({ url }); return result; } catch (error) { lastError = error; if (error instanceof InternalServerError || error instanceof RateLimitError) { if (attempt < maxRetries - 1) { 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; } } throw error; } } throw new Error(`Failed after ${maxRetries} attempts: ${lastError.message}`); } // Usage const client = new Tabstack({ apiKey: process.env.TABSTACK_API_KEY! }); try { const result = await extractWithRetry(client, 'https://example.com'); console.log('Success:', result.content); } catch (error) { console.error('Failed:', error.message); } ``` ``` const Tabstack = require('@tabstack/sdk').default; const { InternalServerError, RateLimitError } = require('@tabstack/sdk'); async function extractWithRetry( client, url, maxRetries = 3, baseDelay = 1000 ) { let lastError; for (let attempt = 0; attempt < maxRetries; attempt++) { try { const result = await client.extract.markdown({ url }); return result; } catch (error) { lastError = error; if (error instanceof InternalServerError || error instanceof RateLimitError) { if (attempt < maxRetries - 1) { 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; } } throw error; } } throw new Error(`Failed after ${maxRetries} attempts: ${lastError.message}`); } // Usage const client = new Tabstack({ apiKey: process.env.TABSTACK_API_KEY }); try { const result = await extractWithRetry(client, 'https://example.com'); console.log('Success:', result.content); } catch (error) { console.error('Failed:', error.message); } ``` ### Batch Processing with Error Tracking - [TypeScript](#tab-panel-80) - [JavaScript](#tab-panel-81) ``` import Tabstack, { TabstackError } from '@tabstack/sdk'; interface BatchResult { url: string; success: boolean; data?: T; error?: string; } async function batchExtractWithErrorHandling( client: Tabstack, urls: string[], schema: object ): Promise[]> { const results: BatchResult[] = []; for (const url of urls) { try { const result = await client.extract.json({ url, json_schema: schema }); results.push({ url, success: true, data: result as T }); 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 client = 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(client, urls, schema); 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}`)); } ``` ``` const Tabstack = require('@tabstack/sdk').default; const { TabstackError } = require('@tabstack/sdk'); async function batchExtractWithErrorHandling(client, urls, schema) { const results = []; for (const url of urls) { try { const result = await client.extract.json({ url, json_schema: schema }); results.push({ url, success: true, data: result }); 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 client = 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(client, urls, schema); 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 - [TypeScript](#tab-panel-82) - [JavaScript](#tab-panel-83) ``` import Tabstack, { TabstackError, APIError } from '@tabstack/sdk'; interface ErrorLog { timestamp: Date; operation: string; url?: string; errorType: string; message: string; status?: 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.status = error.status; } this.errors.push(errorLog); 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); return { total: this.errors.length, byType }; } exportToJSON() { return JSON.stringify(this.errors, null, 2); } } // Usage const client = new Tabstack({ apiKey: process.env.TABSTACK_API_KEY! }); const errorTracker = new ErrorTracker(); async function monitoredExtract(url: string) { try { return await client.extract.markdown({ url }); } catch (error) { errorTracker.log('extract.markdown', error, url); throw error; } } 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 } } console.log('\nError Summary:', errorTracker.getSummary()); ``` ``` const Tabstack = require('@tabstack/sdk').default; const { APIError } = require('@tabstack/sdk'); class ErrorTracker { constructor() { this.errors = []; } log(operation, error, url) { const errorLog = { timestamp: new Date(), operation, url, errorType: error.constructor.name, message: error.message, stack: error.stack }; if (error instanceof APIError) { errorLog.status = error.status; } this.errors.push(errorLog); 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; }, {}); return { total: this.errors.length, byType }; } exportToJSON() { return JSON.stringify(this.errors, null, 2); } } // Usage const client = new Tabstack({ apiKey: process.env.TABSTACK_API_KEY }); const errorTracker = new ErrorTracker(); async function monitoredExtract(url) { try { return await client.extract.markdown({ url }); } catch (error) { errorTracker.log('extract.markdown', error, url); throw error; } } 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 } } console.log('\nError Summary:', errorTracker.getSummary()); ``` ## Automate Error Handling Automate operations stream events and require different error handling: - [TypeScript](#tab-panel-84) - [JavaScript](#tab-panel-85) ``` import Tabstack, { TabstackError } from '@tabstack/sdk'; const client = new Tabstack({ apiKey: process.env.TABSTACK_API_KEY! }); async function automateWithErrorHandling(task: string, url: string) { try { const stream = await client.agent.automate({ task, url, guardrails: 'browse and extract only' }); for await (const event of stream) { if (event.event === 'error') { const error = event.data?.error; console.error('Automation error:', error); throw new Error(`Automation failed: ${error}`); } if (event.event === 'task:aborted') { const reason = event.data?.reason; console.warn('Task aborted:', reason); throw new Error(`Task aborted: ${reason}`); } if (event.event === 'task:validation_error') { const error = event.data?.error; console.error('Validation failed:', error); } if (event.event === 'task:completed') { const result = event.data?.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', 'https://news.example.com' ); } catch (error) { console.error('Automation failed completely:', error.message); } ``` ``` const Tabstack = require('@tabstack/sdk').default; const { TabstackError } = require('@tabstack/sdk'); const client = new Tabstack({ apiKey: process.env.TABSTACK_API_KEY }); async function automateWithErrorHandling(task, url) { try { const stream = await client.agent.automate({ task, url, guardrails: 'browse and extract only' }); for await (const event of stream) { if (event.event === 'error') { const error = event.data?.error; console.error('Automation error:', error); throw new Error(`Automation failed: ${error}`); } if (event.event === 'task:aborted') { const reason = event.data?.reason; console.warn('Task aborted:', reason); throw new Error(`Task aborted: ${reason}`); } if (event.event === 'task:validation_error') { const error = event.data?.error; console.error('Validation failed:', error); } if (event.event === 'task:completed') { const result = event.data?.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', 'https://news.example.com' ); } catch (error) { console.error('Automation failed completely:', error.message); } ``` ## Best Practices ### 1. Always Use Try-Catch ``` // Bad: No error handling const result = await client.extract.markdown({ url }); // Good: Proper error handling try { const result = await client.extract.markdown({ url }); } catch (error) { // Handle error } ``` ### 2. Handle Specific Errors First ``` try { // API call } catch (error) { if (error instanceof AuthenticationError) { // Handle auth error } else if (error instanceof UnprocessableEntityError) { // Handle URL error } else if (error instanceof TabstackError) { // Handle other API errors } else { // Handle unexpected errors } } ``` ### 3. Implement Retry Logic for Transient Errors ``` if (error instanceof InternalServerError || error instanceof RateLimitError) { // Implement retry with backoff } else if (error instanceof AuthenticationError || error instanceof BadRequestError) { // Don't retry - fix the issue first throw error; } ``` ### 4. Log Errors for Debugging ``` try { const result = await client.extract.json({ url, json_schema: 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 ``` try { const result = await client.extract.markdown({ url }); } catch (error) { if (error instanceof AuthenticationError) { return 'Your API key is invalid. Please check your credentials and try again.'; } else if (error instanceof UnprocessableEntityError) { 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 ``` let logFile; try { logFile = openLog(); const result = await client.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 ``` try { const result = await client.extract.markdown({ url }); } catch (error) { if (error instanceof AuthenticationError) { console.error('Please set a valid API key in TABSTACK_API_KEY environment variable'); process.exit(1); } } ``` ### URL Cannot Be Accessed ``` try { const result = await client.extract.markdown({ url: 'https://localhost:3000' }); } catch (error) { if (error instanceof UnprocessableEntityError) { console.error('Cannot access private URLs like localhost'); } } ``` ### Schema Mismatch ``` try { const result = await client.extract.json({ url, json_schema: incompatibleSchema }); } catch (error) { if (error instanceof InternalServerError && error.message.includes('failed to generate')) { console.error('Schema does not match page content. Try generating a schema first.'); } } ``` ## Next Steps - **[Quickstart](./quickstart)**: Get started with the SDK - **[Generate Features](./generate)**: Discover AI-powered transformations - **[Automate Features](./automate)**: Execute browser automation - **[REST API Documentation](/api/index.md)**: View REST API error responses