OpenAPI
wow-openapi builds OpenAPI 3.1 operations and components for Wow's route contracts. The complete path is:
Wow annotations
-> KSP: META-INF/wow-metadata.json
-> MetadataSearcher: runtime aggregate metadata
-> RouterSpecs / RouteCatalog
-> WebFlux RouterFunction
-> OpenAPI paths and components
-> API client or external client generatorThe shared RouteCatalog is the important boundary: runtime WebFlux handlers and the OpenAPI renderer consume the same route contracts. KSP does not generate a running HTTP server, and OpenAPI does not prove that a backend, query capability, authentication policy, or client deployment works.
Installation
wow-openapi supplies route contracts and schema components:
implementation("me.ahoo.wow:wow-openapi")implementation 'me.ahoo.wow:wow-openapi'<dependency>
<groupId>me.ahoo.wow</groupId>
<artifactId>wow-openapi</artifactId>
<version>${wow.version}</version>
</dependency>For a Spring Boot application, use the starter and WebFlux runtime. Add Springdoc only when the service should publish /v3/api-docs or Swagger UI:
implementation("me.ahoo.wow:wow-spring-boot-starter")
implementation("me.ahoo.wow:wow-webflux")
implementation("org.springdoc:springdoc-openapi-starter-webflux-ui")OpenAPIAutoConfiguration creates RouterSpecs; WebFluxAutoConfiguration materializes the catalog into RouterFunction; WowOpenApiCustomizer merges the same catalog into Springdoc. wow.openapi.enabled=false disables Springdoc customization, not the WebFlux route catalog itself.
Modules containing Wow annotations still need KSP plus wow-compiler, and their generated META-INF/wow-metadata.json resources must be present on the service runtime classpath. Do not hand-write or commit generated resources.
Swagger-UI
Swagger UI is a Springdoc application feature, not part of the route contract itself. The default Springdoc pages are normally available at /swagger-ui.html and /v3/api-docs when the matching starter is present and enabled.

