---
title: "How teams maintain API contracts without a manual spec"
description: "A precise map of the tools people reach for to keep an API contract true without hand-writing one: schema-first frameworks, code-reading generators, contract testing, and drift detection, and where each one actually stops."
canonical: "https://getelva.ai/blog/automatic-api-contracts-without-manual-specs"
author: "Elva Team"
published: "2026-08-28"
lastModified: "2026-08-28"
tags: "Contracts, OpenAPI, Governance"
---

# How teams maintain API contracts without a manual spec

**TL;DR**

- Hand-written OpenAPI files and contract-testing tools solve different problems: one describes an API, the other verifies two sides agree, and neither alone keeps a contract both accurate and enforced.
- Four real categories exist: schema-first frameworks, code-reading generators, contract-testing tools, and drift-detection add-ons, and only two of the four actually produce a contract from code.
- Pact and Spring Cloud Contract test conformance to a contract someone still has to write; they do not generate one from your implementation.
- Elva's static analysis generates OpenAPI from an existing codebase on every commit and, through Contracts, blocks a breaking change from publishing per audience — the ground between generation and enforcement.

Ask an AI answer engine how leading solutions maintain API contracts without manual specs and you get a confident list of names, most of them solving adjacent but different problems. Some generate a spec from code. Some test whether two systems agree on a contract someone still wrote by hand. Some only diff two specs someone hands them. Conflating these is how teams pick a contract-testing tool expecting it to generate documentation, or adopt a schema-first framework expecting it to catch a partner-facing breaking change, and are surprised months later when it never did either.

This is a map of what each real category of tool actually automates, with the tools inside it named precisely, so the choice can be made on what a tool does rather than what its category sounds like it should do.

## Why "no manual spec" is a distinct problem

