Skip to content

DOMAIN:CONTENT — TECHNICAL WRITING

OWNER: benjamin, joost
ALSO_USED_BY: jouke, dinand, anna (Spec Writer), antje (TDD Writer)
UPDATED: 2026-03-24


TECHNICAL_WRITING:API_DOCUMENTATION

STANDARD: OpenAPI 3.1 (latest) for REST APIs
STANDARD: AsyncAPI 3.0 for event-driven APIs

RULE: design-first — write OpenAPI spec BEFORE implementation code
RULE: spec file IS the single source of truth — generate reference docs from it
RULE: never hand-write API reference that can be auto-generated from spec

Structure of API Reference

CHECK: every endpoint has summary, description, parameters, request body, responses, examples
CHECK: every parameter has type, required/optional, default value, constraints (min/max/pattern)
CHECK: every response code documented — 200, 201, 400, 401, 403, 404, 409, 422, 429, 500
CHECK: error response schema is consistent across ALL endpoints

REQUIRED SECTIONS (per API docs site):
1. Overview — what the API does, base URL, authentication
2. Authentication — full auth flow with code examples
3. Quick Start — working request in <5 minutes
4. Endpoints Reference — auto-generated from OpenAPI spec
5. Error Reference — all error codes, meanings, fixes
6. Rate Limits — limits per tier, headers, retry strategy
7. Webhooks — event types, payload schemas, verification
8. Changelog — version history with breaking change flags
9. SDKs / Libraries — official clients per language

Error Response Schema

RULE: use consistent error envelope across all endpoints

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Human-readable explanation",
    "details": [
      {
        "field": "email",
        "issue": "Must be a valid email address",
        "value": "not-an-email"
      }
    ],
    "request_id": "req_abc123",
    "doc_url": "https://docs.example.com/errors/VALIDATION_ERROR"
  }
}

ANTI_PATTERN: generic "Something went wrong" with no error code
FIX: every error has a unique code, human message, and doc_url

ANTI_PATTERN: different error shapes per endpoint
FIX: single ErrorResponse schema in OpenAPI components, $ref everywhere

OpenAPI File Organization

RULE: split large specs by resource — one file per tag/resource group
RULE: use $ref to shared components (schemas, parameters, responses)
RULE: directory structure mirrors URL hierarchy

api/
├── openapi.yaml          # root spec, references below
├── paths/
│   ├── users.yaml
│   ├── projects.yaml
│   └── billing.yaml
├── components/
│   ├── schemas/
│   ├── parameters/
│   └── responses/
└── examples/

TOOL: Spectral — lint OpenAPI specs
RUN: npx @stoplight/spectral-cli lint openapi.yaml
TOOL: Redocly CLI — bundle, lint, preview
RUN: npx @redocly/cli lint openapi.yaml
RUN: npx @redocly/cli preview-docs openapi.yaml


TECHNICAL_WRITING:INTEGRATION_GUIDES

RULE: every integration guide follows this structure

INTEGRATION GUIDE TEMPLATE:
1. Prerequisites — what you need before starting
2. Installation — package manager commands (npm, pip, etc.)
3. Authentication — get API key, configure credentials
4. Quick Start — minimal working example (copy-paste-run)
5. Core Concepts — explain the mental model
6. Common Use Cases — 3-5 recipes with full code
7. Error Handling — how to catch and handle errors
8. Testing — how to test the integration (sandbox, mocks)
9. Going to Production — checklist, environment variables
10. Troubleshooting — FAQ, common errors, debug steps

Code Sample Quality

RULE: every code sample MUST compile/run without modification
RULE: every code sample MUST include error handling
RULE: every code sample MUST show imports/requires
RULE: every code sample MUST use realistic (not foo/bar) variable names
RULE: provide samples in ALL languages the SDK supports

ANTI_PATTERN: code snippet that starts mid-function with no context
FIX: show complete, runnable file — from imports to execution

ANTI_PATTERN: happy-path-only examples
FIX: every example shows try/catch or error callback

ANTI_PATTERN: outdated code samples that no longer compile
FIX: CI pipeline runs code samples against latest SDK version
TOOL: Docusaurus code block testing or custom test harness

CHECK: sample uses environment variables for secrets, never hardcoded
CHECK: sample includes comments explaining non-obvious lines
CHECK: sample matches the API version documented on the page


TECHNICAL_WRITING:DEVELOPER_ONBOARDING

README Structure

RULE: every project README follows this structure

README TEMPLATE:
1. Project Name + One-line description
2. Status badges (build, coverage, version)
3. Quick Start (3-5 steps to run locally)
4. Prerequisites (runtime versions, tools)
5. Installation
6. Configuration (environment variables table)
7. Usage (basic examples)
8. Architecture Overview (link to detailed doc)
9. Contributing (link to CONTRIBUTING.md)
10. License

