{"record":{"id":"533edd29e8a3968b","repo":"Wei-Shaw/sub2api","slug":"invalid-request-error-533edd","errorCode":"invalid_request_error","errorMessage":"request body must be valid JSON","messagePattern":"request body must be valid JSON","errorType":"validation","errorClass":null,"httpStatus":400,"severity":"error","filePath":"backend/internal/handler/openai_live.go","lineNumber":126,"sourceCode":"\tc.Header(\"Location\", liveSidebandLocation(c.FullPath(), created.CallID))\n\tc.Data(http.StatusOK, \"application/sdp\", created.SDP)\n}\n\nfunc parseLiveCallRequest(c *gin.Context) (*service.LiveCallRequest, error) {\n\tcontentType := strings.ToLower(c.GetHeader(\"Content-Type\"))\n\tif strings.HasPrefix(contentType, \"multipart/form-data\") {\n\t\tsdp := c.PostForm(\"sdp\")\n\t\tsession := json.RawMessage(c.PostForm(\"session\"))\n\t\trequest := &service.LiveCallRequest{SDP: sdp, Session: session}\n\t\tif err := service.ValidateLiveCallRequest(request); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn request, nil\n\t}\n\tvar request service.LiveCallRequest\n\tdecoder := json.NewDecoder(c.Request.Body)\n\tif err := decoder.Decode(&request); err != nil {\n\t\treturn nil, errors.New(\"request body must be valid JSON\")\n\t}\n\tif err := decoder.Decode(&struct{}{}); !errors.Is(err, io.EOF) {\n\t\treturn nil, errors.New(\"request body must contain one JSON object\")\n\t}\n\tif err := service.ValidateLiveCallRequest(&request); err != nil {\n\t\treturn nil, err\n\t}\n\treturn &request, nil\n}\n\nfunc liveSidebandLocation(fullPath, callID string) string {\n\tprefix := \"/v1/live/\"\n\tif strings.HasPrefix(fullPath, \"/backend-api/codex/\") {\n\t\tprefix = \"/backend-api/codex/\"\n\t}\n\treturn prefix + url.PathEscape(callID)\n}\n","sourceCodeStart":108,"sourceCodeEnd":144,"githubUrl":"https://github.com/Wei-Shaw/sub2api/blob/073e92d17178a1ccdb0a27017f572f10c9c7ab62/backend/internal/handler/openai_live.go#L108-L144","documentation":"Thrown while decoding the LiveCall request body as JSON when json.Decoder.Decode fails (code=invalid_request_error). Covers syntax errors (trailing commas, truncation), wrong content type with raw body, and completely empty bodies on non-multipart requests. The second decode (single-object check) has its own error.","triggerScenarios":"POST to /v1/live/... with a malformed JSON body: truncated payload, single quotes, comments, empty body, or body sent as form fields without multipart content type.","commonSituations":"Clients string-interpolating JSON; proxies truncating large SDP offers; sending x-www-form-urlencoded fields where raw JSON is expected; debug tools posting invalid JSON.","solutions":["Validate the body is syntactically valid JSON before sending (jq, JSON.stringify)","Ensure Content-Type is application/json and the full body is transmitted (check for truncation on large SDP payloads)","Build the payload with a JSON serializer rather than string concatenation"],"exampleFix":"// before\nfetch(url, { body: `{sdp: ${sdp}}` }) // invalid JSON\n// after\nfetch(url, { headers: {'Content-Type':'application/json'}, body: JSON.stringify({ sdp }) })","handlingStrategy":"validation","validationCode":"// TS: serialize + parse round-trip before sending\nconst bodyText = JSON.stringify(payload);\nJSON.parse(bodyText); // throws locally if the serializer produced bad JSON\nawait fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: bodyText });","typeGuard":"function isLiveCallRequest(v: unknown): v is { sdp?: string; session?: unknown } {\n  return typeof v === 'object' && v !== null && !Array.isArray(v);\n}","tryCatchPattern":null,"preventionTips":["Always build JSON with a serializer, never string templates","Send Content-Type: application/json so the raw-JSON branch is taken","For large SDP offers, verify the client is not truncating the body"],"tags":["live","json","validation","http"],"backgroundTag":null,"analyzedSha":"073e92d17178a1ccdb0a27017f572f10c9c7ab62","analyzedAt":"2026-08-15T14:33:00.750Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}