Free & open source — no account required

v1.2.5-PRICING-19
Web & Frontend • Engineering Documentation

OpenAPI Mastery: Automating API Documentation

This technical guide provides an in-depth analysis of the json to openapi 3 engine, best practices for implementation, and data security standards.

JSON to OpenAPI 3: Discriminators, Webhooks, Security Schemes, and Spectral Linting

OpenAPI 3.1 (released 2021) is fully aligned with JSON Schema 2020-12, which changes how several important patterns work compared to 3.0: nullable is replaced with type: ['string', 'null'], exclusiveMinimum becomes a number rather than a boolean, and the $schema keyword is now supported for inline schema meta-references. Beyond the spec upgrade, production OpenAPI work involves: using discriminator for polymorphic responses, generating webhook definitions alongside REST endpoints, wiring OAuth2/OIDC security schemes correctly, and running Spectral to catch spec quality issues before they affect generated clients.

Live Example: Complete OpenAPI 3.1 Schema from JSON

# Input JSON samples (request and response)
# POST /orders body:
{
  "customer_id": "cust_001",
  "items": [{ "sku": "SKU-A", "qty": 2 }],
  "shipping_address": {
    "street": "123 Main St", "city": "Springfield", "country": "US"
  },
  "coupon_code": null
}
# Response:
{
  "id": "ord_abc123",
  "status": "PENDING",
  "total_cents": 4998,
  "created_at": "2024-03-15T10:00:00Z"
}

# Generated OpenAPI 3.1 spec
openapi: 3.1.0
info:
  title: Order Service API
  version: 1.0.0
  description: REST API for order management

components:
  schemas:
    OrderItem:
      type: object
      required: [sku, qty]
      properties:
        sku:  { type: string, example: SKU-A }
        qty:  { type: integer, minimum: 1 }
        price_cents:
          type:        integer
          readOnly:    true
          description: Calculated server-side

    Address:
      type: object
      required: [street, city, country]
      properties:
        street:  { type: string }
        city:    { type: string }
        country: { type: string, minLength: 2, maxLength: 2, example: US }

    CreateOrderRequest:
      type: object
      required: [customer_id, items, shipping_address]
      properties:
        customer_id:
          type: string
          pattern: '^cust_[a-z0-9]+$'
        items:
          type:     array
          items:    { $ref: '#/components/schemas/OrderItem' }
          minItems: 1
        shipping_address:
          $ref: '#/components/schemas/Address'
        coupon_code:
          type:    ['string', 'null']   # OpenAPI 3.1: nullable, not nullable: true
          example: null

    Order:
      type: object
      required: [id, status, total_cents, created_at]
      properties:
        id:          { type: string, readOnly: true }
        status:
          type: string
          enum: [PENDING, CONFIRMED, SHIPPED, DELIVERED, CANCELLED]
        total_cents: { type: integer, readOnly: true }
        created_at:  { type: string, format: date-time, readOnly: true }

    Error:
      type: object
      required: [code, message]
      properties:
        code:    { type: string }
        message: { type: string }
        details: { type: array, items: { type: string } }

paths:
  /orders:
    post:
      operationId: createOrder
      summary:     Create a new order
      tags:        [Orders]
      security:    [{ bearerAuth: [] }]
      requestBody:
        required: true
        content:
          application/json:
            schema:   { $ref: '#/components/schemas/CreateOrderRequest' }
            examples:
              standard: { $ref: '#/components/examples/StandardOrder' }
      responses:
        '201':
          description: Order created
          headers:
            Location: { schema: { type: string }, description: URL of the created order }
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Order' }
        '400':
          description: Validation error
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }
        '401':
          description: Not authenticated

Discriminator for Polymorphic Responses

# OneOf with discriminator — API returns different event types on the same endpoint
components:
  schemas:
    EventBase:
      type: object
      required: [type, timestamp]
      properties:
        type:      { type: string }
        timestamp: { type: string, format: date-time }

    OrderEvent:
      allOf:
        - $ref: '#/components/schemas/EventBase'
        - type: object
          required: [order_id, status]
          properties:
            order_id: { type: string }
            status:   { type: string, enum: [PENDING, CONFIRMED, SHIPPED] }

    PaymentEvent:
      allOf:
        - $ref: '#/components/schemas/EventBase'
        - type: object
          required: [payment_id, amount_cents]
          properties:
            payment_id:   { type: string }
            amount_cents: { type: integer }

    WebhookEvent:
      oneOf:
        - $ref: '#/components/schemas/OrderEvent'
        - $ref: '#/components/schemas/PaymentEvent'
      discriminator:
        propertyName: type
        mapping:
          order_created:   '#/components/schemas/OrderEvent'
          order_shipped:   '#/components/schemas/OrderEvent'
          payment_success: '#/components/schemas/PaymentEvent'
          payment_failed:  '#/components/schemas/PaymentEvent'

Security Schemes: Bearer and OAuth2

