---
title: Error Handling | Tabstack
description: Build robust Python applications with the Tabstack SDK's comprehensive exception hierarchy and handling patterns.
---

Building robust applications requires proper error handling. The Tabstack Python SDK provides specific exception classes for different error scenarios.

## Error Class Hierarchy

```
TabstackError (base class)
├── APIError
│   ├── APIConnectionError
│   │   └── APITimeoutError
│   ├── APIResponseValidationError
│   └── APIStatusError
│       ├── BadRequestError (400)
│       ├── AuthenticationError (401)
│       ├── PermissionDeniedError (403)
│       ├── NotFoundError (404)
│       ├── ConflictError (409)
│       ├── UnprocessableEntityError (422)
│       ├── RateLimitError (429)
│       └── InternalServerError (>=500)
```

## Importing Error Classes

```
import tabstack
from tabstack import (
    Tabstack,
    TabstackError,
    APIError,
    APIStatusError,
    APIConnectionError,
    APITimeoutError,
    APIResponseValidationError,
    BadRequestError,
    AuthenticationError,
    PermissionDeniedError,
    NotFoundError,
    ConflictError,
    UnprocessableEntityError,
    RateLimitError,
    InternalServerError,
)
```

## Error Classes Reference

### TabstackError

Base error class for all SDK errors.

### APIError

Base class for all API-related errors.

### APIConnectionError

Raised when the SDK cannot connect to the API.

**Common causes:**

- Network connectivity issues
- DNS resolution failures
- Firewall blocking requests

### APITimeoutError

A subclass of `APIConnectionError` raised when a request times out.

**Common causes:**

- Server taking too long to respond
- Network latency issues
- Request timeout set too low

### APIStatusError

Base class for HTTP status code errors. All status errors have these attributes:

- `status_code: int` - HTTP status code
- `message: str` - Error message
- `body: object` - Response body

### BadRequestError (400)

Raised when the request is malformed or invalid.

**Common causes:**

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

### AuthenticationError (401)

Raised when authentication fails.

**Common causes:**

- Invalid API key
- Missing API key
- Expired API key

### PermissionDeniedError (403)

Raised when access to a resource is forbidden.

**Common causes:**

- Insufficient permissions
- Resource access denied

### NotFoundError (404)

Raised when the requested resource is not found.

**Common causes:**

- Invalid endpoint URL
- Resource no longer exists

### ConflictError (409)

Raised when there’s a conflict with the current state.

**Common causes:**

- Resource already exists
- Concurrent modification conflict

### UnprocessableEntityError (422)

Raised when the request is valid but cannot be processed.

**Common causes:**

- Invalid URL format
- URL points to private/internal resources
- Semantic validation errors

### RateLimitError (429)

Raised when rate limits are exceeded.

**Common causes:**

- Too many requests in a short period
- API quota exceeded

### InternalServerError (>=500)

Raised when the server encounters an internal error.

**Common causes:**

- Failed to fetch the target URL
- Page content too large
- Failed to generate/extract data
- Service temporarily unavailable (503)

## Basic Error Handling

### Simple Try-Except

```
import os
from tabstack import Tabstack, TabstackError


with Tabstack(api_key=os.getenv('TABSTACK_API_KEY')) as client:
    try:
        result = client.extract.markdown(url='https://example.com')
        print(result.content)
    except TabstackError as error:
        print(f'API error: {error}')
    except Exception as error:
        print(f'Unexpected error: {error}')
```

### Handling Specific Error Types

```
import os
import tabstack
from tabstack import Tabstack


with Tabstack(api_key=os.getenv('TABSTACK_API_KEY')) as client:
    try:
        result = client.extract.markdown(url='https://example.com')
        print(result.content)
    except tabstack.AuthenticationError:
        print('Authentication failed. Please check your API key.')
    except tabstack.UnprocessableEntityError as error:
        print(f'Invalid or inaccessible URL: {error}')
    except tabstack.RateLimitError as error:
        print(f'Rate limited. Status: {error.status_code}')
    except tabstack.InternalServerError as error:
        print(f'Server error occurred: {error}')
    except tabstack.APIConnectionError as error:
        print(f'Connection error: {error.__cause__}')
    except tabstack.APIStatusError as error:
        print(f'API error {error.status_code}: {error.message}')
    except Exception as error:
        print(f'Unexpected error: {error}')
        raise
```

