rgoussu@goussu: ~/library/system-design
~/library/system-design cat api-documentation.md

API documentation

# The contract-artifact formats — OpenAPI, AsyncAPI, protobuf, GraphQL SDL — and the generate-doc-from-code vs generate-code-from-doc trade, with integration notes per Java, Go and Rust ecosystem.

Conceptsaved 2026-08-09 #api#documentation#openapi#asyncapi#contracts#codegen

Overview

API documentation worth the name is not prose next to the code — it is a machine-readable contract artifact (an OpenAPI document, an AsyncAPI document, a .proto file, a GraphQL SDL) from which everything else derives: rendered reference docs, typed clients, server stubs, validators, mock servers, breaking-change gates. The central decision is direction: generate the doc from the code (code-first) or generate the code from the doc (contract-first). Both work; each fails differently. What never works is having two sources of truth.

Key points

  • One flavor per interaction style: OpenAPI for HTTP/REST, AsyncAPI for event-driven and messaging APIs, protobuf for gRPC, SDL + introspection for GraphQL. JSON Schema is the shared payload vocabulary (OpenAPI 3.1 aligned with it; AsyncAPI borrows it).
  • gRPC and GraphQL are contract-shaped by construction — the .proto/SDL is the API; the direction question only truly bites for REST (OpenAPI) and events (AsyncAPI), where the spec is a separate artifact that can drift.
  • Code-first (doc from code): annotations, comments or handler types produce the spec at build/run time. Zero drift by construction on what it covers, cheap to retrofit — but design review happens after implementation, and spec quality is hostage to annotation discipline.
  • Contract-first (code from doc): the spec is authored and reviewed first; servers, clients and validation are generated from it. Consumers start in parallel against mocks, multi-language clients stay consistent — at the price of generator friction and the temptation to hand-edit generated code.
  • The types-first middle path: frameworks that derive the spec from typed handler signatures (huma/fuego in Go, utoipa/poem-openapi in Rust, Micronaut's and SmallRye's build-time processing in Java) get code-first ergonomics with near contract-grade fidelity — correct by construction.
  • Whichever direction: enforce it in CI. Lint the spec (Spectral, vacuum, buf lint), gate breaking changes (oasdiff, buf breaking, graphql-inspector), and publish rendered docs (Swagger UI, Redoc, Scalar; AsyncAPI Studio; Buf Schema Registry; GraphiQL) from the same artifact the gates run on.
  • Docs are contract surface: error shapes (RFC 9457 problem details), auth schemes, rate-limit headers and deprecation/sunset metadata belong in the spec, not in a wiki.

Details

The flavors

Flavor Contract for Artifact Rendered by Direction culture
OpenAPI (3.1) HTTP/REST APIs YAML/JSON, JSON Schema payloads Swagger UI, Redoc, Scalar Both directions thrive; per-ecosystem defaults differ
AsyncAPI (3.0) Event-driven APIs — Kafka, AMQP, MQTT, WebSocket channels YAML/JSON; payloads in JSON Schema or Avro AsyncAPI Studio, Generator (html template) Youngest tooling; often hand-authored, code-first only on the JVM
protobuf gRPC services .proto IDL — schema and API are one file protoc-gen-doc, Buf Schema Registry Contract-first by construction
GraphQL SDL GraphQL APIs Schema file + live introspection GraphiQL, Voyager, docs portals from introspection Schema-first or code-first per ecosystem; SDL is always the output

AsyncAPI deserves the "etc" spotlight: it reuses OpenAPI's mental model (channels ≈ paths, operations ≈ verbs, messages ≈ bodies) for the asynchronous world, and it is the only broadly adopted answer to "where is the contract for our Kafka topics" that isn't just a schema registry entry — the registry versions payload schemas, AsyncAPI documents the channels, semantics and bindings around them.

Doc from code vs code from doc

Doc from code (code-first) Code from doc (contract-first)
Design review After the code exists — the spec is a report Before implementation — the spec is a proposal
Drift risk None on generated surface; blind spots where annotations were forgotten (errors, middleware behavior) None on generated surface; hand-written glue can still diverge
Consumer parallelism Clients wait for a deployed spec Clients + mock servers (e.g. Prism) generated from day one
Polyglot consistency Each consumer re-derives types from the emitted spec One spec fans out to every language's generator
Ceremony Low — annotate as you go Real — spec authoring, generator config, regeneration discipline
Retrofitting Ideal for existing services Painful to adopt late
Typical failure Docs that subtly lie; annotation rot Hand-edited generated code; spec merge conflicts
Fits Internal APIs, single-team services, fast iteration Public APIs, many/polyglot consumers, API-as-product teams

The trade softens at both ends: types-first code-first (spec derived from typed handlers, validated against it at runtime or compile time) removes most of the lying-docs failure mode, and contract tests plus breaking-change diffs in CI catch drift whichever way the artifacts flow. Direction matters less than the invariant: one artifact is the source of truth, and the pipeline fails when the other side disagrees.

Ecosystem integration

Per-language notes map every doc flavor onto the frameworks documented in this bundle:

  • API documentation in Java — springdoc, SmallRye/Micronaut OpenAPI, Springwolf, OpenAPI Generator across Spring, Quarkus and Micronaut; annotation-driven code-first is the JVM's home turf.
  • API documentation in Go — oapi-codegen, swag, huma/fuego, buf and grpc-gateway across net/http/chi, Gin, Echo and Fiber; codegen culture makes contract-first unusually natural.
  • API documentation in Rust — utoipa and friends, tonic-build, async-graphql SDL export across Axum, Actix Web and Rocket; derive-macro code-first dominates, contract-first usually means gRPC.

Related