OpenAPI Docs a.k.a Swagger
After deploying the application, Batman got multiple queries from the users on how to use the endpoints. Robyn showed him how to generate OpenAPI specifications for his application.
Out of the box, the following endpoints are setup for you:
/docsThe Swagger UI/openapi.jsonThe JSON Specification
To use a custom openapi configuration, you can:
- Place the
openapi.jsonconfig file in the root directory. - Or, pass the file path to the
openapi_file_pathparameter in theRobyn()constructor. (the parameter gets priority over the file).
However, if you don't want to generate the OpenAPI docs, you can disable it by passing --disable-openapi flag while starting the application.
python app.py --disable-openapi
How to use?
- Query Params: The typing for query params can be added as
def get(r: Request, query_params: GetRequestParams)whereGetRequestParamsis a subclass ofQueryParams - Path Params are defaulted to string type (ref: https://en.wikipedia.org/wiki/Query_string)
Basic App
from robyn.robyn import QueryParams
from robyn import Robyn, Request
app = Robyn(
file_object=__file__,
openapi=OpenAPI(
info=OpenAPIInfo(
title="Sample App",
description="This is a sample server application.",
termsOfService="https://example.com/terms/",
version="1.0.0",
contact=Contact(
name="API Support",
url="https://www.example.com/support",
email="support@example.com",
),
license=License(
name="BSD2.0",
url="https://opensource.org/license/bsd-2-clause",
),
externalDocs=ExternalDocumentation(description="Find more info here", url="https://example.com/"),
components=Components(),
),
),
)
@app.get("/")
async def welcome():
"""welcome endpoint"""
return "hi"
class GetRequestParams(QueryParams):
appointment_id: str
year: int
@app.get("/api/v1/name", openapi_name="Name Route", openapi_tags=["Name"])
async def get(r: Request, query_params: GetRequestParams):
"""Get Name by ID"""
return r.query_params
@app.delete("/users/:name", openapi_tags=["Name"])
async def delete(r: Request):
"""Delete Name by ID"""
return r.path_params
if __name__ == "__main__":
app.start()
How does it work with subrouters?
Subrouters
from robyn.robyn import QueryParams
from robyn import Request, SubRouter
subrouter: SubRouter = SubRouter(prefix="/sub")
@subrouter.get("/")
async def subrouter_welcome():
"""welcome subrouter"""
return "hiiiiii subrouter"
class SubRouterGetRequestParams(QueryParams):
_id: int
value: str
@subrouter.get("/name")
async def subrouter_get(r: Request, query_params: SubRouterGetRequestParams):
"""Get Name by ID"""
return r.query_params
@subrouter.delete("/:name")
async def subrouter_delete(r: Request):
"""Delete Name by ID"""
return r.path_params
app.include_router(subrouter)
Other Specification Params
We support all the params mentioned in the latest OpenAPI specifications (https://swagger.io/specification/). See an example using request & response bodies below:
Request & Response Body
from robyn.types import JSONResponse, Body
class Initial(Body):
is_present: bool
letter: Optional[str]
class FullName(Body):
first: str
second: str
initial: Initial
class CreateItemBody(Body):
name: FullName
description: str
price: float
tax: float
class CreateResponse(JSONResponse):
success: bool
items_changed: int
@app.post("/")
def create_item(request: Request, body: CreateItemBody) -> CreateResponse:
return CreateResponse(success=True, items_changed=2)
With the reference documentation deployed and running smoothly, Batman had a powerful new tool at his disposal. The Robyn framework had provided him with the flexibility, scalability, and performance needed to create an effective crime-fighting application, giving him a technological edge in his ongoing battle to protect Gotham City.
Using Pydantic Models
If you have Pydantic installed (pip install "robyn[pydantic]" or pip install "robyn[all]"), you can use Pydantic BaseModel classes directly as handler parameter annotations. Robyn will automatically validate the request body and generate a rich OpenAPI schema — including property types, required fields, defaults, and $ref for nested models.
Pydantic + OpenAPI
from pydantic import BaseModel
class UserCreate(BaseModel):
name: str
email: str
age: int
active: bool = True
@app.post("/users", openapi_tags=["Users"])
def create_user(user: UserCreate) -> dict:
"""Create a new user"""
return {"name": user.name}
For the full guide on Pydantic validation, nested models, error responses, and OpenAPI integration, see the dedicated Pydantic Integration page.
Documenting routes (status codes, deprecation, extra responses)
Every route decorator (@app.get, @app.post, … and the SubRouter equivalents) accepts a set of flags so you can document — and in some cases change the runtime behaviour of — a route directly from the decorator:
status_code— the default success status code. It is reflected in the spec (e.g.201instead of200) and applied at runtime to plain returns (dict/list/str/bytes). An explicitResponse(...)return always keeps its own status.response_model— a type (typically a Pydantic model) used as the success response schema. When the handler returns a dict, it is validated and re-serialized through the model so the wire response matches the docs.responses— additional documented responses keyed by status code. Each value may be a plain description string, a{"description": ..., "model": SomeType}dict, or a full OpenAPI response object.deprecated=True— marks the operation deprecated (rendered with strikethrough in Swagger UI).include_in_schema=False— hides the route from the spec entirely (useful for health checks and internal endpoints).
Route documentation flags
from robyn.types import JSONResponse
class UserResponse(JSONResponse):
id: int
name: str
class ErrorResponse(JSONResponse):
message: str
@app.post(
"/users",
status_code=201,
response_model=UserResponse,
responses={
404: "User not found",
422: {"description": "Validation error", "model": ErrorResponse},
},
openapi_tags=["Users"],
)
def create_user(body: UserResponse) -> UserResponse:
"""Create a user (responds with 201)"""
return {"id": 1, "name": "Bruce"}
@app.get("/legacy", deprecated=True)
def legacy():
"""Old endpoint kept for backwards-compat"""
return "use /users instead"
@app.get("/healthz", include_in_schema=False)
def healthz():
return "ok"
Authentication & the Swagger "Authorize" button
When you call app.configure_authentication(...), Robyn automatically registers a matching security scheme (BearerAuth for a BearerGetter) so Swagger UI's Authorize button works out of the box. Routes declared with auth_required=True advertise that requirement in the spec, so they render with a lock icon and send the credential when you try them out.
You can also declare schemes explicitly via Components(securitySchemes=...):
Security schemes
from robyn.openapi import OpenAPI, OpenAPIInfo, Components
app = Robyn(
__file__,
openapi=OpenAPI(
info=OpenAPIInfo(
components=Components(
securitySchemes={
"BearerAuth": {"type": "http", "scheme": "bearer", "bearerFormat": "JWT"},
}
)
)
),
)
@app.get("/me", auth_required=True)
def me(request):
"""Requires a Bearer token — shows a lock in Swagger UI"""
return "secret"
If no security scheme is configured, Robyn no longer emits an empty securitySchemes object, so the Authorize button stays hidden instead of opening an empty popup.
A note on auto-generated parameters
Robyn builds the OpenAPI spec by inspecting your handler's type annotations — not its body. Reading values dynamically via request.query_params, request.json(), or request.path_params produces working endpoints, but Robyn has nothing to introspect, so those parameters won't appear in /docs.
To document them, annotate the handler with typed params instead:
- Query params: a subclass of
QueryParams(e.g.query_params: MyQueryParams) - Request body: a subclass of
Body/JsonBody, aTypedDict, or a PydanticBaseModel - Path params: derived automatically from the route string (
/users/:id)
Stdlib return/field types such as datetime, date, UUID, Decimal, Enum, and Literal are mapped to their proper JSON Schema type/format, and container types like list[str] and Optional[str] render correctly.
What's next?
Batman wondered about whether Robyn handlers can be dispatched to multiple processes.
Robyn showed him the way!