## Advanced Error Handling

### Retry Logic with Exponential Backoff

The SDK has built-in retry logic (default: 2 retries), but you can implement custom retry behavior:

```
import os
import time
import tabstack
from tabstack import Tabstack


def extract_with_retry(url: str, max_retries: int = 3, base_delay: float = 1.0):
    """Extract markdown with custom retry logic for server errors."""
    last_error = None


    for attempt in range(max_retries):
        try:
            with Tabstack(api_key=os.getenv('TABSTACK_API_KEY')) as client:
                result = client.extract.markdown(url=url)
                return result


        except (tabstack.InternalServerError, tabstack.RateLimitError) as error:
            last_error = error


            if attempt < max_retries - 1:
                # Exponential backoff: 1s, 2s, 4s, etc.
                delay = base_delay * (2 ** attempt)
                print(f"Attempt {attempt + 1} failed. Retrying in {delay}s...")
                time.sleep(delay)
                continue


        except tabstack.APIStatusError as error:
            # Don't retry client errors (4xx except 429)
            raise


    raise Exception(f"Failed after {max_retries} attempts: {last_error}")


# Usage
try:
    result = extract_with_retry('https://example.com')
    print('Success:', result.content[:100])
except Exception as error:
    print(f'Failed: {error}')
```

### Batch Processing with Error Tracking

```
import os
from typing import List, Any
from dataclasses import dataclass
import tabstack
from tabstack import Tabstack


@dataclass
class BatchResult:
    url: str
    success: bool
    data: Any = None
    error: str = None


def batch_extract_with_error_handling(
    urls: List[str],
    schema: dict
) -> List[BatchResult]:
    """Extract from multiple URLs with error tracking."""
    results = []


    with Tabstack(api_key=os.getenv('TABSTACK_API_KEY')) as client:
        for url in urls:
            try:
                result = client.extract.json(url=url, json_schema=schema)
                results.append(BatchResult(
                    url=url,
                    success=True,
                    data=result
                ))
                print(f"Success: {url}")


            except tabstack.APIStatusError as error:
                results.append(BatchResult(
                    url=url,
                    success=False,
                    error=f"{error.status_code}: {error.message}"
                ))
                print(f"Failed: {url} - {error.status_code}: {error.message}")


            except Exception as error:
                results.append(BatchResult(
                    url=url,
                    success=False,
                    error=str(error)
                ))
                print(f"Failed: {url} - {error}")


    return results


# Usage
urls = [
    'https://example.com/page1',
    'https://example.com/page2',
    'https://example.com/page3'
]
schema = {"type": "object", "properties": {"title": {"type": "string"}}}


results = batch_extract_with_error_handling(urls, schema)


successful = [r for r in results if r.success]
failed = [r for r in results if not r.success]


print(f"\nBatch Complete: {len(successful)} succeeded, {len(failed)} failed")


if failed:
    print('\nFailed URLs:')
    for result in failed:
        print(f"  - {result.url}: {result.error}")
```

### Error Logging

```
import os
import logging
import tabstack
from tabstack import Tabstack


# Configure logging
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger('tabstack_app')


def logged_extract(url: str):
    """Extract with comprehensive error logging."""
    with Tabstack(api_key=os.getenv('TABSTACK_API_KEY')) as client:
        try:
            logger.info(f"Starting extraction from {url}")
            result = client.extract.markdown(url=url)
            logger.info(f"Successfully extracted {len(result.content)} characters")
            return result


        except tabstack.APIStatusError as error:
            logger.error(
                f"API error during extraction",
                extra={
                    'url': url,
                    'error_type': type(error).__name__,
                    'status_code': error.status_code,
                    'message': str(error)
                }
            )
            raise


        except tabstack.APIConnectionError as error:
            logger.error(f"Connection error during extraction from {url}: {error}")
            raise


        except Exception as error:
            logger.exception(f"Unexpected error during extraction from {url}")
            raise


# Usage
logged_extract('https://example.com')
```

## Automate Error Handling

