Gravity is now available.

See pricing
Gravity

Whitepaper · For engineering leaders and platform teams

Reference docs as a build output

Why API documentation falls behind the code, and how to keep it in step with the pipeline you already run.

Abstract

Reference docs drift for a structural reason: the code and the page that describes it change in different places, at different times, by different people. This paper describes the mechanism, offers a small model to estimate it with your own numbers, and sets out an approach where the reference is built from the source, every change is checked in the pull request that causes it, and people still decide what gets published.

GravityAbout 10 minutesUpdated October 2026

The short version

  • Reference docs drift because the code and the page change in different places, at different times. More care does not fix a structural gap.
  • Build the reference from the source, and check every pull request for docs it would make wrong.
  • Generated updates arrive as change requests. Nothing is published until a person approves it.
  • Coding agents read the same reviewed docs over MCP, with the permissions of the person who connected them.

Every engineering team that publishes an API knows the moment: a customer quotes the docs back at you, and the docs are wrong. Nobody lied. The code moved, and the page did not move with it.

This paper is for the people who own that problem: engineering leaders, platform teams and the engineers who end up answering the support ticket. It explains why the gap opens, gives you a way to size it for your own team, and describes how Gravity closes it without taking the final word away from your people.

Why reference docs drift

A fact in your reference has two homes. One is the code that makes it true: a handler, a schema, a constant. The other is the sentence that states it. The first changes in a pull request, reviewed by engineers, merged and deployed by the pipeline. The second changes when somebody remembers.

Drift is not a discipline problem. It is what happens when one fact lives in two places that change on different schedules.

Three things make it worse as a team grows. Changes get smaller and more frequent, so each one feels too minor to document. The person who knows the page is rarely the person changing the code. And nothing fails when the page is wrong: the build is green, the tests pass, and the first signal is a reader.

The cost lands far from its cause. It shows up as integration bugs on the customer’s side, support tickets, and engineers pulled away to explain behaviour the docs should have explained. It also erodes trust: once readers find one wrong fact, they stop believing the rest and go and read the code, or ask.

Set these to your own team. The model only does the arithmetic.

8

Merged changes that alter something the reference states.

50%

Share of those where someone also edits the page.

10%

Share of wrong facts someone spots and corrects later.

Wrong facts after 12 months

29

Where it levels off

≈ 40

Merged without a flag, with the check

0

02040Today6 mo12 mo18 mo24 mo
  • Kept by hand
  • Checked in the pull request that changes it
Figure. A model of drift with your own numbers. Each month some changes alter a documented fact; some are documented in the same change; some wrong facts are noticed and fixed later. The curve rises until fixes keep up with new drift. It is arithmetic on your inputs, not a measurement.

Try it with your own team. Two things stand out. The level where it settles depends far more on how much is documented in the same change than on how hard people work to fix pages later. And the only way to reach zero is to never let the change and the page come apart.

Docs as a build output

You would never hand-edit a compiled binary to match a change in the source. The reference deserves the same treatment: it should be built from the code, every time the code changes, by the same pipeline.

Gravity reads the OpenAPI documents in your repository and writes the reference from them: every operation, with its parameters, request bodies, responses and types. Every block it writes remembers the operation it came from. When a later change touches that operation, Gravity knows exactly which blocks to refresh, and which ones to leave alone.

A pull request that…

api/openapi.yaml

- required: [amount]

+ required: [amount, reason]

Docs site, by binding

