API reference pages stay accurate when owned facts live in a manifest and models draft only narrative fields. A fluent summary can still invent a status code, a header, or a path the service never shipped. This workflow stores those facts in a checked-in manifest and limits generation to fields marked draftable. A local checker then rejects the page when an owned value drifts or a draft cites an unregistered token.
Why a full rewrite hides contract drift
Reference documentation is a client contract, so one wrong token can send an integration down the wrong path. Models often complete a thin prompt with a neighboring status, such as 422 where the handler returns 400. Reviewers tend to judge sentence flow and miss a changed path when the surrounding explanation still sounds familiar. A full-page draft therefore conceals the few claims that must match the running service on every release.
Split the page before inviting a model
A narrower split keeps model help available while protecting the tokens a client will copy into code. The manifest holds the method, the path, the status list, the required headers, and a fixture digest. The model may write the summary, the usage note, and recovery text that cites only registered codes. Owned regions stay byte-stable during drafting, even when a rewrite would make the surrounding page smoother.
Ownership rules for each field class
Humans, or an extractor pointed at the OpenAPI document, must own every value a caller might copy. That set covers the method, the path, required headers, documented statuses, and the hashed example body. The model may explain those values, but each concrete token in the draft must already exist in the manifest. The OpenAPI Specification, published by the OpenAPI Initiative, is the upstream format for those owned values.
Read the specification maintained by the OpenAPI Initiative before you choose which fields belong in the manifest. Link that file revision in the manifest so a later reader can open the same contract the page claims to follow. Do not cite a blog summary of the specification when the service file is available in the repository. The page can name the specification, but the owned tokens still come from the pinned service file.
Primary source for the contract format: https://spec.openapis.org/oas/latest.html
Draftable text may discuss retries only when the manifest already records that behavior as an owned note. If the behavior is missing, the model must omit it instead of inventing a timeout or a default. The human owner adds the missing fact to the manifest, then asks for a new narrative pass. That order prevents a polished paragraph from becoming the only place a contract detail is recorded.
| Field class | Examples | Writer | Checker rule |
|---|---|---|---|
| Owned fact | Method, path, statuses, required headers, fixture hash | Human or contract extractor | Exact match against the manifest |
| Draftable prose | Summary, when to use, recovery notes | Model, then human edit | Cite only tokens listed as allowed |
| Unregistered claim | Quotas, SLAs, legal terms, unlisted errors | Nobody until a human registers it | Fail the check if the token appears |
Steps that keep ownership visible
1. Pin the contract before prose exists
Export the operation from the service OpenAPI file and record the file path plus the git revision. Do not ask a model to recall that pin from an older page or from training memory. The pin gives the reviewer a concrete file to open when a status later looks suspicious. If the revision is unknown, stop and locate the contract instead of drafting around the gap.
2. Freeze example bodies as fixtures
Store request and response examples as files, then record each SHA-256 digest inside the ownership manifest. The page may display those examples, but an owned block must not paraphrase JSON keys or reorder fields. A later prose edit therefore cannot rename a payload field without failing the fixture hash comparison. Treat a hash mismatch as a contract change, not as a wording problem for the model to smooth over.
3. Mark regions in the Markdown page
Wrap each owned value and each draftable section with HTML comments that name the field exactly. Keep owned regions to a single line, or to a fenced block copied from the fixture file. Leave draftable regions empty, or fill them with a placeholder, before the model is invited to write. Visible markers make the ownership boundary reviewable in an ordinary diff, without a separate documentation tool.
4. Draft only the empty narrative fields
Prompt the model with the manifest and ask it to fill draftable regions without new codes, paths, or headers. Refuse any prompt that requests a full-page rewrite, because that request erases the ownership boundary entirely. Keep the owned regions byte-stable while the narrative is generated, and do not paste the model output over them. If the model returns a new status or header, discard the draft and tighten the prompt before trying again.
5. Run the checker before human review
The script compares owned regions with the manifest and scans draftable regions for unregistered contract tokens. A failing check blocks the documentation pull request until the manifest or the prose is corrected. The reviewer then reads the narrative for clarity, rather than hunting for a hidden contract change. A passing check is a gate, not evidence that the service implements every sentence in the prose.
6. Record a human review, not a generated sign-off
Add the reviewer name and the review date only after a person has read the draft. The model must not fill that line, because a generated sign-off is not a completed review. If the contract changes, return to the pin step rather than patching the narrative in place. Stale prose wrapped around a new path is a documentation defect, even when the sentences remain grammatical.
Manifest and page skeleton
The manifest below is a minimal contract for one operation, written so a reviewer can audit it quickly. Replace the fixture path and the allowed tokens with values taken from your own OpenAPI document. Do not copy these invoice fields into a real page unless that operation actually exists in your service. The sample is illustrative, and the checker does not contact a network or a model provider.
{
"source_pin": "openapi/invoices.yaml@<git-revision>",
"method": "POST",
"path": "/v1/invoices",
"statuses": [201, 400, 401, 409],
"required_headers": ["Authorization", "Idempotency-Key"],
"example_fixture": "fixtures/create-invoice.json",
"draftable": ["summary", "when_to_use", "recovery_notes"],
"allowed_tokens": [
"POST",
"/v1/invoices",
"201",
"400",
"401",
"409",
"Authorization",
"Idempotency-Key"
]
}
{
"currency": "USD",
"amount_cents": 2500
}
<!-- owned:method -->
POST
<!-- /owned:method -->
<!-- owned:path -->
/v1/invoices
<!-- /owned:path -->
<!-- owned:statuses -->
201 400 401 409
<!-- /owned:statuses -->
<!-- owned:required_headers -->
Authorization Idempotency-Key
<!-- /owned:required_headers -->
<!-- owned:example_sha256 -->
PASTE_FIXTURE_SHA256
<!-- /owned:example_sha256 -->
<!-- draftable:summary -->
Creates an invoice with POST /v1/invoices. A successful call returns 201.
<!-- /draftable:summary -->
<!-- draftable:when_to_use -->
Use this operation when a client must send Authorization and Idempotency-Key.
<!-- /draftable:when_to_use -->
<!-- draftable:recovery_notes -->
On 400, fix the body. On 401, refresh Authorization. On 409, reuse the same Idempotency-Key.
<!-- /draftable:recovery_notes -->
Reviewed by: <name> on YYYY-MM-DD
The checker is a local workflow example. It uses the Python standard library, and it does not call a model.
#!/usr/bin/env python3
"""Compare a reference page with an ownership manifest."""
import hashlib
import json
import re
import sys
from pathlib import Path
REGION = re.compile(
r"<!--\s*(owned|draftable):([a-z0-9_]+)\s*-->(.*?)<!--\s*/\1:\2\s*-->",
re.DOTALL,
)
TOKEN = re.compile(
r"\b(?:GET|POST|PUT|PATCH|DELETE)\b"
r"|/(?:v\d+)(?:/[A-Za-z0-9_{}.-]+)+"
r"|\b\d{3}\b"
r"|\b[A-Z][A-Za-z0-9]*(?:-[A-Za-z0-9]+)+\b"
)
def regions(text: str) -> dict:
found = {"owned": {}, "draftable": {}}
for kind, name, body in REGION.findall(text):
found[kind][name] = body.strip()
return found
def sha256(path: Path) -> str:
return hashlib.sha256(path.read_bytes()).hexdigest()
def main() -> int:
if len(sys.argv) != 3:
print("usage: check_reference_ownership.py manifest.json page.md")
return 2
manifest = json.loads(Path(sys.argv[1]).read_text(encoding="utf-8"))
page = Path(sys.argv[2]).read_text(encoding="utf-8")
parsed = regions(page)
errors = []
fixture = Path(manifest["example_fixture"])
if not fixture.is_file():
print(f"missing fixture {fixture}")
return 2
expected_hash = sha256(fixture)
owned_expect = {
"method": manifest["method"],
"path": manifest["path"],
"statuses": " ".join(str(code) for code in manifest["statuses"]),
"required_headers": " ".join(manifest["required_headers"]),
"example_sha256": expected_hash,
}
for key, value in owned_expect.items():
if parsed["owned"].get(key) != value:
errors.append(f"owned field {key} does not match manifest")
if key == "example_sha256":
errors.append(f"expected example_sha256 {expected_hash}")
extra_owned = set(parsed["owned"]) - set(owned_expect)
if extra_owned:
errors.append(
"unexpected owned fields: " + ", ".join(sorted(extra_owned))
)
if set(parsed["draftable"]) != set(manifest["draftable"]):
errors.append("draftable regions do not match manifest")
allowed = set(manifest["allowed_tokens"])
for name, body in parsed["draftable"].items():
for token in TOKEN.findall(body):
if token not in allowed:
errors.append(f"unregistered token {token} in {name}")
if errors:
print("\n".join(errors))
return 1
print("ok")
return 0
if __name__ == "__main__":
raise SystemExit(main())
Run the checker
Run the checker from the repository root after you place the script, the manifest, and the page. A zero exit status means the owned fields match and the draftable text cited only allowed tokens. A nonzero status prints one line per failure, which you can attach to the pull request comment. Re-run the same command in continuous integration so a laptop pass and a server pass share one definition.
mkdir -p fixtures docs/api
python3 -c "import hashlib, pathlib; p = pathlib.Path('fixtures/create-invoice.json'); print(hashlib.sha256(p.read_bytes()).hexdigest())"
python3 check_reference_ownership.py manifest.json docs/api/create-invoice.md
If a recovery note adds 422, the checker is written to exit nonzero and name that unregistered token. If the method line changes from POST to PUT, the owned-field comparison is written to fail before review begins. Paste the printed fixture digest into the owned hash line, then run the command again until it prints ok. Keep that failure drill in the pull request template so new authors see a rejected token before they trust a green run.
Where a free model and a free server fit
Disclosure: This article was prepared as part of MonkeyCode's product outreach.
MonkeyCode's free model access and free server option can carry the draft step and the checker step when you already have them. Use the model to fill empty narrative fields from the manifest, and refuse prompts that ask it to rewrite owned lines. The free server option is a place you may use to run the same checker command, if that option is available on your account. This article does not name a model, a quota, a hardware shape, or a duration, because those details were not supplied.
If your account shows different availability, follow the product surface you can see rather than this description. The workflow remains useful without MonkeyCode, since any editor can fill draftable regions and any host can run Python. Readers who already have that access can point the draft step at narrative fields and run the checker before review.
Limitations of the checker
The token scan catches methods, versioned paths, three-digit statuses, and hyphenated header names, and it misses facts written only as words. A sentence that says the call always succeeds can pass, because that claim contains none of the watched tokens. The hash check fails when line endings change, so normalize fixtures before you record the digest. The script does not prove that the OpenAPI file matches production, and it does not execute the documented call.
Semantic drift inside allowed tokens still needs a human reader, because the checker cannot judge whether the advice is wise. Quoted examples inside draftable prose can trip the scanner, so keep raw JSON inside the owned fixture instead. HTML comments are easy to break during a visual edit, and a missing end marker will hide a region from the check. Treat a checker crash as a failed gate, and do not merge a page the script could not parse.
Order in the status line is significant, because the checker compares that line as a single string. Changing 201 400 401 409 to another sequence is a mismatch, even when the same codes remain present. The sample scanner flags versioned paths such as /v1/invoices, and it will not notice an unversioned path written in prose. An empty draftable region can still pass, so the human review must confirm that the narrative actually explains the owned facts.
Who should not use this approach
Skip this workflow when the page has no enumerable contract, such as an opinion essay or a marketing overview. Skip it for legal terms, incident narratives, and security advisories, where a regex gate would create false confidence. Skip it if nobody will maintain the manifest, because an abandoned pin is worse than a clearly human-written page. Teams that already generate the full reference from OpenAPI should keep that generator, and use this split only for the hand-written notes beside it.
What to keep after the first page
Start with one operation, one fixture, and one pull request, then extend the manifest only after the checker has failed usefully. The useful failure is an unregistered status or a drifted path, not a complaint about sentence rhythm. Owned facts should remain boring, copyable, and traceable to a file revision a teammate can open. Narrative help is optional, while the manifest and the human review are the parts the page cannot lose.
Top comments (0)