The automate endpoint streams events, so error handling works differently:

```
import os
import tabstack
from tabstack import Tabstack


def automate_with_error_handling(task: str, url: str):
    """Execute automation with proper error handling."""
    with Tabstack(api_key=os.getenv('TABSTACK_API_KEY')) as client:
        try:
            stream = client.agent.automate(task=task, url=url)


            for event in stream:
                # Check event type for errors
                if event.event == 'error':
                    error_data = event.data
                    print(f'Automation error: {error_data}')
                    raise Exception(f'Automation failed: {error_data}')


                elif event.event == 'task:aborted':
                    reason = event.data
                    print(f'Task aborted: {reason}')
                    raise Exception(f'Task aborted: {reason}')


                elif event.event == 'complete':
                    result = event.data
                    print(f'Success: {result}')
                    return result


        except tabstack.APIStatusError as error:
            print(f'API error during automation: {error.status_code} - {error}')
            raise


        except tabstack.APIConnectionError as error:
            print(f'Connection error: {error}')
            raise


        except Exception as error:
            print(f'Unexpected error: {error}')
            raise


# Usage
automate_with_error_handling(
    'Extract the top 5 articles',
    'https://news.example.com'
)
```

## Best Practices

### 1. Always Use Try-Except

```
# Good
try:
    result = client.extract.markdown(url=url)
except tabstack.TabstackError as error:
    # Handle error
    pass
```

### 2. Handle Specific Errors First

```
# Good: Specific to generic
try:
    result = client.extract.markdown(url=url)
except tabstack.AuthenticationError:
    # Handle auth error - don't retry
    pass
except tabstack.RateLimitError:
    # Handle rate limit - wait and retry
    pass
except tabstack.UnprocessableEntityError:
    # Handle invalid URL - check the URL
    pass
except tabstack.InternalServerError:
    # Handle server error - maybe retry
    pass
except tabstack.APIStatusError:
    # Handle other API errors
    pass
except tabstack.APIConnectionError:
    # Handle connection issues
    pass
except Exception:
    # Handle unexpected errors
    pass
```

### 3. Implement Retry for Transient Errors

```
# Good: Retry server errors and rate limits
try:
    result = client.extract.markdown(url=url)
except (tabstack.InternalServerError, tabstack.RateLimitError):
    # Retry with backoff
    pass
except tabstack.AuthenticationError:
    # Don't retry - fix the API key
    raise
```

### 4. Use Context Managers

```
# Good: Automatic cleanup even on errors
with Tabstack(api_key=api_key) as client:
    result = client.extract.markdown(url=url)
```

### 5. Configure Retries at Client Level

```
# Disable retries for faster failure
client = Tabstack(max_retries=0)


# Increase retries for important requests
client = Tabstack(max_retries=5)


# Per-request retry override
result = client.with_options(max_retries=5).extract.markdown(url=url)
```

## Common Error Scenarios

### Invalid API Key

```
import tabstack
from tabstack import Tabstack


try:
    with Tabstack(api_key='invalid-key') as client:
        result = client.extract.markdown(url='https://example.com')
except tabstack.AuthenticationError:
    print('Please set a valid API key in TABSTACK_API_KEY environment variable')
```

### URL Cannot Be Accessed

```
import tabstack


try:
    result = client.extract.markdown(url='https://localhost:3000')
except tabstack.UnprocessableEntityError:
    print('Cannot access private URLs like localhost')
```

### Request Timeout

```
import tabstack
from tabstack import Tabstack


try:
    # Set a short timeout
    with Tabstack(timeout=5.0) as client:
        result = client.extract.markdown(url='https://slow-site.example.com')
except tabstack.APITimeoutError:
    print('Request timed out - try increasing the timeout')
```

### Rate Limiting

```
import tabstack
import time


try:
    result = client.extract.markdown(url='https://example.com')
except tabstack.RateLimitError as error:
    print(f'Rate limited (status {error.status_code}). Waiting before retry...')
    time.sleep(60)  # Wait before retrying
```

## Next Steps

- **[Quickstart](./quickstart)**: Get started with the SDK
- **[Generate Features](./generate)**: Discover AI transformations
- **[Automate Features](./automate)**: Execute browser automation
