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
- API design — the design discipline these artifacts capture; this note is its "contract-first" bullet expanded.
- API documentation in Java — per-framework integration on the JVM.
- API documentation in Go — per-framework integration in Go.
- API documentation in Rust — per-framework integration in Rust.
- Message brokers & event streaming — schema registries and compatibility rules, the payload-level cousin of AsyncAPI.
- Microservice architecture — many services, many contracts: where documentation discipline compounds.