Skip to content

openapi.yaml declares voice/transcribe/captions/phone/podcast endpoints that 404 in production (SDK is codegen’d from it) #33

Description

@yakimoto

Found while grounding wave-context#72 (building real ADK modules + MCP tools against the shipped fleet products). Part of wave-context#48.

The problem

openapi.yaml declares endpoints for voice, transcribe, and captions that do not exist in the deployed spokes. Because the SDK is code-generated from this spec, the generated client exposes methods that will 404 against production.

The spec's servers: is https://api.wave.online/v1, and the gateway forwards /v1/<product> as-is to the product spoke — spoke-routing.ts is a pure prefix map with no path rewriting:

wave-gateway/src/spoke-routing.ts
  if (path === "/v1/voice"      || path.startsWith("/v1/voice/"))      return env.VOICE_ORIGIN      || null;
  if (path === "/v1/transcribe" || path.startsWith("/v1/transcribe/")) return env.TRANSCRIBE_ORIGIN || null;
  if (path === "/v1/captions"   || path.startsWith("/v1/captions/"))   return env.CAPTIONS_ORIGIN   || null;

So a declared path routes to the right spoke — and then the spoke 404s it, because each spoke owns its whole /v1 namespace and matches paths exactly.

Declared vs implemented

Spec declares Spoke actually implements Result
GET /voice/voices
POST /voice/generate
POST /voice/clone
wave-voice-edge/src/api.ts: POST /v1/voice + GET /v1/healthz only all 3 → 404
GET /transcribe
POST /transcribe
GET /transcribe/{transcriptionId}
wave-transcribe-edge/src/api.ts: POST /v1/transcribe + GET /v1/healthz only POST works; GET /transcribe and /{id}404
GET /captions
POST /captions
GET,DELETE /captions/{jobId}
GET /captions/{jobId}/download
wave-captions-edge/src/api.ts: POST /v1/captions, POST /v1/live/{caption,pipeline,fanout}, GET /v1/healthz POST works; the 4 job-resource routes → 404
GET,POST /phone/lines
GET,POST /phone/calls
wave-phone-edge has no api.ts at allsrc/ is favicon.ts, landing.ts, tokens.css.ts, worker.ts unimplemented
GET /podcast/shows
GET /podcast/shows/{showId}/episodes
wave-podcast-edge — same: landing shim only unimplemented

Each spoke's router ends with an explicit catch-all, so this is by design, not a routing accident:

/** Route /v1/* requests. Owns the whole /v1 namespace (unknown /v1 path → 404). */

return json({ error: 'not found' }, 404);

Note also that /v1/phone and /v1/podcast appear nowhere in spoke-routing.ts, so those paths don't even reach a product spoke — they fall through to the WSC origin.

How I verified (and what I did not verify)

  • Read each spoke's src/api.ts at origin/main via git show (branch-truth, not a working tree).
  • Read wave-gateway/src/spoke-routing.ts at origin/main to confirm passthrough, no rewrite.
  • Live probing could not settle this and should not be used to: unauthenticated requests return 402 for any path under a routed product prefix — /v1/voice/definitely-not-real returns 402 exactly like /v1/voice/generate. The billing gate fires before routing, so a 402 proves only that the prefix is routed, never that the endpoint exists. (An unrouted prefix returns 403.)
  • Not verified: the /editor/*, /studio-ai/*, /search/*, /sentiment/*, /collab/* groups. Those have no entry in spoke-routing.ts, so they fall through to WSC, whose source I did not read. They may well be real — this report deliberately does not claim either way.

Why it matters

A spec is a contract, and this one is the input to SDK codegen — so an over-declared path becomes a shipped client method that fails at runtime for whoever calls it. It is also the artifact an agent or integrator reads to decide what WAVE can do. This is the same failure class the Content Engine exists to catch (a confident, precise, well-formatted artifact that is wrong), just in a different medium.

Suggested fix

  1. Cut the unimplemented paths from openapi.yaml, or move them behind an explicit x-wave-phase: planned extension that codegen skips, so "planned" never renders as a callable SDK method.
  2. Correct the three real ones to the paths that actually exist: POST /voice, POST /transcribe, POST /captions (relative to the /v1 server base) — noting that voice returns audio bytes and captions returns a caption file, not JSON.
  3. Add a CI check that diffs declared paths against the spokes' routers, so the spec can't silently drift from the deployed surface again.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions