decolua/9router · warning

Invalid JSON body

Error message

Invalid JSON body

What it means

handleSearch (src/sse/handlers/search.js:28) parses the request body with request.json() inside a try/catch; any parse failure returns HTTP 400 'Invalid JSON body'. The endpoint requires a JSON document with provider/model and query fields, so a malformed body is rejected before any auth or routing happens. This is a client-side request formatting problem, not a server fault.

Source

Thrown at src/sse/handlers/search.js:28

import { handleSearchCore } from "open-sse/handlers/search/index.js";
import { errorResponse, unavailableResponse } from "open-sse/utils/error.js";
import { HTTP_STATUS } from "open-sse/config/runtimeConfig.js";
import * as log from "../utils/logger.js";
import { updateProviderCredentials, checkAndRefreshToken } from "../services/tokenRefresh.js";
import { handleComboChat, getComboModelsFromData } from "open-sse/services/combo.js";

/**
 * Handle web search request for the SSE/Next.js server.
 * Provider IS the model (no model field). Mirrors handleEmbeddings auth + fallback flow.
 *
 * @param {Request} request
 */
export async function handleSearch(request) {
  let body;
  try {
    body = await request.json();
  } catch {
    log.warn("SEARCH", "Invalid JSON body");
    return errorResponse(HTTP_STATUS.BAD_REQUEST, "Invalid JSON body");
  }

  const url = new URL(request.url);
  // Accept either `provider` or `model` (UI sends `model` since provider IS the model for webSearch)
  const providerInput = body.provider || body.model;
  const query = body.query;

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

  // 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)");
  }

View on GitHub (pinned to 90b52e06ff)

Solutions

  1. Send a valid JSON object body, e.g. {"provider":"exa","query":"..."} or {"model":"...","query":"..."}.
  2. Set the Content-Type: application/json header on the request.
  3. Validate the JSON with JSON.parse (or a linter) on the client before sending.
  4. Check for double-serialization: if your payload looks like '{\"provider\"...' when logged, un-nest the string.
  5. Inspect the raw body actually received server-side (proxy logs) to catch middleware or gateway corruption.

Example fix

// before: body is double-stringified / wrong type
fetch(base + '/v1/search', { method: 'POST', body: JSON.stringify(JSON.stringify(payload)) });
// after
fetch(base + '/v1/search', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ provider: 'exa', query: 'langchain error handling' })
});
Defensive patterns

Strategy: validation

Validate before calling

const payload = { provider: 'exa', query: 'test' };
const bodyText = JSON.stringify(payload);
JSON.parse(bodyText); // throws locally if you accidentally serialized twice
if (!payload.provider || !payload.query) throw new Error('provider and query are required');

Type guard

function isSearchPayload(v) {
  return typeof v === 'object' && v !== null
    && typeof (v.provider ?? v.model) === 'string'
    && typeof v.query === 'string' && v.query.trim().length > 0;
}

Try / catch

let body;
try { body = await res.json(); } catch { throw new Error('Non-JSON response — check request encoding and endpoint'); }

Prevention

When it happens

Trigger: POST to the /v1 search endpoint with a body that request.json() cannot parse: empty body, HTML/plain-text content, truncated JSON, or an unparseable content-type/body combination (e.g. form-encoded or raw bytes).

Common situations: A script sending JSON.stringify on an already-stringified string or concatenating objects; a proxy/gateway mangling or truncating the payload; forgetting 'Content-Type: application/json' while a client library then encodes the body differently; curl with -d instead of --data and no proper quoting; a file uploaded raw instead of read-and-parsed.

Understand the failure class

Related errors


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