Hermes Wiki
Developer/APIDesign/SchemasContracts/Fundamentals/api-contracts-openapi-protobuf-and-schema-first-design

API Contracts: OpenAPI, Protobuf, and Schema-First Design

Concept

A schema (or contract) is a formal, machine-checkable description of what an API's requests and responses look like — field names, types, required-ness, valid ranges — that both producer and consumer can validate against, instead of relying on documentation prose or tribal knowledge. The two dominant approaches differ in when the schema is written relative to the code:

Code-first starts with the implementation (handler functions, ORM models) and generates a schema from it after the fact — fast to start, but the schema is only ever as accurate as the last generation run, and it's easy for the implementation to drift from any hand-maintained parts of the spec.

Schema-first (contract-first) writes the contract — an OpenAPI document for REST/JSON, a .proto file for gRPC, or a Zod/Pydantic schema at the application boundary — before implementation, and treats it as the source of truth that server and client code, docs, and mocks are all generated or validated from. The core discipline is: agree on the interface before building against it, so integration problems (a field renamed, a type narrowed, a required field silently made optional) get caught by a schema diff at design/review time rather than discovered by a client crashing in staging or production.

OpenAPI (formerly Swagger) is the dominant schema-first format for REST/JSON APIs — a YAML/JSON document describing every endpoint, method, parameter, request/response body shape per status code, and security requirement, from which documentation, client SDKs, server stubs, and mock servers can all be generated. Protobuf plays the same role for gRPC: a .proto file is the single definition compiled into strongly-typed stubs for every language the system uses, so a Go service and a Python service calling each other are validated against the exact same message shapes at compile time, not at runtime. At the application boundary (rather than the wire), libraries like Zod (TypeScript) and Pydantic (Python) serve a related but distinct purpose: validating that a parsed request body actually matches the expected shape before it reaches business logic, independent of whether the wire-level contract is OpenAPI or something looser.

Tradeoffs

Approach Benefit Cost
Code-first (schema generated from implementation) Fast to start; no separate spec to keep in sync manually Schema is only as accurate as the last generation run; easy to drift silently if framework integration isn't strict
Schema-first / contract-first (OpenAPI, .proto, written before code) Breaking changes caught at design/review time; one source of truth drives docs, SDKs, mocks, and tests Upfront design cost; a poorly-scoped review step becomes a bottleneck if the contract is treated as bureaucracy rather than a real design artifact
Protobuf (compiled, strongly-typed) Compile-time enforcement across every language; smallest wire payload Requires a build/codegen step in every consumer's pipeline; not human-readable on the wire for ad hoc debugging
OpenAPI (declarative document, JSON-native) Human-readable, huge tooling ecosystem (Swagger UI, codegen, linters), works with plain JSON/HTTP Nothing enforces the implementation actually matches the document unless a framework generates one from the other
Runtime validation only (Zod/Pydantic, no wire-level schema) Cheap to add incrementally to an existing API; catches malformed requests at the boundary Doesn't give consumers a contract to build against ahead of time — no generated client SDK, no design-time review of shape changes

When to use / when not to

  • Adopt a schema-first contract from the first endpoint of a new service, not retroactively — retrofitting OpenAPI or Protobuf onto an API whose shape has already drifted across multiple ad hoc changes is materially more painful than starting with the contract.
  • OpenAPI is the right default for REST/JSON APIs, especially anything with external consumers who'll want generated SDKs or interactive docs (Swagger UI).
  • Protobuf is the natural choice when the API is already gRPC — the schema is the wire format, not an optional add-on describing it.
  • Runtime-only validation (Zod/Pydantic with no OpenAPI doc) is a reasonable minimum for a small internal API with a single, trusted consumer where generated client SDKs and interactive docs add little value — but it stops paying off once a second team or a third-party consumer needs to integrate without reading the source code.
  • Skip a heavyweight contract-first process for a genuinely internal, single-owner script or job with no external caller — the review/design overhead isn't buying anything there.

Common pitfall

Treating the schema as documentation that's written once and never re-verified against the running implementation. OpenAPI specs in particular are prone to drifting from the actual code because they're bolted on after the fact by hand; the only durable fix is strict framework integration where either the code generates the spec (e.g. from route decorators and Pydantic models) or the spec generates the server stubs — never a hand-maintained YAML file next to hand-written route handlers with nothing forcing them to agree. The same failure mode shows up with Zod/Pydantic when the frontend and backend maintain separate schemas by hand for the same request shape: they will eventually diverge, and the fix is either a shared package both sides import, or generating one side's schema from the other's (e.g. TypeScript types from a backend OpenAPI doc).

Engineering Lens

The real question a schema-first contract answers in a design review isn't "do we have an OpenAPI file" — it's "when this field's type or requiredness changes, who finds out, and when?" A contract that's enforced (spec generates code, or a CI check diffs the schema against the previous version and fails on a breaking change with no version bump) turns an integration failure into a design-time conversation. A contract that's just a document sitting next to hand-written code turns it into a production incident discovered by whichever consumer happens to hit the changed field first. The tie to contract testing (Pact, or schema-diff tooling in CI) is the natural next step once a team has a real schema: verifying that provider and consumer both honor it isn't a nice-to-have, it's what makes the "single source of truth" claim actually true instead of aspirational.

Sources

Hermes Wiki