Warning
The current page still doesn't have a translation for this language.
But you can help translating it: Contributing.
hide:
- navigation¶
OpenAPI: Interactive API Documentation¶
Ravyn automatically generates beautiful, interactive API documentation. Test your endpoints, authenticate, and explore your API. all from your browser.
What You'll Learn¶
- Accessing built-in API documentation
- Documenting your endpoints
- Adding authentication to docs
- Using security schemes
- Testing APIs in the browser
Quick Start¶
from ravyn import Ravyn, get
from ravyn.openapi.datastructures import OpenAPIResponse
@get(
"/users",
tags=["Users"],
summary="List all users",
description="Returns a list of all users in the system",
responses={
200: OpenAPIResponse(model=list[dict], description="List of users"),
404: OpenAPIResponse(model=dict, description="No users found")
}
)
def list_users() -> list[dict]:
return [{"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"}]
app = Ravyn()
app.add_route(list_users)
# Visit http://localhost:8000/docs/swagger to see your docs!
Built-In Documentation UIs¶
Ravyn provides four interactive documentation interfaces out of the box:
| UI | URL | Best For |
|---|---|---|
| Swagger | /docs/swagger |
Testing & exploration |
| Redoc | /docs/redoc |
Clean, readable docs |
| Stoplight | /docs/elements |
Modern design |
| Rapidoc | /docs/rapidoc |
Customizable interface |
Tip
See OpenAPIConfig to customize URLs and settings.
Documenting Endpoints¶
Basic Documentation¶
from ravyn import get
@get(
"/products",
tags=["Products"],
summary="Get all products",
description="Returns a list of all available products"
)
def get_products() -> list[dict]:
return [{"id": 1, "name": "Product 1"}]
With Response Models¶
from ravyn import get
from ravyn.openapi.datastructures import OpenAPIResponse
from pydantic import BaseModel
class Product(BaseModel):
id: int
name: str
price: float
class Error(BaseModel):
detail: str
@get(
"/products/{product_id}",
tags=["Products"],
summary="Get product by ID",
responses={
200: OpenAPIResponse(model=Product, description="Product found"),
404: OpenAPIResponse(model=Error, description="Product not found")
}
)
def get_product(product_id: int) -> Product:
return Product(id=product_id, name="Product 1", price=99.99)
Complete Example¶
from ravyn import get, post, put, delete
from ravyn.openapi.datastructures import OpenAPIResponse
from pydantic import BaseModel
class UserCreate(BaseModel):
name: str
email: str
class User(BaseModel):
id: int
name: str
email: str
@get(
"/users",
tags=["Users"],
summary="List all users",
description="Returns a paginated list of users",
responses={
200: OpenAPIResponse(model=list[User], description="List of users")
}
)
def list_users() -> list[User]:
return [User(id=1, name="Alice", email="alice@example.com")]
@post(
"/users",
tags=["Users"],
summary="Create user",
description="Creates a new user in the system",
responses={
201: OpenAPIResponse(model=User, description="User created"),
400: OpenAPIResponse(model=dict, description="Invalid data")
}
)
def create_user(data: UserCreate) -> User:
return User(id=1, **data.dict())
QUERY Operations¶
Ravyn documents HTTP QUERY routes as native OpenAPI query operations. QUERY is defined by RFC 10008 as a safe, idempotent method for server-side query operations that may include request content.
from pydantic import BaseModel
from ravyn import query
class SearchPayload(BaseModel):
term: str
filters: list[str]
@query("/search", tags=["Search"])
def search(payload: SearchPayload, page: int) -> dict:
return {"term": payload.term, "page": page}
Generated schemas include the query operation, any query parameters, and the request body:
{
"openapi": "3.2.0",
"paths": {
"/search": {
"query": {
"parameters": [
{
"name": "page",
"in": "query",
"required": true
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SearchPayload"
}
}
},
"required": true
}
}
}
}
}
OpenAPI added native QUERY operations in 3.2, so Ravyn emits openapi: 3.2.0 when a schema contains QUERY routes. Existing schemas without QUERY routes keep the configured OpenAPI version.
Authentication in Documentation¶
Add authentication to your docs so you can test protected endpoints.
Supported Security Schemes¶
| Scheme | Use Case | Import |
|---|---|---|
HTTPBasic |
Basic auth | ravyn.security.http |
HTTPBearer |
JWT tokens | ravyn.security.http |
HTTPDigest |
Digest auth | ravyn.security.http |
APIKeyInHeader |
API keys in headers | ravyn.security.api_key |
APIKeyInCookie |
API keys in cookies | ravyn.security.api_key |
APIKeyInQuery |
API keys in query params | ravyn.security.api_key |
OAuth2 |
OAuth2 flow | ravyn.security.oauth2 |
OpenIdConnect |
OpenID Connect | ravyn.security.open_id |
Import Security Schemes¶
from ravyn.security.http import HTTPBasic, HTTPBearer, HTTPDigest
from ravyn.security.api_key import APIKeyInCookie, APIKeyInHeader, APIKeyInQuery
from ravyn.security.oauth2 import OAuth2
from ravyn.security.open_id import OpenIdConnect
Using Security Schemes¶
HTTPBearer (JWT Tokens)¶
from ravyn import get
from ravyn.security.http import HTTPBearer
@get(
"/protected",
summary="Protected endpoint",
security=[HTTPBearer]
)
def protected_route() -> dict:
return {"message": "You are authenticated!"}
When you visit the docs, you'll see an "Authorize" button where you can enter your token.
HTTPBasic (Username/Password)¶
from ravyn.security.http import HTTPBasic
@get(
"/admin",
summary="Admin endpoint",
security=[HTTPBasic]
)
def admin_route() -> dict:
return {"message": "Admin access"}
APIKeyInHeader¶
from ravyn.security.api_key import APIKeyInHeader
# Define the security scheme
api_key_header = APIKeyInHeader(
name="X-API-Key",
scheme_name="API Key (Header)",
description="Enter your API key"
)
@get(
"/api/data",
summary="Get data",
security=[api_key_header]
)
def get_data() -> dict:
return {"data": "sensitive information"}
APIKeyInQuery¶
from ravyn.security.api_key import APIKeyInQuery
api_key_query = APIKeyInQuery(
name="api_key",
scheme_name="API Key (Query)",
description="Pass API key as query parameter"
)
@get(
"/api/users",
summary="Get users",
security=[api_key_query]
)
def get_users() -> dict:
# Accessed as: /api/users?api_key=YOUR_KEY
return {"users": []}
APIKeyInCookie¶
from ravyn.security.api_key import APIKeyInCookie
api_key_cookie = APIKeyInCookie(
name="session_id",
scheme_name="Session Cookie",
description="Session cookie for authentication"
)
@get(
"/dashboard",
summary="User dashboard",
security=[api_key_cookie]
)
def dashboard() -> dict:
return {"dashboard": "data"}
Multiple Security Schemes¶
You can use multiple security schemes on the same endpoint:
from ravyn import get
from ravyn.security.http import HTTPBearer
from ravyn.security.api_key import APIKeyInHeader
bearer_auth = HTTPBearer(scheme_name="JWT Token")
api_key_auth = APIKeyInHeader(name="X-API-Key", scheme_name="API Key")
@get(
"/secure",
summary="Secure endpoint",
security=[bearer_auth, api_key_auth]
)
def secure_endpoint() -> dict:
return {"message": "Authenticated with multiple methods"}
Application-Level Security¶
Apply security to all routes in an Include or the entire app:
Include-Level¶
from ravyn import Ravyn, Include, get
from ravyn.security.http import HTTPBearer
@get("/users")
def list_users() -> dict:
return {"users": []}
@get("/products")
def list_products() -> dict:
return {"products": []}
# All routes in this Include require authentication
api_routes = Include(
"/api",
routes=[
Gateway(handler=list_users),
Gateway(handler=list_products)
],
security=[HTTPBearer]
)
app = Ravyn(routes=[api_routes])
App-Level¶
from ravyn import Ravyn
from ravyn.security.http import HTTPBearer
# All routes in the app require authentication
app = Ravyn(
routes=[...],
security=[HTTPBearer]
)
OAuth2 Authentication¶
For complex OAuth2 flows, see the Security section for detailed examples.
from ravyn.security.oauth2 import OAuth2
oauth2_scheme = OAuth2(
flows={
"password": {
"tokenUrl": "/token",
"scopes": {
"read": "Read access",
"write": "Write access"
}
}
}
)
@get("/protected", security=[oauth2_scheme])
def protected() -> dict:
return {"message": "OAuth2 protected"}
OpenID Connect¶
from ravyn.security.open_id import OpenIdConnect
openid_scheme = OpenIdConnect(
openIdConnectUrl="https://example.com/.well-known/openid-configuration",
scheme_name="OpenID Connect"
)
@get("/sso", security=[openid_scheme])
def sso_endpoint() -> dict:
return {"message": "SSO authenticated"}
Common Pitfalls & Fixes¶
Pitfall 1: Missing Response Models¶
Problem: Docs don't show response structure.
# Wrong - no response documentation
@get("/users")
def list_users() -> dict:
return {"users": []}
Solution: Add OpenAPIResponse:
# Correct
from ravyn.openapi.datastructures import OpenAPIResponse
@get(
"/users",
responses={
200: OpenAPIResponse(model=dict, description="List of users")
}
)
def list_users() -> dict:
return {"users": []}
Pitfall 2: Security Not Showing in Docs¶
Problem: Authorize button doesn't appear.
# Wrong - security not configured
@get("/protected")
def protected() -> dict:
return {}
Solution: Add security parameter:
# Correct
from ravyn.security.http import HTTPBearer
@get("/protected", security=[HTTPBearer])
def protected() -> dict:
return {}
Pitfall 3: Wrong Security Scheme Type¶
Problem: Using wrong scheme for your auth method.
# Wrong - using HTTPBasic for JWT
from ravyn.security.http import HTTPBasic
@get("/jwt-protected", security=[HTTPBasic])
def protected() -> dict:
return {}
Solution: Use HTTPBearer for JWT:
# Correct
from ravyn.security.http import HTTPBearer
@get("/jwt-protected", security=[HTTPBearer])
def protected() -> dict:
return {}
Best Practices¶
1. Use Tags to Organize¶
# Good - organized by tags
@get("/users", tags=["Users"])
def list_users() -> dict:
pass
@get("/products", tags=["Products"])
def list_products() -> dict:
pass
2. Add Descriptions¶
# Good - clear descriptions
@get(
"/users/{user_id}",
summary="Get user by ID",
description="Retrieves a single user by their unique identifier"
)
def get_user(user_id: int) -> dict:
pass
3. Document All Responses¶
# Good - all responses documented
@get(
"/users/{user_id}",
responses={
200: OpenAPIResponse(model=User, description="User found"),
404: OpenAPIResponse(model=Error, description="User not found"),
500: OpenAPIResponse(model=Error, description="Server error")
}
)
def get_user(user_id: int) -> User:
pass
Customizing OpenAPI¶
See OpenAPIConfig for:
- Changing documentation URLs
- Customizing titles and descriptions
- Adding contact information
- Setting API version
- Configuring servers
Next Steps¶
Now that you understand OpenAPI documentation, explore:
- OpenAPIConfig - Customize docs
- Security - Authentication & authorization
- Responses - Response types
- Requests - Request handling