diff --git a/.github/scripts/published-drift-allowlist.json b/.github/scripts/published-drift-allowlist.json index a43e25e..5e92edd 100644 --- a/.github/scripts/published-drift-allowlist.json +++ b/.github/scripts/published-drift-allowlist.json @@ -65,6 +65,15 @@ "direction": "unpublished-repo", "justification": "Same reasoning and same measurement date as DELETE /videos/{videoId}/chapters/{chapterId}: live-probed and confirmed answering, not ROUTE_NOT_MAPPED, but absent from the published /openapi.json document. A gap in the service's own spec generation. Lapses (and should be deleted) the moment the published document lists this operation." }, + { + "path": "/enhance", + "method": "POST", + "direction": "shared-drift", + "justification": "Measured live 2026-09-05: an unauthenticated POST to https://api.wave.online/v1/enhance returns 402 (x402 challenge, atomicAmount 600000 matching this repo's documented wave_enhance_minutes meter), confirming the route is genuinely live and priced. The published /openapi.json document still carries the auto-generated skills-index draft placeholder (x-schema-status: draft, operationId enhance, application/json additionalProperties:true body) because the service's own spec generation has not caught up to the real handler. openapi.yaml has been hand-documented ahead of that (operationId enhanceVideo, binary video body, model/url query params, per-job response headers) from the actual endpoint behavior, not guessed. Exempt only while the published operation still claims x-schema-status: draft; the moment the service promotes it out of draft, the two shapes get compared for real.", + "expect": { + "x-schema-status": "draft" + } + }, { "path": "/identity/resolve", "method": "GET", diff --git a/CHANGELOG.md b/CHANGELOG.md index 5037045..bf3a02f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,99 @@ All notable changes to this project are documented here. The format is based on ### Added +- **Enhance AI video super-resolution surface** (`openapi.yaml`) — the live-routed + `POST /v1/enhance` route had no spec entry, so no SDK or CLI method could be + generated for it. Adds the `Enhance` tag and the `enhanceVideo` operation: + - `POST /enhance` (scope `enhance:write`), x402-payable via the reusable `PaymentRequired` + 402 response. v1 ships exactly one model, `espcn` (ESPCN super-resolution, fixed 3x factor + baked into the trained weights); any other `model` value 400s. + - Input as raw request body (`video/*` / `application/octet-stream`) or a server-side `?url=` + fetch (`https` only, 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 against `wave_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 with + `Retry-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 the `MoQ` tag and both mint operations: + - `POST /moq/publish/{ns}/{track}` (`mintMoqPublishToken`, scope `moq:write`) and + `GET /moq/subscribe/{ns}/{track}` (`mintMoqSubscribeToken`, scope `moq:read`), with the + optional `x-wave-declare-protocol` publish header. + - `MoqJoinToken` response schema (`relayWsUrl`, `joinToken`, `expiresIn`, `ns`, `track`, `role`, + `scope`, optional `protocol`) and the `MoqNamespaceParam` / `MoqTrackParam` path 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-closed `503 MOQ_JOIN_UNCONFIGURED`. + - `X402PaymentRequired` / `X402Accepts` schemas and a reusable `PaymentRequired` response — the + 402 body is **not** the `Error` envelope (its `error` member is a string and the normalized + error object is nested under `error_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 `join` query parameter / + `x-wave-moq-join` header token carriers, and pins the surface to + `draft-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 `$defs` for `RenderAttestationSubject`, `ContextAttestationSubject`, + and `SettlementAttestationSubject`. Enforces the `alg:"none"` ⟺ `sig:null` invariant + via `allOf/if/then/else`. + - `attestation/verifier-reference.md` — Standalone verifier procedure: step-by-step + algorithm, `VerifyResult`/`VerifyError` type definitions, helper function signatures + (`attestationSubject`, `canonicalJson`, `attestationId`, `fetchKeys`), and outcome + reference table. + - `attestation/well-known-keys.md` — `/.well-known/wave-attestation-keys.json` endpoint + specification: JWKS-style OKP/Ed25519 response shape, `kid`/`x`/`iat`/`exp` field + definitions, key rotation policy (30-day overlap window, ≤5 keys at once), signature + wire encoding, and security requirements. +- **OpenAPI component schemas** (`openapi.yaml` `components/schemas`): + - `RenderAttestation` — render attestation v1 subject payload (the `subject` field for + `kind: render` envelopes). + - `WaveAttestation` — full v1 wire envelope schema with `id`/`kind`/`v`/`subject`/ + `alg`/`sig`/`created` fields and the `alg`/`sig` invariant. +||||||| 6649096 +- **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 the `MoQ` tag and both mint operations: + - `POST /moq/publish/{ns}/{track}` (`mintMoqPublishToken`, scope `moq:write`) and + `GET /moq/subscribe/{ns}/{track}` (`mintMoqSubscribeToken`, scope `moq:read`), with the + optional `x-wave-declare-protocol` publish header. + - `MoqJoinToken` response schema (`relayWsUrl`, `joinToken`, `expiresIn`, `ns`, `track`, `role`, + `scope`, optional `protocol`) and the `MoqNamespaceParam` / `MoqTrackParam` path 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-closed `503 MOQ_JOIN_UNCONFIGURED`. + - `X402PaymentRequired` / `X402Accepts` schemas and a reusable `PaymentRequired` response — the + 402 body is **not** the `Error` envelope (its `error` member is a string and the normalized + error object is nested under `error_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 `join` query parameter / + `x-wave-moq-join` header token carriers, and pins the surface to + `draft-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 `$defs` for `RenderAttestationSubject`, `ContextAttestationSubject`, + and `SettlementAttestationSubject`. Enforces the `alg:"none"` ⟺ `sig:null` invariant + via `allOf/if/then/else`. + - `attestation/verifier-reference.md` — Standalone verifier procedure: step-by-step + algorithm, `VerifyResult`/`VerifyError` type definitions, helper function signatures + (`attestationSubject`, `canonicalJson`, `attestationId`, `fetchKeys`), and outcome + reference table. + - `attestation/well-known-keys.md` — `/.well-known/wave-attestation-keys.json` endpoint + specification: JWKS-style OKP/Ed25519 response shape, `kid`/`x`/`iat`/`exp` field + definitions, key rotation policy (30-day overlap window, ≤5 keys at once), signature + wire encoding, and security requirements. +- **OpenAPI component schemas** (`openapi.yaml` `components/schemas`): + - `RenderAttestation` — render attestation v1 subject payload (the `subject` field for + `kind: render` envelopes). + - `WaveAttestation` — full v1 wire envelope schema with `id`/`kind`/`v`/`subject`/ + `alg`/`sig`/`created` fields and the `alg`/`sig` invariant. - **Streams, productions, cameras, moderation, live pipeline, billing, and analytics operations** (`openapi.yaml`) — 26 hand-documented operations backing the hosted MCP tool surface, replacing the draft diff --git a/contract-drift.json b/contract-drift.json index 91dcdea..e5cceb1 100644 --- a/contract-drift.json +++ b/contract-drift.json @@ -1,6 +1,6 @@ { "about": "Point-in-time operation-level diff between this repo's openapi.yaml and the contract the gateway publishes. It is a dated receipt, not a live view: regenerate with `node .github/scripts/published-drift.mjs openapi.yaml --out contract-drift.json`. The published-contract-drift workflow uploads a fresh copy on every scheduled run. The PUBLISHED half of this receipt ages on the gateway's schedule and only the scheduled drift job can refresh it; the REPO half is pinned by sources.repoOperationsDigest, which the freshness job checks offline on every run so this file cannot quietly disagree with the openapi.yaml sitting next to it.", - "generatedAt": "2026-09-06T03:13:47.098Z", + "generatedAt": "2026-09-06T03:29:53.883Z", "criterion": [ "CONTRACT-001", "COMPAT-001", @@ -10,7 +10,7 @@ "repoSpec": "openapi.yaml", "repoCommit": null, "publishedSpec": "https://api.wave.online/openapi.json", - "repoOperationsDigest": "370de508452ec90d02d457c2aa66d1c5c3b1811957b23d14beeea6037ff7ba41" + "repoOperationsDigest": "c444b8649ef29f806ee2a0a178ed1fca487582e7e10ddd5e7f4fa651b1c98c8a" }, "headline": { "repoVersion": "1.1.0", @@ -26,7 +26,7 @@ "liveProbed": 0, "sharedDrift": 0, "draftNotYetPublished": 0, - "allowlisted": 9, + "allowlisted": 10, "lapsedAllowlistEntries": 0, "unmatchedAllowlistEntries": 0 }, @@ -88,6 +88,12 @@ "direction": "shared-drift", "justification": "Same editorial-only reasoning as GET /leaderboard: an assigned operationId and a Title-Cased tag versus the gateway's unnamed, lowercase-tagged generated shape. Keyed on the exact published summary so a real content change still surfaces as drift." }, + { + "path": "/enhance", + "method": "POST", + "direction": "shared-drift", + "justification": "Measured live 2026-09-05: an unauthenticated POST to https://api.wave.online/v1/enhance returns 402 (x402 challenge, atomicAmount 600000 matching this repo's documented wave_enhance_minutes meter), confirming the route is genuinely live and priced. The published /openapi.json document still carries the auto-generated skills-index draft placeholder (x-schema-status: draft, operationId enhance, application/json additionalProperties:true body) because the service's own spec generation has not caught up to the real handler. openapi.yaml has been hand-documented ahead of that (operationId enhanceVideo, binary video body, model/url query params, per-job response headers) from the actual endpoint behavior, not guessed. Exempt only while the published operation still claims x-schema-status: draft; the moment the service promotes it out of draft, the two shapes get compared for real." + }, { "path": "/identity/resolve", "method": "GET", @@ -133,12 +139,14 @@ "GET /videos/{videoId}/chapters", "POST /videos/{videoId}/chapters", "POST /videos/{videoId}/chapters/detect", + "POST /studio-ai/enhancements", "GET /realtime/connect", "POST /realtime/channels/{channel}/publish", "GET /realtime/channels/{channel}/presence", "GET /realtime/channels/{channel}/history", "POST /braid/publish", "DELETE /braid/publish/{ns}", + "POST /enhance", "POST /av/remux", "POST /av/demux", "POST /moq/publish/{ns}/{track}", @@ -202,7 +210,6 @@ "POST /embeddings", "POST /encode", "POST /engagement", - "POST /enhance", "POST /example", "POST /experiments", "POST /fleet", @@ -304,11 +311,12 @@ "POST /zoom-integration" ], "operationIdsSynthesized": 0, - "errorResponsesInjected": 799, + "errorResponsesInjected": 797, "errorResponsesOverwritten": [ "GET /videos/{videoId}/chapters (404)", "POST /videos/{videoId}/chapters (404)", - "POST /videos/{videoId}/chapters/detect (404)" + "POST /videos/{videoId}/chapters/detect (404)", + "POST /enhance (400, 401)" ], "parametersStripped": 77 }, diff --git a/generated/api-types.d.ts b/generated/api-types.d.ts index 94a8365..900f0cd 100644 --- a/generated/api-types.d.ts +++ b/generated/api-types.d.ts @@ -1063,7 +1063,10 @@ export interface paths { /** List enhancement jobs */ get: operations["listEnhancements"]; put?: never; - /** Create an enhancement job */ + /** + * Create an enhancement job + * @description Creates an asynchronous, credit-billed enhancement job (including `upscale` and `super_resolution`) against a library video, returning a job envelope to poll. For synchronous, pay-per-call super-resolution of ad-hoc video bytes (billed against `wave_enhance_minutes`), use `POST /enhance` instead. + */ post: operations["createEnhancement"]; delete?: never; options?: never; @@ -1347,6 +1350,28 @@ export interface paths { patch?: never; trace?: never; }; + "/enhance": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Super-resolve a video with an AI model (x402-payable) + * @description Upscales a video with an on-graph AI model. v1 ships exactly one model, `espcn` (ESPCN super-resolution, a fixed exact 3x factor baked into the trained weights — not a runtime parameter). Internally every input frame is letterboxed to a fixed working canvas before inference, so the v1 output resolution is fixed regardless of input resolution; a future model may support other output shapes. Send either the raw video bytes as the request body or a `url` query parameter pointing at an `https` source (the source is fetched server-side; redirects are not followed and non-public/loopback/private hosts are rejected). Auth is either lane: a bearer API key carrying the `enhance:write` entitlement, or no key at all — an unauthenticated call receives the 402 x402 challenge and is served once paid (pay-per-call). Billed against the `wave_enhance_minutes` meter: the OUTPUT artifact's rendered duration in minutes, rounded up to the next whole minute. + * + * Distinct from the Studio AI enhancement surface (`POST /studio-ai/enhancements`, which also offers `upscale`/`super_resolution` job types): Studio AI runs asynchronous, credit-billed jobs against library videos and returns a job envelope, whereas this endpoint synchronously super-resolves ad-hoc video bytes (or an `https` source) and streams the result back, billing `wave_enhance_minutes`. Use Studio AI for library workflows; use this endpoint for direct, pay-per-call enhancement. + */ + post: operations["enhanceVideo"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; "/av/remux": { parameters: { query?: never; @@ -2595,26 +2620,6 @@ export interface paths { patch?: never; trace?: never; }; - "/enhance": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - get?: never; - put?: never; - /** - * WAVE enhance API - * @description Generated from the live gateway skills index (not yet hand-documented). The route is confirmed live at the gateway; the request/response shape below is a draft placeholder (`additionalProperties: true`) pending the product team's schema. Method is POST, inferred from the `enhance:write` scope; the gateway's paywall is a flat per-product gate, so other verbs may also be live. - */ - post: operations["enhance"]; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; "/example": { parameters: { query?: never; @@ -9136,6 +9141,110 @@ export interface operations { }; }; }; + enhanceVideo: { + parameters: { + query?: { + /** @description AI model to apply. v1 supports only `espcn`; any other value 400s. */ + model?: "espcn"; + /** @description Fetch the source video from this `https` URL instead of sending it as the request body. Must be publicly reachable — loopback, private, link-local, and `.local`/ `.internal` hosts are rejected before any fetch. */ + url?: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + /** @description The source video, raw bytes. Omit the body (and use `?url=` instead) to have the source fetched server-side. Max 200 MiB either way. */ + requestBody?: { + content: { + "video/*": string; + "application/octet-stream": string; + }; + }; + responses: { + /** @description The enhanced video, binary, streamed back with the same content-type as the source. Per-job receipt and billing metadata are carried on response headers, not a JSON body. */ + 200: { + headers: { + /** @description The model that ran, e.g. `espcn`. */ + "x-enhance-model"?: string; + /** @description Upscale factor actually applied. */ + "x-enhance-scale-factor"?: number; + /** @description Input frame dimensions as `WIDTHxHEIGHT`, e.g. `1280x720`. */ + "x-enhance-input-dimensions"?: string; + /** @description Output frame dimensions as `WIDTHxHEIGHT`, e.g. `672x672`. */ + "x-enhance-output-dimensions"?: string; + /** @description The meter this job billed against — `wave_enhance_minutes`. */ + "x-wave-meter"?: string; + /** @description Output-duration minutes billed for this job (rounded up). */ + "x-wave-usage-minutes"?: number; + [name: string]: unknown; + }; + content: { + "video/*": string; + "application/octet-stream": string; + }; + }; + /** @description Invalid request — unrecognized `model`, an unsafe or invalid `url`, or no video supplied (neither a request body nor `?url=`). */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Authentication failed AND the x402 pay-per-call lane is not armed in this environment. When the pay lane is armed (production default), a missing or unrecognized API key yields the 402 x402 challenge instead — never this 401. A client should treat 401 as "re-authenticate with a valid bearer key" (pay-per-call is unavailable here). */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + 402: components["responses"]["PaymentRequired"]; + 403: components["responses"]["Forbidden"]; + /** @description The source video (request body, or the resolved `?url=` source) exceeds the 200 MiB limit. */ + 413: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description `INPUT_TOO_LARGE` — the source video's frame dimensions exceed the v1 input-size cap. Downscale the source and retry. */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + 429: components["responses"]["RateLimitError"]; + /** @description Enhance is not yet available in this environment (the spoke is not provisioned). */ + 501: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + 502: components["responses"]["UpstreamError"]; + /** @description The enhance backend is temporarily unavailable (e.g. cold-start / pool exhaustion). Retryable; honor the `Retry-After` header. */ + 503: { + headers: { + /** @description Seconds to wait before retrying. */ + "Retry-After"?: number; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; avRemux: { parameters: { query?: never; @@ -11168,37 +11277,6 @@ export interface operations { 429: components["responses"]["RateLimitError"]; }; }; - enhance: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody?: { - content: { - "application/json": { - [key: string]: unknown; - }; - }; - }; - responses: { - /** @description Capability response (draft — shape not yet published). */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - [key: string]: unknown; - }; - }; - }; - 402: components["responses"]["PaymentRequired"]; - 403: components["responses"]["Forbidden"]; - 429: components["responses"]["RateLimitError"]; - }; - }; example: { parameters: { query?: never; diff --git a/openapi.yaml b/openapi.yaml index 6e23453..30cb292 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -88,6 +88,12 @@ tags: Synchronous container-transform for local IP video + Dante/AES67 audio: mux separate RTP H.264 video and Dante audio into one sync-locked MPEG-TS or fMP4 stream, and demux the inverse. x402 metered per minute (`wave_av_minutes`) plus per transform-second (`wave_av_transform_seconds`). + - name: Enhance + description: >- + AI video super-resolution. v1 ships one model, `espcn` (ESPCN, a fixed exact 3x upscale) — + unrecognized `model` values 400. Requires the `enhance:write` entitlement; x402-payable + per-output-minute (`wave_enhance_minutes`, rounded up to the nearest minute of rendered + output duration). - name: Braided Audio description: >- On-demand WAVE Braided Audio publish — mixes multiple named audio sources into one @@ -3338,6 +3344,11 @@ paths: tags: [Studio AI] summary: Create an enhancement job operationId: createEnhancement + description: >- + Creates an asynchronous, credit-billed enhancement job (including `upscale` and + `super_resolution`) against a library video, returning a job envelope to poll. For + synchronous, pay-per-call super-resolution of ad-hoc video bytes (billed against + `wave_enhance_minutes`), use `POST /enhance` instead. requestBody: required: true content: @@ -3838,6 +3849,168 @@ paths: schema: $ref: '#/components/schemas/Error' + # Enhance API + /enhance: + post: + tags: [Enhance] + summary: Super-resolve a video with an AI model (x402-payable) + operationId: enhanceVideo + description: >- + Upscales a video with an on-graph AI model. v1 ships exactly one model, `espcn` (ESPCN + super-resolution, a fixed exact 3x factor baked into the trained weights — not a runtime + parameter). Internally every input frame is letterboxed to a fixed working canvas before + inference, so the v1 output resolution is fixed regardless of input resolution; a future + model may support other output shapes. Send either the raw video bytes as the request body + or a `url` query parameter pointing at an `https` source (the source is fetched + server-side; redirects are not followed and non-public/loopback/private hosts are + rejected). Auth is either lane: a bearer API key carrying the `enhance:write` + entitlement, or no key at all — an unauthenticated call receives the 402 x402 challenge + and is served once paid (pay-per-call). Billed against the + `wave_enhance_minutes` meter: the OUTPUT artifact's rendered duration in minutes, rounded + up to the next whole minute. + + + Distinct from the Studio AI enhancement surface (`POST /studio-ai/enhancements`, which + also offers `upscale`/`super_resolution` job types): Studio AI runs asynchronous, + credit-billed jobs against library videos and returns a job envelope, whereas this + endpoint synchronously super-resolves ad-hoc video bytes (or an `https` source) and + streams the result back, billing `wave_enhance_minutes`. Use Studio AI for library + workflows; use this endpoint for direct, pay-per-call enhancement. + security: + - {} + - BearerAuth: [] + parameters: + - name: model + in: query + required: false + schema: + type: string + enum: [espcn] + default: espcn + description: AI model to apply. v1 supports only `espcn`; any other value 400s. + - name: url + in: query + required: false + schema: + type: string + format: uri + description: >- + Fetch the source video from this `https` URL instead of sending it as the request + body. Must be publicly reachable — loopback, private, link-local, and `.local`/ + `.internal` hosts are rejected before any fetch. + requestBody: + required: false + description: >- + The source video, raw bytes. Omit the body (and use `?url=` instead) to have the source + fetched server-side. Max 200 MiB either way. + content: + video/*: + schema: + type: string + format: binary + application/octet-stream: + schema: + type: string + format: binary + responses: + '200': + description: >- + The enhanced video, binary, streamed back with the same content-type as the source. + Per-job receipt and billing metadata are carried on response headers, not a JSON body. + headers: + x-enhance-model: + schema: + type: string + description: The model that ran, e.g. `espcn`. + x-enhance-scale-factor: + schema: + type: number + description: Upscale factor actually applied. + x-enhance-input-dimensions: + schema: + type: string + description: Input frame dimensions as `WIDTHxHEIGHT`, e.g. `1280x720`. + x-enhance-output-dimensions: + schema: + type: string + description: Output frame dimensions as `WIDTHxHEIGHT`, e.g. `672x672`. + x-wave-meter: + schema: + type: string + description: The meter this job billed against — `wave_enhance_minutes`. + x-wave-usage-minutes: + schema: + type: integer + description: Output-duration minutes billed for this job (rounded up). + content: + video/*: + schema: + type: string + format: binary + application/octet-stream: + schema: + type: string + format: binary + '400': + description: >- + Invalid request — unrecognized `model`, an unsafe or invalid `url`, or no video + supplied (neither a request body nor `?url=`). + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '401': + description: >- + Authentication failed AND the x402 pay-per-call lane is not armed in this environment. + When the pay lane is armed (production default), a missing or unrecognized API key + yields the 402 x402 challenge instead — never this 401. A client should treat 401 as + "re-authenticate with a valid bearer key" (pay-per-call is unavailable here). + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '402': + $ref: '#/components/responses/PaymentRequired' + '403': + $ref: '#/components/responses/Forbidden' + '413': + description: The source video (request body, or the resolved `?url=` source) exceeds the 200 MiB limit. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '422': + description: >- + `INPUT_TOO_LARGE` — the source video's frame dimensions exceed the v1 input-size cap. + Downscale the source and retry. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '429': + $ref: '#/components/responses/RateLimitError' + '501': + description: Enhance is not yet available in this environment (the spoke is not provisioned). + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '502': + $ref: '#/components/responses/UpstreamError' + '503': + description: >- + The enhance backend is temporarily unavailable (e.g. cold-start / pool exhaustion). + Retryable; honor the `Retry-After` header. + headers: + Retry-After: + schema: + type: integer + description: Seconds to wait before retrying. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + # AV Mux/Demux API — synchronous container-transform path (RTP H.264 video + Dante/AES67 audio). /av/remux: post: @@ -6603,52 +6776,6 @@ paths: $ref: '#/components/responses/Forbidden' '429': $ref: '#/components/responses/RateLimitError' - - /enhance: - post: - tags: [Enhance] - operationId: enhance - summary: WAVE enhance API - x-schema-status: draft - x-skill-url: https://gateway.wave.online/.well-known/wave-skills/enhance.json - description: > - Generated from the live gateway skills index (not yet hand-documented). The - route is confirmed live at the gateway; the request/response shape below is a - draft placeholder (`additionalProperties: true`) pending the product team's - schema. Method is POST, inferred from the `enhance:write` scope; the - gateway's paywall is a flat per-product gate, so other verbs may also be live. - x-price: - model: x402 - meter: wave_enhance_minutes - currency: USDC - network: base - atomicAmount: "600000" - asset: "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913" - x-price-note: Observed live via an unauthenticated x402 402 challenge on enhance; a flat per-call gate, not necessarily the metered rate. - security: - - bearerWithScopes: [enhance:write] - requestBody: - required: false - content: - application/json: - schema: - type: object - additionalProperties: true - responses: - '200': - description: Capability response (draft — shape not yet published). - content: - application/json: - schema: - type: object - additionalProperties: true - '402': - $ref: '#/components/responses/PaymentRequired' - '403': - $ref: '#/components/responses/Forbidden' - '429': - $ref: '#/components/responses/RateLimitError' - /example: post: tags: [Example]