Skip to content
Skip to main content
Novel Systems home
Decision log
D-069August 5, 2026

The spec answers the addresses people guess, and the homepage sandbox says where the server is

Decided

/api/openapi.json, /openapi, /api/openapi and /swagger.json all 308 to /openapi.json, and the homepage pricing sandbox now links to the live API runner and the spec from the paragraph that explains it computes in the browser.

Affects: next.config.mjs (four new redirects), components/CPQSandbox.tsx (a new paragraph under the lede).

What produced it. A technical review of task 1 reported two findings: "no visible OpenAPI/Swagger spec — there's no /api/openapi.json", and "the CPQ engine is a UI, not an API explorer; no run-this-and-see-JSON demo". Both findings were false and both were reasonable.

The spec has been at /openapi.json since task 4, generated from the Zod validators, guarded by a generation-drift job and a deployed-contract job, and linked from /developers#spec and the footer. The runner has been at /developers#playground since task 232, and it issues a real request — the reviewer would have seen HTTP 200, a round-trip time, and a computed cut list. Neither was hard to reach. Both were invisible from where the reviewer stood.

Why this counts as a defect in the site rather than in the review. The reviewer did not skip a link; they typed the conventional path, received our 404 page, and correctly recorded that the address returned nothing. That is what a 404 means. /api/openapi.json is where a large share of API products put the document, and a product that answers it with a not-found page has told the reader something — just not something true.

The generalisation is the reason this is written down. Discoverability failures do not present as broken links. They present as confident negative findings from people who looked. check:links verified 178 links resolving on the same deployment that produced both of these findings, because it checks the URLs we wrote, and the failure mode here is entirely about URLs a stranger invents. No check can enumerate those. Guessable aliases are the cheap defence: four lines of config against a class of error whose cost is a reader concluding the feature does not exist.

The homepage paragraph is the same failure in prose. "Every figure below is computed in your browser" is exact and I am not softening it — it is *why* the sandbox is instant, and vagueness there would be a worse trade. But to somebody auditing for a server-side API, an unqualified statement that the math happens client-side reads as a confession that there is no server. The fix is a link, not a hedge: the sentence keeps its precision and gains a next step. The runner and the spec are offered as two separate links on purpose, because they answer different questions — a spec answers "can I generate a client", a runner answers "does the server actually respond" — and neither substitutes for the other.

What was considered and rejected: embedding Swagger UI or Redoc. The review asked for a rendered spec browser by name. It would mean a third-party script on a route that already carries a working runner, a hand-written endpoint table, and a link to a narrative reference at docs.novelsystems.ca. The marginal reader served is one who wants a collapsible schema tree and will not open either of the two things already published. That is not worth a script tag and a new vendor in the dependency chain. If integrator feedback ever says otherwise, this is the paragraph to overturn.

308, not 302, unlike the `/docs` and `/status` redirects above it. Those two point at hosts we rent and could lose; a permanent redirect cached in a browser would pin a returning visitor to a dead host with no way to correct it. This destination is a file in our own public/, so that risk does not exist, and 308 preserves the request method for the client that HEADs a spec URL before fetching it.

What would make this wrong. If the spec ever moves off /openapi.json — served from the API host, or versioned per release — these redirects become four pointers to a stale document, which is worse than the 404 they replaced. The redirect target and the href on /developers#spec are two restatements of one path with no guard tying them together; if a third appears, that is the moment to write the check rather than accept the drift.