Fell out of #241, closed by #244. Now that a 429 answers the documented { error } shape, the prose contract is complete but the machine-readable one is not.
openapi.json contains zero "429" responses. Meanwhile 17 route definitions attach a per-flow limiter (magicLinkIpLimiter, otpIpLimiter, oauthIpLimiter, and their identity-keyed siblings), and every route is additionally behind the global dynamicRateLimit. So a 429 is reachable on effectively every endpoint and documented on none.
Two consequences:
- The generated client is wrong by omission.
src/generated/api.ts is emitted from the spec and committed, so a consumer reading it, or anyone generating their own client, gets a response union with no 429 in it. They will write code that cannot see the case.
- It reads as an undocumented failure mode. For a repo heading into certification where the OpenAPI document is a deliverable, "every endpoint can return a status the spec never mentions" is the kind of gap an assessor asks about, and the answer today is that it was never wired up rather than that it was considered.
Why it happens
Limiters are Express middleware attached via middleware: [...] in the route definition. defineRoute builds OpenAPI responses only from schemas.response, which handlers declare, so nothing about a middleware-produced status reaches the registry. Note the same is already true of the 403 the CORS handler produces and the 500 from the top-level error handler, so this is one instance of a general shape rather than a one-off.
Scope
Decide where the truth should live, then wire it:
- Simplest: have
defineRoute add a 429: ErrorSchema to every route it registers, the way it already injects the 400 ValidationErrorSchema for any route that validates a request. That precedent exists and is the closest fit.
- Or: add it only to routes that carry a limiter, which is more precise but leaves the global limiter undocumented, and the global one applies to everything.
The first is more honest about what a caller can actually receive. Regenerate openapi.json and src/generated/api.ts with npm run generate:api afterwards; tests/unit/openapi/generatedContract.spec.ts fails if they drift.
Worth considering the same treatment for the CORS 403 and the unhandled-error 500 (#230, closed by #239) in the same pass, since they have the identical cause.
Fell out of #241, closed by #244. Now that a 429 answers the documented
{ error }shape, the prose contract is complete but the machine-readable one is not.openapi.jsoncontains zero"429"responses. Meanwhile 17 route definitions attach a per-flow limiter (magicLinkIpLimiter,otpIpLimiter,oauthIpLimiter, and their identity-keyed siblings), and every route is additionally behind the globaldynamicRateLimit. So a 429 is reachable on effectively every endpoint and documented on none.Two consequences:
src/generated/api.tsis emitted from the spec and committed, so a consumer reading it, or anyone generating their own client, gets a response union with no 429 in it. They will write code that cannot see the case.Why it happens
Limiters are Express middleware attached via
middleware: [...]in the route definition.defineRoutebuilds OpenAPI responses only fromschemas.response, which handlers declare, so nothing about a middleware-produced status reaches the registry. Note the same is already true of the403the CORS handler produces and the500from the top-level error handler, so this is one instance of a general shape rather than a one-off.Scope
Decide where the truth should live, then wire it:
defineRouteadd a429: ErrorSchemato every route it registers, the way it already injects the400ValidationErrorSchemafor any route that validates a request. That precedent exists and is the closest fit.The first is more honest about what a caller can actually receive. Regenerate
openapi.jsonandsrc/generated/api.tswithnpm run generate:apiafterwards;tests/unit/openapi/generatedContract.spec.tsfails if they drift.Worth considering the same treatment for the CORS
403and the unhandled-error500(#230, closed by #239) in the same pass, since they have the identical cause.