All errors raised by the Python SDK extend SurrealError, so you can catch every SDK error with a single except clause. Server-originated errors use the 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, which applies to every SDK and protocol.
See the Errors reference for the complete error hierarchy and all available properties.
API references
Error class | Description |
|---|---|
SurrealError | Base class for all SDK errors |
ServerError | Structured server error with kind, details, and cause |
NotAllowedError | Thrown when permission is denied |
NotFoundError | Thrown when a resource is not found |
ConnectionUnavailableError | Thrown when no connection is active |
UnsupportedFeatureError | Thrown for features not supported by the connection type |
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() returns every statement instead of raising. Each entry carries a status of OK or ERR, and a failing one also carries the kind.
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"])Thrown: An error occurred: second
0 OK 1
1 ERR An error occurred: secondError 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. 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.
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.
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.
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, and NotFoundError.
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.
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 is raised when you try to perform an operation before establishing a connection.
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 is raised when you attempt to use a feature that requires a specific connection type. For example, sessions and transactions require a WebSocket connection.
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 is raised when the URL scheme is not recognized.
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 for complete error hierarchy
ErrorKind constants for error kind matching
Authentication for auth-related error patterns
Connecting to SurrealDB for connection error patterns