OpenAPI Specs: The Documentation Format Worth Getting Right From the Start

by Eric Hanson, Backend Developer at Clean Systems Consulting

What you are actually creating

An OpenAPI specification is not documentation in the traditional sense — it is a machine-readable contract. Everything downstream flows from it: generated client SDKs, contract tests, API gateway configurations, interactive documentation portals, mock servers for frontend development.

When the spec is accurate, all of these stay in sync automatically. When the spec drifts from the implementation, none of them can be trusted. The quality of your OpenAPI spec is a force multiplier on everything that consumes it.

Code-first vs. spec-first

Code-first: You write the implementation, then generate or annotate the spec from the code. The spec is derived from reality.

Tools: springdoc-openapi for Spring Boot, FastAPI's built-in generation, drf-spectacular for Django REST Framework, tsoa for TypeScript/Express.

Pros: spec is always synchronized with the running code. No risk of implementation divergence.

Cons: the spec reflects what you built, not what you should have built. Design feedback comes after implementation, when changes are more expensive.

Spec-first: You write the OpenAPI spec before implementation. Code is generated or scaffolded from the spec.

Tools: OpenAPI Generator for scaffolding, Stoplight Studio for GUI-based spec editing, Redocly for spec linting and organization.

Pros: the spec is a design artifact — you can review it, test it with mock servers, and share it with consumers before writing a line of implementation code. Design problems surface cheaply.

Cons: requires discipline to keep spec and implementation in sync if you are not generating implementation from the spec. Spec generators for every language do not produce production-ready code — they produce scaffolding you then maintain manually.

The hybrid approach that works in practice: Design in the spec (spec-first for the design phase), generate scaffolding, implement against the scaffolding, then use code-generation tools to maintain the spec as you iterate. Run spec validation in CI to catch divergence.

What a well-written schema looks like

The difference between a schema that helps developers and one that merely compiles:

Minimal (compiles but not useful):

components:
  schemas:
    Order:
      type: object
      properties:
        id:
          type: string
        status:
          type: string
        amount:
          type: number

Complete (actually documents the contract):

components:
  schemas:
    Order:
      type: object
      required: [id, status, amount, currency]
      properties:
        id:
          type: string
          format: ulid
          example: "01HZQK7P3WVXBN4Y9MRDTJC8E6"
          description: Unique order identifier (ULID format)
          readOnly: true
        status:
          type: string
          enum: [pending, confirmed, shipped, delivered, cancelled]
          description: |
            Order lifecycle state.
            Clients should handle unknown values gracefully — new states
            may be added in minor API versions.
          example: confirmed
        amount:
          type: integer
          description: |
            Order total in minor currency units (e.g., cents for USD).
            Always an integer. Never use floating point for monetary values.
          example: 4999
          minimum: 1
        currency:
          type: string
          pattern: '^[A-Z]{3}$'
          description: ISO 4217 currency code
          example: USD

The required array is often omitted. Without it, code generators cannot determine which fields to mark as mandatory in generated types. Every field should be explicitly required or optional.

format hints for common patterns — ulid, date-time, email, uri — help code generators produce correct types. readOnly: true prevents code generators from including id in request body types.

The note about unknown enum values handling is a versioning policy embedded in the documentation — the right place for it.

Keeping the spec accurate over time

The single biggest failure mode for OpenAPI specs: they are accurate at launch and drift from reality over the following months as the implementation changes and the spec does not.

Prevention:

Validate the spec against running responses in CI. Tools like Schemathesis run property-based tests against your API using the OpenAPI spec as the test oracle — it generates requests based on the spec and validates that responses match the declared schemas. This catches divergence automatically:

schemathesis run openapi.yaml --url http://localhost:8000 --checks all

Require spec updates in the same PR as implementation changes. Make the spec a first-class part of the codebase. A PR that changes a response shape without updating the spec should not pass review.

Generate the spec from annotations where possible. For frameworks that support it, maintaining the spec as code comments or annotations tied to the implementation is more reliable than maintaining a separate YAML file:

@Operation(summary = "Retrieve an order")
@ApiResponse(responseCode = "200", content = @Content(schema = @Schema(implementation = OrderResponse.class)))
@ApiResponse(responseCode = "404", content = @Content(schema = @Schema(implementation = ApiError.class)))
@GetMapping("/orders/{id}")
public ResponseEntity<OrderResponse> getOrder(@PathVariable String id) { ... }

The spec as a design review tool

Before implementation, circulate the spec. Ask:

  • Do the endpoint names and paths match how engineers and product think about the domain?
  • Are the request and response shapes what a client would find natural to work with?
  • Are error responses documented for all failure modes, not just happy paths?
  • Are all required fields actually required? Are all optional fields actually optional?

A PR-based review of an OpenAPI spec change is cheaper than a code review of a poorly designed implementation. Getting feedback at spec time costs minutes. Getting the same feedback after clients have integrated costs version bumps.

Scale Your Backend - Need an Experienced Backend Developer?

We provide backend engineers who join your team as contractors to help build, improve, and scale your backend systems.

We focus on clean backend design, clear documentation, and systems that remain reliable as products grow. Our goal is to strengthen your team and deliver backend systems that are easy to operate and maintain.

We work from our own development environments and support teams across US, EU, and APAC timezones. Our workflow emphasizes documentation and asynchronous collaboration to keep development efficient and focused.

  • Production Backend Experience. Experience building and maintaining backend systems, APIs, and databases used in production.
  • Scalable Architecture. Design backend systems that stay reliable as your product and traffic grow.
  • Contractor Friendly. Flexible engagement for short projects, long-term support, or extra help during releases.
  • Focus on Backend Reliability. Improve API performance, database stability, and overall backend reliability.
  • Documentation-Driven Development. Development guided by clear documentation so teams stay aligned and work efficiently.
  • Domain-Driven Design. Design backend systems around real business processes and product needs.

Tell us about your project

Our offices

  • Copenhagen
    1 Carlsberg Gate
    1260, København, Denmark
  • Magelang
    12 Jalan Bligo
    56485, Magelang, Indonesia

More articles

How to Recognize a Failing Software Project Early

Not all disasters happen overnight. Sometimes, projects fail slowly, and the warning signs are subtle. Spotting them early can save you money, time, and a lot of frustration.

Read more

SSRF, Path Traversal, and Other Spring Boot Vulnerabilities That Don't Get Enough Attention

SQL injection and XSS get attention. SSRF, path traversal, ReDoS, XXE, and deserialization vulnerabilities are less discussed but appear regularly in penetration tests and bug bounty reports. Here is how each manifests in Spring Boot and how to prevent it.

Read more

The Error Message That Tells the Developer Nothing at All

Vague error messages are not just unhelpful — they are a tax on every developer who integrates your API. Here is how to stop writing them.

Read more

Lisbon Is No Longer the Affordable Tech Hub It Used to Be — Here Is What Startups Do Now

Lisbon built its reputation as a place where startups could hire well without spending like San Francisco. That window has mostly closed.

Read more