Skip to content

docs(tts): document Content-Type=audio/wav + fix Accept-header claim in TTS spec - #394

Open
abhishekmishragithub wants to merge 4 commits into
mainfrom
fix/tts-content-type-note
Open

docs(tts): document Content-Type=audio/wav + fix Accept-header claim in TTS spec#394
abhishekmishragithub wants to merge 4 commits into
mainfrom
fix/tts-content-type-note

Conversation

@abhishekmishragithub

Copy link
Copy Markdown
Collaborator

Summary

Two TTS API-ref corrections against source-verified server behavior. Triggered by a support ticket where output_format: mp3 "returned WAV" (customer's WhatsApp Cloud API upload rejected it).

The two things wrong on main

  1. Accept: audio/wav was documented as required and its omission was said to return "empty or unplayable" responses. Both false.
  2. The response Content-Type header behavior was undocumented. Server always sends audio/wav regardless of output_format. That is what tripped the customer's WhatsApp upload (WhatsApp MIME-sniffs the header).

Source of truth (waves-platform)

apps/main-backend/src/routes/speech/tts/lightning-v3/lightning-v3.2.controller.ts:110
    res.setHeader("Content-Type", "audio/wav");
    res.send(finalBuffer);

Hardcoded, no branch on output_format, no Accept read upstream. Same pattern in the lightning, lightning-large, and streaming controllers (9 call sites total, all identical).

apps/main-backend/src/routes/speech/tts/lightning-v3/lightning-v3.schema.ts
    output_format: z.enum(["wav", "ulaw", "alaw", "pcm", "mp3"])

Matches the API's own 400 invalid_enum_value error message. ogg, opus, aac are not supported on this endpoint.

Live verification captured

  • output_format: mp3 returns MPEG audio at the byte level: file reports MPEG ADTS, layer III, v2, 128 kbps, 24 kHz, Monaural; body starts with ID3 or 0xFF Fx frame sync.
  • Response Content-Type header is audio/wav for mp3, wav, pcm, ulaw, alaw alike.
  • Accept header variations (MISSING, */*, audio/wav, audio/mpeg, audio/mp3, audio/*, application/json) all return identical bytes and identical response headers.
  • Request IDs from probes are in the changelog entry.

Spec changes

  • output_format field: expanded description with the hardcoded-Content-Type paragraph, per-format recommended MIME overrides (audio/mpeg for mp3, audio/basic for ulaw/alaw, no MIME for pcm), and the Accept-is-not-honored note.
  • 200 response: new Content-Type header schema (enum with the single value audio/wav), plus a byte-vs-header caveat in the description and the audio/wav content schema.
  • Accept header parameter: required: truerequired: false. Description rewritten to say ignored.
  • "Common gotchas" list: dropped the false "Set Accept: audio/wav" bullet, added two real ones (Content-Type is hardcoded; Accept is ignored).
  • 4 cURL examples in the description block: dropped the misleading -H "Accept: audio/wav" line.
  • New changelog entry.

Test plan

  • fern check clean
  • Em-dash sweep on diff clean
  • Accept: audio/wav count in the file: 0 (was 5)
  • Live-verified byte content of mp3 output against file(1) and hex dump (ID3 + frame sync)
  • Cross-checked source: res.setHeader("Content-Type", "audio/wav") at lightning-v3.2.controller.ts:110; Zod enum at lightning-v3.schema.ts
  • Preview render: verify the output_format field description + 200 response block on the API-ref page

Support ticket flagged that output_format=mp3 was 'returning WAV'. The
bytes are actually correct MPEG audio (verified with file(1)); what
tripped the customer's WhatsApp Cloud API upload was the response
Content-Type header, which the platform sets unconditionally to
audio/wav regardless of output_format.

Cross-checked against waves-platform source:

- src/routes/speech/tts/lightning-v3/lightning-v3.2.controller.ts:110
    res.setHeader('Content-Type', 'audio/wav');
  (Hardcoded, no branch on output_format. Same pattern in the lightning,
  lightning-large, and the streaming controllers, 9 call sites total.)
- src/routes/speech/tts/lightning-v3/lightning-v3.schema.ts
    output_format: z.enum(['wav', 'ulaw', 'alaw', 'pcm', 'mp3'])
  (Matches the API's own 400 invalid_enum_value error. ogg / opus / aac
  are not supported.)

Spec change:

- output_format field description: adds the Content-Type-is-hardcoded
  paragraph, per-format recommended MIME overrides (audio/mpeg for mp3,
  audio/basic for ulaw/alaw, raw PCM note for pcm), and a note that the
  request Accept header is not honored.
- 200 response: adds the Content-Type response header schema (enum with
  the single value audio/wav) and mirrors the byte-vs-header caveat in
  both the response description and the audio/wav content schema.

No behavior change on the server; docs now match reality.
The spec claimed Accept: audio/wav was required and its omission returned
empty/unplayable responses. Live probe showed the header is ignored: with
no Accept, or Accept: application/json, or audio/mpeg, the response bytes
and headers are unchanged.

- Accept header parameter: required true → false; description says
  ignored + points at output_format
- Common gotchas: drop the false "Set Accept: audio/wav" bullet, add the
  real gotcha (Content-Type response header is always audio/wav) and the
  Accept-is-ignored bullet
- 4 curl examples: drop the misleading -H "Accept: audio/wav" line

Source proof, waves-platform:
- src/routes/speech/tts/lightning-v3/lightning-v3.2.controller.ts:110:
  res.setHeader("Content-Type", "audio/wav") — hardcoded, no branch on
  output_format, no Accept read upstream
- src/routes/speech/tts/lightning-v3/lightning-v3.schema.ts:
  output_format: z.enum(["wav","ulaw","alaw","pcm","mp3"]) — matches the
  API's own 400 invalid_enum_value error; ogg/opus/aac not supported
@crim-app

crim-app Bot commented Aug 25, 2026

Copy link
Copy Markdown

crim doesn't review pull requests automatically here.

Comment crim review on this pull request whenever you want a review.

@github-actions

Copy link
Copy Markdown

Platform now sets the response Content-Type per output_format:

  wav            -> audio/wav
  mp3            -> audio/mpeg
  ulaw / alaw    -> audio/basic
  pcm            -> application/octet-stream

Live-verified against api.smallest.ai across all five formats. Rewrite:
- Kill the 'always audio/wav' gotcha and its per-format MIME-override
  guidance; consumers no longer need to override client-side.
- Add a Content-Type mapping table on the 200 response block and expose
  the accepted set on the Content-Type response header enum.
- Keep the Accept-request-header-ignored note; still true.
- Rewrite the changelog entry to describe the current behavior; the
  older 'header is misleading' framing was accurate only until platform
  waves-platform#1081 landed today (2026-08-31).
abhishekmishragithub added a commit that referenced this pull request Aug 31, 2026
)

The sibling SDK-generation override at fern/apis/waves/openapi/
get-voices-openapi-overrides.yaml was updated in #402 to match the
base spec on the pool-scoped enum, but the description stopped short:
the two markdown links (unified TTS route + get-all-models cross-link)
were omitted from the override.

The Waves spec drift CI check compares base vs sibling override, and
this mismatch was failing every open PR that touched any waves spec
file (#394, #401, #406, and any future spec edit). Copy the two link
lines verbatim so the two files match token-for-token.

Local run of scripts/spec-live-tests/spec_drift_check.py now reports
PASS — no drift.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant