Skip to content

Errors API Reference

Complete reference for error handling classes in FastOpenAPI.

Error Class Hierarchy

APIError (base)
├── BadRequestError
├── ValidationError
├── ResourceNotFoundError
├── AuthenticationError
├── AuthorizationError
├── ResourceConflictError
├── InternalServerError
│   └── DependencyError
│       ├── CircularDependencyError
│       └── SecurityError
└── ServiceUnavailableError

APIError

Base exception class for all API errors.

from fastopenapi.errors import APIError

Class Definition

class APIError(Exception):
    status_code = HTTPStatus.INTERNAL_SERVER_ERROR
    default_message = "An error occurred"
    error_type = ErrorType.INTERNAL_SERVER_ERROR

    def __init__(
        self,
        message: str | None = None,
        details: Any | None = None,
    )

Attributes

  • status_code (HTTPStatus): HTTP status code (default: 500)
  • default_message (str): Default error message
  • error_type (ErrorType): Error type identifier
  • message (str): Error message
  • details (Any): Additional error details

Methods

to_response()

Convert to standardized error response.

def to_response() -> dict[str, Any]

Returns: Standardized error dictionary

Example:

error = BadRequestError("Invalid input")
response = error.to_response()
# {
#     "error": {
#         "type": "bad_request",
#         "message": "Invalid input",
#         "status": 400
#     }
# }

from_exception()

Convert any exception to an APIError.

@classmethod
def from_exception(
    exc: Exception,
    mapper: dict[type[Exception], type[APIError]] | None = None,
) -> APIError

Parameters: - exc: Exception to convert - mapper: Optional mapping of exception types to APIError subclasses

Returns: APIError instance

Example:

try:
    # Some code that may raise ValueError
    raise ValueError("Invalid value")
except Exception as e:
    api_error = APIError.from_exception(e)
    # Returns APIError with message "Invalid value" and status 500


BadRequestError

HTTP 400 - Bad Request

from fastopenapi.errors import BadRequestError

Definition

class BadRequestError(APIError):
    status_code = HTTPStatus.BAD_REQUEST  # 400
    default_message = "Bad request"
    error_type = ErrorType.BAD_REQUEST

Usage

@router.post("/items")
def create_item(name: str = Body(...)):
    if not name.strip():
        raise BadRequestError("Item name cannot be empty")

    return {"name": name}

Response Format

HTTP/1.1 400 Bad Request
Content-Type: application/json

{
  "error": {
    "type": "bad_request",
    "message": "Item name cannot be empty",
    "status": 400
  }
}

ValidationError

HTTP 422 - Unprocessable Entity

from fastopenapi.errors import ValidationError

Definition

class ValidationError(APIError):
    status_code = HTTPStatus.UNPROCESSABLE_ENTITY  # 422
    default_message = "Validation error"
    error_type = ErrorType.VALIDATION_ERROR

Usage

@router.post("/items")
def create_item(price: float):
    if price < 0:
        raise ValidationError(
            message="Price must be positive",
            details={"field": "price", "value": price}
        )

    return {"price": price}

Response Format

HTTP/1.1 422 Unprocessable Entity
Content-Type: application/json

{
  "error": {
    "type": "validation_error",
    "message": "Validation error",
    "status": 422,
    "details": "Value is not a valid email address"
  }
}

ResourceNotFoundError

HTTP 404 - Not Found

from fastopenapi.errors import ResourceNotFoundError

Definition

class ResourceNotFoundError(APIError):
    status_code = HTTPStatus.NOT_FOUND  # 404
    default_message = "Resource not found"
    error_type = ErrorType.RESOURCE_NOT_FOUND

Usage

@router.get("/users/{user_id}")
def get_user(user_id: int):
    user = database.get_user(user_id)
    if not user:
        raise ResourceNotFoundError(f"User {user_id} not found")

    return user

Response Format

