Fern
Fern treats an API definition as the one artifact everything else derives from: SDKs in
multiple languages, a CLI, and a documentation site are all generated outputs of the same
source, not three things maintained by hand and kept in sync by discipline. The definition
lives in a fern/ directory alongside fern.config.json (workspace identity, not the API
shape itself) and generators.yml (which generators run, against which output targets and
versions).
The format choice that decides everything downstream
An API can be defined two ways: import an existing OpenAPI spec, or author it directly in
Fern's own Intermediate Representation (the "Fern Definition," YAML files under fern/apis/).
These are not equivalent inputs producing identical output. OpenAPI import is lower-effort when
a spec already exists, but Fern's IR carries constructs OpenAPI expresses awkwardly —
discriminated unions, more precise auth schemes, examples tied to types rather than only to
operations — so a spec leaning on OpenAPI extensions to work around those gaps generates
rougher SDKs than a Fern Definition authored natively. A team that hand-wrote an OpenAPI spec
plus a separate docs site (Redoc, Docusaurus) and now wants SDKs is choosing between
reformatting that spec for Fern or re-authoring in Fern's IR; the second costs more upfront and
produces better-shaped generated code.
Generated code is not a starting point to edit
SDK output is meant to be regenerated on every definition change, not patched by hand — a
hand-edit is silently overwritten by the next fern generate, with no merge step that
preserves it. Fern's answer to "I need custom logic in the generated client" is explicit
extension points (custom code injected via configured hooks), a different mental model from a
scaffolded project meant to be customized in place. Docs content under fern/docs.yml and
referenced markdown, by contrast, is hand-authored and never regenerates — the generated/authored
split is per-directory, not uniform, and getting it backwards either loses edits silently or
blocks the definition from updating cleanly.
Versioning is per-language, not per-repo
Each SDK generator target (TypeScript, Python, Go, Java, Ruby, ...) has its own version and
release cadence in generators.yml, independent of the API definition's own version and of
each other — a breaking API change does not imply every language SDK bumps together, and
generator versions themselves receive updates independent of the API surface. Treat "the SDK
version" as a per-language question, not a single number.
What to verify rather than recall
Exact generators.yml and docs.yml keys, which OpenAPI extensions Fern currently reads,
supported generator languages and their individual versioning schemes, and CLI command syntax
all move across releases. Confirm these against the mirrored corpus under references/vendor/
rather than asserting a remembered value.