OpenAPI design

Help Agents model resources, unify error bodies and cursor pagination, and produce validatable OpenAPI 3.x drafts for review and codegen; this page follows flow → metadata → paths → schemas → errors → versioning → lint → skeleton lab.

A SKILL should fix info.version, servers, and securitySchemes; use plural resources and a max nesting depth, and reuse components for query params and request bodies.

Error responses use Problem Details (RFC 7807) or a team envelope, with typical 4xx/5xx documented; success responses include request/response examples and discriminator notes.

Describe breaking changes and version coexistence via path or header; after generating YAML, run Spectral (or similar) in CI—Agents should output a fix list, not silently ignore violations.

  • Declare required, nullable, and defaults explicitly in schema.
  • Prefer cursor pagination; document offset risks on large datasets.
  • Put webhooks and callbacks in dedicated tags with signature verification placeholders.

$ref and components reuse examples

# OpenAPI 3.0 $ref and components reuse — avoid repeating inline definitions
components:
  schemas:
    # Base entity
    User:
      type: object
      required: [id, email]
      properties:
        id:    { type: string, format: uuid }
        email: { type: string, format: email }
        name:  { type: string, nullable: true }

    # Cursor-paginated response (reusable)
    UserPage:
      type: object
      required: [data, pagination]
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/User'  # reference, not inline
        pagination:
          $ref: '#/components/schemas/CursorPage'

    # Cursor pagination vs offset pagination — OpenAPI definition comparison
    CursorPage:  # cursor-based: suitable for large datasets, stable traversal
      type: object
      properties:
        nextCursor:  { type: string, nullable: true, description: null means last page }
        hasNextPage: { type: boolean }
        limit:       { type: integer, minimum: 1, maximum: 100 }

    OffsetPage:  # offset-based: simple but poor performance at large offsets; document the risk
      type: object
      description: "offset performance degrades on large datasets; switch to cursor above ~1M records"
      properties:
        total:  { type: integer }
        page:   { type: integer, minimum: 1 }
        limit:  { type: integer, minimum: 1, maximum: 100 }

    # oneOf + discriminator: polymorphic types
    Notification:
      oneOf:
        - $ref: '#/components/schemas/EmailNotification'
        - $ref: '#/components/schemas/SmsNotification'
      discriminator:
        propertyName: type   # client uses this field to determine the concrete type
        mapping:
          email: '#/components/schemas/EmailNotification'
          sms:   '#/components/schemas/SmsNotification'

From requirements to mergeable draft

  [ Resource and verb inventory ]
        │
        ▼
  ┌─────────────┐     Set: info / servers / security / tags
  │ Metadata    │──── Version semantics, env URLs, auth model
  └─────────────┘
        │
        ▼
  ┌─────────────┐     Plural paths, nesting depth, query & pagination
  │ paths draft │──── Each operation: summary, operationId, $ref params
  └─────────────┘
        │
        ▼
  ┌─────────────┐     Request/response schemas, examples, error refs
  │ components  │──── discriminator / oneOf polymorphism rules
  └─────────────┘
        │
        ▼
  ┌─────────────┐     Spectral / CI; breaking-change notes & migration
  │ Lint & review│──── Emit fixes per rule; do not swallow violations
  └─────────────┘

Merge order: align team template (errors, pagination, auth), then paths and examples, then run rules; avoid pasting large inline objects before security and error schemas exist.

info, servers, and securitySchemes

info.title / description target humans; contact and license per repo policy. servers lists dev/stage/prod or variables—avoid only-local hardcoded URLs.

# Security schemes — complete definition examples
components:
  securitySchemes:
    BearerJWT:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: "RS256-signed JWT, issuer=https://auth.example.com"
    OAuth2:
      type: oauth2
      flows:
        authorizationCode:
          authorizationUrl: https://auth.example.com/authorize
          tokenUrl:         https://auth.example.com/token
          scopes:
            read:items:  "Read item list"
            write:items: "Create and update items"
    ApiKey:
      type: apiKey
      in: header
      name: X-API-Key

# Global security (default requirement) + per-operation override for exceptions
security:
  - BearerJWT: []

paths:
  /public/status:
    get:
      security: []  # overrides global: no auth required for this endpoint (add comment explaining why)
  • Multi-tenant or region prefix: pick server variables or path prefix once and use consistently.

Paths and resource modeling

Use plural collections (/users, /users/{userId}); cap sub-resource nesting per team (often 2–3). operationId should be stable for codegen.

Prefer

  • Filtering, sorting, field selection via query params; document defaults and caps
  • Cursor + limit; if page is optional, say when it applies
  • Bulk operations on dedicated paths or explicit Prefer semantics if used

Avoid

  • Verbs in paths (unless RPC style is explicitly approved)
  • Multiple URL shapes for the same resource without migration notes
  • Undocumented magic query toggles

components reuse and constraints

Shared types live in components.schemas; bodies and responses use $ref. Spell out required, nullable, default, enum, format (uuid, date-time, …).

  • One schema each for paginated responses, errors, and empty successes—avoid rewrites everywhere.
  • Polymorphism uses discriminator; describe extension strategy in description.
  • Keep examples in sync with schema; CI can validate examples against schema.

Error bodies and status codes

Unify 4xx/5xx content: RFC 7807 application/problem+json or a team envelope (code, message, details, …).

  • List meanings for 400, 401, 403, 404, 409, 422, 429, 500, and business-specific codes.
  • Document 429 with Retry-After behavior at the doc layer.

Versioning and breaking changes

Choose one: path prefix (/v1), header (Accept-Version), or subdomain; document when to bump major and coexistence period.

Breaking examples: remove fields, change types, change enum meaning, tighten auth. List in description or a migration section; keep old paths in-doc or mark sunset.

Spectral and rule sets

Use a team Spectral ruleset or OWASP/API extensions; run in PR/CI and print rule id and JSON path on failure.

  • For each finding, choose: change contract, change rule, or documented waiver—no silent ignore without record.
  • Match JSON Schema draft to OpenAPI 3.0 vs 3.1; do not mix incompatible features.

OpenAPI skeleton lab

Pick OpenAPI version, auth, and error style, enter API title, then copy the generated YAML skeleton; add paths and components as needed.

OpenAPI version
securitySchemes (multi-select)
Default error response

Output has no concrete paths; $ref components.schemas.Problem or ErrorEnvelope from each operation’s responses. Do not treat Bearer and OAuth2 as the only scheme together unless the gateway supports it.

---
name: openapi-design
description: Draft OpenAPI 3 contracts, error models, and reviewable examples
---
# Essentials
1. components reuse and security conventions
2. Unified error body and common status codes
3. Linting and breaking-change callouts

Back to skills More skills