Skip to content

An Undocumented 404 Is a Contract Bug

Adityo Guni Waluyo

Six lines of OpenAPI YAML and a regeneration changed what the frontend compiler knows. Error responses left undocumented are contract bugs.

TL;DR

What looked like a trivial six-line OpenAPI cleanup turned out to be critical for type safety. Since openapi-typescript only generates types for explicitly documented responses, an undocumented 404 forced the frontend to guess or use any. Documenting the error locks the contract so TypeScript catches missing handling early, saving hours of debugging later.

I opened the terminal, ran git diff, and only saw two files change. Six added lines in the OpenAPI YAML file, followed by nine regenerated lines in the frontend's v1.d.ts. Zero runtime code changes.

At the time, I was cleaning up the public reviews list endpoint for a fictional tourism project. I initially thought this was purely an administrative task. Just tidying up the documentation to look neat in the Swagger UI. I thought, "Ah, this is just adding an error description to be complete, it won't change how the application works." I almost skipped this commit in code review because it looked so trivial.

Turns out my guess was completely wrong. This tiny change fundamentally locked down the type contract.

Types Only Acknowledge What Is Written

The openapi-typescript library works in a very literal way. It turns OpenAPI 3.0 or 3.1 schemas into runtime-free TypeScript types [8]. This means the generated types will only acknowledge what is explicitly written in the specification, nothing more, nothing less.

By adding the 404 entry, the generated types allow API consumers to safely reach the error payload through a very specific path [8]. For example:

My frontend developer was previously confused. He had to use the any type because he wasn't sure what the error format looked like. After those nine lines appeared in v1.d.ts, he could check the generated error type directly, something like type ReviewErrorResponse = paths["/pariwisata/entities/{id}/reviews"]["get"]["responses"]["404"]["content"]["application/json"]["schema"] [8], without manually opening the documentation or guessing the JSON structure.

Without an explicit 404 entry in the specification, consumers can only guess the error shape or are forced to fall back to any. This is clearly dangerous. Developers end up guessing whether the error returns { message: string } or { error: { code: number } }.

I finally realized that an undocumented 404 is a contract bug, not just a documentation chore that can be postponed.

An API Is a Contract, Not Just Success Stories

An API is fundamentally a contract. If we only document the success stories (200 responses), we are implicitly telling consumers that errors will never happen. But in the real world, resources can be deleted, IDs can be wrong, or access might be restricted.

Imagine this scenario. An API consumer sends a request to the reviews endpoint. The entity ID is invalid. The server replies with a 404. Without a clear contract, the frontend might crash or display a raw error that confuses the user. With a firm contract, we can ensure the UI always displays a friendly and predictable fallback.

Treating an undocumented 404 as a contract bug is a preventive step that is far cheaper than fixing any type bugs in production. When the types are correct, the TypeScript compiler will immediately complain if a developer forgets to handle the 404 scenario. The compiler becomes our first line of defense.

I have a firm opinion on this. Adding error schemas to the OpenAPI specification is not optional. It is a basic obligation when releasing a public endpoint. It is better to spend five minutes writing six lines of YAML now than five hours tracking down an any type bug in the middle of the night.

Sources

Related articles