{"record":{"id":"161581ebef614faf","repo":"AlexxIT/go2rtc","slug":"err-error-161581","errorCode":null,"errorMessage":"err.Error()","messagePattern":"err\\.Error\\(\\)","errorType":"http","errorClass":null,"httpStatus":400,"severity":"warning","filePath":"internal/hass/api.go","lineNumber":25,"sourceCode":"\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/AlexxIT/go2rtc/internal/api\"\n\t\"github.com/AlexxIT/go2rtc/internal/streams\"\n\t\"github.com/AlexxIT/go2rtc/internal/webrtc\"\n)\n\nfunc apiOK(w http.ResponseWriter, r *http.Request) {\n\tapi.Response(w, `{\"status\":1,\"payload\":{}}`, api.MimeJSON)\n}\n\nfunc apiStream(w http.ResponseWriter, r *http.Request) {\n\tswitch {\n\t// /stream/{id}/add\n\tcase strings.HasSuffix(r.RequestURI, \"/add\"):\n\t\tvar v addJSON\n\t\tif err := json.NewDecoder(r.Body).Decode(&v); err != nil {\n\t\t\thttp.Error(w, err.Error(), http.StatusBadRequest)\n\t\t\treturn\n\t\t}\n\n\t\t// we can get three types of links:\n\t\t// 1. link to go2rtc stream: rtsp://...:8554/{stream_name}\n\t\t// 2. static link to Hass camera\n\t\t// 3. dynamic link to Hass camera\n\t\tif _, err := streams.Patch(v.Name, v.Channels.First.Url); err == nil {\n\t\t\tapiOK(w, r)\n\t\t} else {\n\t\t\thttp.Error(w, err.Error(), http.StatusBadRequest)\n\t\t}\n\n\t// /stream/{id}/channel/0/webrtc\n\tdefault:\n\t\ti := strings.IndexByte(r.RequestURI[8:], '/')\n\t\tif i <= 0 {\n\t\t\thttp.Error(w, \"\", http.StatusBadRequest)","sourceCodeStart":7,"sourceCodeEnd":43,"githubUrl":"https://github.com/AlexxIT/go2rtc/blob/c245815e75e2a5fd60b4290f12bfc04e55a984d3/internal/hass/api.go#L7-L43","documentation":"apiStream's /add branch decodes the request body as JSON into addJSON ({name, channels.0.url}); on decode failure it returns the Go JSON error with 400 Bad Request. The Hass rtsp-to-webrtc integration sends this payload, so any malformed or non-JSON body triggers it.","triggerScenarios":"POST to /stream/{id}/add with empty body, non-JSON content, or JSON missing the expected shape (name, channels[\"0\"].url).","commonSituations":"Wrong Content-Type causing the client to send form data; older/newer Home Assistant rtsp_to_webrtc integration versions sending a different payload schema; manually curling the endpoint without a body.","solutions":["Send a JSON body matching {\"name\":\"...\",\"channels\":{\"0\":{\"url\":\"...\"}}}","Set Content-Type: application/json on the request","Capture the raw body and validate it with a JSON linter before posting","Check the rtsp_to_webrtc integration version matches the go2rtc API expected by your go2rtc version"],"exampleFix":"// before\ncurl -X POST http://go2rtc:1984/stream/x/add\n// after\ncurl -X POST -H 'Content-Type: application/json' -d '{\"name\":\"cam\",\"channels\":{\"0\":{\"url\":\"rtsp://cam/stream\"}}}' http://go2rtc:1984/stream/x/add","handlingStrategy":"validation","validationCode":"const body = {name, channels: {0: {url}}};\nconst payload = JSON.stringify(body);\nJSON.parse(payload); // sanity check serializable/valid\nawait fetch('/api/stream/x/add', {\n  method:'POST',\n  headers:{'Content-Type':'application/json'},\n  body: payload\n});","typeGuard":null,"tryCatchPattern":"try {\n  const res = await fetch(addUrl, opts);\n  if (res.status === 400) console.error('add rejected:', await res.text());\n} catch (e) {}","preventionTips":["Always send a JSON body matching {name, channels:{\"0\":{url}}}","Set Content-Type: application/json","Never POST an empty body to /add","Keep HA rtsp_to_webrtc integration and go2rtc versions compatible"],"tags":["http","json","bad-request","request-body"],"backgroundTag":"json-decode-failed","analyzedSha":"c245815e75e2a5fd60b4290f12bfc04e55a984d3","analyzedAt":"2026-09-07T11:47:02.965Z","contentChangedAt":"2026-09-07T11:47:02.965Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}