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"},
    ],
)