SkillAgentSearch skills...

APIException

Standardize FastAPI error/exception handling with APIException. Custom error codes, fallback logging, and beautiful Swagger UI integration.

Install / Use

/learn @akutayural/APIException

README

<p align="center"> <img src="https://raw.githubusercontent.com/akutayural/APIException/main/assets/logo.png" alt="APIException Logo" width="450"/> </p> <p align="center"><b><i>Standardising FastAPI responses with clarity, consistency, and control.</i></b></p>

APIException: Standardised Exception Handling for FastAPI

PyPI version Documentation Downloads Python Versions License Ruff uv Poetry PyPI status Sponsor

APIException is a robust, production-ready Python library for FastAPI that simplifies exception handling and ensures consistent, well-structured API responses. Designed for developers who want to eliminate boilerplate error handling and improve Swagger/OpenAPI documentation, APIException makes your FastAPI projects cleaner and easier to maintain.

  • 🔒 Consistent JSON responses for both success and errors.
  • 📚 Beautiful Swagger/OpenAPI documentation with clear error cases.
  • ⚙️ Customizable error codes with BaseExceptionCode.
  • 🔗 Global fallback for unhandled server-side errors.
  • 🗂️ Use with multiple FastAPI apps.
  • 📜 Automatic logging of every exception detail.
  • ✔️ Production-ready with unit test examples.

· View on PyPI

· Full Documentation

Reading the full documentation is highly recommended — it’s clear, thorough, and helps you get started in minutes.


[!IMPORTANT] Actively maintained. Highly recommended.

APIException is actively maintained and used in real-world, production FastAPI services. It is designed to be integrated as a library, not copied piece by piece into projects.

New features, bug fixes, performance improvements, and documentation updates are shipped continuously through releases. If you copy the implementation instead of installing the library, you will miss future improvements, fixes, and enhancements by design.

If you find yourself copy-pasting error handling logic across services, this library exists to replace that pattern with something cleaner, safer, and more sustainable over time.


❤️ Support & Sponsorship

APIException is actively maintained and used in production FastAPI services.

If this library saves you time or replaces copy-pasted error-handling logic in your projects, you can support its continued development and long-term maintenance.

👉 Sponsor the project:
https://github.com/sponsors/akutayural


📦 Installation via pip

pip install apiexception

pip-install-apiexception-1.gif


⚡ Quickstart: How to Integrate APIException

1️⃣ Plug and Play

from fastapi import FastAPI
from pydantic import BaseModel, Field
from api_exception import (
    register_exception_handlers,
    APIException,
    BaseExceptionCode,
    ResponseModel,
    APIResponse,
)

app = FastAPI()
register_exception_handlers(app)  # uses ResponseModel by default


class CustomExceptionCode(BaseExceptionCode):
    USER_NOT_FOUND = ("USR-404", "User not found.", "The user ID does not exist.")


class UserModel(BaseModel):
    id: int = Field(..., example=1)
    username: str = Field(..., example="John Doe")


@app.get(
    "/user/{user_id}",
    response_model=ResponseModel[UserModel],
    responses=APIResponse.default(),
)
async def user(user_id: int):
    if user_id == 1:
        raise APIException(
            error_code=CustomExceptionCode.USER_NOT_FOUND,
            http_status_code=404,
        )
    
    if user_id == 3:
        a = 1
        b = 0
        c = a / b  # This will raise ZeroDivisionError and be caught by the global exception handler
        return c

    return ResponseModel[UserModel](
        data=UserModel(id=user_id, username="John Doe"),
        description="User retrieved successfully.",
    )

user{user_id}.gif

apiexception-indexApiExceptionLog.png

Enjoying APIException?
If this library saved you time or helped standardise your FastAPI responses,
consider giving it a ⭐ on GitHub. It really helps the project grow.


🔍 Example: More Detailed Error Handling Example with Custom Codes

