OpenAPI
OpenAPI specification patterns, auto-generation with FastAPI, and ISE OpenAPI.
OpenAPI Specification
OpenAPI 3.x structure — what lives where
openapi: 3.1.0 info: # API metadata (title, version, description) servers: # Base URLs for the API paths: # Endpoints and operations (GET /devices, POST /devices) components: # Reusable schemas, parameters, responses, security security: # Global auth requirements tags: # Group operations by domain
FastAPI Auto-Generation
FastAPI generates OpenAPI spec automatically from type hints
from fastapi import FastAPI
from pydantic import BaseModel, Field
app = FastAPI(
title="domus-api",
version="0.1.0",
description="Domus Digitalis infrastructure API",
)
class Device(BaseModel):
name: str = Field(description="Device hostname")
ip: str = Field(description="Management IP address")
vlan: int = Field(ge=1, le=4094, description="VLAN ID")
@app.get("/v1/devices", response_model=list[Device], tags=["Devices"])
async def list_devices():
"""List all managed network devices."""
...
Access the generated spec
# JSON format
curl -s http://localhost:8000/openapi.json | jq .
# Interactive docs (Swagger UI)
# http://localhost:8000/docs
# Alternative docs (ReDoc)
# http://localhost:8000/redoc
Exploring API Specs
Download and inspect any OpenAPI spec
# Download spec
curl -s https://api.example.com/openapi.json -o openapi.json
# List all endpoints
jq -r '.paths | keys[]' openapi.json
# List all operations with methods
jq -r '.paths | to_entries[] | .key as $path | .value | to_entries[] | "\(.key | ascii_upcase) \($path)"' openapi.json | sort
Extract schema for a specific model
jq '.components.schemas.Device' openapi.json
List all required fields for a model
jq '.components.schemas.Device.required // []' openapi.json
ISE OpenAPI
ISE 3.x exposes OpenAPI on port 443 (separate from ERS on 9060)
# Download ISE OpenAPI spec
curl -sk -u "$ISE_USER:$ISE_PASS" \
https://10.50.1.20:443/api/swagger.json -o ise-openapi.json
# List ISE OpenAPI endpoints
jq -r '.paths | keys[]' ise-openapi.json | head -20
| ISE has two REST APIs: ERS (legacy, port 9060) and OpenAPI (modern, port 443). New integrations should prefer OpenAPI where the endpoint exists. |
Code Generation
Generate Python client from OpenAPI spec
# Install openapi-generator
pip install openapi-python-client
# Generate client
openapi-python-client generate --url https://api.example.com/openapi.json
Generate curl commands from spec — quick testing
# Extract a POST endpoint and generate curl
jq -r '.paths["/v1/devices"].post.requestBody.content["application/json"].schema' openapi.json
Validation
Validate an OpenAPI spec
# Install spectral (OpenAPI linter)
npm install -g @stoplight/spectral-cli
# Lint the spec
spectral lint openapi.json
Validate request/response against schema in Python
from pydantic import BaseModel, ValidationError
class DeviceCreate(BaseModel):
name: str
ip: str
vlan: int
try:
device = DeviceCreate(name="sw-01", ip="10.50.1.100", vlan=9999)
except ValidationError as e:
print(e.json()) # Pydantic validates against constraints
Documentation Patterns
FastAPI docstrings become OpenAPI descriptions
@app.get("/v1/devices/{device_id}", tags=["Devices"])
async def get_device(device_id: int):
"""
Retrieve a single device by ID.
- **device_id**: Unique device identifier from the database
- Returns 404 if device does not exist
"""
...
Custom OpenAPI metadata via FastAPI
app = FastAPI(
title="domus-api",
version="0.1.0",
contact={"name": "Evan", "email": "admin@domusdigitalis.dev"},
license_info={"name": "MIT"},
openapi_tags=[
{"name": "Devices", "description": "Network device management"},
{"name": "Health", "description": "Service health checks"},
],
)