Files
gh-greyhaven-ai-claude-code…/skills/documentation-architecture/reference/openapi-patterns.md
2025-11-29 18:29:15 +08:00

8.8 KiB

OpenAPI 3.1 Patterns and Best Practices

Comprehensive guide to OpenAPI 3.1 specification patterns for Grey Haven stack (FastAPI + TanStack Start).

OpenAPI 3.1 Overview

OpenAPI 3.1 is fully compatible with JSON Schema Draft 2020-12.

Key Differences from 3.0: Full JSON Schema compatibility, examples replaces example, webhooks support, better discriminator

Basic Structure

openapi: 3.1.0
info:
  title: Grey Haven API
  version: 1.0.0

servers:
  - url: https://api.greyhaven.com

paths:
  /users:
    get:
      operationId: listUsers
      tags: [users]
      responses:
        '200':
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/User'

Authentication

JWT Bearer

components:
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT

security:
  - BearerAuth: []

OAuth2

components:
  securitySchemes:
    OAuth2:
      type: oauth2
      flows:
        authorizationCode:
          authorizationUrl: https://auth.greyhaven.com/oauth/authorize
          tokenUrl: https://auth.greyhaven.com/oauth/token
          scopes:
            read:users: Read user data

API Key

components:
  securitySchemes:
    ApiKey:
      type: apiKey
      in: header
      name: X-API-Key

Schema Patterns

Pydantic v2 to OpenAPI

from pydantic import BaseModel, Field
from typing import Literal

class User(BaseModel):
    id: str = Field(..., pattern="^usr_[a-z0-9]{16}$")
    email: str = Field(..., examples=["user@example.com"])
    role: Literal["admin", "member", "guest"] = "member"

Generates:

User:
  type: object
  required: [id, email]
  properties:
    id:
      type: string
      pattern: ^usr_[a-z0-9]{16}$
    email:
      type: string
      examples: ["user@example.com"]
    role:
      type: string
      enum: [admin, member, guest]
      default: member

Nullable and Optional

# Optional (can be omitted)
username:
  type: string

# Nullable (can be null)
middle_name:
  type: [string, 'null']

# Both
nickname:
  type: [string, 'null']

Discriminated Unions

PaymentMethod:
  type: object
  required: [type]
  discriminator:
    propertyName: type
    mapping:
      card: '#/components/schemas/CardPayment'
      bank: '#/components/schemas/BankPayment'

CardPayment:
  allOf:
    - $ref: '#/components/schemas/PaymentMethod'
    - type: object
      properties:
        card_number:
          type: string
          pattern: ^\d{16}$

Response Patterns

Error Response

ErrorResponse:
  type: object
  required: [error, message]
  properties:
    error:
      type: string
      examples: ["VALIDATION_ERROR"]
    message:
      type: string
    details:
      type: object
      additionalProperties: true

# Use in responses
responses:
  '400':
    content:
      application/json:
        schema:
          $ref: '#/components/schemas/ErrorResponse'

Paginated Response

PaginatedUsers:
  type: object
  properties:
    data:
      type: array
      items:
        $ref: '#/components/schemas/User'
    pagination:
      type: object
      properties:
        page:
          type: integer
          minimum: 1
        per_page:
          type: integer
          minimum: 1
          maximum: 100
        total:
          type: integer
        total_pages:
          type: integer

Multiple Status Codes

responses:
  '201':
    description: Created
    content:
      application/json:
        schema:
          $ref: '#/components/schemas/User'
  '202':
    description: Accepted (async)
    content:
      application/json:
        schema:
          type: object
          properties:
            job_id:
              type: string

Request Body

Required vs Optional

requestBody:
  required: true
  content:
    application/json:
      schema:
        type: object
        required: [email, password]  # Required
        properties:
          email:
            type: string
            format: email
          password:
            type: string
            minLength: 8
          name:
            type: string  # Optional

File Upload

requestBody:
  content:
    multipart/form-data:
      schema:
        properties:
          file:
            type: string
            format: binary

