API-first Development: Design, Documentation, and Versioning with OpenAPI

A comprehensive guide to API-first development: OpenAPI specifications, automated documentation and SDK generation, versioning strategies, and contract testing to ensure backward compatibility.

API-first development is a methodology where the application programming interface (API) is designed, described, and approved before writing server and client logic. This approach radically changes the process of creating digital products: teams agree on interaction contracts at the start, which accelerates parallel work and reduces compatibility risks. In this article, we will cover key practices: OpenAPI-based design, automatic documentation and SDK generation, versioning strategies, and contract testing. Our experience in web application development shows that a clear API becomes the foundation for scalable systems.

Why API-first?

The traditional "code-first" approach often leads to the interface being formed haphazardly — as a byproduct of implementation. As a result, client and server teams face lengthy integrations, breaking contracts, and the need to rewrite code when requirements change. API-first flips this logic: the contract becomes the single source of truth, and the code is merely its implementation.

Key benefits of the approach:

  • Parallel development. Frontend and mobile teams can start working immediately after the specification is approved, using API mocks, while the backend is not yet ready.
  • Reduced number of integration errors. Clearly described request and response models, status codes, and error format eliminate misunderstandings between teams.
  • Simplified testing. The contract can be tested in isolation without spinning up the entire infrastructure.
  • Accelerated onboarding. A new developer receives a comprehensive description of the system and immediately sees how to interact with it.

If you are planning to launch a SaaS application, API-first becomes critical: the public interface will be the integration point for your clients, and any contract change can disrupt their business processes. That is why we always start projects with collaborative API design.

API Design with OpenAPI Specification

The OpenAPI Specification (formerly Swagger) is an industry standard for describing REST APIs. It allows you to describe endpoints, methods, parameters, data models, authentication, and even example responses in a machine-readable JSON or YAML format. The specification becomes a living document that can be used throughout the entire lifecycle.

The API-first design process with OpenAPI typically includes the following steps:

  1. Define resources and collections. Identify key entities (e.g., /users, /orders) and operations on them in accordance with REST principles.
  2. Describe data models (schemas). Define a JSON Schema for each resource — fields, types, constraints, and required fields. This ensures unambiguous interpretation by both client and server.
  3. Document requests, responses, and errors. For each method, specify the expected status codes (200, 201, 400, 404, 500, etc.), headers, and response bodies. Include examples — they significantly simplify understanding.
  4. Align the contract with all stakeholders. Conduct a review involving backend, frontend, mobile teams, and the product manager. The approved OpenAPI file is placed in the repository and becomes the starting point.

Modern tools like Swagger Editor or Stoplight Studio allow you to visually design APIs and see results immediately. At this stage, it is important to think long-term: build in extensibility (for example, through additional fields in responses with an x- prefix) to avoid breaking the contract in the future, and plan pagination, filtering, and sorting.

Automatic Documentation and SDK Generation

One of the main advantages of OpenAPI is the ability to automatically generate interactive documentation and client libraries (SDKs). This frees the team from manually updating wiki pages and lowers the barrier to entry for API consumers.

Interactive documentation: based on the specification, tools like Swagger UI or ReDoc build a web interface where you can read the description and immediately send test requests. This becomes a "living" guide that always matches the actual behavior of the service.

SDK generation: OpenAPI Generator (and its predecessor Swagger Codegen) creates client code for dozens of languages from a single specification — JavaScript, TypeScript, Java, Python, Swift, Kotlin, and others. This means that mobile development teams, web clients, and integrators get ready-made typed calls instead of writing HTTP wrappers manually. The SDK includes contract validation, authorization, and error handling, which drastically reduces the number of bugs.

We implement similar processes when building cloud services, where necessary client libraries are generated for multiple languages — this saves time and ensures identical behavior across all platforms. Generation can be embedded in CI/CD: every change to the specification automatically triggers the build of a new SDK version and publication to package repositories (npm, Maven, PyPI).

API Versioning Strategies

Any public API evolves over time: new fields are added, business logic changes, new resources appear. The goal of versioning is to make improvements without breaking existing clients. Without a well-thought-out strategy, every change becomes a risk of crashing dependent services.

The most common methods:

  • URI versioning. The version is explicitly specified in the path: /api/v1/users, /api/v2/users. Simple to implement and clear; downside — with frequent releases, nearly identical controllers proliferate.
  • Query parameter or custom header. For example, /api/users?version=2 or the Accept-Version header. The URI stays clean, but routing and caching become more complex.
  • Content negotiation (Accept). The client sends the header Accept: application/vnd.company.v2+json. This is the "canonical" REST approach, but it requires more complex server-side logic.

Within the team, we recommend using semantic versioning (major.minor.patch), and for external consumers, supporting no more than two major versions simultaneously. Any backward-incompatible changes (removing fields, changing the meaning of a parameter) must result in a new major version. The previous version is declared deprecated with a clearly defined support period (sunset) — usually 3 to 12 months, to give clients time to migrate.

Particularly stringent requirements apply to CRM systems: web clients, mobile apps, and external integrators work simultaneously, and a wrong approach to versioning can paralyze sales. Therefore, every contract change must go through a formal approval process, and deprecation messages must be actively sent to API users.

Contract Testing: Preventing Breakages

Even with an approved specification, discrepancies can arise during implementation: the server starts returning fields that are not described in the contract, or the client interprets the model not according to the specification. Contract testing solves this problem by verifying that the actual behavior of the provider matches the expectations of the consumer as recorded in the contract.

The most popular approach is consumer-driven contract testing using tools like Pact or Spring Cloud Contract. The process looks like this:

  1. The client team (consumer) describes what requests it will send and what responses it expects to receive, in the form of a separate contract (pact file). This contract is registered in a shared repository (Pact Broker).
  2. The server team (provider) imports all contracts from its consumers and runs tests that emulate real HTTP requests and compare responses with expectations. If the response differs — the test fails.
  3. When the API changes on the server side, developers immediately see which client contracts will be broken. This allows them either to refine the implementation without breaking changes, or to coordinate a transition to a new version with consumers.

Contract testing does not replace E2E tests, but it catches issues at service boundaries early, without requiring a full system deployment. In projects developing corporate portals, contract testing becomes a mandatory stage of the CI/CD pipeline, as internal and external services intensively exchange data, and any inconsistency immediately affects users.

Frequently Asked Questions

What is API-first and how is it different from Code-first?

API-first means that interface design comes first, and the contract is approved before writing code. Code-first, on the contrary, starts with implementation, and the API emerges as a consequence — it is later documented retroactively. The first approach increases predictability and allows teams to work in parallel; the second is faster at the start but often leads to lengthy fixes during integration.

Can OpenAPI be used for an existing API?

Yes, there are reverse generation tools (e.g., from Spring annotations or Express routes), as well as manual specification writing. It is better to start by documenting the current behavior, then iteratively bring it to the desired contract. Even an incomplete specification helps clients understand the API faster.

How often should a new version of the API be released?

Major versions should be released only for backward-incompatible changes. Minor versions (adding new fields or resources) can be done frequently without changing the version. A guideline is no more than one major update per year, so as not to force clients to constantly migrate. Actively use a deprecation policy with notification.

How long does it take to implement contract testing?

Basic setup (connecting Pact, writing the first set of tests for one consumer) can take from a few days to two weeks, depending on the complexity of the API and the team's experience. The main costs come from maintaining contracts during service evolution, but they are offset many times over by reducing the number of incidents in production.