infiniflow/ragflow · warning · TypeError

Request body is not a valid form payload.

Error message

Request body is not a valid form payload.

What it means

Raised by _coerce_request_data when the request has a body with a non-JSON content type but await request.form yields no parseable form fields. The parser falls back to form data; when that is also empty/unparseable, the body is rejected as an invalid form payload.

Source

Thrown at api/utils/api_utils.py:86

    has_body = bool(body_bytes)
    content_type = (request.content_type or "").lower()
    is_json = content_type.startswith("application/json")

    if not has_body:
        payload = {}
    elif is_json:
        payload = await request.get_json(force=False, silent=False)
        if isinstance(payload, dict):
            payload = payload or {}
        elif isinstance(payload, str):
            raise AttributeError("'str' object has no attribute 'get'")
        else:
            raise TypeError("JSON payload must be an object.")
    else:
        form = await request.form
        payload = form.to_dict() if form else None
        if payload is None:
            raise TypeError("Request body is not a valid form payload.")

    request._cached_payload = payload
    return payload


async def get_request_json():
    return await _coerce_request_data()


def serialize_for_json(obj):
    """
    Recursively serialize objects to make them JSON serializable.
    Handles ModelMetaclass and other non-serializable objects.
    """
    if hasattr(obj, "__dict__"):
        # For objects with __dict__, try to serialize their attributes
        try:
            return {key: serialize_for_json(value) for key, value in obj.__dict__.items() if not key.startswith("_")}

View on GitHub (pinned to 554fb1133a)

Solutions

  1. Set Content-Type: application/json and send a JSON object for these endpoints.
  2. For uploads, use proper multipart/form-data with a boundary (requests files=/data= does this correctly).
  3. Remove stray Content-Type headers on empty-body requests.
  4. Verify intermediate proxies do not rewrite the content type.

Example fix

# before
requests.post(url, data=payload_str, headers={'Content-Type': 'text/plain'})

# after
requests.post(url, json=payload_obj)
Defensive patterns

Strategy: validation

Validate before calling

if body and content_type not in ('application/json', 'application/x-www-form-urlencoded', 'multipart/form-data'):
    return json_error_response('unsupported content type', 415)

Type guard

ALLOWED = ('application/json', 'application/x-www-form-urlencoded', 'multipart/form-data')

def is_supported_content_type(ct: str | None) -> bool:
    return (ct or '').split(';')[0].strip().lower() in ALLOWED or not body

Try / catch

try:
    data = await get_request_json()
except TypeError:
    return json_error_response('send JSON object or valid form fields', 400)

Prevention

When it happens

Trigger: Sending a body with Content-Type: text/plain, application/octet-stream, or a malformed multipart/urlencoded body (bad boundary, empty multipart) to an endpoint that reads the payload via get_request_json.

Common situations: curl -d with default content type not matching what was intended; broken multipart boundaries from hand-rolled clients; proxies stripping or rewriting content-type; empty bodies with a non-JSON content type set.

Related errors


AI-assisted analysis of infiniflow/ragflow@554fb1133a (2026-08-15). Data as JSON: /api/errors/8e385675525146b9. Report an issue: GitHub.