Repository navigation
OpenAPI spec for the WaveHouse API + machine-readable discovery (/.well-known/api-catalog) #302
Description
Activity
- addedenhancementNew feature or requestNew feature or requestarea/apiHTTP handlers, routing, middlewareHTTP handlers, routing, middlewarearea/sdkTypeScript SDK (clients/ts/)TypeScript SDK (clients/ts/)area/docsDocumentation, site/, READMEDocumentation, site/, README
on Jun 8, 2026 🔗 Related PRs
#123 - fix(api): drop CORS credentials + skip same-origin decoration [merged]
#137 - feat(deploy): local dev o11y stack (#121) [merged]
📝 Issue Planner
Check the box below or use the
@coderabbitai plancommand to generate an implementation plan and prompts that you can use with your favorite coding assistant.- Create Plan
🧪 Issue enrichment is currently in open beta.
You can configure auto-planning by selecting labels in the issue_enrichment configuration.
To disable automatic issue enrichment, add the following to your
.coderabbit.yaml:issue_enrichment: auto_enrich: enabled: false
💬 Have feedback or questions? Drop into our discord!
- added a commit that references this issue
on Jun 9, 2026 One consumer worth designing for explicitly, because it adds a few requirements the current acceptance list doesn't cover: a gateway or reverse proxy fronting several WaveHouse deployments of differing versions, deriving its own behavior from each deployment's spec.
That's the generalization of the "self-hosted WaveHouse self-describes at its own origin" bullet already in the Why — but a fronting system needs more from the document than a human or a one-deployment SDK does. Everything below is a gap I hit reading the current router and docs, and each one is the difference between "the proxy derives this" and "the proxy hardcodes a table that silently goes stale on upgrade."
Stable
operationIds, treated as API surface. Anything mapping operations onto its own handlers, permissions, or tools needs a stable key — path+method is workable but brittle when a path changes shape. Worth adding to the acceptance list, and worth saying in the docs that renaming one is a breaking change, since downstream mappings are invisible from here.Express the admin/non-admin split in the spec. Right now that gate is a router fact:
RequireAdminwraps/v1/admin/*, and also/v1/schema,/v1/schema/refresh, and/v1/dlq/stats. So the/v1/admin/prefix is not a reliable signal for whether an operation is privileged — which is exactly the trap a fronting proxy falls into, because path-shape is the only thing it can see today. Encoding the gate (a distinctsecurityrequirement, a scope, or anx-extension) lets a proxy derive authorization tiers instead of guessing, and means a new privileged endpoint landing outside the prefix doesn't silently widen what that proxy allows. This is the single most valuable addition on this list.Mark the operational endpoints. The reverse-proxy guide already recommends keeping
/livez//readyz//healthzinternal — a public/readyzturns each hit into a ClickHousePing— and exposing only/v1/health. If the spec marks which operations are operational rather than API surface, a fronting proxy can exclude them automatically instead of maintaining its own denylist, which is the thing that goes stale precisely when the deployment is upgraded.info.versiontied to the build. So a consumer can cache the derived surface per engine version and invalidate on upgrade. Without it, cache invalidation is guesswork against a moving target.State whether discovery itself is authenticated.
/openapi.jsonand/.well-known/api-catalogshould be explicitly unauthenticated (or explicitly not). If learning the surface requires a credential, the discovery story doesn't work for a fronting system — and arguably not for the agent/crawler cases either, which is the motivating use here.Document the two non-obvious response shapes on
/v1/admin/query. It returns ClickHouse'sFORMAT JSONenvelope unwrapped — just thedataarray — rather than the envelope itself; and an inlineFORMAToverride (SELECT 1 FORMAT CSV) bypassesdefault_format=JSONentirely and returns a non-JSON body with the upstreamContent-Type. Both are correct and deliberate, and both are the kind of thing a spec-generated client gets wrong by assuming a uniform JSON response. Worth pinning in the schema rather than leaving to the handler comments.Decide how
/v1/streamis represented. SSE doesn't model as an ordinary response, and a consumer generating a typed method from the spec will produce something broken unless the document saystext/event-streamand marks it as streaming. Also relevant: it accepts the?token=query credential the other endpoints don't need, so itssecuritydiffers.None of these change the recommendation between A / C / D — they're requirements on the resulting document whichever way it's produced. If it helps, the ones I'd put in acceptance are the operationIds, the admin-gate encoding, and the operational-endpoint marking; the rest are documentation quality.
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsBacklog
Goal
Publish a machine-readable OpenAPI 3.1 spec for the WaveHouse HTTP API, serve it for discovery, and use it to close the agent/crawler API-discovery gaps that #301 deliberately deferred.
#301 ("improve discoverability for AI agents and crawlers") handled the docs-site discovery surface (sitemap,
Linkheaders, content-signals;llms.txt+ markdown content-negotiation already shipped). It explicitly skipped RFC 9727/.well-known/api-catalogbecause we have no OpenAPI spec yet — that's this issue.Why
service-desc)./.well-known/api-catalog(RFC 9727) → flips the Cloudflare agent-readiness "API catalog" finding that fix(docs): real per-page Last-updated dates + open AI content signals #301 couldn't./openapi.jsonis genuinely self-describing per deployment.clients/ts/src/types.ts) from one source of truth.What we're starting from
internal/api/router.go(~17 routes under/v1+ health/version).StructuredQuery,Aggregation/Filter/OrderClause/TimeRange,NamedQuery/ParamDef,Policy/TablePolicy/RolePermissions,batchResult/recordResult,queryRequest).{"error": "..."}(internal/api/errors.go).?token=query fallback (middleware on all/v1; admin gate on/v1/admin/*). →securitySchemes:http/bearer/JWT+ anapiKey-in-querytoken./v1/schemaendpoint for ClickHouse table/column types (clients/ts/src/cli/codegen.ts). SDK request/response types are hand-written (clients/ts/src/types.ts).Because the API already exists and is well-typed, the textbook spec-first (spec → generated server) path is out — we'd be regenerating handlers we already have.
Options
openapi.yaml+ CI contract testkin-openapi/pb33f/libopenapi) and validates real handler responses against it// @Summary/@Param/@Successon handlers,swag initviago:generateinvopop/jsonschema) intoopenapi.yaml; structs stay the schema source of truthRecommendation
any-typed fields (Filter.Value,Columnsasstring | []string) need explicit hand-written schema fragments.Wiring once the spec exists
go:embedat/openapi.json(each self-hosted deployment self-describes at its own origin).servers:can be relative/placeholder for a self-hosted product.wavehouse.dev/openapi.yaml(static) + render it in the docs./.well-known/api-catalog(RFC 9727,application/linkset+json):service-desc→ the OpenAPI,service-doc→/api,status→/healthz.clients/ts/src/types.tsfrom the spec (openapi-typescript), unifying with the existing/v1/schematable-type codegen.Acceptance
openapi.yaml(3.1) covering the/v1routes + health/version, withsecuritySchemes(Bearer +tokenquery)/openapi.jsonfrom the binary (go:embed)wavehouse.dev+ docs render/.well-known/api-catalog(RFC 9727)Refs: #301 (deferred this), RFC 9727, RFC 9264 linkset.