HTTP/1.1 404 Not Found
Content-Type: application/json

{
  "error": {
    "type": "resource_not_found",
    "message": "User 123 not found",
    "status": 404
  }
}

AuthenticationError

HTTP 401 - Unauthorized

from fastopenapi.errors import AuthenticationError

Definition

class AuthenticationError(APIError):
    status_code = HTTPStatus.UNAUTHORIZED  # 401
    default_message = "Authentication required"
    error_type = ErrorType.AUTHENTICATION_ERROR

Usage

def get_current_user(token: str = Header(..., alias="Authorization")):
    if not token.startswith("Bearer "):
        raise AuthenticationError("Invalid token format")

    user = verify_token(token[7:])
    if not user:
        raise AuthenticationError("Invalid or expired token")

    return user

@router.get("/profile")
def get_profile(user = Depends(get_current_user)):
    return user

Response Format

HTTP/1.1 401 Unauthorized
Content-Type: application/json

{
  "error": {
    "type": "authentication_error",
    "message": "Invalid or expired token",
    "status": 401
  }
}

AuthorizationError

HTTP 403 - Forbidden

from fastopenapi.errors import AuthorizationError

Definition

class AuthorizationError(APIError):
    status_code = HTTPStatus.FORBIDDEN  # 403
    default_message = "Permission denied"
    error_type = ErrorType.AUTHORIZATION_ERROR

Usage

def require_admin(current_user = Depends(get_current_user)):
    if not current_user.is_admin:
        raise AuthorizationError("Admin access required")

    return current_user

@router.delete("/users/{user_id}")
def delete_user(user_id: int, admin = Depends(require_admin)):
    database.delete_user(user_id)
    return {"deleted": user_id}

Response Format

HTTP/1.1 403 Forbidden
Content-Type: application/json

{
  "error": {
    "type": "authorization_error",
    "message": "Admin access required",
    "status": 403
  }
}

ResourceConflictError

HTTP 409 - Conflict

from fastopenapi.errors import ResourceConflictError

Definition

class ResourceConflictError(APIError):
    status_code = HTTPStatus.CONFLICT  # 409
    default_message = "Resource conflict"
    error_type = ErrorType.RESOURCE_CONFLICT

Usage

@router.post("/users")
def create_user(username: str, email: str):
    if database.user_exists(username):
        raise ResourceConflictError(
            f"Username '{username}' already exists"
        )

    if database.email_exists(email):
        raise ResourceConflictError(
            f"Email '{email}' already registered"
        )

    user = database.create_user(username, email)
    return user

Response Format

HTTP/1.1 409 Conflict
Content-Type: application/json

{
  "error": {
    "type": "resource_conflict",
    "message": "Username 'john' already exists",
    "status": 409
  }
}

InternalServerError

HTTP 500 - Internal Server Error

from fastopenapi.errors import InternalServerError

Definition

class InternalServerError(APIError):
    status_code = HTTPStatus.INTERNAL_SERVER_ERROR  # 500
    default_message = "Internal server error"
    error_type = ErrorType.INTERNAL_SERVER_ERROR

Usage

@router.get("/items")
def list_items():
    try:
        items = database.get_all_items()
        return items
    except DatabaseError as e:
        raise InternalServerError(
            message="Database error occurred",
            details=str(e)
        )

Response Format

HTTP/1.1 500 Internal Server Error
Content-Type: application/json

{
  "error": {
    "type": "internal_server_error",
    "message": "Database error occurred",
    "status": 500,
    "details": "Connection timeout"
  }
}

ServiceUnavailableError

HTTP 503 - Service Unavailable

from fastopenapi.errors import ServiceUnavailableError

Definition

class ServiceUnavailableError(APIError):
    status_code = HTTPStatus.SERVICE_UNAVAILABLE  # 503
    default_message = "Service unavailable"
    error_type = ErrorType.SERVICE_UNAVAILABLE

Usage

