Lesson 04 / 15
API Reference
13 of the reference's 15 claims are bound to signature, and a hand-written reference is left at 0/15 by version six; a generated one is left at 13/15 at the same version, the 2 unrecoverable claims sit on the name surface, and because reference carries no example every one of its stale claims is silent.
Contents
The previous lesson measured three repository documents, and in none of them did the signature share exceed half. The repository’s most crowded signature pile sits elsewhere. The second lesson left the interface reference at 2/15 by version two, and the reason was not the writer’s carelessness: 13 of the fifteen claims hung on the fastest-changing surface.
This lesson takes that type on its own terms. First it establishes how a reference is sectioned — endpoint, parameter, error — then it measures a single question: do these claims have to be hand-written? If not, how many survive, and what exactly is left?
The Reference’s Three Sections
A reference is not written to be read, it is written to be looked up. The reader knows what they are looking for and wants to confirm a single cell. This is why a reference’s layout is not narrative, it is a table; and being a table is not merely a style choice — a table ensures every cell carries a single claim and makes its absence noticeable. The table has three sections.
Endpoint gives the call’s identity: the method, the path, and the variable parts inside the path. Parameter gives every input’s name, where it comes from, its type, whether it is required, and its default. Error lists the ways the call can fail: status code, error code, and the condition it returns under.
# an entry from the invented product's interface reference
# example text, not executed
GET /measurement/{station}/summary
this endpoint is referred to in the reference as "station summary"
station path parameter string required
interval query parameter integer default 60, lower bound 1
format query parameter string "plain" | "table"
200 returns a summary object
404 station-not-found the given station is not defined
422 interval-invalid interval is below the lower bound
Nearly everything written in this entry is the interface’s shape: the path pattern, parameter names, types, defaults, status codes. Only two things are not shape — the name the endpoint is referred to by in the reference, and the error codes’ names. Both sit on the name surface, and shortly they will turn into a result on their own.
The Fields of a Parameter Line
A parameter line has to say six things, and missing one sends the reader to the source:
name, location (path, query, or body), type, required, default, and
bound. In the entry above, the interval line carries all six; the format line
carries no default, and the reader is left with the question “what happens if I do not
give one.”
All six of these fields sit on the signature surface, so all six carry the risk of going stale once every two versions. But there is a distinction among them: the first four can be read from the source’s declaration; default and bound most often sit in the code’s body and cannot be read from the declaration. This distinction predetermines how much generation can recover; the measurement counts the six as a whole, but in practice generation’s scope starts exactly here.
There is also something the line deliberately does not carry: why. Why the
interval parameter’s lower bound is one is not written in the reference, it is
written in the explanation. If it were written in both, it would sit in two places,
the two would go stale at two separate speeds, and the reader would not know which one
is current.
Status Code and Error Code
The error section’s two columns do not say the same thing, and confusing them is
costly. The status code classifies roughly: was the request wrong, was the
resource missing, did the server crash. The error code names a single specific
condition — station-not-found and measurement-not-found can return the same status
code, and the reader’s next action is completely different for each.
A practical consequence follows: a client branches on the error code, not the status code. So the reference’s most heavily relied-upon cell is the one sitting, in this measurement, on the name surface. It is small in number — two of fifteen claims — but it is the claim code gets written against. When it goes stale, branching silently enters the wrong path; no type error is thrown, no warning appears.
The error section is the weakest section in most references, and the reason is measurable. The successful path is written once and exercised on every call; failure paths wait to be written and no one calls them. Yet the reader looking at a reference is usually someone who has already hit an error; the cell they are looking for sits exactly there.
Defining a contract in a machine-readable form and verifying it with a test are the subject of the Web API Design course and are not repeated here. In this lesson the reference is measured not as a contract tool but as a text that needs maintenance: how many claims it carries, how much it holds up, how many of them can be recovered.
Writing by Hand and Generating
The same reference can be brought into existence two ways.
In a hand-written reference, every cell is a sentence written by a person. When a parameter’s type changes in the source, the reference’s cell keeps showing the old type; nothing binds the two together. In a source-generated reference, the cells sit not in prose but inside the source itself, and are reread on every publish. If the type changes, the cell changes with it, because the cell is already a copy of the source.
What generation recovers is fixed: only what is written in the source. A parameter’s name, type, and default are in the source. The name an endpoint is referred to by in the reference is not in the source; an error code’s human-readable name most often is not either. These stay outside generation and remain in hand-written text. The measurement gives this distinction as a number.
The measurement’s assumptions:
- DT27 — The measured set is the shared setup’s reference type: 15 claims, 13 on signature, 2 on name. This mix is not changed.
- DT28 — The reference has no flow or concept claim. Reference does not narrate order and builds no reasoning; this comes from the type’s definition, not a choice made in this lesson.
- DT29 — The fifteen claims are split across three sections: endpoint 4, parameter 7, error 4. Sectioning does not change the surface mix, it only makes claims addressable.
- DT30 — The reference’s share of claims embedded in a runnable example is 0.00. Reference carries no example; this is why every stale reference claim is silent.
- DT31 — Surface frequencies are unchanged: signature every 2, flow every 3, name every 5, concept every 12 versions.
- DT32 — In a generated reference, signature claims never go stale. Because they are read from the source, they change together with it; the lag between the two is counted as zero in this measurement.
- DT33 — Generation does not recover the name surface: the name an endpoint is referred to by, and an error code’s readable name, do not sit in the source, they sit in hand-written text.
- DT34 — Generation does not recover the flow surface either; but this cannot be measured in this reference, because the reference has no flow claim. Claims bound to flow are measured in the other types.
- DT35 — The measurement builds versions 0, 1, 2, 3, 5, 6, and 12.
- DT36 — The set’s resolution is 1/15; differences below one claim cannot be defended with this set.
Measurement
"""API reference: the lifespan of the hand-written versus the generated. Part 1 - how the reference's 15 claims split across sections and surfaces. Part 2 - true/stale/silent across versions for hand-written versus generated reference. """ FREQUENCY = {"signature": 2, "flow": 3, "name": 5, "concept": 12} VERSIONS = (0, 1, 2, 3, 5, 6, 12) # The reference's 15 claims: which section, which surface they are bound to. # Total signature 13, name 2 - exactly the reference mix from the shared setup. SECTIONS = { "endpoint": {"signature": 3, "name": 1}, "parameter": {"signature": 7, "name": 0}, "error": {"signature": 3, "name": 1}, } # The reference's embedded claim share is zero: reference carries no runnable example. EMBEDDED = 0.00 def claims(): entries, no = [], 0 for section, mix in SECTIONS.items(): for surface, count in mix.items(): for _ in range(count): no += 1 entries.append({"no": no, "section": section, "surface": surface, "embedded": EMBEDDED >= 1}) return entries def stale(i, version): return version // FREQUENCY[i["surface"]] >= 1 def measure(entries, version, generated): """In a generated reference, signature claims come from the source and never go stale.""" gone_stale = [i for i in entries if stale(i, version) and not (generated and i["surface"] == "signature")] silent = [i for i in gone_stale if not i["embedded"]] return len(entries), len(entries) - len(gone_stale), len(gone_stale), len(silent) L = claims() print(f"reference claims {len(L)} | embedded in example " f"{sum(i['embedded'] for i in L)}") print() print(f"{'section':<12s} {'claims':>6s} {'signature':>9s} {'name':>4s} {'flow':>5s} " f"{'concept':>7s}") for b in SECTIONS: g = [i for i in L if i["section"] == b] print(f"{b:<12s} {len(g):6d} " + " ".join(f"{sum(1 for i in g if i['surface'] == y):>{w}d}" for y, w in zip(("signature", "name", "flow", "concept"), (9, 4, 5, 7)))) print(f"{'total':<12s} {len(L):6d} " + " ".join(f"{sum(1 for i in L if i['surface'] == y):>{w}d}" for y, w in zip(("signature", "name", "flow", "concept"), (9, 4, 5, 7)))) print() print(f"{'version':>7s} {'hand-written':>20s} {'generated':>20s}") print(f"{'':7s} {'true/stale/silent':>20s} {'true/stale/silent':>20s}") for s in VERSIONS: h = measure(L, s, False) g = measure(L, s, True) print(f"{s:7d} {'{}/{}/{}'.format(*h[1:]):>20s} " f"{'{}/{}/{}'.format(*g[1:]):>20s}") print() print(f"{'section':<12s} {'v6 hand-written':>15s} {'v6 generated':>13s} " f"{'recovered':>9s}") for b in SECTIONS: g = [i for i in L if i["section"] == b] h, gen = measure(g, 6, False), measure(g, 6, True) print(f"{b:<12s} {f'{h[1]}/{h[0]}':>15s} {f'{gen[1]}/{gen[0]}':>13s} " f"{gen[1] - h[1]:9d}") print() remaining = [i for i in L if stale(i, 6) and i["surface"] != "signature"] print("stale claims remaining in the generated reference at version 6:", [(i["section"], i["surface"]) for i in remaining]) print(f"surface recovered by generation: signature " f"({sum(1 for i in L if i['surface'] == 'signature')} claims) | not recovered: " f"{', '.join(sorted({i['surface'] for i in remaining}))} ({len(remaining)} claims)")
reference claims 15 | embedded in example 0
section claims signature name flow concept
endpoint 4 3 1 0 0
parameter 7 7 0 0 0
error 4 3 1 0 0
total 15 13 2 0 0
version hand-written generated
true/stale/silent true/stale/silent
0 15/0/0 15/0/0
1 15/0/0 15/0/0
2 2/13/13 15/0/0
3 2/13/13 15/0/0
5 0/15/15 13/2/2
6 0/15/15 13/2/2
12 0/15/15 13/2/2
section v6 hand-written v6 generated recovered
endpoint 0/4 3/4 3
parameter 0/7 7/7 7
error 0/4 3/4 3
stale claims remaining in the generated reference at version 6: [('endpoint', 'name'), ('error', 'name')]
surface recovered by generation: signature (13 claims) | not recovered: name (2 claims)
What Generation Recovers and What It Does Not
The middle table gives the lesson’s main number. By version six, the hand-written reference is at 0/15; not a single claim is recovered, all fifteen of fifteen are stale. The generated reference at the same version is at 13/15. The difference is 13 claims, and all of it comes from the signature cells of parameter and endpoint.
The bottom table spreads the difference across the sections: the parameter section goes from 0/7 hand-written to 7/7 generated — fully recovered, because every cell inside it is written in the source. Endpoint climbs from 0/4 to 3/4, error from 0/4 to 3/4. Both sections are left with one unrecoverable claim.
The last row states plainly which two claims cannot be recovered: one is the endpoint’s name in the reference, the other is the error code’s readable name. Both sit on the name surface. Generation’s boundary is drawn cleanly here: generation recovers what is written in the source, it does not recover what a human made up. The source does not decide what a name should be.
The same boundary applies to the flow surface too — the order calls must be made in cannot be generated from the source either — but it cannot be measured in this reference, because the reference’s flow claim count is 0. This is not a measurement gap, it is the type’s definition: reference does not narrate order. It is also the reference’s biggest blind spot, and this topic’s sixth lesson enters exactly there.
The silent column is the lesson’s uncomfortable finding. Because the reference’s embedded claim count is 0, the stale count and the silent count are identical on every row: 15/15 hand-written, 2/2 generated. So generation brings the stale count down from fifteen to two but does not change the silence ratio at all — the remaining two claims still sit wrong without telling anyone anything.
This matters not to belittle generation but to name what it actually does correctly. Generation is not a document audit, it is a copying mechanism: it forms the link between source and text, it does not report the text’s correctness to anyone. Once thirteen claims are recovered, the remaining two are exactly as silent as before — and since the rest of the reference is now trustworthy, no one is expected to look at those two with suspicion either. The recovered claims hide the unrecoverable ones.
Which two claims specifically remain unrecoverable also matters. An earlier section said the client branches on the error code, not the status code; one of the unrecoverable claims is exactly that error code’s name. So the 13/15 ratio makes the practical recovery look better than it is: the thirteen claims recovered are the cells the reader checks to confirm; the two unrecovered are the cells code is written against. Read over the denominator, generation looks like it has recovered 0.8667 of the set; read over impact, it has left the two most expensive claims exactly where they were. The measure itself does not say this — it counts claims, not their weight — and this is a limit of claim-counting worth knowing.
The version-two and version-three rows give one last detail: the generated reference stands at 15/15 there, meaning it takes no loss at all through the first three versions. The hand-written one is at 2/13/13 at the same point. Generation’s gain starts at the first signature change and stays complete through version five; the name surface changes there for the first time, and the count drops from 15 to 13. After that it holds steady: still 13/15 at version twelve. The generated reference stops decaying any further past a certain point, because it has already broken everything it could.
Summary
- Reference is a table, not narration; it has three sections — endpoint, parameter, error — and error is the weakest section because no one calls the failure paths.
- 13 of the reference’s 15 claims are bound to signature, 2 to name; it has no flow or concept claim, and its embedded claim count is 0.
- The hand-written reference gives 0/15 by version six; the source-generated one gives 13/15 at the same version. The difference comes from the parameter section’s 7/7 recovery and two sections’ 3/4 recovery.
- The 2 unrecoverable claims sit on the name surface — the endpoint’s referred name and the error code’s readable name; generation recovers what is written in the source, not what a human made up.
- Because reference carries no example, its stale count and silent count are equal on every row; generation brings stale down from 15 to 2 and changes the silence ratio not at all.
- The client branches on the error code, not the status code; one of the two unrecoverable claims is exactly that. Claim-counting does not weigh claims, and this is the measure’s limit worth knowing.
Next Step
Up to here, the four types and the repository documents described the product’s current state. This lesson’s table left the reader owing a new question: with a 13/15 reference in hand, how will they know when the remaining two dropped? The second lesson had already named this question — “what version did this change in” landed on no type. The next lesson measures the text that closes that gap: release notes. The question will be — what difference does the list of changes make against the list of what those changes mean, and how many claims does it make datable for the reader?
To keep your progress and take notes, Log in
My notes
Log in to take notes.