{"record":{"id":"f414efa0d2f20a3e","repo":"OtterMind/Chat2DB","slug":"request-body-must-be-a-json-object","errorCode":null,"errorMessage":"request body must be a JSON object","messagePattern":"request body must be a JSON object","errorType":"http","errorClass":"RequestError","httpStatus":400,"severity":"error","filePath":"script/github/qq_relay/relay_server.py","lineNumber":214,"sourceCode":"        if not hmac.compare_digest(supplied, expected):\n            raise RequestError(HTTPStatus.UNAUTHORIZED, \"unauthorized\")\n\n    def _read_payload(self) -> Mapping[str, Any]:\n        content_type = self.headers.get(\"Content-Type\", \"\")\n        if not content_type.lower().startswith(\"application/json\"):\n            raise RequestError(HTTPStatus.UNSUPPORTED_MEDIA_TYPE, \"Content-Type must be JSON\")\n        try:\n            content_length = int(self.headers.get(\"Content-Length\", \"\"))\n        except ValueError as error:\n            raise RequestError(HTTPStatus.LENGTH_REQUIRED, \"Content-Length is required\") from error\n        if content_length < 1 or content_length > MAX_REQUEST_BYTES:\n            raise RequestError(HTTPStatus.REQUEST_ENTITY_TOO_LARGE, \"request body is too large\")\n        try:\n            payload = json.loads(self.rfile.read(content_length).decode(\"utf-8\"))\n        except (UnicodeDecodeError, json.JSONDecodeError) as error:\n            raise RequestError(HTTPStatus.BAD_REQUEST, \"request body is not valid JSON\") from error\n        if not isinstance(payload, Mapping):\n            raise RequestError(HTTPStatus.BAD_REQUEST, \"request body must be a JSON object\")\n        return payload\n\n    def _validate_payload(self, payload: Mapping[str, Any]) -> tuple[str, str]:\n        config = self.relay_state.config\n        if payload.get(\"repository\") != config.repository:\n            raise RequestError(HTTPStatus.FORBIDDEN, \"repository is not allowed\")\n        delivery_id = payload.get(\"delivery_id\")\n        if not isinstance(delivery_id, str) or not DELIVERY_ID_PATTERN.fullmatch(delivery_id):\n            raise RequestError(HTTPStatus.BAD_REQUEST, \"delivery_id is invalid\")\n        message = payload.get(\"message\")\n        if not isinstance(message, str) or not message.strip():\n            raise RequestError(HTTPStatus.BAD_REQUEST, \"message must be non-empty text\")\n        if len(message) > config.max_message_length:\n            raise RequestError(HTTPStatus.BAD_REQUEST, \"message is too long\")\n        if CONTROL_CHARACTER_PATTERN.search(message):\n            raise RequestError(HTTPStatus.BAD_REQUEST, \"message contains control characters\")\n        return delivery_id, message\n","sourceCodeStart":196,"sourceCodeEnd":232,"githubUrl":"https://github.com/OtterMind/Chat2DB/blob/5ee1e990e73fbcae1969dc554be254fedb3ab888/script/github/qq_relay/relay_server.py#L196-L232","documentation":"Raised after the request body parses as valid JSON but its top-level value is not a JSON object (it is an array, number, string, boolean, or null). The relay checks isinstance(payload, Mapping) at relay_server.py:213 because it reads named fields (repository, delivery_id, message) off the root; a non-object body cannot be dispatched. Returned to the client as HTTP 400 with {\"error\": \"request body must be a JSON object\"}.","triggerScenarios":"POST /v1/qq/github whose body is a JSON array like [{\"message\":\"x\"}], a bare scalar such as \"hello\" or 42, the literal null, or true/false.","commonSituations":"Sender serializes a list of events instead of one object; a middleware wraps the payload in an array; a test harness posts a raw string; client posts the wrong schema after a refactor.","solutions":["Send a top-level JSON object (curly-brace map), not an array or scalar.","Serialize with a dict/object on the sender and set Content-Type: application/json.","If you must batch, loop and post one object per delivery_id."],"exampleFix":"# before\nrequests.post(url, json=[{\"delivery_id\": \"1\", \"message\": \"hi\"}])\n# after\nrequests.post(url, json={\"delivery_id\": \"1\", \"message\": \"hi\", \"repository\": \"OtterMind/Chat2DB\"})","handlingStrategy":"validation","validationCode":"import json\nif not isinstance(payload, dict):\n    raise ValueError('payload must be a JSON object, got ' + type(payload).__name__)\nbody = json.dumps(payload, ensure_ascii=False)","typeGuard":"def is_object_payload(value: object) -> bool:\n    return isinstance(value, dict)","tryCatchPattern":"resp = requests.post(url, json=payload)\nif resp.status_code == 400 and resp.json().get('error') == 'request body must be a JSON object':\n    # payload root was not an object; re-serialize as a dict","preventionTips":["Always build the body from a dict/object, never from a list or scalar.","Set Content-Type: application/json and let the client serialize the dict."],"tags":["http","json","validation","qq-relay"],"backgroundTag":null,"analyzedSha":"5ee1e990e73fbcae1969dc554be254fedb3ab888","analyzedAt":"2026-08-14T07:05:03.077Z","schemaVersion":2},"datasetVersion":"2026-08-14T10:17:34.591Z"}