ANTI_PATTERN: README with only "npm install && npm start"
FIX: include prerequisites, configuration, and what to expect

ANTI_PATTERN: README that duplicates the full docs
FIX: README is entry point — link to detailed docs for depth

Architecture Overview Document

RULE: every project has an architecture overview at docs/architecture.md
CHECK: includes system diagram (Mermaid or similar)
CHECK: lists all services/components and their responsibilities
CHECK: documents data flow for primary use cases
CHECK: lists external dependencies and why they were chosen
CHECK: links to relevant ADRs


TECHNICAL_WRITING:DOCS_AS_CODE_TOOLING

Tool Selection Matrix

Criterion Docusaurus Mintlify MkDocs
Cost Free (MIT) $300+/mo Free (BSD)
Framework React/MDX MDX (hosted) Python/Markdown
Versioning Built-in Manual mike plugin
i18n Built-in Limited i18n plugin
Search Meilisearch (FR)/local preferred. Algolia (US) secondary — sovereignty risk. Built-in AI search plugin
Self-hosted Yes No Yes
Maintenance Self-managed Managed Self-managed
AI features None llms.txt, AI search None

NOTE: GE uses MkDocs for wiki brain — all internal docs stay MkDocs
RULE: client projects — recommend Docusaurus (free, full control, React ecosystem)
RULE: if client wants managed docs — Mintlify (best DX, API playground)
RULE: never recommend Nextra — single maintainer, uncertain future

Docs-as-Code Workflow

RULE: docs live in same repo as code
RULE: docs PRs require review just like code PRs
RULE: docs deploy on merge to main (CI/CD)
RULE: broken links = failed build

TOOL: Docusaurus
RUN: npx create-docusaurus@latest docs classic --typescript
TOOL: vale — prose linter
RUN: vale docs/
TOOL: markdownlint
RUN: npx markdownlint-cli docs/**/*.md


TECHNICAL_WRITING:VERSIONING_DOCUMENTATION

RULE: multi-version docs required when API has breaking changes
RULE: latest version is default — older versions clearly labeled
RULE: deprecated versions show banner with migration guide link

Changelog Format

RULE: follow Keep a Changelog format (keepachangelog.com)

## [2.1.0] - 2026-03-24
### Added
- New `/webhooks` endpoint for event subscriptions
### Changed
- `GET /users` now returns pagination metadata in headers
### Deprecated
- `GET /users?page=N` query param — use cursor-based pagination
### Removed
- `DELETE /legacy/endpoint` (deprecated since 1.8.0)
### Fixed
- Rate limit headers now return correct reset timestamp
### Security
- Patched CVE-2026-XXXXX in auth middleware

ANTI_PATTERN: changelog that says "various improvements"
FIX: every entry is specific, actionable, links to docs

ANTI_PATTERN: no migration guide for breaking changes
FIX: every REMOVED/CHANGED entry links to migration instructions


TECHNICAL_WRITING:ARCHITECTURE_DECISION_RECORDS

STANDARD: Michael Nygard ADR format (original, lightweight)

ADR Template

# ADR-NNN: [Short Noun Phrase Title]

STATUS: proposed | accepted | deprecated | superseded by ADR-NNN
DATE: YYYY-MM-DD
DECIDERS: [who participated]

## Context
[Forces at play — technical, business, organizational. Value-neutral facts.]

## Decision
[The decision, in active voice: "We will..."]

## Consequences
[All consequences — positive, negative, neutral. Be honest.]

When to Write an ADR

WHEN_REQUIRED: choosing a framework, language, or major library
WHEN_REQUIRED: changing authentication/authorization approach
WHEN_REQUIRED: choosing between build vs. buy
WHEN_REQUIRED: changing data storage strategy
WHEN_REQUIRED: introducing a new external dependency
WHEN_REQUIRED: changing deployment strategy
WHEN_REQUIRED: any decision that would be hard to reverse

RULE: ADR is 1-2 pages max — link to supplemental docs for depth
RULE: ADRs are immutable once accepted — supersede, don't edit
RULE: store ADRs in docs/adr/ in the project repo
RULE: number sequentially: ADR-001, ADR-002, etc.
RULE: review ADRs monthly — compare decision vs. actual outcome

ANTI_PATTERN: ADR written after implementation as paperwork
FIX: write ADR BEFORE implementation — it's a decision tool, not documentation

ANTI_PATTERN: ADR with no alternatives considered
FIX: list at least 2 alternatives with pros/cons

TOOL: adr-tools CLI
RUN: adr new "Use PostgreSQL for primary data store"
TOOL: Log4brains — static site generator for ADR browsing