# Error handling

The Python SDK provides a structured error hierarchy for handling server and client-side failures.

All errors raised by the Python SDK extend [`SurrealError`](/docs/reference/python/api/errors/#surrealerror), so you can catch every SDK error with a single `except` clause. Server-originated errors use the [`ServerError`](/docs/reference/python/api/errors/#servererror) subtree with structured kinds, details, and cause chains. SDK-side errors cover connection, parsing, and feature support failures.

The error kinds, wire codes and structured shape behind these are documented once in [Errors](/docs/reference/rest-api/errors.md), which applies to every SDK and protocol.

See the [Errors reference](/docs/reference/python/api/errors/) for the complete error hierarchy and all available properties.

## API references

<table>
	<thead>
		<tr>
			<th scope="col">Error class</th>
			<th scope="col">Description</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td scope="row" data-label="Error class"><a href="/docs/reference/python/api/errors/#surrealerror"><code>SurrealError</code></a></td>
			<td scope="row" data-label="Description">Base class for all SDK errors</td>
		</tr>
		<tr>
			<td scope="row" data-label="Error class"><a href="/docs/reference/python/api/errors/#servererror"><code>ServerError</code></a></td>
			<td scope="row" data-label="Description">Structured server error with kind, details, and cause</td>
		</tr>
		<tr>
			<td scope="row" data-label="Error class"><a href="/docs/reference/python/api/errors/#notallowederror"><code>NotAllowedError</code></a></td>
			<td scope="row" data-label="Description">Thrown when permission is denied</td>
		</tr>
		<tr>
			<td scope="row" data-label="Error class"><a href="/docs/reference/python/api/errors/#notfounderror"><code>NotFoundError</code></a></td>
			<td scope="row" data-label="Description">Thrown when a resource is not found</td>
		</tr>
		<tr>
			<td scope="row" data-label="Error class"><a href="/docs/reference/python/api/errors/#connectionunavailableerror"><code>ConnectionUnavailableError</code></a></td>
			<td scope="row" data-label="Description">Thrown when no connection is active</td>
		</tr>
		<tr>
			<td scope="row" data-label="Error class"><a href="/docs/reference/python/api/errors/#unsupportedfeatureerror"><code>UnsupportedFeatureError</code></a></td>
			<td scope="row" data-label="Description">Thrown for features not supported by the connection type</td>
		</tr>
	</tbody>
</table>

## Where an error surfaces

A query travels through two layers, and it is worth knowing which one a failure comes from.

The first is the request itself: a connection that is unavailable, a rejected sign-in, a query that will not parse. The second is the individual statements inside the query, which can fail while the request as a whole succeeds.

`.execute()` collapses both into an exception. It raises on the first statement that fails, so a query whose earlier statements succeeded returns nothing at all: those results are lost along with the error.

Where the per-statement outcome matters, [`query_raw()`](/docs/reference/python/api/core/surreal.md#query-raw) returns every statement instead of raising. Each entry carries a `status` of `OK` or `ERR`, and a failing one also carries the `kind`.

```python
from surrealdb import Surreal, ThrownError

with Surreal("ws://localhost:8000") as db:
    db.signin({"username": "root", "password": "secret"})
    db.use("test", "test")

    # .execute() raises on the first statement that fails, so the result of
    # the statement that succeeded is not returned.
    try:
        db.query("RETURN 1; THROW 'second'").execute()
    except ThrownError as e:
        print(f"{e.kind}: {e}")

    # query_raw() reports every statement instead of raising.
    response = db.query_raw("RETURN 1; THROW 'second'")
    for index, statement in enumerate(response["result"]):
        print(index, statement["status"], statement["result"])
```

```python title="Output"
Thrown: An error occurred: second
0 OK 1
1 ERR An error occurred: second
```

## Error kinds

Every server error carries a `.kind`, and the SDK raises a dedicated class for each of the kinds below. The meaning of each kind is described in [Errors](/docs/reference/rest-api/errors.md#error-kinds). Match on the kind or the class rather than on the message text, which is free to change between releases.

| Kind | Exception class |
| --- | --- |
| `Validation` | `ValidationError` |
| `Configuration` | `ConfigurationError` |
| `Query` | `QueryError` |
| `Serialization` | `SerializationError` |
| `NotAllowed` | `NotAllowedError` |
| `NotFound` | `NotFoundError` |
| `AlreadyExists` | `AlreadyExistsError` |
| `Thrown` | `ThrownError` |
| `Internal` | `InternalError` |

Any kind without a dedicated class, including one added by a newer server, arrives as the base `ServerError` with its `.kind` intact. Catching `ServerError` therefore stays correct as the server grows new kinds, and the `ErrorKind` enum can be used with `.has_kind()` to test for one without importing its class.

## Catching all SDK errors

The simplest way to handle errors is to catch `SurrealError`, which is the base class for every exception the SDK raises.

```python
from surrealdb import Surreal, SurrealError

with Surreal("ws://localhost:8000") as db:
    db.use("my_ns", "my_db")
    db.signin({"username": "root", "password": "secret"})

    try:
        result = db.query("SELECT * FROM users").execute()
    except SurrealError as e:
        print("SDK error:", e)
```

This pattern is useful at the top level of your application where you want to ensure no SDK error goes unhandled.

## Handling server errors

Server errors carry structured information beyond the error message. A `ServerError` has a `.kind` string, an optional `.details` dictionary, and an optional `.server_cause` linking to the underlying error in the chain.

You can check whether an error is a `ServerError` and then inspect its kind using the constants defined on [`ErrorKind`](/docs/reference/python/api/errors/#errorkind).

```python
from surrealdb import ServerError, ErrorKind

try:
    result = db.query("INVALID QUERY").execute()
except ServerError as e:
    print("Kind:", e.kind)
    print("Details:", e.details)

    if e.kind == ErrorKind.VALIDATION:
        print("The query has a validation issue")
    elif e.kind == ErrorKind.NOT_ALLOWED:
        print("Permission denied")
```

The `ErrorKind` constants include `VALIDATION`, `CONFIGURATION`, `THROWN`, `QUERY`, `SERIALIZATION`, `NOT_ALLOWED`, `NOT_FOUND`, `ALREADY_EXISTS`, `CONNECTION`, and `INTERNAL`.

## Inspecting the error cause chain

Server errors can form a chain where one error caused another. The `.has_kind()` method checks whether this error or any error in its cause chain matches a given kind. The `.find_cause()` method returns the first matching error in the chain.

```python
from surrealdb import ServerError, ErrorKind

try:
    db.signin({"username": "user", "password": "wrong"})
except ServerError as e:
    if e.has_kind(ErrorKind.NOT_ALLOWED):
        print("Authentication failure somewhere in the chain")

    auth_cause = e.find_cause(ErrorKind.NOT_ALLOWED)
    if auth_cause:
        print("Root auth error:", auth_cause)
        print("Details:", auth_cause.details)
```

These methods are especially useful when a high-level error wraps a more specific cause, such as a query error that was ultimately caused by a permission denial.

## Catching specific error types

For fine-grained control, catch the specific error subclass you need. The SDK maps server error kinds to dedicated Python classes such as `ValidationError`, [`NotAllowedError`](/docs/reference/python/api/errors/#notallowederror), and [`NotFoundError`](/docs/reference/python/api/errors/#notfounderror).

```python
from surrealdb import NotAllowedError

try:
    db.signin({
        "namespace": "surrealdb",
        "database": "docs",
        "access": "account",
        "variables": {
            "email": "user@example.com",
            "password": "wrong_password",
        },
    })
except NotAllowedError as e:
    if e.is_invalid_auth:
        print("Invalid credentials")
    elif e.is_token_expired:
        print("Token expired, please re-authenticate")
```

You can also catch `NotFoundError` to handle missing resources.

```python
from surrealdb import NotFoundError, RecordID

try:
    user = db.select(RecordID("users", "nonexistent"))
except NotFoundError as e:
    if e.table_name:
        print(f"Table not found: {e.table_name}")
    elif e.record_id:
        print(f"Record not found: {e.record_id}")
```

## Handling SDK-side errors

Some errors originate from the SDK itself rather than the server. These cover situations like missing connections and unsupported features.

A [`ConnectionUnavailableError`](/docs/reference/python/api/errors/#connectionunavailableerror) is raised when you try to perform an operation before establishing a connection.

```python
from surrealdb import Surreal, ConnectionUnavailableError

db = Surreal("ws://localhost:8000")

try:
    db.select("users")
except ConnectionUnavailableError:
    print("Not connected - call db.connect() first")
```

An [`UnsupportedFeatureError`](/docs/reference/python/api/errors/#unsupportedfeatureerror) is raised when you attempt to use a feature that requires a specific connection type. For example, sessions and transactions require a WebSocket connection.

```python
from surrealdb import Surreal, UnsupportedFeatureError

with Surreal("http://localhost:8000") as db:
    db.use("my_ns", "my_db")
    db.signin({"username": "root", "password": "secret"})

    try:
        session = db.new_session()
    except UnsupportedFeatureError:
        print("Sessions require a WebSocket connection")
```

An [`UnsupportedEngineError`](/docs/reference/python/api/errors/#unsupportedengineerror) is raised when the URL scheme is not recognized.

```python
from surrealdb import Surreal, UnsupportedEngineError

try:
    db = Surreal("ftp://localhost:8000")
except UnsupportedEngineError as e:
    print(f"Unsupported protocol: {e.url}")
```

## Learn more

- [Errors reference](/docs/reference/python/api/errors/) for complete error hierarchy
- [ErrorKind constants](/docs/reference/python/api/errors/#errorkind) for error kind matching
- [Authentication](/docs/reference/python/concepts/authentication.md) for auth-related error patterns
- [Connecting to SurrealDB](/docs/reference/python/concepts/connecting-to-surrealdb.md) for connection error patterns
