{"record":{"id":"9589c8692fd4f675","repo":"decolua/9router","slug":"invalid-json-body","errorCode":null,"errorMessage":"Invalid JSON body","messagePattern":"Invalid JSON body","errorType":"validation","errorClass":null,"httpStatus":400,"severity":"error","filePath":"src/sse/handlers/chat.js","lineNumber":37,"sourceCode":"import { augmentModelsWithCapacityAdapter, withCapacityAdapterStripping, getActiveAdapterStrategy } from \"open-sse/services/capacityAdapter.js\";\nimport { handleBypassRequest } from \"open-sse/utils/bypassHandler.js\";\nimport { HTTP_STATUS } from \"open-sse/config/runtimeConfig.js\";\nimport { detectFormatByEndpoint } from \"open-sse/translator/formats.js\";\nimport * as log from \"../utils/logger.js\";\nimport { updateProviderCredentials, checkAndRefreshToken } from \"../services/tokenRefresh.js\";\nimport { getProjectIdForConnection } from \"open-sse/services/projectId.js\";\n\n/**\n * Handle chat completion request\n * Supports: OpenAI, Claude, Gemini, OpenAI Responses API formats\n * Format detection and translation handled by translator\n */\nexport async function handleChat(request, clientRawRequest = null) {\n  let body;\n  try {\n    body = await request.json();\n  } catch {\n    log.warn(\"CHAT\", \"Invalid JSON body\");\n    return errorResponse(HTTP_STATUS.BAD_REQUEST, \"Invalid JSON body\");\n  }\n\n  // Build clientRawRequest for logging (if not provided)\n  if (!clientRawRequest) {\n    const url = new URL(request.url);\n    clientRawRequest = {\n      endpoint: url.pathname,\n      body,\n      headers: Object.fromEntries(request.headers.entries())\n    };\n  }\n  const modelStr = body.model;\n\n  // Request summary is emitted as the unified \"▶\" line in chatCore (has fmt/thinking/account)\n\n  // Log API key (masked)\n  const authHeader = request.headers.get(\"Authorization\");","sourceCodeStart":19,"sourceCodeEnd":55,"githubUrl":"https://github.com/decolua/9router/blob/90b52e06ffd666b7929554211474d01588f6b1f8/src/sse/handlers/chat.js#L19-L55","documentation":"The chat endpoint (/v1/chat/completions) could not parse the request body as JSON. handleChat calls request.json() inside a try/catch; any parse failure (SyntaxError) is converted into a 400 response with this message. The gateway requires a well-formed JSON body in one of the supported formats (OpenAI, Claude, Gemini, Responses).","triggerScenarios":"POST to /v1/chat/completions where the raw body is not valid JSON: empty body, trailing garbage, truncated body, or a body with invalid JSON syntax such as unquoted keys, single quotes, or an unclosed brace.","commonSituations":"Sending the body without a JSON Content-Type while the client serializes incorrectly; curl commands where quotes get mangled by the shell; proxy/intermediary truncating large request bodies; a script posting FormData or plain text instead of JSON; SDK misconfiguration pointing at the gateway with a text payload.","solutions":["Validate the request body with JSON.parse before sending, or rely on your client's JSON serializer (fetch with JSON.stringify, axios default).","Set Content-Type: application/json and ensure the client actually serializes the object instead of passing a raw string incorrectly.","If using curl, wrap the -d payload in single quotes and check for shell quoting issues; print the exact bytes being sent.","Check for proxies/middleware that may truncate or re-encode the body between client and the 9Router gateway."],"exampleFix":"// before\nawait fetch(url, { method: 'POST', body: payload }); // payload is an object, sent as '[object Object]'\n\n// after\nawait fetch(url, {\n  method: 'POST',\n  headers: { 'Content-Type': 'application/json' },\n  body: JSON.stringify(payload)\n});","handlingStrategy":"validation","validationCode":"const payload = JSON.stringify(body);\nJSON.parse(payload); // throws before the request if body is not serializable/valid JSON\nif (!body || typeof body !== 'object') throw new TypeError('chat body must be an object');","typeGuard":"function isValidJsonBody(text) {\n  try { const v = JSON.parse(text); return v !== null && typeof v === 'object'; }\n  catch { return false; }\n}","tryCatchPattern":"const res = await fetch(url, opts);\nif (res.status === 400) {\n  const err = await res.json();\n  if (err?.error?.message === 'Invalid JSON body') {\n    console.error('Payload was not valid JSON:', opts.body);\n  }\n}","preventionTips":["Always serialize with JSON.stringify and set Content-Type: application/json.","Round-trip JSON.parse(JSON.stringify(payload)) in tests for hand-built payloads.","Beware shell quoting when using curl with inline JSON.","Check proxies/middleware for body truncation or re-encoding."],"tags":["http","bad-request","json","request-body"],"backgroundTag":"invalid-json-body","analyzedSha":"90b52e06ffd666b7929554211474d01588f6b1f8","analyzedAt":"2026-08-30T21:05:45.952Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}