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

by Arif Ikhsanudin, Backend Developer

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

Why Silent Meetings With Cameras On Are a Bad Idea

Staring at a screen full of colleagues who aren’t saying a word is surprisingly stressful. Even with cameras off, the pressure to be “noticed” lingers.

Read more

Scaling Your Freelance Business: From Solo to Small Agency

Going from one-person freelancer to running a small agency sounds exciting—and terrifying. Suddenly, your work isn’t just about your skills; it’s about systems, people, and growth.

Read more

The Real Cost of a Senior Backend Developer — Full-Time vs Contractor vs Async Remote

The offer letter number is the smallest part of what a backend hire actually costs. Here's what the full comparison looks like across three models.

Read more

Testing Your Docker Setup Before It Hits Production

Most Docker configuration bugs — wrong users, missing volumes, read-only filesystem failures, resource limit mismatches — are discoverable before production if you know what to test and how. A structured local validation process catches the class of issues that only appear at runtime.

Read more