Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,22 @@ 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
Expand Down
173 changes: 173 additions & 0 deletions openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,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
Expand Down Expand Up @@ -1053,6 +1059,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:
Expand Down Expand Up @@ -1590,6 +1601,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
Comment thread
devin-ai-integration[bot] marked this conversation as resolved.
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.
Comment thread
devin-ai-integration[bot] marked this conversation as resolved.


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
Comment on lines +1658 to +1666

@devin-ai-integration devin-ai-integration Bot Aug 6, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 Wildcard media type video/* in request/response content

The request body and 200 response use the media-type range video/* alongside application/octet-stream. OpenAPI 3.1 permits ranges, but many generators (openapi-generator, oazapfts, some Spectral rulesets) handle wildcard keys poorly or emit awkward method signatures — relevant because the stated motivation of this PR is enabling SDK/CLI generation. Worth verifying the generators used downstream produce a usable binary-body method for this operation.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

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).
Comment on lines +1672 to +1696

@devin-ai-integration devin-ai-integration Bot Aug 6, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 Response headers use an x-enhance- prefix instead of the repo's x-wave- convention

Four of the six documented response headers use an x-enhance- prefix (x-enhance-model, x-enhance-scale-factor, x-enhance-input-dimensions, x-enhance-output-dimensions) while the metering headers on the same response use x-wave- (x-wave-meter, x-wave-usage-minutes), and other surfaces in this spec consistently use x-wave-* (e.g. x-wave-sha256 on /render at openapi.yaml:117, x-wave-moq-join at openapi.yaml:92). Worth confirming with the gateway implementation which prefix is actually emitted, since the spec is the SDK source of truth.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

content:
video/*:
schema:
type: string
format: binary
Comment thread
qodo-code-review[bot] marked this conversation as resolved.
Comment thread
devin-ai-integration[bot] marked this conversation as resolved.
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'
Comment on lines +1724 to +1725

@devin-ai-integration devin-ai-integration Bot Aug 6, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 402 handling differs from the other x402-payable endpoints

Other x402-payable operations (/render at openapi.yaml:145-161, /av/remux, /av/demux) define their 402 inline with a WWW-Authenticate challenge header and the Error schema, while /enhance reuses #/components/responses/PaymentRequired, whose documented body is X402PaymentRequired (error as a plain string, normalized error under error_detail) and which advertises the x-payment retry flow rather than WWW-Authenticate. Generated clients will therefore parse enhance 402s differently from render/av 402s; confirm which challenge form the enhance route actually returns.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified against wave-gateway source: unauthenticated /v1/enhance requests hit the gateway's paymentChallenge (x402-pay-lane.ts), which returns the X402PaymentRequired JSON envelope with no WWW-Authenticate header, so referencing #/components/responses/PaymentRequired is the accurate form. The inline WWW-Authenticate 402s on /render and /av/* are a pre-existing inconsistency in those entries, out of scope for this PR.

Comment on lines +1714 to +1725

@devin-ai-integration devin-ai-integration Bot Aug 6, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 Optional-auth pattern differs from other x402 routes in this spec

This operation declares security: [{}, {BearerAuth: []}], whereas the other x402-payable routes (/render at openapi.yaml:118, and the AV routes) use security: [] to disable the global bearer requirement. Both are valid OpenAPI, but the two forms generate different SDK/CLI auth handling (optional-auth vs no-auth). Worth aligning the convention across x402 routes so generated clients behave uniformly.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment thread
devin-ai-integration[bot] marked this conversation as resolved.
'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:
Expand Down
Loading