Parameters

Path

parameters:
  - name: user_id
    in: path
    required: true
    schema:
      type: string
      pattern: ^usr_[a-z0-9]{16}$

Query

parameters:
  - name: status
    in: query
    schema:
      type: string
      enum: [pending, processing, shipped]
  - name: created_after
    in: query
    schema:
      type: string
      format: date-time
  - name: sort
    in: query
    schema:
      type: string
      enum: [created_at:asc, created_at:desc]
      default: created_at:desc

Headers

components:
  parameters:
    TenantId:
      name: X-Tenant-ID
      in: header
      required: true
      schema:
        type: string

paths:
  /orders:
    post:
      parameters:
        - $ref: '#/components/parameters/TenantId'

Response Headers

responses:
  '200':
    headers:
      X-RateLimit-Limit:
        schema:
          type: integer
      X-RateLimit-Remaining:
        schema:
          type: integer
      X-Request-ID:
        schema:
          type: string
          format: uuid

Multi-Language Examples

x-codeSamples:
  - lang: TypeScript
    source: |
      const response = await fetch("https://api.greyhaven.com/users", {
        method: "POST",
        headers: { "Authorization": `Bearer ${token}` },
        body: JSON.stringify({ email, password })
      });
  
  - lang: Python
    source: |
      async with httpx.AsyncClient() as client:
          response = await client.post(
              "https://api.greyhaven.com/users",
              headers={"Authorization": f"Bearer {token}"},
              json={"email": email, "password": password}
          )
  
  - lang: Shell
    source: |
      curl -X POST https://api.greyhaven.com/users \
        -H "Authorization: Bearer $TOKEN" \
        -d '{"email": "user@example.com"}'

Webhooks (OpenAPI 3.1)

webhooks:
  orderCreated:
    post:
      requestBody:
        content:
          application/json:
            schema:
              type: object
              required: [event, data]
              properties:
                event:
                  type: string
                  const: order.created
                data:
                  $ref: '#/components/schemas/Order'
      responses:
        '200':
          description: Received

Best Practices

  1. Use $ref: Define schemas once, reference everywhere
  2. Examples: Realistic examples for all schemas
  3. Error Schemas: Consistent error format
  4. Validation: Use pattern, minLength, minimum
  5. Descriptions: Document every field
  6. operationId: Unique for SDK generation
  7. Tags: Group related endpoints
  8. Deprecation: Mark with deprecated: true
  9. Security: Define at global or operation level
  10. Versioning: Include in URL (/api/v1/)

Common Patterns

Multi-Tenant

components:
  parameters:
    TenantId:
      name: X-Tenant-ID
      in: header
      required: true
      schema:
        type: string

Idempotency

components:
  parameters:
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      schema:
        type: string
        format: uuid

Rate Limiting

responses:
  '429':
    description: Rate limit exceeded
    headers:
      X-RateLimit-Reset:
        schema:
          type: integer

FastAPI Integration

from fastapi import FastAPI

app = FastAPI(
    title="Grey Haven API",
    version="1.0.0",
    openapi_version="3.1.0"
)

# Customize OpenAPI
def custom_openapi():
    if app.openapi_schema:
        return app.openapi_schema
    
    openapi_schema = get_openapi(
        title=app.title,
        version=app.version,
        routes=app.routes
    )
    
    # Add security schemes
    openapi_schema["components"]["securitySchemes"] = {
        "BearerAuth": {
            "type": "http",
            "scheme": "bearer",
            "bearerFormat": "JWT"
        }
    }
    
    app.openapi_schema = openapi_schema
    return app.openapi_schema

app.openapi = custom_openapi

Validation

Use @redocly/cli for validation:

npx @redocly/cli lint openapi.yaml

Common issues:

  • Missing operationId
  • Missing response descriptions
  • Inconsistent naming
  • Missing examples
  • Invalid $ref paths

Related: mermaid-diagrams.md | documentation-standards.md | Return to INDEX