OtterMind/Chat2DB · error · RequestError
request body must be a JSON object
Error message
request body must be a JSON object
What it means
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"}.
Source
Thrown at script/github/qq_relay/relay_server.py:214
if not hmac.compare_digest(supplied, expected):
raise RequestError(HTTPStatus.UNAUTHORIZED, "unauthorized")
def _read_payload(self) -> Mapping[str, Any]:
content_type = self.headers.get("Content-Type", "")
if not content_type.lower().startswith("application/json"):
raise RequestError(HTTPStatus.UNSUPPORTED_MEDIA_TYPE, "Content-Type must be JSON")
try:
content_length = int(self.headers.get("Content-Length", ""))
except ValueError as error:
raise RequestError(HTTPStatus.LENGTH_REQUIRED, "Content-Length is required") from error
if content_length < 1 or content_length > MAX_REQUEST_BYTES:
raise RequestError(HTTPStatus.REQUEST_ENTITY_TOO_LARGE, "request body is too large")
try:
payload = json.loads(self.rfile.read(content_length).decode("utf-8"))
except (UnicodeDecodeError, json.JSONDecodeError) as error:
raise RequestError(HTTPStatus.BAD_REQUEST, "request body is not valid JSON") from error
if not isinstance(payload, Mapping):
raise RequestError(HTTPStatus.BAD_REQUEST, "request body must be a JSON object")
return payload
def _validate_payload(self, payload: Mapping[str, Any]) -> tuple[str, str]:
config = self.relay_state.config
if payload.get("repository") != config.repository:
raise RequestError(HTTPStatus.FORBIDDEN, "repository is not allowed")
delivery_id = payload.get("delivery_id")
if not isinstance(delivery_id, str) or not DELIVERY_ID_PATTERN.fullmatch(delivery_id):
raise RequestError(HTTPStatus.BAD_REQUEST, "delivery_id is invalid")
message = payload.get("message")
if not isinstance(message, str) or not message.strip():
raise RequestError(HTTPStatus.BAD_REQUEST, "message must be non-empty text")
if len(message) > config.max_message_length:
raise RequestError(HTTPStatus.BAD_REQUEST, "message is too long")
if CONTROL_CHARACTER_PATTERN.search(message):
raise RequestError(HTTPStatus.BAD_REQUEST, "message contains control characters")
return delivery_id, message
View on GitHub (pinned to 5ee1e990e7)
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.
Example fix
# before
requests.post(url, json=[{"delivery_id": "1", "message": "hi"}])
# after
requests.post(url, json={"delivery_id": "1", "message": "hi", "repository": "OtterMind/Chat2DB"}) Defensive patterns
Strategy: validation
Validate before calling
import json
if not isinstance(payload, dict):
raise ValueError('payload must be a JSON object, got ' + type(payload).__name__)
body = json.dumps(payload, ensure_ascii=False) Type guard
def is_object_payload(value: object) -> bool:
return isinstance(value, dict) Try / catch
resp = requests.post(url, json=payload)
if resp.status_code == 400 and resp.json().get('error') == 'request body must be a JSON object':
# payload root was not an object; re-serialize as a dict Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- request body is not valid JSON
- delivery_id is invalid
- message must be non-empty text
- message is too long
- message contains control characters
AI-assisted analysis of OtterMind/Chat2DB@5ee1e990e7 (2026-08-14).
Data as JSON: /api/errors/f414efa0d2f20a3e.
Report an issue: GitHub.