- name
- openapi-spec
- description
- Write and update OpenAPI 3.0 specification files from REST API code. Use when: documenting REST endpoints, creating API specs, generating swagger.yaml, updating API documentation from implementation.
- argument-hint
- Describe the API endpoints to document or specify the code files containing REST handlers
# OpenAPI 3.0 Specification Writer
## When to Use
Load this skill when asked to:
- Create an OpenAPI/Swagger specification from existing REST API code
- Document REST endpoints in OpenAPI 3.0 format
- Update an existing `openapi.yaml` or `swagger.yaml` file
- Generate API documentation that follows OpenAPI 3.0 standards
- Extract endpoint definitions, parameters, and responses from code
## Ground Rules
- Target specification: **OpenAPI 3.0.x** (not Swagger 2.0)
- Output format: YAML (preferred) or JSON
- Follow [OpenAPI 3.0 Specification](https://swagger.io/docs/specification/v3_0/about/)
- Include examples for complex schemas
- Document all response codes actually returned by the implementation
## Procedure
### 1. Analyze the REST API Code
#### Static Route Registration
Search for and identify:
- HTTP request handlers and route definitions (e.g., `server.on()`, route arrays)
- HTTP methods (GET, POST, PUT, PATCH, DELETE, OPTIONS)
- Path patterns and parameters (`:id`, `{id}`, wildcards)
- Query parameters from `request->getParam()` or equivalent
- Request body parsing (JSON, form data, multipart)
- Response codes and JSON structures
- Authentication/authorization requirements
- Error responses and status codes
#### Dynamic Route Registration
Some REST APIs use plugin or service architectures where endpoints are registered dynamically:
**Common patterns to search for:**
- `getTopics()` or `registerTopics()` methods in plugins/services
- Topic handler or topic registration services
- Topic constants (e.g., `TOPIC_CONFIG`, `TOPIC_STATUS`)
- Dynamic endpoint builders that construct paths from entity IDs and topic names
**Topic-based endpoint patterns:**
- Plugin endpoints: `/api/v1/display/uid/{uid}/{topic}` or `/display/alias/{alias}/{topic}`
- Service endpoints: `/api/v1/{entityId}/{topic}`
- System endpoints: `/api/v1/{topic}` (empty entityId)
- Indexed endpoints: `/api/v1/{entityId}/{index}/{topic}`
**How to find dynamic endpoints:**
1. Search for `getTopics()` implementations - these list available topics
2. Find `getTopic()` and `setTopic()` methods - these handle GET/POST requests
3. Locate topic registration code - shows how topics become REST endpoints
4. Check for `TopicHandlerService` or similar dynamic registration systems
5. Look for topic constants defined in header or source files
**Example (C++):**
```cpp
// Plugin defines topics
const char* TOPIC_CONFIG = "config";
const char* TOPIC_STATUS = "status";
void Plugin::getTopics(JsonArray& topics) const
{
topics.add(TOPIC_CONFIG); // Creates GET/POST /display/uid/{uid}/config
topics.add(TOPIC_STATUS); // Creates GET/POST /display/uid/{uid}/status
}
bool Plugin::getTopic(const String& topic, JsonObject& value) const
{
if (topic.equals(TOPIC_CONFIG)) {
// Handle GET request
}
}
bool Plugin::setTopic(const String& topic, const JsonObjectConst& value)
{
if (topic.equals(TOPIC_CONFIG)) {
// Handle POST request
}
}
```
### 2. Discover All Endpoints
**Critical**: Don't assume you've found all endpoints after discovering static routes.
**Complete discovery workflow:**
1. **Static routes**: Find route registration arrays or explicit route handlers
2. **Plugin topics**: Search for classes implementing plugin interfaces
- Look for `getTopics()` implementations
- Each plugin may expose multiple topics as REST endpoints
3. **Service topics**: Search for service classes that register topics
- Services often register multiple topics (e.g., files, upload, remove)
4. **Topic registration**: Find the topic handler or registration service
- Understand how topics are converted to REST paths
- Check for path prefix construction (e.g., base + entityId + topic)
5. **Validate completeness**: Cross-reference existing OpenAPI spec
- Check for endpoints in old spec that might still exist
- Verify each documented endpoint still exists in code
- Add missing endpoints found in code
**Search patterns:**
```bash
# Find topic definitions
grep -r "TOPIC_" --include="*.cpp" --include="*.h"
# Find getTopics implementations
grep -r "getTopics" --include="*.cpp"
# Find topic handlers
grep -r "getTopic\|setTopic" --include="*.cpp"
# Find topic registration
grep -r "registerTopic" --include="*.cpp"
```
### 3. Build the OpenAPI Structure
Start with the [base template](./references/openapi-template.yaml):
```yaml
openapi: 3.0.3
info:
title: [API Name]
version: [Version from VERSION constant or git tag]
description: [Brief API description]
contact:
name: [From LICENSE or README]
email: [If available]
servers:
- url: http://{host}/rest/api/v1
description: REST API base path
variables:
host:
default: localhost
description: Device hostname or IP
paths:
# Endpoints go here
components:
schemas:
# Reusable schemas
responses:
# Common responses
securitySchemes:
# Auth schemes
```
### 4. Document Each Endpoint
For each route/handler found:
```yaml
/path/{param}:
get:
summary: [One-line description from code comments]
description: [Detailed behavior from docstrings/comments]
operationId: [camelCase unique identifier]
tags:
- [Logical grouping]
parameters:
- name: param
in: path
required: true
schema:
type: string
description: [From parameter docs]
- name: query
in: query
required: false
schema:
type: integer
description: [From code inspection]
responses:
'200':
description: Success
content:
application/json:
schema:
$ref: '#/components/schemas/SuccessResponse'
example:
status: "ok"
data: {}
'400':
$ref: '#/components/responses/BadRequest'
'404':
$ref: '#/components/responses/NotFound'
```
### 5. Extract Response Schemas
From JSON response building code, create reusable schemas:
```yaml
components:
schemas:
SuccessResponse:
type: object
required:
- status
- data
properties:
status:
type: string
enum: [ok]
data:
type: object
description: Endpoint-specific response data
ErrorResponse:
type: object
required:
- status
- msg
properties:
status:
type: string
enum: [error]
msg:
type: string
description: Human-readable error message
```
### 6. Document Authentication
If authentication is present:
```yaml
components:
securitySchemes:
basicAuth:
type: http
scheme: basic
description: HTTP Basic Authentication
# Then apply to protected endpoints:
paths:
/protected:
get:
security:
- basicAuth: []
```
### 7. Validate and Refine
- Check that all paths start with `/`
- Verify all `$ref` references exist
- Ensure required fields are present
- Add examples for complex request/response bodies
- Group related endpoints with `tags`
- Document error responses consistently
## Output Format
Save the specification as:
- `docs/openapi.yaml` or `docs/swagger.yaml` (YAML preferred)
- `docs/api-spec.yaml` (alternative naming)
Include a comment header:
```yaml
# OpenAPI 3.0.3 Specification
# Generated from: [source files]
# Last updated: [date]
# See: https://swagger.io/docs/specification/v3_0/
```
## Common Patterns
### REST API with CRUD Operations
```yaml
/items:
get:
summary: List all items
responses:
'200':
description: Array of items
post:
summary: Create new item
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/ItemCreate'
responses:
'201':
description: Item created
/items/{id}:
get:
summary: Get single item
put:
summary: Update item
delete:
summary: Delete item
```
### Query Parameters
```yaml
parameters:
- name: limit
in: query
schema:
type: integer
minimum: 1
maximum: 100
default: 20
- name: offset
in: query
schema:
type: integer
minimum: 0
default: 0
```
### File Upload
```yaml
requestBody:
content:
multipart/form-data:
schema:
type: object
properties:
file:
type: string
format: binary
path:
type: string
```
## Common Pitfalls & Lessons Learned
### Missing Dynamic Endpoints
**Issue**: Forgetting to document endpoints that are registered dynamically via plugin/service systems.
**Solution**:
1. Don't rely only on static route definitions
2. Search for `getTopics()`, `registerTopics()`, or similar methods
3. Check for topic registration services that create REST endpoints
4. Look for topic handler implementations
5. Verify each plugin/service that registers topics
**Example**: In Pixelix, sensor endpoints (`/sensors/{index}/{channelName}`) are created dynamically by `SensorDataProvider` registering topics via `TopicHandlerService`, not found in static route arrays.
### Path Pattern Consistency
**Issue**: API paths may have multiple formats depending on registration method.
**Check for**:
- Service endpoints: `/{serviceId}/{topic}`
- Plugin endpoints by UID: `/display/uid/{uid}/{topic}`
- Plugin endpoints by alias: `/display/alias/{alias}/{topic}`
- Indexed endpoints: `/{entityId}/{index}/{topic}`
- System endpoints: `/{topic}` (no prefix)
### Parameter Types from Code
**Issue**: Documentation doesn't match actual parameter types used in code.
**Verify**:
- Check actual JSON key names in `getTopic()`/`setTopic()` implementations
- Confirm data types (string, number, boolean, array, object)
- Note optional vs required parameters
- Check for parameter validation rules (min/max, enums)
### Configuration vs Command Topics
**Issue**: Some topics serve dual purposes.
**Pattern**:
- **Config topics**: GET returns current config, POST updates and persists config
- **Command topics**: GET returns status, POST executes actions with `action` parameter
- **Status topics**: GET only, returns current state
**Example:**
```yaml
# Command topic - action-based
/display/uid/{uid}/playCtrl:
post:
parameters:
- name: action
schema:
type: string
enum: [next, previous, pause, continue]
```
## Tips
- **Use `$ref` for reusability**: Common responses and schemas should be defined once in `components`
- **Include examples**: Especially for complex nested objects
- **Document all response codes**: Even error cases (400, 401, 403, 404, 500)
- **Add operation IDs**: Unique, descriptive `operationId` for code generation tools
- **Group with tags**: Logical grouping improves generated documentation (e.g., by plugin name or service name)
- **Version properly**: Use `info.version` matching your API versioning scheme
- **Add curl examples**: Include practical curl command examples in endpoint descriptions to show authentication and parameter usage
- **Consistency matters**: Keep response schemas, error formats, and authentication patterns consistent across all endpoints
- **Trace the implementation**: Don't guess parameter types or structures - read the actual code that builds JSON responses
## Validation
After generating the spec, validate it using:
- [Swagger Editor](https://editor.swagger.io/) - paste YAML to check for errors
- `swagger-cli validate openapi.yaml` - CLI validation
- VS Code OpenAPI extensions for real-time validation
## References
- [OpenAPI 3.0 Specification](https://swagger.io/docs/specification/v3_0/about/)
- [OpenAPI Data Types](https://swagger.io/docs/specification/data-models/data-types/)
- [Authentication](https://swagger.io/docs/specification/authentication/)
- [Example template](./references/openapi-template.yaml)
Auf GitHub ansehen