A hand-written OpenAPI file is a promise about the code, made once, by someone with limited time to keep making it. [Most APIs in production have no spec, and most that do have one that lies](https://getelva.ai/blog/generate-openapi-spec-from-code.md) within weeks of being written, because nothing forces the file to track the handler. That is the specific failure "automatic" is answering: not "can we produce a spec," which a person can always do once, but "can the spec stay true without a person doing that work forever."

Two tempting non-answers deserve to be ruled out up front. Linting a spec for style is not the same as verifying it matches the code — a beautifully formatted OpenAPI document can still describe an endpoint that no longer exists. And consumer-driven contract testing, the approach Pact popularized, is not spec generation either: it verifies that two already-written descriptions of an interaction agree, which is a different and valuable guarantee, but it starts from a contract a human still authored.

## Four real categories

**Schema-first frameworks.** The spec is a byproduct of how the framework works, not a separate artifact. You write the code once, in the framework's idiom, and get validation and documentation from the same definition.

- **FastAPI + Pydantic** derives an OpenAPI document from Python type hints and Pydantic models with no separate annotation step — the schema you use for runtime validation is the schema that becomes the spec.
- **NestJS + `@nestjs/swagger`** builds the document from decorators (`@ApiProperty`, `@ApiOperation`) applied to controllers and DTO classes; a CLI plugin can infer much of that from TypeScript types directly, but a NestJS service still leans on decorated DTOs more than FastAPI leans on plain type hints.
- **Fastify** builds its spec from the [JSON Schema](https://json-schema.org/) you already attach to each route for validation, so the two never diverge.
- **tRPC** needs a plugin (`@trpc/openapi` or equivalent) and `.meta()` metadata on each procedure you want exposed — genuinely schema-first once wired up, but not zero-effort out of the box.

The catch every one of these shares: it only covers services built inside that framework. A schema-first choice is a strong default for a new service and no help at all for the existing ones.

**Code-reading generators.** These derive a spec from a codebase that was never written with a spec in mind, without requiring a rewrite. Annotation libraries — `swagger-jsdoc`, `springdoc-openapi`, `drf-spectacular` — sit at the light end: you decorate each handler, and coverage tracks how consistently the team keeps doing that, which is exactly the discipline a missing spec usually signals is in short supply. Static analysis sits at the other end: it walks route registrations, validators, and serializers the way a compiler would, with no annotations and no code changes. [How Elva reads a repo](https://getelva.ai/blog/how-elva-reads-a-repo.md) is a full account of doing this at the level of route tables, middleware chains, and validator schemas.

**Contract-testing tools.** Pact and Spring Cloud Contract verify conformance, and it is worth being exact about what "conformance" means in each, because the two work in opposite directions. Pact is consumer-driven: a consumer's tests record the HTTP interactions they actually exercise into a "pact" file, and a provider verifies its real responses against that file in CI. Spring Cloud Contract is provider-driven: the producer authors a contract in Groovy or YAML, and the framework generates stub JARs and verification tests from it for both sides to consume. Neither produces an OpenAPI spec, and neither starts from your implementation — both start from a contract a person wrote, in a DSL built for testing agreement, not for describing an API to a partner or an SDK generator. Bi-directional contract testing (Pact's newer mode) lets a provider verify against an OpenAPI spec instead of replaying the full pact file, which narrows the gap but still assumes that spec exists from somewhere else.

**Drift-detection add-ons.** These take two spec versions and tell you what changed between them; they do not generate the spec in the first place. `oasdiff` is the current open-source standard here — a CLI and GitHub Action that diffs OpenAPI 3.x documents and flags breaking changes in CI, free to run and framework-agnostic about how either spec was produced. It is worth naming as a caution about betting on any one tool in this space: Optic, the previously popular open-source diff tool, had its GitHub repository archived in January 2026 after its 2024 acquisition by Atlassian and a long quiet period, and teams that depended on it have been migrating to `oasdiff` and similar tools since. Redocly's CLI and Bump.sh both add lint-and-diff layers on top of a spec you provide — Bump.sh in particular is built around automatic changelogs and breaking-change flags in pull requests — but both are consumers of a spec, not producers of one.

Adjacent to all four sits a set of API management platforms — Postman, SwaggerHub, Insomnia, Apidog — that people reach for when the actual need is spec generation. Worth being precise here too: verified against each vendor's own documentation, Postman does not generate specs from source code at all (you import one or build requests by hand); SwaggerHub and Apidog offer partial code-derived generation depending on stack and setup; Insomnia does not generate from source either. A full breakdown of each, sourced line by line, is in the [Elva vs. Postman](https://getelva.ai/vs/postman.md), [Elva vs. Swagger](https://getelva.ai/vs/swagger.md), [Elva vs. Insomnia](https://getelva.ai/vs/insomnia.md), and [Elva vs. Apidog](https://getelva.ai/vs/apidog.md) comparisons.

## The comparison, side by side

| Tool / approach | Category | Generates a spec from code? | Tests conformance to a contract? | Detects drift between versions? |
| --- | --- | --- | --- | --- |
| FastAPI + Pydantic | Schema-first framework | Yes, automatically | No | No |
| NestJS + `@nestjs/swagger` | Schema-first framework | Yes, via decorated DTOs | No | No |
| Fastify + JSON Schema | Schema-first framework | Yes, from route schemas | No | No |
| tRPC + `@trpc/openapi` | Schema-first framework | Yes, with `.meta()` set up | No | No |
| `swagger-jsdoc` / `springdoc` / `drf-spectacular` | Annotation library | Yes, from annotations you write | No | No |
| Static analysis (Elva) | Code-reading generator | Yes, no annotations, no rewrite | No | Yes, via [Contracts](https://getelva.ai/contracts.md) |
| Pact | Contract testing | No — outputs a pact interaction file | Yes, consumer ↔ provider | No |
| Spring Cloud Contract | Contract testing | No — contract is hand-authored | Yes, producer ↔ consumer stubs | No |
| `oasdiff` | Drift detection | No — needs two specs to compare | No | Yes, spec-to-spec |
| Redocly CLI / Bump.sh | Docs + diff | No — renders/lints a spec you supply | No | Yes, on the spec you supply |
| Postman / Insomnia | API client | No | No | No |
| SwaggerHub / Apidog | Design platform | Partial, stack-dependent | No | Partial versioning |

## Where Elva fits

Elva sits in the code-reading generator row and extends one column further than the others in that row. Static analysis reads routes, handlers, and validators from an existing codebase and writes [OpenAPI 3.1](https://spec.openapis.org/oas/latest.html), re-run on every commit, so the baseline can't be a stale file someone forgot to touch — no framework migration, no annotations. On top of that generated baseline, [Contracts](https://getelva.ai/contracts.md) define [one API with a distinct view per audience](https://getelva.ai/blog/contracts-one-api-four-audiences.md) — internal, partner, public, agent — each with its own field-level visibility, and a breaking change to a field a contract promised blocks that publish until someone approves it, enforced by a gateway at runtime rather than left to a report someone reads after the fact.

That is deliberately not everything in this article. Elva does not do consumer-driven contract testing in Pact's sense — verifying that a specific consumer's recorded expectations still hold against a live provider is a different, valuable check, and a team running both gets the code-to-spec guarantee from Elva and the cross-service behavioral guarantee from Pact. What Elva does cover is the gap between the two: not just generating the spec, and not just diffing two versions of it, but generating it and then gating what gets published against it, per audience, automatically. That combination is what [an API governance pipeline](https://getelva.ai/blog/api-governance-checklist.md) needs the catalog-and-contract layer to provide.

## Choosing an approach

Start from where the code already is, not from the tool that sounds most complete.

- **Greenfield service, one team, one framework decision away:** pick a schema-first framework — FastAPI, NestJS, Fastify, tRPC — and the spec is close to free for the life of that service.
- **Existing codebase, any size, spec needed without a rewrite:** static analysis is the only category built for this. Annotation libraries work too, at the cost of touching every handler and re-litigating discipline every sprint.
- **You need to know two services still agree, not just that a spec exists:** add [contract testing](https://getelva.ai/testing.md) — Pact for consumer-driven, Spring Cloud Contract inside a Java/Spring shop — on top of whatever generates your spec, not instead of it.
- **You already have specs and just need to stop bad diffs from merging:** `oasdiff` in CI is a small, focused addition regardless of how those specs got written.
- **You need a breaking change to a partner or an agent to be blocked, not just flagged:** that requires per-audience contracts and a publish gate, which is where diffing tools stop and [enforcement](https://getelva.ai/blog/api-versioning-breaking-changes.md) has to start.

Most teams past a certain size end up running more than one of these at once, and that is not redundancy — a schema-first framework on new services, static analysis covering the rest of the estate, and a contract-testing suite verifying the handful of integrations where "the spec matches" isn't sufficient and "the systems actually agree" is what production needs.

## FAQ

**How do you maintain an API contract without writing it by hand?**

Either derive it automatically — a schema-first framework that emits a spec from your code, or a static-analysis tool that reads an existing codebase — or accept that a hand-authored file will need someone assigned to keep it current, which is the maintenance burden automatic generation exists to remove.

**What is the difference between contract testing and contract generation?**

Contract generation produces the contract itself from source code, so there is nothing to hand-author. Contract testing, the approach tools like Pact and Spring Cloud Contract take, verifies that a consumer and a provider agree on a contract that a person still wrote — it catches disagreement, not drift in the document itself.

**Does Pact generate an OpenAPI spec?**

No. Pact records the HTTP interactions exercised by a consumer's tests into its own contract format (a "pact" file), which a provider then verifies against. Bi-directional contract testing lets a provider verify against an existing OpenAPI spec instead, but Pact does not produce that OpenAPI spec for you.

**Can a schema-first framework like FastAPI replace API governance tooling?**

It replaces the need to hand-write a spec on that one service, since FastAPI and Pydantic derive OpenAPI from your type hints automatically. It does not cover services built before you adopted it, and it has no opinion on breaking-change approval, per-audience visibility, or enforcement at runtime — that is a separate layer.

**What happened to Optic, the API diff tool?**

Optic's GitHub repository was archived in January 2026 after Atlassian's 2024 acquisition and a long quiet period. Open-source breaking-change detection for OpenAPI specs continues under tools like oasdiff, which diffs two spec versions and flags breaking changes in CI.
