Spec Writing
Parent: Code Plan Writing · Published reference · snapshot 2026-09-08 · skill technical-writing-craft/references/spec-writing.md
↓ Facts as markdownall context files
An engineering spec is a contract. It says WHAT a system, component, endpoint, or message must do — independent of HOW it is implemented.
These notes link each claim to its source. Some citations identify archived research reports that are no longer hosted on this site. A published reference means the content is available; it does not certify independent review or accuracy.Read the editorial policy and follow the sources before relying on a claim.
Overview
- An engineering spec is a contract. It says WHAT a system, component, endpoint, or message must do - independent of HOW it is implemented. [archived source: https://llms-explorer.com/sources/mdb-context-hub/spec-writing/#overview]
- Three forms dominate modern practice: [archived source: https://llms-explorer.com/sources/mdb-context-hub/spec-writing/#overview]
- API specs - machine-readable contracts for HTTP (OpenAPI 3.1) and event-driven (AsyncAPI 3.0) interfaces [archived source: https://llms-explorer.com/sources/mdb-context-hub/spec-writing/#overview]
- Behavior specs - executable narratives (Gherkin / Given-When-Then) [archived source: https://llms-explorer.com/sources/mdb-context-hub/spec-writing/#overview]
- Data specs - schema contracts (JSON Schema, Avro, Protobuf) [archived source: https://llms-explorer.com/sources/mdb-context-hub/spec-writing/#overview]
- Joel Spolsky's 2000 series "Painless Functional Specifications" introduced the discipline: a functional spec describes how a product works from the user's perspective; a technical spec describes internal implementation. [archived source: https://llms-explorer.com/sources/mdb-context-hub/spec-writing/#overview]
1. Spec says WHAT, not HOW
- A spec that says "internally we'll use a Redis cache with 5-minute TTL" has leaked implementation. [archived source: https://llms-explorer.com/sources/mdb-context-hub/spec-writing/#1-spec-says-what-not-how]
- Litmus test: If you could swap out the implementation entirely and the spec still holds, the spec is at the right level. [archived source: https://llms-explorer.com/sources/mdb-context-hub/spec-writing/#1-spec-says-what-not-how]
2. Contract-first design
- Contract-first development: agree on the contract before writing code. The OpenAPI / AsyncAPI document is the source of truth. [archived source: https://llms-explorer.com/sources/mdb-context-hub/spec-writing/#2-contract-first-design]
3. OpenAPI 3.1 for HTTP APIs
- Key top-level fields: [archived source: https://llms-explorer.com/sources/mdb-context-hub/spec-writing/#3-openapi-31-for-http-apis]
- openapi - version (3.1.0) [archived source: https://llms-explorer.com/sources/mdb-context-hub/spec-writing/#3-openapi-31-for-http-apis]
- info - name, version, description [archived source: https://llms-explorer.com/sources/mdb-context-hub/spec-writing/#3-openapi-31-for-http-apis]
- servers - base URLs per environment [archived source: https://llms-explorer.com/sources/mdb-context-hub/spec-writing/#3-openapi-31-for-http-apis]
- paths - endpoints, verbs, parameters, request/response shapes [archived source: https://llms-explorer.com/sources/mdb-context-hub/spec-writing/#3-openapi-31-for-http-apis]
- components - reusable schemas, parameters, responses, security schemes [archived source: https://llms-explorer.com/sources/mdb-context-hub/spec-writing/#3-openapi-31-for-http-apis]
- YAML is preferred over JSON for human authoring. [archived source: https://llms-explorer.com/sources/mdb-context-hub/spec-writing/#3-openapi-31-for-http-apis]
4. AsyncAPI 3.0 for events
- Key concepts in v3.0: [archived source: https://llms-explorer.com/sources/mdb-context-hub/spec-writing/#4-asyncapi-30-for-events]
- Channels are addressable destinations (Kafka topic, AMQP queue) - decoupled from operations [archived source: https://llms-explorer.com/sources/mdb-context-hub/spec-writing/#4-asyncapi-30-for-events]
- Operations describe what an application does on a channel using action: send or action: receive [archived source: https://llms-explorer.com/sources/mdb-context-hub/spec-writing/#4-asyncapi-30-for-events]
- Messages are defined once and referenced from channels and operations [archived source: https://llms-explorer.com/sources/mdb-context-hub/spec-writing/#4-asyncapi-30-for-events]
5. Gherkin / Given-When-Then for behavior specs
- Use Gherkin for: business-logic behavior (pricing rules, eligibility checks, state transitions), workflows spanning multiple systems. [archived source: https://llms-explorer.com/sources/mdb-context-hub/spec-writing/#5-gherkin-given-when-then-for-behavior-specs]
- Avoid Gherkin for: API shape (use OpenAPI), data format (use JSON Schema). [archived source: https://llms-explorer.com/sources/mdb-context-hub/spec-writing/#5-gherkin-given-when-then-for-behavior-specs]
6. Spec-as-contract mindset
- A spec is a promise. Once published: [archived source: https://llms-explorer.com/sources/mdb-context-hub/spec-writing/#6-spec-as-contract-mindset]
- Versioning - non-breaking changes bump minor; breaking changes bump major. [archived source: https://llms-explorer.com/sources/mdb-context-hub/spec-writing/#6-spec-as-contract-mindset]
- Backward compatibility - additive changes (new optional field) are safe; removals, renames, type changes are breaking. [archived source: https://llms-explorer.com/sources/mdb-context-hub/spec-writing/#6-spec-as-contract-mindset]
- Deprecation policy - published timeline (e.g., 12 months notice for major version sunset). [archived source: https://llms-explorer.com/sources/mdb-context-hub/spec-writing/#6-spec-as-contract-mindset]
7. Versioning conventions
- URL versioning (/v1/users) - explicit, cacheable [archived source: https://llms-explorer.com/sources/mdb-context-hub/spec-writing/#7-versioning-conventions]
- Date-pinned versioning (Stripe: Stripe-Version: 2024-04-10) - fine-grained [archived source: https://llms-explorer.com/sources/mdb-context-hub/spec-writing/#7-versioning-conventions]
- Version in the topic name (orders.v1, orders.v2) [archived source: https://llms-explorer.com/sources/mdb-context-hub/spec-writing/#7-versioning-conventions]
- Schema registry with explicit compatibility modes (BACKWARD, FORWARD, FULL) [archived source: https://llms-explorer.com/sources/mdb-context-hub/spec-writing/#7-versioning-conventions]
8. Examples are part of the spec
- OpenAPI's examples field and Gherkin's Examples: table are not decorative - they are the spec. CI tools validate that examples conform to the declared schema. [archived source: https://llms-explorer.com/sources/mdb-context-hub/spec-writing/#8-examples-are-part-of-the-spec]
Anti-Patterns
- Implementation leakage - spec mentions Redis, Postgres, language choice. [archived source: https://llms-explorer.com/sources/mdb-context-hub/spec-writing/#anti-patterns]
- No examples - schemas without concrete payloads. [archived source: https://llms-explorer.com/sources/mdb-context-hub/spec-writing/#anti-patterns]
- Hand-waved errors - "returns an error on failure" without listing codes. [archived source: https://llms-explorer.com/sources/mdb-context-hub/spec-writing/#anti-patterns]
- Missing versioning policy. [archived source: https://llms-explorer.com/sources/mdb-context-hub/spec-writing/#anti-patterns]
- Spec-test drift - spec says one thing, implementation does another, no CI check. [archived source: https://llms-explorer.com/sources/mdb-context-hub/spec-writing/#anti-patterns]
- Optional everything - every field is optional, every status code is default. [archived source: https://llms-explorer.com/sources/mdb-context-hub/spec-writing/#anti-patterns]
References
- Joel Spolsky, "Painless Functional Specifications, Part 2" [archived source: https://llms-explorer.com/sources/mdb-context-hub/spec-writing/#references]
- OpenAPI Specification v3.1: https://spec.openapis.org/oas/v3.1.0 [archived source: https://llms-explorer.com/sources/mdb-context-hub/spec-writing/#references]
- AsyncAPI 3.0.0 Specification: https://www.asyncapi.com/docs/reference/specification/v3.0.0 [archived source: https://llms-explorer.com/sources/mdb-context-hub/spec-writing/#references]
- Cucumber, "Gherkin Reference": https://cucumber.io/docs/gherkin/reference/ [archived source: https://llms-explorer.com/sources/mdb-context-hub/spec-writing/#references]
Where this helps
- Writing an HTTP API contract before implementation begins, using OpenAPI 3.1 so client and server teams can build against the same source of truth in parallel. [source] — AI-suggested, synthesized from this pack's existing facts — not extracted from a source document.
- Specifying an event-driven interface (a Kafka topic or AMQP queue's message shape) with AsyncAPI 3.0 instead of describing it only in prose. [source] — AI-suggested, synthesized from this pack's existing facts — not extracted from a source document.
- Capturing business-logic behavior (pricing rules, eligibility checks, state transitions) as Given-When-Then scenarios so engineering and product can verify the same acceptance criteria. [source] — AI-suggested, synthesized from this pack's existing facts — not extracted from a source document.
- Deciding how to version a published spec — URL versioning, date-pinned versioning, or a version embedded in a topic name — before the first breaking change forces the question under pressure. [source] — AI-suggested, synthesized from this pack's existing facts — not extracted from a source document.
How to apply this
- Before writing a line of the spec, apply the "swap the implementation and see if it still holds" litmus test to keep implementation details (a specific cache, a specific database) out of the contract. [source] — AI-suggested, synthesized from this pack's existing facts — not extracted from a source document.
- Treat contract-first as the default: agree the OpenAPI/AsyncAPI document is the source of truth, and build the implementation from it rather than reverse-documenting an already-built system. [source] — AI-suggested, synthesized from this pack's existing facts — not extracted from a source document.
- Use Gherkin for cross-system behavioral rules and reserve OpenAPI/JSON Schema for API shape and data format — don't try to make one tool do both jobs. [source] — AI-suggested, synthesized from this pack's existing facts — not extracted from a source document.
- Include concrete example payloads (OpenAPI's examples field, Gherkin's Examples: table) as load-bearing parts of the spec, and validate them in CI so the examples never drift from the schema. [source] — AI-suggested, synthesized from this pack's existing facts — not extracted from a source document.
Common mistakes
- Letting implementation details leak into the spec — naming a specific cache, database, or language choice — instead of stating only the contract's WHAT. [source] — AI-suggested, synthesized from this pack's existing facts — not extracted from a source document.
- Publishing a spec with no concrete example payloads, leaving schemas that are technically complete but impossible to sanity-check by eye. [source] — AI-suggested, synthesized from this pack's existing facts — not extracted from a source document.
- Hand-waving error behavior ("returns an error on failure") instead of enumerating the actual error codes and their meanings. [source] — AI-suggested, synthesized from this pack's existing facts — not extracted from a source document.
- Bumping a spec's version incorrectly — treating a breaking change (a removed or renamed field, a changed type) as a minor version bump instead of a major one. [source] — AI-suggested, synthesized from this pack's existing facts — not extracted from a source document.
Limitations
- A spec is only as trustworthy as its examples; if CI doesn't validate that examples conform to the declared schema, the spec and the running system can silently drift apart. [source] — AI-suggested, synthesized from this pack's existing facts — not extracted from a source document.
- Gherkin captures behavior well but says nothing about API shape or data format — a team that tries to spec an HTTP interface entirely in Given-When-Then loses the machine-readable contract OpenAPI provides. [source] — AI-suggested, synthesized from this pack's existing facts — not extracted from a source document.
- Once a spec is published, it becomes a promise to every consumer; even a well-intentioned cleanup of a poorly-designed field now requires a version bump and a migration path rather than a quiet fix. [source] — AI-suggested, synthesized from this pack's existing facts — not extracted from a source document.
- A spec that correctly says WHAT and not HOW can still be wrong or incomplete — the discipline prevents implementation leakage, but it doesn't by itself guarantee the WHAT was fully thought through. [source] — AI-suggested, synthesized from this pack's existing facts — not extracted from a source document.
Where this helps
- Writing an HTTP API contract before implementation begins, using OpenAPI 3.1 so client and server teams can build against the same source of truth in parallel. [source] — AI-suggested, synthesized from this pack's existing facts — not extracted from a source document.
- Specifying an event-driven interface (a Kafka topic or AMQP queue's message shape) with AsyncAPI 3.0 instead of describing it only in prose. [source] — AI-suggested, synthesized from this pack's existing facts — not extracted from a source document.
- Capturing business-logic behavior (pricing rules, eligibility checks, state transitions) as Given-When-Then scenarios so engineering and product can verify the same acceptance criteria. [source] — AI-suggested, synthesized from this pack's existing facts — not extracted from a source document.
- Deciding how to version a published spec — URL versioning, date-pinned versioning, or a version embedded in a topic name — before the first breaking change forces the question under pressure. [source] — AI-suggested, synthesized from this pack's existing facts — not extracted from a source document.
How to apply this
- Before writing a line of the spec, apply the "swap the implementation and see if it still holds" litmus test to keep implementation details (a specific cache, a specific database) out of the contract. [source] — AI-suggested, synthesized from this pack's existing facts — not extracted from a source document.
- Treat contract-first as the default: agree the OpenAPI/AsyncAPI document is the source of truth, and build the implementation from it rather than reverse-documenting an already-built system. [source] — AI-suggested, synthesized from this pack's existing facts — not extracted from a source document.
- Use Gherkin for cross-system behavioral rules and reserve OpenAPI/JSON Schema for API shape and data format — don't try to make one tool do both jobs. [source] — AI-suggested, synthesized from this pack's existing facts — not extracted from a source document.
- Include concrete example payloads (OpenAPI's examples field, Gherkin's Examples: table) as load-bearing parts of the spec, and validate them in CI so the examples never drift from the schema. [source] — AI-suggested, synthesized from this pack's existing facts — not extracted from a source document.
Common mistakes
- Letting implementation details leak into the spec — naming a specific cache, database, or language choice — instead of stating only the contract's WHAT. [source] — AI-suggested, synthesized from this pack's existing facts — not extracted from a source document.
- Publishing a spec with no concrete example payloads, leaving schemas that are technically complete but impossible to sanity-check by eye. [source] — AI-suggested, synthesized from this pack's existing facts — not extracted from a source document.
- Hand-waving error behavior ("returns an error on failure") instead of enumerating the actual error codes and their meanings. [source] — AI-suggested, synthesized from this pack's existing facts — not extracted from a source document.
- Bumping a spec's version incorrectly — treating a breaking change (a removed or renamed field, a changed type) as a minor version bump instead of a major one. [source] — AI-suggested, synthesized from this pack's existing facts — not extracted from a source document.
Limitations
- A spec is only as trustworthy as its examples; if CI doesn't validate that examples conform to the declared schema, the spec and the running system can silently drift apart. [source] — AI-suggested, synthesized from this pack's existing facts — not extracted from a source document.
- Gherkin captures behavior well but says nothing about API shape or data format — a team that tries to spec an HTTP interface entirely in Given-When-Then loses the machine-readable contract OpenAPI provides. [source] — AI-suggested, synthesized from this pack's existing facts — not extracted from a source document.
- Once a spec is published, it becomes a promise to every consumer; even a well-intentioned cleanup of a poorly-designed field now requires a version bump and a migration path rather than a quiet fix. [source] — AI-suggested, synthesized from this pack's existing facts — not extracted from a source document.
- A spec that correctly says WHAT and not HOW can still be wrong or incomplete — the discipline prevents implementation leakage, but it doesn't by itself guarantee the WHAT was fully thought through. [source] — AI-suggested, synthesized from this pack's existing facts — not extracted from a source document.
Children
- WHAT not HOW (Spolsky) (frontier)
- Contract-first design (frontier)
- OpenAPI 3.1 (frontier)
- AsyncAPI 3.0 (frontier)
- Gherkin / Given-When-Then (frontier)
- Spec-as-contract mindset (frontier)
- Versioning conventions (frontier)
- Spec file vs code doc decision (frontier)
- Examples as part of spec (frontier)
Frontier under this node: AsyncAPI 3.0, Contract-first design, Examples as part of spec, Gherkin / Given-When-Then, OpenAPI 3.1, Spec file vs code doc decision, Spec-as-contract mindset, Versioning conventions, WHAT not HOW (Spolsky)