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

Parallelizing Your Pipeline Is Easier Than You Think

Most teams run sequential pipelines not because parallelism is hard, but because they never explicitly designed for it. The structural changes required are small and the time savings are immediate.

Read more

The First Impression You Make Before the Client Even Talks to You

Before any call or email exchange, clients have already formed an opinion about you. What they find when they look you up is the first impression that matters most.

Read more

The Real Cost of Hiring a Backend Developer in Amsterdam (And the Smarter Alternative)

You budgeted for a backend developer. You didn't budget for the three months of interviews, the signing bonus someone else offered first, and the onboarding period where nothing ships. That's the part of the cost nobody puts in the job req.

Read more

Why “Simple Features” Are Often Not Simple

“It’s just a small feature” is one of the most expensive sentences in software. What looks simple on the surface often hides layers of complexity underneath.

Read more