A unified, type-safe API response format for Python β with full type inference, Pydantic support, and consistent structure for both success and error responses. Just pass your data or exception β build_api_response() handles the rest.
Works seamlessly with FastAPI, Django Ninja, or any Pydantic-based Python project. Accepts Pydantic models, dataclasses, or any structured object.
This library is:
- Designed for Pylance and mypy strict mode
- Fully generic β type-safe through all layers
- Uses overloads to preserve type inference
No need to type hint manually:
response = build_api_response(data=MySchema(...), status=200) # response.payload.data is inferred as MySchema β
β‘οΈ Want proof? See the typecheck file
This is a static analysis file for
mypy. It usesreveal_type()to confirm that generic types and payload structures are preserved correctly.
You can run it withmypyor open it in VSCode and hover to inspect types inline β no need to execute the file.
- β Typed response builders for both success and error responses
- β Fully generic, Pylance-compliant with strict mode enabled
- β
Single unified function:
build_api_response(...) - β
Extensible metadata support via
ResponseMeta - β
Automatically distinguishes between
dataanderror - β Raises clean custom exceptions on misconfiguration
pip install typed-api-response
from pydantic import BaseModel class MyOutputSchema(BaseModel): product_name: str description: str price: float qty: int on_sale: bool
@router.post("/foo") def foo(): your_data = MyOutputSchema( product_name="Big Bag of Rice", description="The world's greatest rice ever made. Anywhere. Ever.", price=17.99, qty=47328, on_sale=False, ) response = build_api_response(data=your_data, status=200) # response.payload.data is inferred as MyOutputSchema β
β build_api_response() accepts any Pydantic model or well-typed object (e.g. a dataclass) and wraps it into a fully structured, metadata-rich response β with full type hint propagation and IDE support via generics.
You can also return exceptions using the same unified response format:
try: ... except Exception as e: return build_api_response(error=e, status=418)
β build_api_response() wraps the exception in a type-safe, structured error payload β so your failure responses stay as consistent and predictable as your success ones.
def build_api_response( *, data: T | None = None, error: Exception | None = None, status: int, meta: ResponseMeta | None = None, ) -> ApiSuccessResponse[T] | ApiErrorResponse
- Provide either
dataorerror, not both metalets you attach timing, versioning, request ID, etc.- If neither
datanorerroris passed, raisesApiResponseBuilderError
{
"status": 200,
"meta": {
"duration": null,
"extra": null,
"method": null,
"path": null,
"request_id": null,
"timestamp": "2025εΉ΄07ζ30ζ₯T04:33:44.833Z",
"version": null
},
"payload": {
"success": true,
"data": {
"product_name": "Big Bag of Rice",
"description": "The world's greatest rice ever made. Anywhere. Ever.",
"price": 17.99,
"qty": 47328,
"on_sale": false
},
"error": null
}
}{
"status": 418,
"meta": {
"duration": null,
"extra": null,
"method": null,
"path": null,
"request_id": null,
"timestamp": "2025εΉ΄07ζ30ζ₯T00:13:55.531Z",
"version": null
},
"payload": {
"success": false,
"data": null,
"error": {
"type": "ZeroDivisionError",
"msg": "division by zero"
}
}
}Use ResponseMeta to attach custom fields:
meta = ResponseMeta( request_id="abc123", version="v1.2.0", extra={"model": "en_streetninja", "debug": True} ) return build_api_response(data=result, status=200, meta=meta)
This package raises:
ApiResponseBuilderErrorβ if payload generation failsApiPayloadBuilderErrorβ if payload data is inconsistent or incomplete
ApiResponseBuilderβ abstract base class for buildersApiSuccessResponseBuilderβ buildsApiSuccessResponse[T]ApiErrorResponseBuilderβ buildsApiErrorResponseResponseMetaβ optional metadata blockPayload/SuccessPayload[T]/ErrorPayloadβ structured payload schemas
If this saved you time or made your API cleaner, feel free to buy me a coffee. Thanks for your support π