# Security schemes defined in components — referenced by operations
components:
  securitySchemes:
    bearerAuth:
      type:         http
      scheme:       bearer
      bearerFormat: JWT

    oauth2:
      type: oauth2
      flows:
        authorizationCode:
          authorizationUrl: https://auth.example.com/oauth/authorize
          tokenUrl:         https://auth.example.com/oauth/token
          scopes:
            orders:read:   Read orders
            orders:write:  Create and modify orders
            admin:         Full access

    apiKey:
      type: apiKey
      in:   header
      name: X-API-Key

# Apply security globally (can be overridden per-operation)
security:
  - bearerAuth: []

paths:
  /admin/orders:
    get:
      security:
        - oauth2: [admin]   # overrides global security for this endpoint

Webhooks (OpenAPI 3.1)

# Webhooks describe events the server sends to clients — new in OpenAPI 3.1
webhooks:
  orderStatusChanged:
    post:
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/WebhookEvent'
      responses:
        '200':
          description: Webhook received (client must return 2xx to acknowledge)

  # Describe the webhook registration endpoint separately as a path
paths:
  /webhooks:
    post:
      summary:     Register a webhook endpoint
      operationId: registerWebhook
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [url, events]
              properties:
                url:    { type: string, format: uri }
                secret: { type: string, description: HMAC signing secret }
                events:
                  type:  array
                  items: { type: string, enum: [order_created, order_shipped, payment_success] }

Spectral Linting: Catching Spec Quality Issues

// Spectral lints OpenAPI specs against quality rules
// npm install -D @stoplight/spectral-cli

// .spectral.yml — quality ruleset
extends: ['spectral:oas']
rules:
  # Require all operations to have an operationId
  operation-operationId:
    severity: error

  # Require all schemas to have examples
  oas3-schema-examples:
    given: "$.components.schemas.*"
    severity: warn
    then:
      field: example
      function: truthy

  # Require all 2xx responses to have a content schema
  operation-success-response:
    severity: warn

  # Disallow inline schemas (require $ref for reuse)
  no-inline-schemas:
    given: "$.paths.*.*.responses.*.content.*.schema"
    severity: warn
    then:
      function: schema
      functionOptions:
        schema: { required: ['$ref'] }

// Run in CI:
// npx spectral lint openapi.yaml --ruleset .spectral.yml

// Or use Spectral JS API:
import { Spectral } from '@stoplight/spectral-core';
import { oas } from '@stoplight/spectral-formats';

const spectral = new Spectral();
spectral.setRuleset({ extends: [oas], rules: {} });
const issues = await spectral.run(specDocument);
const errors = issues.filter(i => i.severity === 0);  // 0 = error

Best Practices for Production

  • Use readOnly and writeOnly on schema properties: Fields like id, created_at, and total_cents are server-generated and should be readOnly: true. Password fields should be writeOnly: true. Code generators use these hints to omit read-only fields from request bodies and write-only fields from response types.
  • Add operationId to every endpoint: Code generators (openapi-generator, oazapfts, hey-api) use operationId as the function name for generated API clients. Without it, generators create awkward names from the path and method. Use snake_case or camelCase consistently: createOrder, getOrderById, listOrders.
  • Define error schemas in components and reuse them: Every 4xx and 5xx response should reference a shared error schema. Consumers need to know the error format to handle it. Defining it once in components/schemas/Error ensures consistency across all endpoints.
  • Migrate from OpenAPI 3.0 nullable: true to 3.1 type: ['string', 'null']: OpenAPI 3.1 uses JSON Schema 2020-12 which doesn't have the nullable keyword. Use the type array form. Many tools still support nullable for backward compatibility, but write new specs using the correct 3.1 syntax.

FAQ

Q: What's the difference between allOf, anyOf, and oneOf?
A: allOf means the data must satisfy ALL listed schemas (used for inheritance/extension). anyOf means the data must satisfy AT LEAST ONE schema. oneOf means the data must satisfy EXACTLY ONE schema (used for mutually exclusive variants). For polymorphism with a discriminator property, use oneOf. For adding properties to a base schema, use allOf.

Q: How do I handle pagination in an OpenAPI spec?
A: OpenAPI doesn't have a built-in pagination concept — define it as a pattern. Use a reusable Pagination component schema with page, page_size, total, and has_more. For cursor-based pagination, use next_cursor and prev_cursor. Add these as query parameters on list endpoints and include the pagination schema in list response wrappers.

Q: Should I write the OpenAPI spec by hand or generate it from code?
A: Design-first (spec by hand → generate server stubs) produces better API design but requires discipline to keep the spec in sync with implementation. Code-first (implement → generate spec) is easier to maintain but produces worse API design (exposes internal naming, missing descriptions). Use code-first with heavy Spectral linting to enforce quality, or design-first with contract testing to verify sync.

Developer FAQ

Is the processing local-only?

Absolutely. TypeMorph operates entirely within your browser's sandbox. We use Web Workers for high-performance computation without ever transmitting your JSON, SQL, or API data to a remote server.

Can I use this for enterprise projects?

Yes. The tool is designed for professional software engineers who require GDPR compliance and data privacy. It is trusted by developers at top-tier startups and financial institutions.