# OpenAPI describes the refusal too

> For agents: start with the [agent guide](https://ratstack.sh/llms.txt). Every page is Markdown by default; add `Accept: text/html` for HTML.

Free workshop: [how to burn a trillion tokens and get good results](/tokenmaxx#interested).

[OpenAPI](https://spec.openapis.org/oas/v3.1.0.html) describes HTTP paths, operations, request bodies, and responses. An agent can read our generated [/openapi.json](/openapi.json) before choosing an HTTP call.

[`apps/mischief/src/app.ts`](https://github.com/joelhooks/rat-stack/blob/main/apps/mischief/src/app.ts) builds the `/api` projection. It passes the API to `HttpApiBuilder.layer`, with `openapiPath: "/openapi.json"`.

Effect's [`OpenApi.fromApi`](https://github.com/Effect-TS/effect/blob/effect%404.0.0/packages/effect/src/http-api/OpenApi.ts) derives the document from that HttpApi.

## The document must describe a decode refusal

[Commit 8766790](https://github.com/joelhooks/rat-stack/commit/876679087c9fb1b10827140c54c0bfc3c2c4ff2e) adds `HttpApiError.BadRequestNoContent` to every endpoint built by `toHttpApi`. Request decoding already returns an empty 400. The document needs to say so.

This refusal is **not** `application/problem+json`. The [route test](https://github.com/joelhooks/rat-stack/blob/main/packages/capability/test/to-http-api-routes.test.ts) sends an invalid payload and checks:

- Status 400.
- No content type.
- An empty body.

The document has a `BadRequest` response description with no `content` field. A reader uses that distinction to decide whether to parse the error body.

A declared domain failure differs from a request decode failure. See [schema-driven development](/lore/schemas-define-the-boundary) for that boundary. See [generating surfaces](/lore/one-schema-three-surfaces) for the document's source.

See [how we do it with Effect](/lore/how-we-do-it-with-effect) for the whole path.

## Sources

1. [OpenAPI 3.1.0 specification](https://spec.openapis.org/oas/v3.1.0.html)
   Primary source. OpenAPI 3.1.0 specification Accessed 2026-10-02.

2. [apps/mischief/src/app.ts](https://github.com/joelhooks/rat-stack/blob/main/apps/mischief/src/app.ts)
   GitHub. apps/mischief/src/app.ts Accessed 2026-10-02.

3. [packages/capability/src/to-http-api.ts](https://github.com/joelhooks/rat-stack/blob/main/packages/capability/src/to-http-api.ts)
   GitHub. packages/capability/src/to-http-api.ts Accessed 2026-10-02.

4. [Document empty HTTP decode failures](https://github.com/joelhooks/rat-stack/commit/876679087c9fb1b10827140c54c0bfc3c2c4ff2e)
   GitHub. Document empty HTTP decode failures Accessed 2026-10-02.

5. [packages/capability/test/to-http-api-routes.test.ts](https://github.com/joelhooks/rat-stack/blob/main/packages/capability/test/to-http-api-routes.test.ts)
   GitHub. packages/capability/test/to-http-api-routes.test.ts Accessed 2026-10-02.

6. [Effect 4 OpenApi.fromApi](https://github.com/Effect-TS/effect/blob/effect%404.0.0/packages/effect/src/http-api/OpenApi.ts)
   GitHub. Effect 4 OpenApi.fromApi Accessed 2026-10-02.

## Linked from

- add-a-capability → Learn how one contract and its handler become a command, HTTP route, MCP tool, browser RPC, and sandbox call. → [Read page](https://ratstack.sh/skills/add-a-capability)

- Agent guide → An Effect stack so pure (aspirational) Kit Langton will blush. → [Read page](https://ratstack.sh/llms.txt)

- Capabilities → Domain behaviors on generated surfaces share one schema-typed contract and one handler. → [Read page](https://ratstack.sh/systems/capabilities)

- Change log → What changed in the files served here, newest first. → [Read page](https://ratstack.sh/log)

- Effect keeps the surfaces on one contract → Schema owns values, projections build interfaces, and Layers supply implementations. → [Read page](https://ratstack.sh/lore/how-we-do-it-with-effect)

- Full agent guide → An Effect stack so pure (aspirational) Kit Langton will blush. → [Read page](https://ratstack.sh/llms-full.txt)

- keep-or-cut → Learn which pieces depend on each other, then keep only the ones your project needs. → [Read page](https://ratstack.sh/skills/keep-or-cut)

- log.md → The generated change log as Markdown. → [Read page](https://ratstack.sh/log.md)

- One schema, three surfaces → A capability contract feeds CLI, HTTP, and MCP; OpenAPI is generated output, not their input. → [Read page](https://ratstack.sh/lore/one-schema-three-surfaces)

- Rat Stack lore | rat-stack → Short, source-grounded notes on the ideas and decisions behind rat-stack. → [Read page](https://ratstack.sh/lore)

- README.md → What is in the repo, how the example works, and how to run it. → [Read page](https://ratstack.sh/README.md)

- Schema projections: 2026-09-18 history → Historical Effect rc.115 proposal and implementation receipts from 2026-09-18; use the one-capability-every-surface lore page for the current pattern. → [Read page](https://ratstack.sh/resources/schema-projections-and-code-mode.svx)

- Schemas define the boundary → Schema-driven development defines accepted values and failures before projecting or decoding them. → [Read page](https://ratstack.sh/lore/schemas-define-the-boundary)

- Two lines of curl is a front door → One observed application used the Markdown page and llms.txt, then one POST; confirmation followed 88 seconds later. → [Read page](https://ratstack.sh/lore/two-lines-of-curl-is-a-front-door)

## Unlinked mentions

- [One schema, three surfaces](https://ratstack.sh/lore/one-schema-three-surfaces) → CLI and MCP do not consume the generated OpenAPI JSON.¶

- [Schema projections: 2026-09-18 history](https://ratstack.sh/resources/schema-projections-and-code-mode.svx) → Integration layer, not a tool author: ingests MCP servers, OpenAPI, GraphQL, Google Discovery into one catalog, adds auth and per-tool policy (allow / approve / block), serves the catalog back over MCP.
