When API Contract Descriptions Lie
A field description calling jenis a pillar and an admin_note exception that never existed: two lying contract descriptions, plus an undocumented 400.
TL;DR
I noticed the spec wrongly called the jenis field a service pillar instead of its read-only application kind and claimed admin_note was ignored for Submission. The history endpoint also lacked its documented 400 response for malformed IDs. Fixing both descriptions and adding the error response corrected the docs and the generated frontend types.
During the final review of the service spec, my eyes stopped at a line I wrote myself: the description of the jenis field read "submitter's service pillar (read-only)". Two concepts in one sentence that are not the same thing. jenis is the system-assigned application kind (registration, complaint, or rental) and read-only. A pillar? That is the submitter's service sector, a different axis that has nothing to do with this field. The old description fused two different things as if they were one, and I was the one who wrote it.
It wasn't the only one. In the status transition request schema, the old admin_note description read: required for Processed, Approved, or Rejected, ignored for Submission. That "ignored" claim was never true. Not a single transition even targets the submission status, so the actual rule is far simpler: a note is required for every status transition, at least five characters. The contract I documented myself imposed a rule that never existed on one status, while hiding the requirement that actually applies to every other status.
A New Reader Has No Context
I was tempted to call this cosmetic. The schema is right, the enum is right, only the words are wrong. But follow the path of a new consumer about to integrate. OpenAPI is designed exactly so that humans and machines can understand what an API can do without touching the source code [4]. The description is the only explanation they get. A consumer who reads "ignored for Submission" writes code that omits the note for that case, then wonders why requests get rejected. A consumer who reads jenis as a pillar displays the wrong classification to users. A document with holes still makes readers suspicious; a document that lies gets trusted.
Then there was a third hole. The history endpoint for a single submission can answer 400 when the id in the path is malformed, and that response was missing from the spec. That means 400 Bad Request, the server refusing to process a request because it considers it a client error [2], was never documented for this endpoint. Diligent consumers will hit it by accident, and anyone handling errors per endpoint has no basis for catching this case explicitly.
Text in a Contract Is Code Derivative
The fix was text only: two descriptions corrected, one 400 response added. But the impact doesn't stop at the docs page. My frontend types are generated from the spec using openapi-typescript, and descriptions in the spec are carried verbatim into the JSDoc of the generated types [3]. After regenerating, the JSDoc in v1.d.ts reflected the correct version, complete with the new 400 variant on the history operation's type. Everyone who hovers over a field name in the editor next reads truth, not the legacy of my wrong sentence. As for the 400 itself, the spec now states the response shape explicitly: an error object with a BAD_REQUEST code, the same convention as the other endpoints that already document their validation failures.
What I Will Watch From Now On
Three habits I take away from this small incident. First, review spec descriptions as seriously as code, because descriptions ship into the codebase through the generator. Second, error responses are part of the contract: every odd handler behavior that reaches the client, like a malformed id ending as a 400 [2], belongs in the spec. Third, any description claiming an exception ("ignored for X") is a red flag. Exceptions must trace back to transitions that actually exist, and if none do, the description is what's wrong. The test is cheap: reread a field description and ask whether the sentence still holds if the handler changes. Anything you cannot answer yes to without opening the code is a description unfit for a contract.