3 of 6 blocks reached

  • API › Create a refund

    Request body

    openapi.yaml · POST /v1/refunds

    Updated, marked breaking
  • API › Create a refund

    “When to refund in part”

    Written by a person

    Never touched
  • API › Get a refund

    Response fields

    openapi.yaml · GET /v1/refunds/{id}

    Not reached
  • API › List refunds

    Query parameters

    openapi.yaml · GET /v1/refunds

    Not reached
  • Guides › Refunds

    “Refunds within 60 days”

    Claim about src/refunds/**

    Checked against the code
  • Changelog › Unreleased

    Entries

    Commits and the OpenAPI diff

    Entry drafted

A newly required field is a breaking change, so it is called out in the change request and the changelog.

Figure. Bindings at work. Choose a change: only the blocks bound to what it touched are reached. Breaking changes are called out, and a change that touches no operation does not rebuild the reference at all.

The reference is not the only output. The same run can draft guides that explain the change, add entries to the changelog from your commits and release tags, and record durable facts in Nucleus, Gravity’s memory. Each of these is a pass. You choose which ones a repository runs, and each can be tried locally with gravity preview before anything is written.

Drift as a check in CI

If the docs are a build output, a pull request that makes them wrong should be visible in the pull request. Gravity runs as one step in the CI you already have. On a pull request it checks; on a push to the default branch it proposes the updates; on a release it writes the changelog.

.github/workflows/gravity.yml

name: Gravity
on:
  push:
    branches: [main]
  pull_request:
  release:
    types: [published]
  workflow_dispatch:
permissions:
  contents: read
  pull-requests: write
jobs:
  gravity:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - uses: Grupo-Impulso-Digital/gravity-cli/ci/github@v1
        with:
          token: ${{ secrets.GRAVITY_REPO_TOKEN || secrets.GRAVITY_TOKEN }}

gravity init --ci github writes this file and can set the secret for you.

Figure. The CI file, as gravity init writes it. Pick your CI to see the exact file; the token is the repository token init stored as a secret.

The check looks for four kinds of finding. Drift: the reference no longer matches the code. Claims: a guide states something the changed code contradicts. Coverage: an operation has no page. Verbatim: a document you locked to the repository disagrees with the code.

Every finding is reported on the pull request. Which ones block the merge is your decision, written in the repository’s .gravity.yaml so it is reviewed like any other change to your pipeline.

What blocks a merge

.gravity.yaml

passes:
  - name: pr-check
    kind: check
    triggers: [pr]
    options:
      failOn: [drift, claims, verbatim]

The same thing for one run: gravity check --fail-on drift,claims,verbatim

Pull request #1287 · Checks

Gravity · 2 findings block this merge

Exits with 1 · 3 findings reported in the pull request

  • POST /v1/refunds now requires reason

    API › Create a refund still lists it as optional.

    Drift · blocks
  • Guides › Refunds says “within 60 days”

    src/refunds/policy.ts allows 30 days.

    Claims · blocks
  • GET /v1/refunds/{id}/events has no page

    Added in this pull request.

    Coverage · noted

The check also lists what each pass would write. Once the change merges, the push to the default branch proposes those updates for review.

Figure. Choosing what blocks a merge. The same three findings are reported either way; the setting decides which ones fail the check.

A good way in is to start with the check reporting only, read what it finds on a few real pull requests, and then make drift blocking. If your guides are detailed, claims are a natural next step.

People still decide

Generating the reference does not mean publishing it unread. Everything a pass writes arrives as a change request in the same queue as edits written by hand. Reviewers see the difference block by block, can ask for changes, and approve. Only then does it go live.

The words around the facts stay yours. Gravity tracks who owns each block, and the rule is simple: a pass may refresh what it generated, never what a person wrote.

The push: reason becomes required

API › Refunds

Create a refund

Generated, reworded by a person

Gives money back on a charge, in full or in part.

POST /v1/refunds

Generated
  • amountinteger · required
  • chargestring · required
  • reasonstring · optional
Written by a person

Refund in part when the customer keeps some of the order, and explain it to them in your own words.

The published page, before the change merges.

Live
Figure. The same page before and after a push. Each block reacts by its owner. Choose “Use pipeline version” to take the generated wording for a block a person had reworded.

Some teams later let a well-proven pass publish without review, for example the changelog of an internal service. That is a choice per pass, and it only takes effect once someone with write access to the target space has allowed it.

Agents read the same source

Your engineers are no longer the only readers. Coding agents write a growing share of integration code, and they are only as accurate as what they read. If they read a stale wiki or guess from old code, they reproduce the drift at machine speed.

Gravity serves the same reviewed docs, search and memory to agents over MCP. An agent works as the person who connected it, so it reads exactly what that person may read: published pages, and internal spaces only where they have access.

Your agent’s MCP settings

{
  "mcpServers": {
    "gravity": { "url": "https://docs.acme.com/api/mcp" }
  }
}

The first call opens a sign-in in the browser. You approve the agent once; you can see and revoke that grant at any time, and every call it makes is recorded.

Add a refund button to the order page. Use our API.

AI

POST /v1/refunds needs amount, charge and reason. I’ll add a reason picker to the dialog and send it with the request.

Open a call to see what it returned.

Figure. A coding agent working from your docs. It recalls facts from memory, searches the reviewed pages and writes code that sends the newly required field. Open a call to see what it returned.

A plan to adopt it

Adoption does not need a migration project. It starts with one repository and one engineer, and widens one stage at a time, each stage useful on its own. The plan below is the order we recommend; tick steps as you go, or copy it into your own tracker.

0 of 15 done

  1. One repository

    0 of 4

    See what Gravity would write, before anything is published.

  2. In the pipeline

    0 of 4

    Every pull request is checked, every merge proposes the update.

  3. The whole product

    0 of 4

    Every repository behind the product feeds the same docs.

  4. Across the organisation

    0 of 3

    Review stays with the people who own each space.

Figure. A rollout plan you can keep. Ticks stay in this browser only.

Questions teams ask

The CLI runs where your code already is: on an engineer’s machine or as a step in your CI. It reads the repository, works out what changed since its last run, and sends Gravity what it needs to write the docs.

The reference itself is built from your OpenAPI documents, without AI. Passes that write prose, such as guides and the changelog, send the relevant changes through Gravity’s AI gateway.

Further reading

How the reference is generated, with a page you can explore: Generation. How review works for generated and written changes alike: Review and publish. Cited answers for readers and memory for agents: AI and search.

Point it at your repository.

Generate your reference from your OpenAPI documents and catch the first drift in a pull request.