moeru-ai/airi · error
Invalid request body
Error message
Invalid request body
What it means
The custom fetch wrapper for Gemini TTS intercepts the request before it goes to the network and requires the request body to be a JSON string. This error is thrown when init.body is absent or not a string, which means the underlying speech SDK did not serialize a JSON body as expected. It is an internal contract guard between the SDK client and the Gemini generateContent endpoint.
Source
Thrown at packages/provider-inference/src/providers/cloud/google-gemini-audio-speech/index.ts:71
]
function normalizeBaseUrl(baseUrl: string | undefined) {
const value = baseUrl?.trim() || DEFAULT_BASE_URL
return value.endsWith('/') ? value : `${value}/`
}
function decodeBase64(base64: string) {
const binary = atob(base64)
const bytes = new Uint8Array(binary.length)
for (let index = 0; index < binary.length; index++)
bytes[index] = binary.charCodeAt(index)
return bytes
}
function createAudioFetch(apiKey: string, baseUrl: string) {
return async (_input: RequestInfo | URL, init?: RequestInit) => {
if (!init?.body || typeof init.body !== 'string')
throw new Error('Invalid request body')
const body = JSON.parse(init.body) as {
input?: string
model?: string
voice?: string
temperature?: number
}
if (!body.input)
throw new Error('Missing input text for Gemini TTS')
if (!body.model)
throw new Error('Missing model for Gemini TTS')
const response = await globalThis.fetch(new URL(`models/${body.model}:generateContent`, baseUrl), {
method: 'POST',
headers: { 'x-goog-api-key': apiKey, 'Content-Type': 'application/json' },
body: JSON.stringify({
contents: [{ parts: [{ text: body.input }] }],
generationConfig: {View on GitHub (pinned to f679616c34)
Solutions
- Ensure you call the provider through its speech() API so the SDK serializes the request body as a JSON string
- Check that no custom fetch wrapper strips or replaces init.body before this wrapper sees it
- Pass plain JSON-serializable speech options (input text, model, voice) and avoid setting body yourself to FormData/streams
- Read the body in any custom middleware before consuming it, so downstream wrappers still receive the string
Example fix
// before
provider.speech({ fetch: () => fetch(url, { method: 'POST' }) })
// after
const audio = await provider.speech().generate({ text: 'hello', model: 'gemini-2.5-flash-preview-tts', voice: 'Kore' }) Defensive patterns
Strategy: validation
Validate before calling
const body = init?.body
if (typeof body !== 'string' || !body) throw new Error('speech request requires a JSON string body')
JSON.parse(body) Type guard
function hasStringBody(init?: RequestInit): init is RequestInit & { body: string } {
return typeof init?.body === 'string' && init.body.length > 0
} Try / catch
try {
const audio = await provider.speech().generate(options)
} catch (e) {
if (e instanceof Error && e.message === 'Invalid request body') {
// fix request construction: body must be a JSON string
}
} Prevention
- Always call TTS through the provider's speech() API rather than the raw fetch wrapper
- Do not insert middleware that replaces the JSON body with FormData or streams
- When testing the wrapper directly, pass a JSON.stringify'd body
- Keep request serialization inside the SDK client
When it happens
Trigger: Calling the speech() API of the google-gemini-audio-speech provider with a fetch/body configuration that bypasses JSON string serialization, e.g. passing a custom fetch that supplies no body, a FormData/ReadableStream/Blob body, or invoking the returned fetch function directly without init.body.
Common situations: Misconfiguring the provider so the client sends a non-JSON body (wrong endpoint type), wrapping the fetch with middleware that drops or transforms the body, or accidentally calling the wrapped fetch directly instead of through the SDK's speech method.
Understand the failure class
Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.
Related errors
- Invalid request body
- Invalid request body
- Invalid request body
- MiMo voice clone requires a base64 audio sample in data URI
- MiMo voice design requires a style prompt in the user messag
AI-assisted analysis of moeru-ai/airi@f679616c34 (2026-09-08).
Data as JSON: /api/errors/6ff449343e27db37.
Report an issue: GitHub.