Error Handling
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
Section titled “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└── APIConnectionTimeoutErrorImporting Error Classes
Section titled “Importing Error Classes”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
Section titled “Error Classes Reference”TabstackError
Section titled “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
Section titled “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)
Section titled “AuthenticationError (401)”Thrown when authentication fails.
Common causes:
- Invalid API key
- Missing API key
- Expired API key
PermissionDeniedError (403)
Section titled “PermissionDeniedError (403)”Thrown when you don’t have permission to access a resource.
Common causes:
- Insufficient permissions
- Resource access denied
NotFoundError (404)
Section titled “NotFoundError (404)”Thrown when a requested resource is not found.
Common causes:
- Invalid endpoint
- Resource doesn’t exist
ConflictError (409)
Section titled “ConflictError (409)”Thrown when there’s a conflict with the current state of a resource.
Common causes:
- Duplicate resource creation
- Version conflicts
UnprocessableEntityError (422)
Section titled “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)
Section titled “RateLimitError (429)”Thrown when you’ve exceeded the rate limit.
Common causes:
- Too many requests in a short period
- Quota exceeded
InternalServerError (500+)
Section titled “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)
Section titled “BadRequestError (400)”Thrown when the request is malformed or invalid.
Common causes:
- Missing required parameters
- Invalid JSON schema format
- Malformed request body
APIConnectionError
Section titled “APIConnectionError”Thrown when the SDK cannot connect to the API.
Common causes:
- Network connectivity issues
- DNS resolution failures
- Firewall blocking requests
APIConnectionTimeoutError
Section titled “APIConnectionTimeoutError”Thrown when the connection times out.
Common causes:
- Slow network connection
- Server not responding
- Request took too long
Basic Error Handling
Section titled “Basic Error Handling”Simple Try-Catch
Section titled “Simple Try-Catch”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
Section titled “Handling Specific Error Types”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
Section titled “Advanced Error Handling Patterns”Retry Logic with Exponential Backoff
Section titled “Retry Logic with Exponential Backoff”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}`);}
// Usageconst 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}`);}
// Usageconst 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
Section titled “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>( 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;}
// Usageconst 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;}
// Usageconst 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
Section titled “Error Logging and Monitoring”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<string, number>, );
return { total: this.errors.length, byType, }; }
exportToJSON() { return JSON.stringify(this.errors, null, 2); }}
// Usageconst 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); }}
// Usageconst 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
Section titled “Automate Error Handling”Automate operations stream events and require different error handling:
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; }}
// Usagetry { 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; }}
// Usagetry { await automateWithErrorHandling( "Extract the top 5 articles", "https://news.example.com", );} catch (error) { console.error("Automation failed completely:", error.message);}Best Practices
Section titled “Best Practices”1. Always Use Try-Catch
Section titled “1. Always Use Try-Catch”// Bad: No error handlingconst result = await client.extract.markdown({ url });
// Good: Proper error handlingtry { const result = await client.extract.markdown({ url });} catch (error) { // Handle error}2. Handle Specific Errors First
Section titled “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
Section titled “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
Section titled “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
Section titled “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
Section titled “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
Section titled “Common Error Scenarios”Invalid API Key
Section titled “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
Section titled “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
Section titled “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
Section titled “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