All notable changes to this project are documented here. The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
- Composer surface (
openapi.yaml) — the gateway (buildd62760094) serves threecomposer:*-scoped operations that had no spec entry, so no SDK, CLI or MCP method could be generated for them and the published contract'soperation-paritycheck (CONTRACT-001) failed on exactly these three (api-spec#92, api-spec#91). Documented verbatim from the published contract athttps://api.wave.online/openapi.json, fetched 2026-09-08T14:06:58Z:POST /compose(createComposeProposal, scopecomposer:write) — composes a PLAN from a plain-language intent. Never calls a product and never bills; stampsx-wave-meter: wave_compose_proposals(counted, never billed).POST /compose/run(runComposeProposal, scopecomposer:write) — runs a stored proposal once, gated on input validity, idempotency, manifest freshness, budget and the declared-IO typing gate. The 200 documented here is the replay shape (ComposeRunReplay) for a repeat call with the same idempotency key and input.GET /compose/proposals/{proposalId}(getComposeProposal, scopecomposer:read) — org scoped re-read of a stored proposal by id.- Adds the
composertag and seven component schemas the three operations reach transitively:ComposeProposalCreate,ComposeProposal,ComposeCandidate,ComposeRetrievalInfo,ComposeRunCreate,ComposeRunInput,ComposeRunReplay. Twonullable: truefields the published contract carries (ComposeProposal.flowId,ComposeCandidate.product) and one onComposeRunReplay.codeare expressed here astype: [string, 'null']to match this repo's declaredopenapi: 3.1.0dialect — the OpenAPI 3.0-stylenullablekeyword the gateway publishes is not valid 3.1 struct and failsredocly lint'sstructrule; this is the same class of publishing-service leakage the drift checker already treats as non-drift enrichment, not a content difference (compare()diffs unresolved$refstrings at the operation level, so this syntax normalization does not affect the published-contract-driftshared-driftfinding for these operations). - Only the
200response is documented for each operation because that is the only response the published contract declares for them; no 4xx/5xx were invented. This is a real gap in the live surface, not one this PR introduces:POST /compose/run's own description prose (copied verbatim from the published contract) names501 RUN_NOT_YET_DECLARED, a402budget-exceeded refusal and a409 COMPOSE_RUN_CONFLICTidempotency conflict, and the only documented200is the replay shape (ComposeRunReplay,replayed: const true) — the published contract does not model a first-run/fresh-execution response shape at all. Re-verified against a fresh fetch ofhttps://api.wave.online/openapi.json(2026-09-08T15:41:07Z): unchanged, still only a bare200. Modeling the missing error responses or a fresh-run schema here would mean inventing content the gateway does not publish — which would both violate this PR's own verbatim-extraction rule and reintroduceshared-drift/content-digestfindings against the very published-contract-drift check this PR exists to satisfy (CONTRACT-001'scompare()diffs each operation's rawresponsesmap). Tracked as a live-contract completeness gap for the service side, not fixed by fabricating response shapes here. - Not modelled here: 64 pre-existing
x-lifecycle-fieldshared-driftfindings across other, unrelated operations, and CONTRACT-001'scontent-digestcheck (which was already failing before this change, over the same 64 operations) — both out of scope for api-spec#92.
-
POST /voice/generatecontract clarified against the live gateway. The 200 response now documents that the primary path returns rawaudio/mpegbytes directly; theapplication/jsonshapes (VoiceSynthesisInline,VoiceGeneration) are returned only when the request opts out of that default.VoiceGenerateRequestnow requires onlytext—voiceIdis optional (the gateway picks a default when omitted) and is also accepted on the wire as thevoice_idalias the SDK sends; supplying bothvoiceIdandvoice_idon the same request is now rejected at the schema level.Note: this PR originally also proposed renaming
POST /voice/generatetoPOST /voiceand replacingClipCreate's contract. Both are dropped from this revival: the live gateway's x402 paywall gates on the top-level product path segment (/voice/...) for ANY sub-path, including ones that don't exist, so an unauthenticated 402 probe cannot distinguish/voicefrom/voice/generate— the rename claim is unverifiable andmainalready documents/voice/generate, so it stays.ClipCreatewas independently re-verified against the live gateway onmainsince this PR branched (source+in, without/durationas alternatives,sourceType,visibility, and more) and is more thorough than this PR's version;main'sClipCreateis kept as-is.
-
Enhance AI video super-resolution surface (
openapi.yaml) — the live-routedPOST /v1/enhanceroute had no spec entry, so no SDK or CLI method could be generated for it. Adds theEnhancetag and theenhanceVideooperation:POST /enhance(scopeenhance:write), x402-payable via the reusablePaymentRequired402 response. v1 ships exactly one model,espcn(ESPCN super-resolution, fixed 3x factor baked into the trained weights); any othermodelvalue 400s.- Input as raw request body (
video/*/application/octet-stream) or a server-side?url=fetch (httpsonly, non-public hosts rejected), capped at 200 MiB either way. - Binary streaming output with per-job receipt headers:
x-enhance-model,x-enhance-scale-factor,x-enhance-input-dimensions,x-enhance-output-dimensions,x-wave-meter,x-wave-usage-minutes. Billed againstwave_enhance_minutes(output duration in minutes, rounded up). - Failure modes specified alongside the happy path: 400, 401, the 402 x402 challenge, 403,
413,
422 INPUT_TOO_LARGE, 429, 501 (spoke not provisioned), 502, and 503 withRetry-After. Cross-referenced with the async Studio AI enhancement surface (POST /studio-ai/enhancements).
-
MoQ join-token mint surface (
openapi.yaml) — the Media over QUIC product had no spec at all, so no SDK or CLI could be generated for it. Adds theMoQtag and both mint operations:POST /moq/publish/{ns}/{track}(mintMoqPublishToken, scopemoq:write) andGET /moq/subscribe/{ns}/{track}(mintMoqSubscribeToken, scopemoq:read), with the optionalx-wave-declare-protocolpublish header.MoqJoinTokenresponse schema (relayWsUrl,joinToken,expiresIn,ns,track,role,scope, optionalprotocol) and theMoqNamespaceParam/MoqTrackParampath parameters constrained to^[a-z0-9-]{1,64}$.- Failure modes are specified alongside the happy path:
400 MOQ_JOIN_BAD_RESOURCE, 401, the 402 x402 challenge, 403, 429, and the fail-closed503 MOQ_JOIN_UNCONFIGURED. X402PaymentRequired/X402Acceptsschemas and a reusablePaymentRequiredresponse — the 402 body is not theErrorenvelope (itserrormember is a string and the normalized error object is nested undererror_detail), which the spec previously did not capture.- The MoQ media session itself is intentionally not modelled: it is not an HTTP surface. The
tag description explains the direct-to-relay flow, the
joinquery parameter /x-wave-moq-joinheader token carriers, and pins the surface todraft-ietf-moq-transport-18(draft-19, published 2026-07-06, is not yet deployed).
-
WAVE Attestation Standard v1 (
attestation/directory):attestation/ATTESTATION-STANDARD-v1.md— Frozen v1 specification: wire envelope shape, field types, canonicalization algorithm (canonicalJson+attestationId), Ed25519 signature scheme, verification procedure, and v0→v1 version history.attestation/attestation-v1.schema.json— JSON Schema Draft 2020-12 for the v1 envelope. Includes$defsforRenderAttestationSubject,ContextAttestationSubject, andSettlementAttestationSubject. Enforces thealg:"none"⟺sig:nullinvariant viaallOf/if/then/else.attestation/verifier-reference.md— Standalone verifier procedure: step-by-step algorithm,VerifyResult/VerifyErrortype definitions, helper function signatures (attestationSubject,canonicalJson,attestationId,fetchKeys), and outcome reference table.attestation/well-known-keys.md—/.well-known/wave-attestation-keys.jsonendpoint specification: JWKS-style OKP/Ed25519 response shape,kid/x/iat/expfield definitions, key rotation policy (30-day overlap window, ≤5 keys at once), signature wire encoding, and security requirements.
-
OpenAPI component schemas (
openapi.yamlcomponents/schemas):RenderAttestation— render attestation v1 subject payload (thesubjectfield forkind: renderenvelopes).WaveAttestation— full v1 wire envelope schema withid/kind/v/subject/alg/sig/createdfields and thealg/siginvariant.
-
Body content-policy gate (
body-guardCI job,scripts/public-repo-guard/body-policy.sh) — PR titles/bodies, issue bodies, and comment bodies are now scanned server-side, the half of a public repo's surface the tree gate never covered. Blocks credential formats, infrastructure identifiers (internal IPs, operator home paths, hardcoded account IDs), self-identified internal-only markers, and a private repo named near operational detail. A line carryingguard:allow <reason>exempts the infra-identifier tier; credential formats block unconditionally (defang the string to discuss one). -
Streams, productions, cameras, moderation, live pipeline, billing, and analytics operations (
openapi.yaml) — 26 hand-documented operations backing the hosted MCP tool surface, replacing the draftadditionalProperties: trueplaceholders for/billing,/cameras,/moderate,/productions,/streams, and/usage: stream lifecycle (listStreams,createStream,getStream,startStream,stopStream,getStreamStatus,getStreamAnalytics,listStreamHighlights,markStreamHighlight), content moderation (moderateContent), the live transcription pipeline (startLivePipeline,transcribeLiveAudio), multi-camera productions (listProductions,createProduction,getProduction,switchProductionCamera,setProductionOverlay), managed cameras (listCameras,registerCamera,controlCamera), and billing/analytics (getBilling,getBillingUsage,getUsage,getAnalyticsOverview,getAnalyticsTopContent,getAnalyticsEngagement). Every request/response shape is read from the live or in-review route handler, not guessed. -
X402PaymentRequired.error_detail.payment_rejected(openapi.yaml) — documents the additive{ reason, rail }deny verdict the gateway publishes on a 402 when a submitted payment was rejected; absent on an ordinary unpaid challenge. Live receipt: a malformedX-PAYMENTheader returnspayment_rejected: { reason: "invalid_payment_header", rail: "base-usdc" }. Closes #46. (#76) -
Spec coverage from the live gateway skills index (v1.1.0). Diffed the live capability index (
https://gateway.wave.online/.well-known/wave-skills.json, 178 priced capabilities) against this spec's 72 operations and added a draft operation for every capability that had none: 158 newPOST /{name}operations (one per missing product), 157 new tags, and abearerWithScopesOAuth2 security scheme carrying one scope per capability, all drawn verbatim from the live index (no invented fields). Each new operation carries:x-schema-status: draft— the request/response shape isadditionalProperties: truebecause the actual payload contract is not published anywhere the spec can read it.x-skill-url— the capability's own skill document.x-price—model/currency/network/meterfrom the live index, plus, where an unauthenticatedGETon the route returned a real x402 402 challenge, the observedatomicAmountandasset(verified live 2026-09-02; most capabilities gate at a flat 1000-atomic-unit entry price, one at 600000 — these are the gateway's real numbers, not estimates).- Coverage: before 21/178 → after 178/178 (1 allowlisted:
internal, which the live index itself markspricing.model=freeandauth.scope=null).
-
skills-index-coverageCI check (.github/scripts/skills-index-coverage.mjs,.github/scripts/skills-index-allowlist.json) — fetches the live skills index on every PR and push tomainand fails if a live, non-allowlisted priced capability has no matching path segment or tag inopenapi.yaml. Wired intofoundation-gate.yml. -
Console-management operations (
openapi.yaml) — six new operations across four paths give the derived MCP tool plane coverage of the console-management surfaces:GET/POST/pricing/manifests(org-scoped list and validated upsert),POST /custody/{op}with a four-value enum (grant/revoke/inspect/exercise),GET /engine/capabilities, andGET/POST/gpu/infer. Adds thePricingManifestcomponent schema and thePricing,Custody,Engine, andGputags. (#72) -
Agent-auth device authorization ceremony (
openapi.yaml) — two new paths under theAgent Authtag:POST /agent/auth/device(the RFC 8628 bootstrap, deliberately unauthenticated) and its poll counterpart, modelling the device-code grant shape and the honest403while the user has not yet approved. (#66) -
Registered RFC 8628 grant type URN (
openapi.yaml) — the device-authorization poll'sgrant_typeenum now also documents the registered URNurn:ietf:params:oauth:grant-type:device_codealongside the existing baredevice_codeshorthand; both remain valid and the bare form is canonicalized to the URN before forwarding upstream. Non-breaking: no enum value was removed. (#68) -
POST /batchendpoint (openapi.yaml) — documents the batch operation contract: anoperationsarray of method, enforced/v1path, and optional body, capped at 25 items per call. (#64) -
/searchreconciled to the shipped contract (openapi.yaml) — replaces four stale search paths that described an archived contract with the four the gateway actually serves:POST /search(hybrid dense+sparse query),POST /search/index(upsert one document or a batch),DELETE /search/index/{id}, and the shipped suggest/semantic variants. (#56) -
Async render job lifecycle (
openapi.yaml) — documentsGET /render/{jobId}andGET /render/{jobId}/events(server-sent events) so a render started throughPOST /rendercan be polled and streamed to completion. Also fixes a YAML structural bug that had corrupted an adjacent schema. (#34) -
AV Mux/Demuxsurface (openapi.yaml) — documentsPOST /v1/av/remux(combine separate RTP H.264 video and Dante/AES67 audio into one MPEG-TS/fMP4 stream) andPOST /v1/av/demux(the inverse), with theAvRemuxRequest,AvDemuxRequest, andAvTransformResultschemas. (#23) -
Braided Audio publish/stop (
openapi.yaml) — documentsPOST /v1/braid/publishandDELETE /v1/braid/publish/{ns}under a newBraided Audiotag, with theBraidPublishRequest,BraidPublishResult, andBraidStopResultschemas. (#19) -
WAVE Render in the OpenAPI source of truth (
openapi.yaml) — adds theRendertag andPOST /render, payable via the x402 challenge flow, so render gets a generated client instead of a hand-written one. (#12) -
Error responses carry suggestions (
openapi.yaml) — the sharedErrorenvelope gains optionalsuggestions[],did_you_mean[], anddoc_urlfields, matching the gateway's error layer. Additive only. (#10) -
Word-level timestamps on the Voice API (
openapi.yaml) —VoiceGenerateRequestgains atimestampsboolean (defaultfalse); when set,VoiceGeneration.alignmentreturns the newVoiceAlignmentschema with parallelcharacters[]and start/end time arrays. (#9) -
Realtime control/event plane (
openapi.yaml) — adds theRealtimetag and/realtime/connect,/realtime/channels/{channel}/publish,/presence, and/historypaths, each with a per-operationserversoverride pointing athttps://realtime.wave.online. (#4) -
Fleet agent directory resolve (
GET /identity/resolve) — adds theIdentitytag, theidentityResolveoperation (agentquery parameter, optionalorgself-assertion), and theIdentityResolveResponseoneOf (AgentIdentity|TelephonyIdentity). Directory data is public only:key/keysfields are key names, never values. Gated by thedirectory:readscope. (#57) -
SDK types generated from the spec (
generated/api-types.d.ts) —openapi-typescriptnow emits typed paths, operations, and schemas fromopenapi.yaml, and a CI gate regenerates them on every pull request and fails if the committed artifact has drifted. (#49) -
Breaking-change gate — pull requests are diffed against the base branch with
oasdiff; an unacknowledged breaking change fails the build unless the PR body carries an explicitBreaking: yesmarker. (#49) -
MoQ join-token mint surface (
openapi.yaml) — adds theMoQtag and both mint operations:POST /moq/publish/{ns}/{track}andGET /moq/subscribe/{ns}/{track}, theMoqJoinTokenresponse schema, path parameters constrained to^[a-z0-9-]{1,64}$, theX402PaymentRequired/X402Acceptsschemas and the reusablePaymentRequiredresponse (the 402 body is not the standardErrorenvelope). The media session itself is not modelled, since it is not an HTTP surface; the spec pins the direct-to-relay flow todraft-ietf-moq-transport-18. (#30) -
WAVE Attestation Standard v1 (
attestation/directory) — a frozen v1 specification covering the wire envelope shape, canonicalization algorithm, Ed25519 signature scheme, and verification procedure, plus a standalone verifier reference and the/.well-known/wave-attestation-keys.jsonkey-rotation contract (30-day overlap window, up to 5 keys at once). (#14) -
Attestation component schemas (
openapi.yaml) —RenderAttestation(the render attestation v1 subject payload) andWaveAttestation(the full v1 wire envelope, with thealg/siginvariant enforced). (#11) -
capabilities.json— registers this repository with the organization's platform registry for discovery and lifecycle tracking. (#3)
- docs: rewrite README — restructured the top-level README (badges, quick start, repo
layout, related-packages table) and refreshed it against the current spec: clarified that
the x402-payable render operations (
renderVideo,renderPoll,renderEvents) and the device-authorization-flow responses are exceptions to the shared Bearer-token/Error-envelope shape, and fixed the error-envelope example to match theErrorschema's actual field types (detailsis an object,suggestions/did_you_meanare arrays, not strings).
- License: Apache-2.0 — the specification and repository now carry the Apache-2.0
license, with a NOTICE file reserving the WAVE marks;
openapi.yaml'sinfo.licenseand the README were updated to match, and the staging server entry was removed from the spec. (#6)
-
GET/POST /videos/{videoId}/chaptersandPOST /videos/{videoId}/chapters/detect— markeddeprecated: true/x-status: unrouted. Verified live 2026-09-02: the gateway returns403 ROUTE_NOT_MAPPED("this path and method are not part of the WAVE API") for both paths. The live-priced Chapters capability is the flatPOST /chaptersoperation (added above); the nested shape stays documented — deprecated rather than deleted — until it is either wired up or formally removed. -
/leaderboardand/platform— confirmed live 2026-09-02: both return403 ROUTE_NOT_MAPPEDat the gateway. Neither appears in this spec (never did) nor in the live gateway skills index (not a priced capability), so nothing here needed adeprecated: truemarker — they are documented on the publicly servedopenapi.jsonat the API host but are not real operations. Recommend the publicly served copy drop them; out of scope for this spec since they were never present here.
X402PaymentRequired.error_detailnesting (openapi.yaml):error_detailreferenced theErrorenvelope ({ error: { code, ... } }), but the gateway nests the bare error object directly undererror_detail({ code, message, ... }), with no innererrorwrapper — confirmed against a live 402 receipt (curl https://api.wave.online/v1/clips). The inner object is now extracted as theErrorBodycomponent schema (referenced byError, so every other response is unchanged) anderror_detailcomposesErrorBodyinstead, matching the wire shape so generated types no longer expect a nonexistenterror_detail.errormember. (#76)ClipCreate(openapi.yaml) documented{videoId, startTime, endTime}; the livePOST /clipsroute's own request validator requiressource(a recording id) plus anintime string and never acceptedvideoId. Corrected the schema to the real contract and addedClipCreateResponse/ClipErrorfor the operation's actual 201/400 shapes (the 400 body is the route's own{ok, error, detail}envelope, not the sharedErrorschema).GET /leaderboard,GET /platform(openapi.yaml) — documented for real, following the 1.1.0 gateway deploy that moved both gateway-native root surfaces behind operator/tenant auth (measured live 2026-09-06). Both had been exempted in the drift allowlist asundocumented-livewhile unauthenticated; the exemption's own justification named real documentation as the intended remedy once each operation gained a security requirement, which it now has. NewOperatortag for/platform's operator-only shape..github/scripts/published-drift-normalize.mjs: two new normalization rules measured against the 1.1.0 publish — the service overwrites a hand-written 4xx response with its generic injected envelope even when this repo already declares a real one for that code (previously only the "repo lacks the code" case was normalized), and it drops a parameter'sdescription/example/schema.patternwhile publishing itsname/in/required/type faithfully. Both are matched by exact residual shape, never by key name, and both are reported (not silently absorbed) via newenrichmentObservationsfields..github/scripts/published-drift-allowlist.json— newshared-driftexemptions forGET /usage(a key collision between this repo's real/v1billing endpoint and the gateway's own unrelated, operator-only telemetry route that publishes at the same literal path with no distinguishingserversoverride),GET/POST /streams(the gateway still serves the auto-generated skills-index placeholder; this repo has promoted the real/v1shape ahead of the gateway shipping it), andPOST /agent/auth/token(the service's own generated 400 response is a coarser single-schema shape than this repo's accurateoneOfdocumentation of the RFC 8628 device-flow passthrough). Newunpublished-repoexemptions forDELETE /videos/{videoId}/chapters/{chapterId}andGET /videos/{videoId}/chapters/detect/{jobId}: both are live-probed and confirmed answering (notROUTE_NOT_MAPPED), but the published/openapi.jsondocument has not yet registered them — a gap in the service's own spec generation, not in this repo's declaration.
1.0.0 - 2026-04-05
- Initial public release: the WAVE OpenAPI 3.1 specification (12 API modules).