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)
├── 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
- JavaScript
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 messagename: string- Error class namecause?: 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 codemessage: 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
- JavaScript
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
- JavaScript
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.');
// Notify user to update API key
} else if (error instanceof UnprocessableEntityError) {
console.error(`Invalid or inaccessible URL: ${url}`);
// Log and skip this URL
} else if (error instanceof RateLimitError) {
console.error('Rate limit exceeded. Waiting before retry...');
// Implement backoff logic
await new Promise(resolve => setTimeout(resolve, 60000));
return extractWithErrorHandling(url); // Retry
} else if (error instanceof InternalServerError) {
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
}
}
}
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.');
// Notify user to update API key
} else if (error instanceof UnprocessableEntityError) {
console.error(`Invalid or inaccessible URL: ${url}`);
// Log and skip this URL
} else if (error instanceof RateLimitError) {
console.error('Rate limit exceeded. Waiting before retry...');
// Implement backoff logic
await new Promise(resolve => setTimeout(resolve, 60000));
return extractWithErrorHandling(url); // Retry
} else if (error instanceof InternalServerError) {
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
- TypeScript
- JavaScript
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;
// Only retry on server errors and rate limits
if (error instanceof InternalServerError || error instanceof RateLimitError) {
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 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;
// Only retry on server errors and rate limits
if (error instanceof InternalServerError || error instanceof RateLimitError) {
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 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
- JavaScript
import Tabstack, { TabstackError } from '@tabstack/sdk';
interface BatchResult<T> {
url: string;
success: boolean;
data?: T;
error?: string;
}
async function batchExtractWithErrorHandling<T>(
client: Tabstack,
urls: string[],
schema: object
): Promise<BatchResult<T>[]> {
const results: BatchResult<T>[] = [];
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);
// 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}`));
}
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);
// 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
- TypeScript
- JavaScript
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);
// 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 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;
}
}
// 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());
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);
// 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;
}, {});
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;
}
}
// 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:
- TypeScript
- JavaScript
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) {
// Handle error events within the stream
if (event.event === 'error') {
const error = event.data?.error;
console.error('Automation error:', error);
// Decide whether to continue or abort
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);
// May retry or abort based on your needs
}
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) {
// Handle error events within the stream
if (event.event === 'error') {
const error = event.data?.error;
console.error('Automation error:', error);
// Decide whether to continue or abort
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);
// May retry or abort based on your needs
}
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
Never make API calls without error handling:
// 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
Check for specific error types before generic ones:
// Good: Specific to generic
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
Retry on server errors, but not on client errors:
// Good: Retry server errors only
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
Always log errors with context:
// Good: Contextual logging
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
Transform technical errors into actionable messages:
// Good: 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
Ensure resources are cleaned up even when errors occur:
// Good: Use finally for cleanup
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
// Error: AuthenticationError
// Solution: Verify API key is correct and active
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
// Error: UnprocessableEntityError
// Solution: Check URL is valid and publicly accessible
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
// Error: InternalServerError (failed to generate JSON)
// Solution: Adjust schema to match page content
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: Get started with the SDK
- Generate Features: Discover AI-powered transformations
- Automate Features: Execute browser automation
- REST API Documentation: View REST API error responses