> ## Documentation Index
> Fetch the complete documentation index at: https://docs.law4devs.eu/llms.txt
> Use this file to discover all available pages before exploring further.

# Error Handling

> Handle API errors gracefully with the Law4Devs Python SDK exception hierarchy.

## Overview

All SDK errors inherit from `Law4DevsError`. Each exception class maps to a specific HTTP status code or error condition. The SDK automatically retries transient errors (429 and 5xx) with exponential backoff before raising.

***

## Exception Hierarchy

```
Law4DevsError
├── NotFoundError       (HTTP 404)
├── ValidationError     (HTTP 400 / 422)
├── RateLimitError      (HTTP 429)
└── ServerError         (HTTP 500 / 502 / 503 / 504)
```

All exceptions expose a `status_code` attribute containing the underlying HTTP status code.

```python theme={null}
from law4devs.exceptions import (
    Law4DevsError,
    NotFoundError,
    ValidationError,
    RateLimitError,
    ServerError,
)
```

***

## Exception Reference

### `Law4DevsError`

Base class for all SDK exceptions. Catch this to handle any API error.

| Attribute     | Type  | Description                  |
| ------------- | ----- | ---------------------------- |
| `status_code` | `int` | HTTP status code             |
| `message`     | `str` | Human-readable error message |

### `NotFoundError`

Raised when the requested resource does not exist (HTTP 404).

```python theme={null}
try:
    article = client.articles.get("gdpr", 9999)
except NotFoundError as e:
    print(f"Article not found: {e.message}")
```

### `ValidationError`

Raised when the request parameters are invalid (HTTP 400 or 422).

```python theme={null}
try:
    page = client.articles.list("gdpr", per_page=999)  # exceeds max
except ValidationError as e:
    print(f"Bad request: {e.message}")
```

### `RateLimitError`

Raised when the rate limit is exceeded and all retries are exhausted (HTTP 429).

```python theme={null}
try:
    results = client.search.query("encryption")
except RateLimitError as e:
    print(f"Rate limited. Retry after: {e.retry_after} seconds")
```

### `ServerError`

Raised when the server returns a 5xx response and all retries are exhausted.

```python theme={null}
try:
    page = client.frameworks.list()
except ServerError as e:
    print(f"Server error {e.status_code}: {e.message}")
```

***

## Auto-Retry Behaviour

The SDK automatically retries failed requests on HTTP 429 and 5xx responses. Retries use exponential backoff with jitter.

* **Default retries**: 3
* **Backoff**: 1s, 2s, 4s (with jitter)
* **Retried status codes**: 429, 500, 502, 503, 504

If all retries are exhausted, the appropriate exception (`RateLimitError` or `ServerError`) is raised.

Configure the retry count at construction time:

```python theme={null}
from law4devs import Law4DevsClient

# Disable retries
client = Law4DevsClient(max_retries=0)

# More retries for a long-running batch job
client = Law4DevsClient(max_retries=5)
```

***

## Practical Patterns

### Handle a missing resource

```python theme={null}
from law4devs import Law4DevsClient
from law4devs.exceptions import NotFoundError

client = Law4DevsClient()

try:
    article = client.articles.get("gdpr", 999)
except NotFoundError:
    print("That article doesn't exist")
```

### `safe_get` helper

A utility that returns `None` instead of raising on 404:

```python theme={null}
from law4devs.exceptions import NotFoundError

def safe_get_article(client, framework_slug, article_number):
    try:
        return client.articles.get(framework_slug, article_number)
    except NotFoundError:
        return None

article = safe_get_article(client, "gdpr", 999)
if article is None:
    print("Not found")
else:
    print(article.title)
```

### Catch all API errors

```python theme={null}
from law4devs.exceptions import Law4DevsError

try:
    page = client.frameworks.list()
except Law4DevsError as e:
    print(f"API error {e.status_code}: {e.message}")
```

### Handle rate limits explicitly

```python theme={null}
import time
from law4devs.exceptions import RateLimitError

def fetch_with_backoff(client, framework_slug):
    for attempt in range(5):
        try:
            return list(client.articles.iter(framework_slug))
        except RateLimitError as e:
            wait = getattr(e, "retry_after", 2 ** attempt)
            print(f"Rate limited. Waiting {wait}s (attempt {attempt + 1}/5)")
            time.sleep(wait)
    raise RuntimeError("Exhausted retries")
```

### Distinguish error types

```python theme={null}
from law4devs.exceptions import (
    NotFoundError,
    ValidationError,
    RateLimitError,
    ServerError,
    Law4DevsError,
)

try:
    result = client.articles.get("gdpr", article_number)
except NotFoundError:
    print("Article not found — check the article number")
except ValidationError as e:
    print(f"Invalid parameters: {e.message}")
except RateLimitError:
    print("Too many requests — slow down or add an API key")
except ServerError as e:
    print(f"Server problem ({e.status_code}) — try again later")
except Law4DevsError as e:
    print(f"Unexpected error {e.status_code}: {e.message}")
```

### Validate a framework slug before use

```python theme={null}
from law4devs.exceptions import NotFoundError

def framework_exists(client, slug):
    try:
        client.frameworks.get(slug)
        return True
    except NotFoundError:
        return False

if not framework_exists(client, "unknown-framework"):
    print("Framework not found")
```

***

## Timeout Errors

Network timeouts raise Python's built-in `TimeoutError` (not a `Law4DevsError`). Configure the timeout per client:

```python theme={null}
# Allow up to 60 seconds per request
client = Law4DevsClient(timeout=60)
```

```python theme={null}
try:
    page = client.frameworks.list()
except TimeoutError:
    print("Request timed out — the server may be slow")
```

<Tip>
  For batch jobs that process thousands of items, set `max_retries=5` and a generous `timeout=60`. The SDK will handle intermittent failures automatically.
</Tip>

<Note>
  404 responses are **not** retried automatically — they indicate a resource genuinely does not exist, not a transient failure.
</Note>