from typing import List, Optional, Any, Dict
from fastapi import FastAPI, Path, Request
from pydantic import BaseModel, Field
from api_exception import (
    APIException,
    BaseExceptionCode,
    ResponseModel,
    register_exception_handlers,
    APIResponse,
    logger,
    ResponseFormat
)

logger.setLevel("DEBUG")
app = FastAPI()


def my_extra_fields(request: Request, exc: Optional[BaseException]) -> Dict[str, Any]:
    user_id = request.headers.get("x-user-id", "anonymous")
    return {
        "masked_user_id": f"user-{user_id[-2:]}",
        "service": "billing-service",
        "has_exc": exc is not None,
        "exc_type": type(exc).__name__ if exc else None,
    }


register_exception_handlers(app,
                            response_format=ResponseFormat.RESPONSE_MODEL,
                            log_traceback=True,
                            log_traceback_unhandled_exception=False,
                            log_level=10,
                            log=True,
                            response_headers=("x-user-id",),
                            log_request_context=True,
                            log_header_keys=("x-user-id",),
                            extra_log_fields=my_extra_fields
                            )


# Define your custom exception codes extending BaseExceptionCode
class CustomExceptionCode(BaseExceptionCode):
    USER_NOT_FOUND = ("USR-404", "User not found.", "The user ID does not exist.")
    INVALID_API_KEY = ("API-401", "Invalid API key.", "Provide a valid API key.")
    PERMISSION_DENIED = ("PERM-403", "Permission denied.", "Access to this resource is forbidden.")


# Let's assume you have a UserModel that represents the user data
class UserModel(BaseModel):
    id: int = Field(...)
    username: str = Field(...)


# Create the validation model for your response.
class UserResponse(BaseModel):
    users: List[UserModel] = Field(..., description="List of user objects")


@app.get("/user/{user_id}",
         response_model=ResponseModel[UserResponse],
         responses=APIResponse.default()
         )
async def user(user_id: int = Path()):
    if user_id == 1:
        raise APIException(
            error_code=CustomExceptionCode.USER_NOT_FOUND,
            http_status_code=401,
        )
    if user_id == 3:
        a = 1
        b = 0
        c = a / b  # This will raise ZeroDivisionError and be caught by the global exception handler
        return c

    users = [
        UserModel(id=1, username="John Doe"),
        UserModel(id=2, username="Jane Smith"),
        UserModel(id=3, username="Alice Johnson")
    ]
    data = UserResponse(users=users)
    return ResponseModel[UserResponse](
        data=data,
        description="User found and returned."
    )

The above code demonstrates how to handle exceptions in FastAPI using the APIException library.

When you run your FastAPI app and open Swagger UI (/docs),
your endpoints will display clean, predictable response schemas like this below:

user{user_id}.gif

apiexception-indexApiExceptionLog.png

For more examples, check it out in the examples directory.


- Successful API Response?

{
  "data": {
    "users": [
      {
        "id": 1,
        "username": "John Doe"
      },
      {
        "id": 2,
        "username": "Jane Smith"
      },
      {
        "id": 3,
        "username": "Alice Johnson"
      }
    ]
  },
  "status": "SUCCESS",
  "message": "Operation completed successfully.",
  "error_code": null,
  "description": "User found."
}

- Error API Response?

{
  "data": null,
  "status": "FAIL",
  "message": "User not found.",
  "error_code": "USR-404",
  "description": "The user ID does not exist."
}


In both error and the success cases, the response structure is consistent.

  • In the example above, when the user_id is 1, it raises an APIException with a custom error_code, the response is formatted according to the ResponseModel and it's logged automatically as shown below:

apiexception-indexApiExceptionLog.png


- Uncaught Exception API Response?

What if you forget to handle an exception such as in the example above?

  • When the user_id is 3, the program automatically catches the ZeroDivisionError and returns a standard error response, logging it in a clean structure as shown below:
{
  "data":

Related Skills

View on GitHub
GitHub Stars249
CategoryDevelopment
Updated12d ago
Forks12

Languages

Python

Security Score

100/100

Audited on Mar 26, 2026

No findings