Schema-First vs Code-First API Design: When to Write the Spec Before the Code

Schema-first puts the OpenAPI document at the center and generates server stubs and client SDKs from it. Code-first writes the server in your favorite framework and generates the spec from annotations. The right answer depends on who reads the spec and how often it changes.

Every team building an HTTP API eventually has to decide where the OpenAPI specification lives in their workflow. The two camps are well-established. Schema-first treats the OpenAPI document as the canonical source of truth: the team writes the spec by hand or through a dedicated editor, generates server stubs and client SDKs from it, and the application code is downstream of the spec. Code-first treats the application code as the source of truth: the team writes the server in FastAPI or NestJS or whatever framework they prefer, decorates the routes with annotations, and the OpenAPI document is generated as an artifact of the build.

The argument between the camps is older than OpenAPI itself, going back to WSDL and even further to the early SOAP era. The honest answer is that both approaches work and neither is universally right. The choice depends on a small number of structural questions about your team and your API, which we walk through below. Across DocuMint, CronPing, FlagBit, and WebhookVault we use code-first via FastAPI's automatic OpenAPI generation, and we have measured that choice as right for our context.

What each approach actually buys

Schema-first's strongest claim is that the spec is written by humans for humans. A hand-written OpenAPI document tends to have richer descriptions, better examples, more thoughtful error responses, and more useful schemas than a generated one. The spec is treated as a designed artifact, often by a dedicated API designer or technical product manager, before any code exists. This produces a spec that reads well as documentation and that the team can review and iterate on before committing to implementation details.

The second schema-first benefit is that multiple downstream consumers of the spec — server stubs in different languages, client SDKs in different languages, mock servers for early integration testing, contract-test harnesses — all derive from the same source. If a team is shipping client SDKs in five languages, generating them from the spec keeps all five in lockstep without per-language drift. The team can iterate on the spec, regenerate everything, and trust that the consumers do not disagree about what the API does.

The third schema-first benefit is contract enforcement. If the spec is the source of truth, the server can be tested against the spec in CI: requests that the server accepts but the spec does not document, or responses the server returns that violate the spec's schema, are caught as test failures. This is valuable in larger organizations where multiple teams contribute to the same API and the spec is the contract that prevents one team's change from breaking another's client.

Code-first's strongest claim is that the spec cannot drift from the implementation. If the spec is generated from the same code that handles requests, the two cannot disagree by construction: every parameter the server reads is in the spec, every response the server returns is described in the spec, every error path is documented. The spec is always up to date because it is rebuilt every time the code is built.

The second code-first benefit is that the spec costs nothing to maintain. There is no separate document to keep in sync, no review step where the spec lags behind the code, no risk that the team ships an API change and forgets to update the spec. This matters most for small teams where the alternative to code-first is "we'll write a spec when we have time," which means the spec ages out of date and stops being useful.

The third code-first benefit is that it lets developers work in the language and framework they are most productive in, with the spec as a side effect rather than a deliverable. FastAPI developers do not have to learn a separate OpenAPI editor; they write Pydantic models and route handlers, and the spec falls out. The cognitive overhead is lower, and the iteration cycle from idea to working endpoint is shorter.

The factors that should drive the decision

The first factor is the size of the team and the API. Schema-first has a higher up-front cost: the team has to maintain a separate document, learn the OpenAPI syntax well, and run a generation pipeline as part of the build. That cost is justified at scale — when there are dozens of endpoints across multiple services, the discipline of a hand-written spec keeps the API consistent in ways that code-first does not. Below that scale, the cost typically exceeds the benefit.

The second factor is the number of client implementations. If you ship official client SDKs in many languages, schema-first wins almost automatically because the alternative is maintaining many SDKs by hand or hoping that everyone uses an OpenAPI-aware HTTP client. If your API is consumed mostly by raw HTTP requests with no official SDK, code-first works fine.

The third factor is the rate of change. An API that is still being designed — new endpoints appearing weekly, schemas changing as you learn what your customers actually need — benefits from code-first because changes are cheap and the spec follows automatically. A stable API where breaking changes are rare events benefits from schema-first because the discipline of editing the spec slows down accidental changes.

The fourth factor is who reads the spec. If the primary consumer of the spec is the team's own developers using it as documentation, both approaches produce usable specs. If the primary consumer is external partners doing contract-driven development against your API, schema-first tends to produce a more polished spec that is easier to consume. If the primary consumer is a code generator producing SDKs, both approaches work but schema-first gives you tighter control over the generated output.

The fifth factor is how much your framework helps. Modern frameworks like FastAPI, NestJS, .NET Minimal APIs, and Spring Boot with springdoc produce excellent OpenAPI documents from typed code. Older frameworks or ones with weaker introspection produce specs that need substantial annotation overhead, and at that point the cognitive cost of code-first approaches the cognitive cost of schema-first.

The hybrid pattern

Some teams settle on a hybrid where the spec is generated from code but then post-processed by hand: descriptions improved, examples added, errors documented in more detail. The post-processing happens in a separate file that merges with the generated output. This captures most of the readability benefit of schema-first while preserving the cannot-drift guarantee of code-first. The cost is a custom build step and a process discipline around regenerating the merged document.

The hybrid pattern is most useful when you have a stable API consumed by external partners who need polished documentation but cannot tolerate drift. It is overkill for an API consumed mostly internally where the team is fine with the unembellished generated output.

What we chose and why

Across the four products in our studio we use code-first via FastAPI. The decision factors that pushed us this way were team size (one to two engineers per product), framework strength (FastAPI's OpenAPI generation is best-in-class), API stability (still iterating, breaking changes happen), and SDK count (zero — we ship raw HTTP and document curl examples). We have measured this choice as right for our context: the specs are usable enough that customers can navigate the auto-generated Swagger UI and ReDoc, and we have never had a drift bug because there is no separate spec to drift.

If we were building a different shape of company — say, an enterprise API platform with five client SDK languages and a partner-driven contract-development culture — we would almost certainly be schema-first. The structural questions are not opinion; they are answerable from the team and API shape. The mistake to avoid is picking the approach because it is the one your favorite blog post recommended, rather than the one that fits your actual situation.

The deeper observation

The schema-first vs code-first debate is a special case of a more general question: when should the description of a system be a hand-written artifact, and when should it be generated from the implementation? The answer is the same for API specs as it is for database schemas, configuration files, infrastructure-as-code, and any other artifact that describes a system. Generated descriptions cannot drift but tend to lack judgment; hand-written descriptions have judgment but can drift. The right answer depends on whether the cost of drift exceeds the cost of the discipline that keeps the description in sync, and that calculation is different at different scales.

Read more