Skip to content

Routes reference

What Register mounts, what each route answers with, and which headers come back. Paths below use the defaults; substitute whatever you passed to WithSpecPath and WithDocsPath.

What Register mounts on the mux

Three patterns, in net/http 1.22 method-pattern form:

Pattern Handler
GET /openapi.yaml writes the spec bytes you passed to Register
GET /docs/{$} the generated Stoplight index HTML
GET /docs/ http.FileServer over the embedded assets, with the docs prefix stripped

{$} matches the docs path exactly and nothing below it, so the index page and the asset subtree do not collide. Each Register call is independent: two calls with different paths mount six routes and serve two API versions side by side. Two calls sharing a path panic — see what happens when a path is wrong.

Which methods each route answers

Patterns are registered for GET only, and net/http matches HEAD against a GET pattern automatically.

Request Response
GET, HEAD 200, as described below
POST, PUT, DELETE, OPTIONS, anything else 405 Method Not Allowed, Allow: GET, HEAD

The 405 comes from ServeMux, not from this module. There is no OPTIONS handling and no CORS preflight support — the docs are designed to be served same-origin with the API, so there is nothing to preflight.

GET /openapi.yaml — the spec

Returns the spec byte slice verbatim with Content-Type: application/yaml and no other body processing: no parsing, no validation, no reformatting.

Content-Type is application/yaml unconditionally. A JSON OpenAPI document is still served, and still renders in the UI, but with a YAML content type — JSON is a subset of YAML, so parsers accept it, but a client keying off the content type will be misled.

A nil or empty spec is not an error: the route registers and returns 200 with an empty body. The docs page then loads and fails to find a document to render.

GET /docs/ — the generated page

Returns 200 with Content-Type: text/html; charset=utf-8 and a small HTML document — around 530 bytes — that loads the embedded Stoplight assets and mounts one <elements-api> element:

<elements-api
  apiDescriptionUrl="/openapi.yaml"
  router="hash"
  layout="sidebar"
  tryItCredentialsPolicy="same-origin"
></elements-api>

Only apiDescriptionUrl and the page <title> vary — they come from WithSpecPath and WithTitle. router, layout and tryItCredentialsPolicy are fixed; there is no option to change them.

router="hash" means deep links into the reference are fragments — /docs/#/paths/v1-greeting/get — so navigation inside the UI never reaches your server, and the fragment is never sent with the request.

A request for /docs without the trailing slash is redirected by ServeMux with 307 Temporary Redirect to /docs/.

GET /docs/{asset} — the embedded UI

http.FileServer over the two embedded files:

Asset Size Content-Type
web-components.min.js 2,083,081 bytes text/javascript; charset=utf-8
styles.min.css 297,495 bytes text/css; charset=utf-8

Anything else under the prefix is 404. /docs/index.html is redirected to ./ by http.FileServer's own index handling, which lands on the generated page.

Responses carry no cache validators. The embedded filesystem reports a zero modification time, so http.ServeContent emits neither Last-Modified nor ETag, and nothing sets Cache-Control. A conditional request is answered 200 with the full body rather than 304, so every uncached load of the docs page transfers the whole 2.4 MB again. Put a caching proxy or a Cache-Control-setting middleware in front if that traffic matters — this module does not do it for you.

Response headers

Every one of the three routes goes through the same security-header middleware unless you passed WithoutSecurityHeaders:

Header Default value
X-Content-Type-Options nosniff
X-Frame-Options DENY
Content-Security-Policy frame-ancestors 'none'
Referrer-Policy no-referrer
Strict-Transport-Security not set — HSTS is opt-in

The Stoplight console renders and issues try-it requests normally under these defaults; they restrict framing and sniffing, not scripting.

X-Frame-Options: DENY and frame-ancestors 'none' do mean the docs page cannot be embedded in an <iframe> on another page — including your own portal. Relax it with WithSecurityHeaderOptions if you need that.

Replacing the policy with a full CSP is a sharper edge: WithContentSecurityPolicy replaces the whole header, and the generated page carries an inline style="height: 100vh; margin: 0" attribute on <body> while Stoplight injects styles at runtime. A policy without 'unsafe-inline' in style-src will break the page's layout.