@router.get("/items")
def list_items():
    if not database.is_connected():
        raise ServiceUnavailableError("Database is unavailable")

    return database.get_all_items()

Dependency Errors

DependencyError

Base exception for dependency resolution errors.

from fastopenapi.errors import DependencyError
class DependencyError(InternalServerError):
    default_message = "dependency_error"

CircularDependencyError

Raised when circular dependencies are detected.

from fastopenapi.errors import CircularDependencyError
class CircularDependencyError(DependencyError):
    default_message = "circular_dependency_error"

SecurityError

Raised when security requirements are not met.

from fastopenapi.errors import SecurityError
class SecurityError(DependencyError):
    status_code = HTTPStatus.FORBIDDEN  # 403
    default_message = "insufficient_scope"
    error_type = ErrorType.AUTHORIZATION_ERROR

Exception Mapping

Map framework-specific exceptions to API errors:

from fastopenapi.routers.base import BaseAdapter

# Custom exception mapper
EXCEPTION_MAPPER = {
    ValueError: BadRequestError,
    KeyError: ResourceNotFoundError,
    PermissionError: AuthorizationError,
}

class MyRouter(BaseAdapter):
    EXCEPTION_MAPPER = EXCEPTION_MAPPER

STATUS_TO_ERROR_TYPE

A mapping of HTTP status codes to error types, used internally for exception conversion:

from fastopenapi.errors.exceptions import STATUS_TO_ERROR_TYPE

# STATUS_TO_ERROR_TYPE = {
#     HTTPStatus.BAD_REQUEST: ErrorType.BAD_REQUEST,
#     HTTPStatus.UNAUTHORIZED: ErrorType.AUTHENTICATION_ERROR,
#     HTTPStatus.FORBIDDEN: ErrorType.AUTHORIZATION_ERROR,
#     HTTPStatus.NOT_FOUND: ErrorType.RESOURCE_NOT_FOUND,
#     HTTPStatus.CONFLICT: ErrorType.RESOURCE_CONFLICT,
#     HTTPStatus.UNPROCESSABLE_ENTITY: ErrorType.VALIDATION_ERROR,
#     HTTPStatus.INTERNAL_SERVER_ERROR: ErrorType.INTERNAL_SERVER_ERROR,
#     HTTPStatus.SERVICE_UNAVAILABLE: ErrorType.SERVICE_UNAVAILABLE,
# }

This is used by APIError.from_exception() to determine the error type when converting framework exceptions.


Custom Errors

Create custom error classes:

from fastopenapi.errors import APIError
from http import HTTPStatus

class RateLimitError(APIError):
    status_code = HTTPStatus.TOO_MANY_REQUESTS  # 429
    default_message = "Rate limit exceeded"
    error_type = "rate_limit_error"

@router.get("/api/data")
def get_data():
    if rate_limiter.is_exceeded():
        raise RateLimitError("Too many requests. Try again later.")

    return {"data": "..."}

Error Response Structure

All errors follow this structure:

{
  "error": {
    "type": "error_type_identifier",
    "message": "Human-readable error message",
    "status": 400,
    "details": "Optional additional details (string, included only when provided)"
  }
}

Best Practices

1. Use Specific Error Classes

# Good
raise ResourceNotFoundError(f"User {user_id} not found")

# Avoid
raise Exception("User not found")

2. Provide Helpful Messages

# Good
raise BadRequestError("Email format is invalid. Expected: user@example.com")

# Avoid
raise BadRequestError("Invalid input")

3. Include Details for Validation Errors

raise ValidationError(
    message="Validation failed",
    details=[
        {"field": "email", "error": "Invalid format"},
        {"field": "age", "error": "Must be >= 18"}
    ]
)

4. Don't Expose Sensitive Information

# Good
raise InternalServerError("Database error occurred")

# Avoid
raise InternalServerError(f"SQL error: {sql_exception.query}")

See Also