Use the JSON document as the source for exact paths, methods, parameters, media types, operation IDs, and component references. A screenshot is not contract evidence.
Aggregate Resource Ownership
Aggregate metadata combines @AggregateRoute, command-level @CommandRoute, tenant metadata, and generated command/event types. Route ownership affects path shape; it is not caller authorization.
The route catalog also controls whether an aggregate is published at all (@AggregateRoute(enabled = false)). This does not remove command handling or storage behavior outside HTTP.
RESTful URL PATH Spec
The general aggregate route shape is:
[tenant/{tenantId}/][owner/{ownerId}/]{resourceName}[/{resourceId}]/{action}The default route starts at the resource name. Wow does not prepend a bounded-context alias to local paths. Do not construct paths from naming conventions in client code; inspect generated OpenAPI.
Tenant Resources
A dynamic tenant aggregate's default command/state routes receive the tenant/{tenantId} prefix. Snapshot query contributors also retain a base route and add the tenant-scoped variant. Tenant path data is passed to runtime handlers and query rewriting, but the application must bind it to the authenticated principal and protect the unscoped query route explicitly.
Space Resources
Spaced routes declare the Wow-Space-Id request header. Space does not add a path segment. The header participates in command context and query scoping; it is not authentication.
Owner Resources
AggregateRoute.Owner.ALWAYS adds owner/{ownerId} and keeps the resource ID on default owned routes; snapshot queries publish both base and owner-scoped variants:
@AggregateRoot
@AggregateRoute(resourceName = "orders", owner = AggregateRoute.Owner.ALWAYS)
class Order(private val state: OrderState)AGGREGATE_ID uses owner ID as aggregate ID and omits the separate resource-ID segment:
@AggregateRoot
@StaticTenantId
@AggregateRoute(resourceName = "cart", owner = AggregateRoute.Owner.AGGREGATE_ID)
class Cart(private val state: CartState)Query-schema routes are an exception: /{aggregate}/snapshot/schema, /{aggregate}/event/schema, and their /refresh routes describe query models and therefore do not have tenant/owner path variants. A spaced aggregate's common contract may still declare Wow-Space-Id.
Global Routes
Global contracts are contributed independently of aggregate routes:
| Method | Path | Purpose |
|---|---|---|
POST | /wow/command/send | Generic command facade used by the API client |
POST | /wow/command/wait | Wait-signal receiving endpoint |
GET | /wow/metadata | Loaded Wow metadata |
POST | /wow/bi/script | BI synchronization script generation |
GET | /wow/id/global | Global ID generation |
Publishing one of these routes does not secure it. Apply the service's authentication, authorization, rate-limit, and network-exposure policy.
Get Wow Metadata
GET /wow/metadata returns the WowMetadata assembled from compile-time resources on the runtime classpath. It is useful for diagnosing missing annotated modules:
curl 'http://localhost:8080/wow/metadata' \
-H 'accept: application/json'Representative response shape:
{
"contexts": {
"example-service": {
"alias": "example",
"scopes": ["me.ahoo.wow.example.api"],
"aggregates": {
"order": {
"scopes": ["me.ahoo.wow.example.api.order"],
"type": "me.ahoo.wow.example.domain.order.Order",
"tenantId": null,
"id": null,
"commands": ["me.ahoo.wow.example.api.order.CreateOrder"],
"events": ["me.ahoo.wow.example.api.order.OrderCreated"]
}
}
}
}
}The response is runtime evidence that metadata was loaded. It is not proof that every generated route was materialized; inspect /v3/api-docs or the route catalog as the next gate.
Generate BI Sync Script
POST /wow/bi/script generates ClickHouse synchronization and expansion SQL for current local aggregates. The route and OpenAPI operation are present by default and are both removed when wow.bi.script.enabled=false. Enabling the route does not authorize it.
The endpoint requires an application/json body. {} means DEPLOY with server options unchanged. Request fields include deployment overrides, operation, and replayFromEarliestConfirmed; previousManifest is not part of the contract. topology.mode is required when topology is present. STANDALONE rejects a cluster object; CLUSTER accepts only cluster name and installation overrides.
maxExpansionDepth may be lowered by a request but cannot exceed the server ceiling. Length limits apply equally to server configuration and non-null overrides: database and consumerDatabase 128, timezone 64, topicPrefix 128, kafkaBootstrapServers 4096, and cluster name/installation 128. An over-limit server value fails startup; an over-limit request returns 400.
| Status | Contract |
|---|---|
200 application/sql | SQL text; Wow-BI-Diagnostic-Count reports omitted diagnostics |
200 application/json | SQL, destructive flag, diagnostics, and the same count header |
400 | malformed body, invalid override/topology, or unsupported RESET precondition |
406 | no acceptable representation; Wow-Error-Code: NotAcceptable |
415 | missing/unsupported content type; Wow-Error-Code: UnsupportedMediaType |
500 | unexpected generation failure |
502 / 503 / 504 | catalog inconsistency / unavailable inspection / timeout |
RESET requires replayFromEarliestConfirmed=true, a configured server-side consumerGroupNamespace, and an available inspector. DEPLOY and RESET do not migrate databases, consumer-group namespace, or topology. The old GET method has no route.
curl -X POST 'http://localhost:8080/wow/bi/script' \
-H 'content-type: application/json' \
-H 'accept: application/sql' \
--data '{}'See Business Intelligence for expansion semantics and BI Script Configuration for server options.
Generate Global ID
curl 'http://localhost:8080/wow/id/global' \
-H 'accept: text/plain'0U2MNGBQ0001001The returned value is text. Client code should treat its layout as opaque unless a separate CosId contract is explicitly required.
Aggregate Routing Specification
The catalog contributes command, state, event, snapshot, and query routes from aggregate metadata. Common query suffixes are:
| Method | Suffix | Request / response |
|---|---|---|
GET | snapshot/schema | runtime QueryModelSchemaMetadata |
POST | snapshot/schema/refresh | refreshed query-model schema |
GET | event/schema | runtime EventStream QueryModelSchemaMetadata |
POST | event/schema/refresh | refreshed EventStream query-model schema |
POST | snapshot/single | SingleQuery -> materialized snapshot |
POST | snapshot/single/state | SingleQuery -> state only |
POST | snapshot/list / list/state | ListQuery -> array or SSE |
POST | snapshot/paged / paged/state | PagedQuery -> PagedList |
POST | snapshot/cursor / cursor/state | CursorQuery -> complete-snapshot / state-only CursorPage |
POST | event/cursor | CursorQuery -> EventStream CursorPage |
POST | snapshot/count | FilterExpression -> exact count |
POST | snapshot/aggregation | AggregationQuery -> dynamic rows or SSE |
Query contracts appear in three distinct layers:
- Generic query component schemas define the canonical request JSON shapes.
- Every aggregate-specific query request-body component references a generic schema and exposes static
x-wow-query-fields, whose enum combines system fields with fields inferred byJsonQuerySchemaSource. - The runtime
snapshot/schemaandevent/schemaroutes publish their mergedQueryModelSchemaMetadataand backend-proven capabilities.
x-wow-query-fields is OpenAPI design-time metadata on the request-body component; it is not embedded as JSON request properties and is not a backend capability claim.
The CursorQuery component contains filter, projection, sort, size, and an optional cursor, but no pagination. CursorPage contains only list and nullable nextCursor, with no total. These cursor routes declare application/json only; there is no SSE cursor contract.
wow-apiclient contains hand-maintained CoApi interfaces for Wow command and snapshot contracts. External tools such as Fetcher may generate other clients from the published OpenAPI document. Client generation is downstream of OpenAPI: KSP metadata does not generate those clients, and regenerating a client does not change server field semantics. Review generated diffs whenever the OpenAPI contract changes.