MILTONADINA

openapi-first

Generate API code, types, validators, mock servers, and client SDKs from a single OpenAPI 3.1 specification. Use whenever building or modifying an HTTP API endpoint. The OpenAPI doc is source of truth - drift between doc and code is a contract violation caught by tests. Triggers on requests involving REST endpoints, API design, or client/server contracts.

MILTONADINA 0 Updated 5mo ago

Resources

2
GitHub

Install

npx skillscat add miltonadina/devops/openapi-first

Install via the SkillsCat registry.

SKILL.md

OpenAPI First

The API contract is the source of truth. Code derives from it, not the other
way around.

Tradeoff: Adds an upfront OpenAPI authoring step. Worth it: free types,
free validators, free client SDKs, free mocks, and contract testing comes for
free.


When to invoke

  • New API endpoint requested
  • Existing endpoint being modified (request/response shape changes)
  • Client SDK needed for an existing API
  • Migration from undocumented API to documented

Workflow

  1. Write or update the OpenAPI 3.1 spec at openapi/<service>.yml.
  2. Generate types for both server and client.
  3. Generate validators so runtime requests are checked against the schema.
  4. Implement the handler to satisfy the spec (the test is "the
    schema-validator passes against real requests/responses").
  5. Generate the mock server for frontend development.
  6. Add a contract test that exercises every operation against the spec.

Recommended tooling (TypeScript)

  • openapi-typescript — type generation
  • zod-openapi or @asteasolutions/zod-to-openapi — write schemas in
    Zod, derive OpenAPI from them
  • @hono/zod-openapi or fastify-openapi-glue — server framework
    integration
  • orval or @kubb/swagger-client — typed client SDK generation
  • @stoplight/prism — mock server
  • schemathesis or dredd — contract testing

For Python (FastAPI)

FastAPI generates OpenAPI from typed handlers — the spec is derived from code.
This works in reverse to "OpenAPI first." If you have an existing FastAPI app,
treat its /openapi.json output as authoritative and treat the docs export as
the canonical spec for client generation.


For Rust

  • utoipa — Rust code annotations generate OpenAPI
  • paperclip — alternative annotation-based generator
  • progenitor — generate Rust client from OpenAPI

Quality gates

  1. The OpenAPI spec MUST validate against the OpenAPI 3.1 schema.
  2. spectral lint openapi/<service>.yml MUST pass with the default ruleset
    (extend with .spectral.yaml for project rules).
  3. Generated types MUST be checked into the repo and re-generated by CI on
    schema changes. Drift = build failure.
  4. Contract tests MUST run on every PR.
  5. Breaking changes MUST be detected by oasdiff and require a major version bump.

Anti-patterns

Hand-writing types after the schema: defeats the purpose. Always generate.

Schema-after: writing the handler and then describing it in OpenAPI. Don't.
The schema is the design artifact.

Optional everywhere: if a field is always required in practice, mark it
required. Schema laxity becomes runtime errors.

Missing error responses: every operation should document 4xx and 5xx
responses with Problem Details (RFC 9457) bodies.

No versioning strategy: pick header-based (Accept: application/vnd.api+json; version=2) or path-based (/v2/) and stick to it.


Worked example

openapi/auth.yml:

openapi: 3.1.0
info:
  title: Auth Service
  version: 1.0.0
paths:
  /auth/reset:
    post:
      operationId: requestPasswordReset
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ResetRequest'
      responses:
        '200':
          description: Reset link sent
        '422':
          description: Invalid email
          content:
            application/problem+json:
              schema: { $ref: '#/components/schemas/Problem' }
        '429':
          description: Rate limited
components:
  schemas:
    ResetRequest:
      type: object
      required: [email]
      properties:
        email: { type: string, format: email, maxLength: 320 }
    Problem:
      type: object
      properties:
        type: { type: string, format: uri }
        title: { type: string }
        status: { type: integer }
        detail: { type: string }

Generated code provides:

  • Server handler type signature
  • Client SDK method client.requestPasswordReset({ email })
  • Mock server endpoint
  • Contract test for valid + invalid inputs

This skill is working when: every API endpoint in the project is described
in OpenAPI, every type is generated (not hand-written), and contract tests run
on every PR. Track in governance/telemetry/openapi-coverage.jsonl.