Automatic OpenAPI 3.0 specification generation with multiple UI options.
- Overview
- Quick Start
- Configuration
- Multiple UIs
- Customizing Documentation
- Decorators
- Schema Generation
- Security Schemes
- Best Practices
Mitsuki automatically generates OpenAPI 3.0 specifications from your controllers, including:
- Automatic endpoint discovery - All
@RestControllerroutes - Type inference - Request/response schemas from type hints
- Multiple UIs - Swagger UI, ReDoc, and Scalar
- Zero configuration - Works out of the box
- Customizable - Add descriptions, tags, examples via decorators
Available by default:
- OpenAPI JSON:
http://localhost:8000/openapi.json - Documentation UI:
http://localhost:8000/docs(Scalar by default)
from mitsuki import Application, RestController, GetMapping
@RestController("/api/users")
class UserController:
@GetMapping("/{id}")
async def get_user(self, id: int) -> dict:
"""Get user by ID."""
return {"id": id, "name": "John Doe"}
@Application
class MyApp:
pass
if __name__ == "__main__":
MyApp.run()Visit:
http://localhost:8000/docs- Interactive documentationhttp://localhost:8000/openapi.json- OpenAPI spec
application.yml:
openapi:
enabled: true
title: "My API"
version: "1.0.0"
description: "My awesome API built with Mitsuki"application.yml:
openapi:
enabled: true # Enable/disable OpenAPI generation
# API Metadata
title: "My API"
version: "1.0.0"
description: "Comprehensive API documentation"
# Contact Information
contact:
name: "API Support"
email: "support@example.com"
url: "https://example.com/support"
# License
license:
name: "Apache 2.0"
url: "https://www.apache.org/licenses/LICENSE-2.0.html"
# Server Information
server:
url: "https://api.example.com"
description: "Production server"
# UI Configuration
ui:
- swagger # Enable Swagger UI at /swagger
- redoc # Enable ReDoc at /redoc
- scalar # Enable Scalar at /scalar
docs_ui: scalar # Which UI to serve at /docs
docs_url: /docs # Path for main docs
openapi_url: /openapi.json # Path for OpenAPI specFrom mitsuki/config/defaults.yml:
openapi:
enabled: true
ui:
- swagger
docs_ui: scalar
title: "Mitsuki API"
version: "1.0.0"
description: ""
docs_url: /docs
openapi_url: /openapi.jsonOverride configuration via environment:
MITSUKI_OPENAPI_ENABLED=true
MITSUKI_OPENAPI_TITLE="My API"
MITSUKI_OPENAPI_VERSION="2.0.0"Mitsuki supports three OpenAPI UI renderers simultaneously.
application.yml:
openapi:
ui:
- swagger
- redoc
- scalar
docs_ui: scalar # Preferred UI at /docsAccess points:
/docs- Preferred UI (configured bydocs_ui)/swagger- Swagger UI/redoc- ReDoc/scalar- Scalar
Enable only one UI:
openapi:
ui:
- scalar
docs_ui: scalarResult:
/docs- Scalar UI/scalar- Scalar UI/openapi.json- OpenAPI spec
@RestController("/api/users")
class UserController:
@GetMapping("/{id}")
async def get_user(self, id: int) -> dict:
"""
Get user by ID.
Retrieves a single user record from the database
using the provided user ID.
"""
return {"id": id, "name": "John"}Docstrings automatically become operation descriptions.
Use type hints for automatic schema generation:
from dataclasses import dataclass
@dataclass
class User:
id: int
name: str
email: str
@RestController("/api/users")
class UserController:
@GetMapping("/{id}")
async def get_user(self, id: int) -> User:
"""Get user by ID."""
return User(id=id, name="John", email="john@example.com")OpenAPI will automatically generate a User schema.
Add detailed operation metadata.
from mitsuki import GetMapping, RestController
from mitsuki.openapi import OpenAPIOperation
@RestController("/api/users")
class UserController:
@GetMapping("/{id}")
@OpenAPIOperation(
summary="Get user by ID",
description="Retrieve a single user by their unique identifier",
tags=["Users", "Management"],
responses={
404: {"description": "User not found"},
500: {"description": "Internal server error"}
},
deprecated=False
)
async def get_user(self, id: int):
return {"id": id, "name": "John"}Parameters:
summary- Brief operation summarydescription- Detailed descriptiontags- List of tags for groupingresponses- Custom response definitionsparameters- Additional parameter docsdeprecated- Mark as deprecatedoperation_id- Custom operation ID
Add tag metadata to controllers.
from mitsuki import RestController
from mitsuki.openapi import OpenAPITag
@RestController("/api/users")
@OpenAPITag(
name="Users",
description="User management and authentication",
external_docs={
"description": "User API Guide",
"url": "https://docs.example.com/users"
}
)
class UserController:
passParameters:
name- Tag namedescription- Tag descriptionexternal_docs- Link to external documentation
Specify security requirements.
from mitsuki import GetMapping
from mitsuki.openapi import OpenAPISecurity
@GetMapping("/protected")
@OpenAPISecurity(["bearerAuth"])
async def protected_endpoint(self):
return {"data": "sensitive"}Parameters:
schemes- List of security scheme names
Mitsuki automatically generates schemas from:
- Dataclasses
- Enums
- Type hints
- Nested objects
Example:
from dataclasses import dataclass
from enum import Enum
class Status(str, Enum):
ACTIVE = "active"
INACTIVE = "inactive"
@dataclass
class Address:
street: str
city: str
country: str
@dataclass
class User:
name: str
email: str
status: Status
address: Address
@RestController("/users")
class UserController:
@GetMapping("/{id}")
async def get_user(self, id: int) -> User:
return User(
name="John",
email="john@example.com",
status=Status.ACTIVE,
address=Address("123 Main", "NYC", "USA")
)Generated OpenAPI schema:
{
"User": {
"type": "object",
"properties": {
"name": {"type": "string"},
"email": {"type": "string"},
"status": {
"type": "string",
"enum": ["active", "inactive"]
},
"address": {"$ref": "#/components/schemas/Address"}
}
},
"Address": {
"type": "object",
"properties": {
"street": {"type": "string"},
"city": {"type": "string"},
"country": {"type": "string"}
}
}
}@dataclass
class CreateUserRequest:
name: str
email: str
status: Status
@RestController("/users")
class UserController:
@PostMapping("/")
async def create_user(self, request: CreateUserRequest) -> User:
"""Create a new user."""
return User(
name=request.name,
email=request.email,
status=request.status,
address=Address("", "", "")
)Request body schema is automatically inferred from CreateUserRequest.
@GetMapping("/custom")
@OpenAPIOperation(
responses={
200: {
"description": "Success",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"message": {"type": "string"},
"timestamp": {"type": "string"}
}
}
}
}
}
}
)
async def custom_response(self):
return {"message": "Hello", "timestamp": "2024-01-01T00:00:00Z"}Currently, security schemes must be configured in code or via configuration providers. Future versions will support YAML-based security scheme definitions.
Common security schemes:
Bearer Token (JWT):
from mitsuki import Configuration, Provider
@Configuration
class OpenAPIConfig:
@Provider
def security_schemes(self):
return {
"bearerAuth": {
"type": "http",
"scheme": "bearer",
"bearerFormat": "JWT"
}
}API Key:
@Provider
def security_schemes(self):
return {
"apiKey": {
"type": "apiKey",
"in": "header",
"name": "X-API-Key"
}
}OAuth2:
@Provider
def security_schemes(self):
return {
"oauth2": {
"type": "oauth2",
"flows": {
"authorizationCode": {
"authorizationUrl": "https://example.com/oauth/authorize",
"tokenUrl": "https://example.com/oauth/token",
"scopes": {
"read": "Read access",
"write": "Write access"
}
}
}
}
}To apply blockage over an endpoint in the OpenAPI UIs, annotate the route mapping with @OpenAPISecurity:
from mitsuki.openapi import OpenAPISecurity
@RestController("/api/protected")
class ProtectedController:
@GetMapping("/data")
@OpenAPISecurity(["bearerAuth"])
async def get_data(self):
return {"secret": "data"}This, of course, is just for the UI, and provides users with a visual (i.e. lock icon) and interactive way (i.e. "Authorize" button) to provide their security tokens over the UI.
Always specify return types for automatic schema generation:
@GetMapping("/{id}")
async def get_user(self, id: int) -> User: # Type hint provided
...First line becomes the summary, rest becomes description:
@GetMapping("/{id}")
async def get_user(self, id: int) -> User:
"""
Get user by ID.
Retrieves a single user from the database using their unique identifier.
Returns 404 if user not found.
"""
...@RestController("/api/users")
@OpenAPITag(name="User Management", description="CRUD operations for users")
class UserController:
...
@RestController("/api/auth")
@OpenAPITag(name="Authentication", description="Login and token management")
class AuthController:
...@GetMapping("/{id}")
@OpenAPIOperation(
responses={
404: {"description": "User not found"},
403: {"description": "Access denied"},
500: {"description": "Internal server error"}
}
)
async def get_user(self, id: int) -> User:
...@dataclass
class CreateUserRequest:
name: str
email: str
password: str
@PostMapping("/")
async def create_user(self, request: CreateUserRequest) -> User:
...For security-sensitive APIs:
# application-production.yml
openapi:
enabled: false# application-development.yml
openapi:
enabled: true
ui:
- swagger
- redoc
- scalar
# application-production.yml
openapi:
enabled: true
ui:
- redoc # Only ReDoc in production
server:
url: "https://api.production.com"from dataclasses import dataclass
from enum import Enum
from mitsuki import Application, RestController, GetMapping, PostMapping
from mitsuki.openapi import OpenAPIOperation, OpenAPITag, OpenAPISecurity
class Status(str, Enum):
ACTIVE = "active"
INACTIVE = "inactive"
@dataclass
class User:
id: int
name: str
email: str
status: Status
@dataclass
class CreateUserRequest:
name: str
email: str
@RestController("/api/users")
@OpenAPITag(
name="Users",
description="User management endpoints",
external_docs={
"description": "User Guide",
"url": "https://docs.example.com/users"
}
)
class UserController:
@GetMapping("/{id}")
@OpenAPIOperation(
summary="Get user by ID",
description="Retrieve a single user by unique identifier",
tags=["Users"],
responses={
404: {"description": "User not found"}
}
)
async def get_user(self, id: int) -> User:
"""Get user by ID."""
return User(id=id, name="John", email="john@example.com", status=Status.ACTIVE)
@PostMapping("/")
@OpenAPIOperation(
summary="Create new user",
description="Create a new user account",
tags=["Users"]
)
@OpenAPISecurity(["bearerAuth"])
async def create_user(self, request: CreateUserRequest) -> User:
"""Create a new user."""
return User(
id=1,
name=request.name,
email=request.email,
status=Status.ACTIVE
)
@Application
class MyApp:
pass
if __name__ == "__main__":
MyApp.run()application.yml:
openapi:
enabled: true
title: "User Management API"
version: "1.0.0"
description: "API for managing users"
ui:
- swagger
- redoc
- scalar
docs_ui: scalar
contact:
name: "API Team"
email: "api@example.com"Access points:
http://localhost:8000/docs- Scalar UIhttp://localhost:8000/swagger- Swagger UIhttp://localhost:8000/redoc- ReDochttp://localhost:8000/openapi.json- OpenAPI spec
- Controllers - Learn about request handling
- Request/Response Validation - Data validation
- JSON Serialization - Complex type handling
- Configuration - Environment-specific settings