OpenAPI Authoring Skill
When to Use This Skill
Use this skill when:
-
Openapi Authoring tasks - Working on author and validate openapi 3.1 specifications for rest api design, following api-first and contract-first development practices
-
Planning or design - Need guidance on Openapi Authoring approaches
-
Best practices - Want to follow established patterns and standards
Overview
Author OpenAPI 3.1 specifications for REST API design using API-first methodology.
OpenAPI 3.1 Structure
Root Document
openapi: "3.1.0"
info: title: "{Service Name} API" version: "1.0.0" description: | {Service description and purpose} contact: name: "{Team Name}" email: "{team@company.com}" license: name: "MIT" identifier: "MIT"
servers:
- url: "https://api.example.com/v1" description: "Production"
- url: "https://api.staging.example.com/v1" description: "Staging"
- url: "http://localhost:5000/v1" description: "Local development"
tags:
- name: "{Resource}" description: "Operations for {resource} management"
paths:
Path definitions
components:
Reusable components
security:
- bearerAuth: []
Path Operations
paths: /resources: get: operationId: "listResources" summary: "List all resources" description: "Retrieves a paginated list of resources" tags: - Resources parameters: - $ref: "#/components/parameters/PageNumber" - $ref: "#/components/parameters/PageSize" - $ref: "#/components/parameters/SortBy" responses: "200": description: "Successful response" content: application/json: schema: $ref: "#/components/schemas/ResourceListResponse" "400": $ref: "#/components/responses/BadRequest" "401": $ref: "#/components/responses/Unauthorized"
post:
operationId: "createResource"
summary: "Create a new resource"
description: "Creates a new resource with the provided data"
tags:
- Resources
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/CreateResourceRequest"
responses:
"201":
description: "Resource created"
content:
application/json:
schema:
$ref: "#/components/schemas/ResourceResponse"
headers:
Location:
description: "URL of the created resource"
schema:
type: string
format: uri
"400":
$ref: "#/components/responses/BadRequest"
"422":
$ref: "#/components/responses/UnprocessableEntity"
/resources/{resourceId}: parameters: - $ref: "#/components/parameters/ResourceId"
get:
operationId: "getResource"
summary: "Get a resource by ID"
description: "Retrieves a single resource by its unique identifier"
tags:
- Resources
responses:
"200":
description: "Successful response"
content:
application/json:
schema:
$ref: "#/components/schemas/ResourceResponse"
"404":
$ref: "#/components/responses/NotFound"
put:
operationId: "updateResource"
summary: "Update a resource"
description: "Replaces the entire resource with the provided data"
tags:
- Resources
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/UpdateResourceRequest"
responses:
"200":
description: "Resource updated"
content:
application/json:
schema:
$ref: "#/components/schemas/ResourceResponse"
"404":
$ref: "#/components/responses/NotFound"
"409":
$ref: "#/components/responses/Conflict"
patch:
operationId: "patchResource"
summary: "Partially update a resource"
description: "Updates specific fields of the resource"
tags:
- Resources
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/PatchResourceRequest"
responses:
"200":
description: "Resource patched"
content:
application/json:
schema:
$ref: "#/components/schemas/ResourceResponse"
"404":
$ref: "#/components/responses/NotFound"
delete:
operationId: "deleteResource"
summary: "Delete a resource"
description: "Permanently removes a resource"
tags:
- Resources
responses:
"204":
description: "Resource deleted"
"404":
$ref: "#/components/responses/NotFound"
"409":
$ref: "#/components/responses/Conflict"
Component Schemas
components: schemas: # Request schemas CreateResourceRequest: type: object required: - name - type properties: name: type: string minLength: 1 maxLength: 100 description: "Resource name" example: "My Resource" type: $ref: "#/components/schemas/ResourceType" description: type: string maxLength: 500 description: "Optional description" metadata: type: object additionalProperties: true description: "Custom metadata key-value pairs"
UpdateResourceRequest:
allOf:
- $ref: "#/components/schemas/CreateResourceRequest"
PatchResourceRequest:
type: object
properties:
name:
type: string
minLength: 1
maxLength: 100
description:
type: string
maxLength: 500
minProperties: 1
# Response schemas
ResourceResponse:
type: object
required:
- id
- name
- type
- createdAt
- updatedAt
properties:
id:
type: string
format: uuid
description: "Unique identifier"
example: "550e8400-e29b-41d4-a716-446655440000"
name:
type: string
description: "Resource name"
type:
$ref: "#/components/schemas/ResourceType"
description:
type: string
metadata:
type: object
additionalProperties: true
createdAt:
type: string
format: date-time
description: "Creation timestamp (ISO 8601)"
updatedAt:
type: string
format: date-time
description: "Last update timestamp (ISO 8601)"
_links:
$ref: "#/components/schemas/ResourceLinks"
ResourceListResponse:
type: object
required:
- data
- pagination
properties:
data:
type: array
items:
$ref: "#/components/schemas/ResourceResponse"
pagination:
$ref: "#/components/schemas/Pagination"
_links:
$ref: "#/components/schemas/PaginationLinks"
# Enums
ResourceType:
type: string
enum:
- standard
- premium
- enterprise
description: "Type of resource"
# Common schemas
Pagination:
type: object
required:
- page
- pageSize
- totalItems
- totalPages
properties:
page:
type: integer
minimum: 1
description: "Current page number"
pageSize:
type: integer
minimum: 1
maximum: 100
description: "Items per page"
totalItems:
type: integer
minimum: 0
description: "Total number of items"
totalPages:
type: integer
minimum: 0
description: "Total number of pages"
ResourceLinks:
type: object
properties:
self:
type: string
format: uri
collection:
type: string
format: uri
PaginationLinks:
type: object
properties:
self:
type: string
format: uri
first:
type: string
format: uri
prev:
type: string
format: uri
next:
type: string
format: uri
last:
type: string
format: uri
# Error schemas
ErrorResponse:
type: object
required:
- type
- title
- status
properties:
type:
type: string
format: uri
description: "URI reference identifying the problem type"
title:
type: string
description: "Short, human-readable summary"
status:
type: integer
description: "HTTP status code"
detail:
type: string
description: "Human-readable explanation"
instance:
type: string
format: uri
description: "URI reference identifying the specific occurrence"
errors:
type: array
items:
$ref: "#/components/schemas/ValidationError"
ValidationError:
type: object
required:
- field
- message
properties:
field:
type: string
description: "Field path (e.g., 'name' or 'address.city')"
message:
type: string
description: "Validation error message"
code:
type: string
description: "Error code for programmatic handling"
Parameters and Responses
components: parameters: ResourceId: name: resourceId in: path required: true description: "Resource unique identifier" schema: type: string format: uuid
PageNumber:
name: page
in: query
description: "Page number (1-indexed)"
schema:
type: integer
minimum: 1
default: 1
PageSize:
name: pageSize
in: query
description: "Number of items per page"
schema:
type: integer
minimum: 1
maximum: 100
default: 20
SortBy:
name: sortBy
in: query
description: "Sort field and direction"
schema:
type: string
pattern: "^[a-zA-Z]+:(asc|desc)$"
example: "createdAt:desc"
IfMatch:
name: If-Match
in: header
description: "ETag for optimistic concurrency"
schema:
type: string
responses: BadRequest: description: "Bad Request - Invalid input" content: application/problem+json: schema: $ref: "#/components/schemas/ErrorResponse" example: type: "https://api.example.com/problems/bad-request" title: "Bad Request" status: 400 detail: "The request body is malformed"
Unauthorized:
description: "Unauthorized - Authentication required"
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ErrorResponse"
Forbidden:
description: "Forbidden - Insufficient permissions"
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ErrorResponse"
NotFound:
description: "Not Found - Resource does not exist"
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ErrorResponse"
Conflict:
description: "Conflict - Resource state conflict"
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ErrorResponse"
UnprocessableEntity:
description: "Unprocessable Entity - Validation failed"
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ErrorResponse"
example:
type: "https://api.example.com/problems/validation-error"
title: "Validation Error"
status: 422
detail: "One or more validation errors occurred"
errors:
- field: "name"
message: "Name is required"
code: "required"
TooManyRequests:
description: "Too Many Requests - Rate limit exceeded"
headers:
Retry-After:
description: "Seconds until rate limit resets"
schema:
type: integer
X-RateLimit-Limit:
description: "Requests per window"
schema:
type: integer
X-RateLimit-Remaining:
description: "Requests remaining"
schema:
type: integer
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ErrorResponse"
Security Schemes
components: securitySchemes: bearerAuth: type: http scheme: bearer bearerFormat: JWT description: "JWT Bearer token authentication"
apiKey:
type: apiKey
in: header
name: X-API-Key
description: "API key for service-to-service auth"
oauth2:
type: oauth2
description: "OAuth 2.0 authentication"
flows:
authorizationCode:
authorizationUrl: "https://auth.example.com/authorize"
tokenUrl: "https://auth.example.com/token"
refreshUrl: "https://auth.example.com/refresh"
scopes:
"read:resources": "Read access to resources"
"write:resources": "Write access to resources"
"admin:resources": "Administrative access"
C# Models for OpenAPI
using System.Text.Json.Serialization;
namespace SpecDrivenDevelopment.OpenApi;
/// <summary> /// Represents an OpenAPI specification document /// </summary> public record OpenApiSpec { public required string OpenApi { get; init; } = "3.1.0"; public required OpenApiInfo Info { get; init; } public List<OpenApiServer> Servers { get; init; } = []; public Dictionary<string, OpenApiPathItem> Paths { get; init; } = []; public OpenApiComponents? Components { get; init; } public List<OpenApiSecurityRequirement> Security { get; init; } = []; public List<OpenApiTag> Tags { get; init; } = []; }
public record OpenApiInfo { public required string Title { get; init; } public required string Version { get; init; } public string? Description { get; init; } public OpenApiContact? Contact { get; init; } public OpenApiLicense? License { get; init; } }
public record OpenApiContact { public string? Name { get; init; } public string? Email { get; init; } public string? Url { get; init; } }
public record OpenApiLicense { public required string Name { get; init; } public string? Identifier { get; init; } public string? Url { get; init; } }
public record OpenApiServer { public required string Url { get; init; } public string? Description { get; init; } public Dictionary<string, OpenApiServerVariable>? Variables { get; init; } }
public record OpenApiServerVariable { public required string Default { get; init; } public List<string>? Enum { get; init; } public string? Description { get; init; } }
public record OpenApiTag { public required string Name { get; init; } public string? Description { get; init; } }
public record OpenApiPathItem { public string? Summary { get; init; } public string? Description { get; init; } public OpenApiOperation? Get { get; init; } public OpenApiOperation? Post { get; init; } public OpenApiOperation? Put { get; init; } public OpenApiOperation? Patch { get; init; } public OpenApiOperation? Delete { get; init; } public List<OpenApiParameter>? Parameters { get; init; } }
public record OpenApiOperation { public required string OperationId { get; init; } public string? Summary { get; init; } public string? Description { get; init; } public List<string>? Tags { get; init; } public List<OpenApiParameter>? Parameters { get; init; } public OpenApiRequestBody? RequestBody { get; init; } public required Dictionary<string, OpenApiResponse> Responses { get; init; } public List<OpenApiSecurityRequirement>? Security { get; init; } public bool Deprecated { get; init; } }
public record OpenApiParameter { public required string Name { get; init; }
[JsonConverter(typeof(JsonStringEnumConverter))]
public required ParameterLocation In { get; init; }
public string? Description { get; init; }
public bool Required { get; init; }
public OpenApiSchema? Schema { get; init; }
[JsonPropertyName("$ref")]
public string? Ref { get; init; }
}
public enum ParameterLocation { Query, Header, Path, Cookie }
public record OpenApiRequestBody { public string? Description { get; init; } public required Dictionary<string, OpenApiMediaType> Content { get; init; } public bool Required { get; init; } }
public record OpenApiResponse { public required string Description { get; init; } public Dictionary<string, OpenApiMediaType>? Content { get; init; } public Dictionary<string, OpenApiHeader>? Headers { get; init; }
[JsonPropertyName("$ref")]
public string? Ref { get; init; }
}
public record OpenApiMediaType { public OpenApiSchema? Schema { get; init; } public object? Example { get; init; } public Dictionary<string, OpenApiExample>? Examples { get; init; } }
public record OpenApiExample { public string? Summary { get; init; } public string? Description { get; init; } public object? Value { get; init; } }
public record OpenApiHeader { public string? Description { get; init; } public OpenApiSchema? Schema { get; init; } }
public record OpenApiSchema { public string? Type { get; init; } public string? Format { get; init; } public string? Description { get; init; } public List<string>? Enum { get; init; } public object? Default { get; init; } public object? Example { get; init; } public List<string>? Required { get; init; } public Dictionary<string, OpenApiSchema>? Properties { get; init; } public OpenApiSchema? Items { get; init; } public bool? Nullable { get; init; } public int? MinLength { get; init; } public int? MaxLength { get; init; } public int? Minimum { get; init; } public int? Maximum { get; init; } public string? Pattern { get; init; } public List<OpenApiSchema>? AllOf { get; init; } public List<OpenApiSchema>? OneOf { get; init; } public List<OpenApiSchema>? AnyOf { get; init; } public bool? AdditionalProperties { get; init; }
[JsonPropertyName("$ref")]
public string? Ref { get; init; }
}
public record OpenApiComponents { public Dictionary<string, OpenApiSchema>? Schemas { get; init; } public Dictionary<string, OpenApiParameter>? Parameters { get; init; } public Dictionary<string, OpenApiResponse>? Responses { get; init; } public Dictionary<string, OpenApiSecurityScheme>? SecuritySchemes { get; init; } }
public record OpenApiSecurityScheme { public required string Type { get; init; } public string? Scheme { get; init; } public string? BearerFormat { get; init; } public string? Description { get; init; } public string? Name { get; init; } public string? In { get; init; } public OpenApiOAuthFlows? Flows { get; init; } }
public record OpenApiOAuthFlows { public OpenApiOAuthFlow? AuthorizationCode { get; init; } public OpenApiOAuthFlow? ClientCredentials { get; init; } }
public record OpenApiOAuthFlow { public required string AuthorizationUrl { get; init; } public required string TokenUrl { get; init; } public string? RefreshUrl { get; init; } public required Dictionary<string, string> Scopes { get; init; } }
public record OpenApiSecurityRequirement : Dictionary<string, List<string>>;
OpenAPI Design Patterns
Versioning Strategy
versioning_strategies: url_path: description: "Version in URL path" example: "/v1/resources" pros: - "Explicit and visible" - "Easy to route" - "Cache-friendly" cons: - "URL changes on major version"
header: description: "Version in custom header" example: "X-API-Version: 2023-01-15" pros: - "Clean URLs" - "Fine-grained control" cons: - "Less discoverable" - "Harder to test in browser"
query_parameter: description: "Version as query parameter" example: "/resources?version=2" pros: - "Easy to specify" - "Fallback to default" cons: - "Cache complications" - "Less clean"
recommended: "url_path" rationale: "Most explicit, widely adopted, cache-friendly"
Pagination Patterns
pagination_patterns: offset_based: description: "Traditional page/pageSize pagination" parameters: - "page (1-indexed)" - "pageSize (default 20, max 100)" response: pagination: page: 2 pageSize: 20 totalItems: 150 totalPages: 8 pros: - "Simple to implement" - "Random access to pages" cons: - "Inconsistent with concurrent writes" - "Performance degrades at high offsets"
cursor_based: description: "Cursor/continuation token pagination" parameters: - "cursor (opaque token)" - "limit (default 20, max 100)" response: pagination: nextCursor: "eyJpZCI6MTIzfQ==" hasMore: true pros: - "Consistent with concurrent writes" - "Better performance at scale" cons: - "No random access" - "Harder to implement"
recommended: "cursor_based for large datasets, offset_based for small"
Error Handling (RFC 7807)
error_handling: standard: "RFC 7807 Problem Details" content_type: "application/problem+json"
structure: type: "URI reference identifying problem type" title: "Short human-readable summary" status: "HTTP status code" detail: "Human-readable explanation specific to this occurrence" instance: "URI reference to specific occurrence"
extensions: errors: "Array of field-level validation errors" traceId: "Correlation ID for debugging"
example: type: "https://api.example.com/problems/validation-error" title: "Validation Error" status: 422 detail: "The request contains invalid data" instance: "/resources/123" traceId: "abc-123-xyz" errors: - field: "email" message: "Invalid email format" code: "invalid_format"
Validation Checklist
openapi_validation_checklist: structure: - "Valid OpenAPI 3.1.0 syntax" - "All required fields present (openapi, info, paths)" - "No undefined $ref references" - "Consistent naming conventions"
operations: - "Every operation has unique operationId" - "All operations have summary and description" - "All operations tagged appropriately" - "All path parameters defined" - "Response codes cover success and error cases"
schemas: - "All schemas have descriptions" - "Required fields explicitly listed" - "Examples provided for complex types" - "Enums documented with descriptions" - "String formats specified (uuid, date-time, email, uri)"
security: - "Security schemes defined" - "Operations specify security requirements" - "OAuth scopes documented"
documentation: - "API description explains purpose" - "Contact information provided" - "Server URLs for all environments" - "Tags organized logically"
best_practices: - "Use RFC 7807 for errors" - "Consistent pagination approach" - "HATEOAS links where appropriate" - "Idempotency keys for POST operations" - "ETag/If-Match for optimistic concurrency"
References
-
references/openapi-patterns.md
-
Common OpenAPI design patterns
-
references/api-guidelines.md
-
API design guidelines and standards
Last Updated: 2025-12-26