decolua/9router · error

Invalid JSON body

Error message

Invalid JSON body

What it means

The embeddings endpoint (/v1/embeddings) could not parse the request body as JSON. handleEmbeddings wraps request.json() in try/catch and returns 400 'Invalid JSON body' on any parse error, mirroring the chat handler.

Source

Thrown at src/sse/handlers/embeddings.js:37

  const promptTokens = raw.prompt_tokens ?? raw.input_tokens;
  const completionTokens = raw.completion_tokens ?? raw.output_tokens ?? 0;
  const totalTokens = raw.total_tokens;
  if (!Number.isSafeInteger(promptTokens) || promptTokens <= 0 || completionTokens !== 0 || totalTokens !== promptTokens) return null;
  return { prompt_tokens: promptTokens, completion_tokens: 0, total_tokens: totalTokens };
}

/**
 * Handle embeddings request for the SSE/Next.js server.
 * Follows the same auth + fallback pattern as handleChat.
 *
 * @param {Request} request
 */
export async function handleEmbeddings(request) {
  let body;
  try {
    body = await request.json();
  } catch {
    log.warn("EMBEDDINGS", "Invalid JSON body");
    return errorResponse(HTTP_STATUS.BAD_REQUEST, "Invalid JSON body");
  }

  const url = new URL(request.url);
  const modelStr = body.model;

  log.request("POST", `${url.pathname} | ${modelStr}`);

  // Log API key (masked)
  const apiKey = extractApiKey(request);
  if (apiKey) {
    log.debug("AUTH", `API Key: ${log.maskKey(apiKey)}`);
  } else {
    log.debug("AUTH", "No API key provided (local mode)");
  }

  // Enforce API key if enabled in settings
  const settings = await getSettings();

View on GitHub (pinned to 90b52e06ff)

Solutions

  1. JSON.stringify the payload and set Content-Type: application/json before sending to /v1/embeddings.
  2. Validate the body with JSON.parse client-side to catch syntax issues early.
  3. Check shell quoting if using curl for embedding texts (special characters break quoting).
  4. Ensure the body is sent as UTF-8 without BOM and not compressed unexpectedly (Content-Encoding).

Example fix

// before
const res = await fetch(url, { method: 'POST', body: { input: texts } });

// after
const res = await fetch(url, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ model: 'openai/text-embedding-3-small', input: texts })
});
Defensive patterns

Strategy: validation

Validate before calling

const payload = JSON.stringify({ model, input });
JSON.parse(payload); // throws locally if something is wrong before hitting /v1/embeddings
if (!Array.isArray(input) && typeof input !== 'string') throw new TypeError('input must be string or array');

Type guard

function isValidEmbeddingsBody(body) {
  return Boolean(body) && typeof body === 'object' &&
    typeof body.model === 'string' &&
    (typeof body.input === 'string' || Array.isArray(body.input));
}

Try / catch

const res = await fetch(embedUrl, opts);
if (res.status === 400 && (await res.text()).includes('Invalid JSON body')) {
  console.error('embeddings payload not valid JSON:', opts.body);
}

Prevention

When it happens

Trigger: POST to /v1/embeddings with a body that is not valid JSON: empty body, malformed syntax, wrong encoding, or a non-JSON payload (text/FormData/binary).

Common situations: Scripts calling embeddings with hand-built payloads and quoting bugs; clients sending UTF-16/BOM-encoded bodies the JSON parser rejects; proxies mangling the body; forgetting JSON.stringify when using fetch/undici directly.

Understand the failure class

Related errors


AI-assisted analysis of decolua/9router@90b52e06ff (2026-08-30). Data as JSON: /api/errors/3bde8f5bcc6d4883. Report an issue: GitHub.