Hacktoberfest 2026: the issues maintainers tagged for October, open and beginner-friendly. Browse Hacktoberfest issues

Custom tool sends a form-encoded body when the request body content type is application/json; charset=utf-8

Open Beginner friendly
#44,907 0 comments 1 reaction 0 assignees View on GitHub

Maintainers usually reply within 1 day

@Revinand is already working on this.

Since Oct 27, 2026.

  • #44908 by @Revinand — open

Assessment

Difficulty
2/5
Estimated time
1-3 hours
Newbie friendliness
68/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Tech stack
python
Domain
api, backend

Research direction

Start with api/core/tools/custom_tool/tool.py, especially ApiTool.do_http_request, which the issue identifies as selecting JSON serialization based on an exact Content-Type match. Check for existing tests covering custom tool request bodies, then add coverage for a charset parameter and mixed-case media type. Done when those requests send a JSON body and the existing application/json case still works.

Written by the indexing model from the issue text.

Description

1.18.0
Self Checks
  • I have read the Contributing Guide and Language Policy.
  • This is only for bug report, if you would like to ask a question, please head to Discussions.
  • I have searched for existing issues search for existing issues, including closed ones.
  • I confirm that I am using English to submit this report, otherwise it will be closed.
  • 【中文用户 & Non English User】请使用英语提交,否则会被关闭 :)
  • Please do not modify this template :) and fill in all the required fields.
Dify version

1.18.0, and main at 60429fd39f (api/core/tools/custom_tool/tool.py is identical in both)

Cloud or Self Hosted

Self Hosted (Source)

Steps to reproduce

A custom tool sends its request body as JSON only when the OpenAPI spec declares the content type as exactly application/json. With application/json; charset=utf-8, the body goes out form-encoded under a JSON Content-Type header.

Schema:

openapi: 3.0.0
info:
  title: Items
  version: "1.0"
servers:
  - url: https://api.example.com
paths:
  /items:
    post:
      operationId: create_item
      summary: Create an item
      requestBody:
        content:
          "application/json; charset=utf-8":
            schema:
              type: object
              properties:
                name:
                  type: string
                count:
                  type: integer

To reproduce without a real server, I parsed this schema with ApiBasedToolSchemaParser.auto_parse_to_tool_bundle, built an ApiTool from the bundle and invoked it with name=widget and count=2. The request went through ssrf_proxy to an httpx MockTransport that prints what it receives. The same run with the content type set to plain application/json is shown first for comparison:

--- requestBody content type: application/json
server got Content-Type: application/json
server got body:         b'{"name": "widget", "count": 2}'
--- requestBody content type: application/json; charset=utf-8
server got Content-Type: application/json; charset=utf-8
server got body:         b'name=widget&count=2'
Script (run from api/ with PYTHONPATH=.)
import httpx

from core.helper import ssrf_proxy
from core.tools.__base.tool_runtime import ToolRuntime
from core.tools.custom_tool.tool import ApiTool
from core.tools.entities.common_entities import I18nObject
from core.tools.entities.tool_entities import ToolEntity, ToolIdentity
from core.tools.utils.parser import ApiBasedToolSchemaParser

SPEC = """\
openapi: 3.0.0
info:
  title: Items
  version: "1.0"
servers:
  - url: https://api.example.com
paths:
  /items:
    post:
      operationId: create_item
      summary: Create an item
      requestBody:
        content:
          CONTENT_TYPE:
            schema:
              type: object
              properties:
                name:
                  type: string
                count:
                  type: integer
"""


def handler(request: httpx.Request) -> httpx.Response:
    print("server got Content-Type:", request.headers.get("content-type"))
    print("server got body:        ", request.content)
    return httpx.Response(200, json={"ok": True})


ssrf_proxy._get_ssrf_client = lambda verify: httpx.Client(transport=httpx.MockTransport(handler))

for content_type in ("application/json", "application/json; charset=utf-8"):
    print(f"--- requestBody content type: {content_type}")
    bundles, _ = ApiBasedToolSchemaParser.auto_parse_to_tool_bundle(SPEC.replace("CONTENT_TYPE", f'"{content_type}"'))
    bundle = bundles[0]
    tool = ApiTool(
        entity=ToolEntity(identity=ToolIdentity(provider="p", author="a", name="t", label=I18nObject(en_US="t"))),
        api_bundle=bundle,
        runtime=ToolRuntime(tenant_id="t", credentials={"auth_type": "none"}),
        provider_id="p",
    )
    list(tool._invoke(None, "user", {"name": "widget", "count": 2}))
Expected Behavior

The body is sent as JSON, {"name": "widget", "count": 2}, as it is for application/json. A charset parameter does not change the body format, and media types are case-insensitive, which means Application/JSON should get a JSON body too.

Actual Behavior

The body is sent as name=widget&count=2 with Content-Type: application/json; charset=utf-8. An API that expects JSON cannot parse it.

ApiTool.do_http_request in api/core/tools/custom_tool/tool.py copies the requestBody content type from the spec into the Content-Type header, then calls json.dumps on the body only when that header equals "application/json" exactly. For any other value the body stays a dict and is passed to ssrf_proxy as data, which httpx form-encodes while keeping the JSON header. Application/JSON takes the same path.

I have a fix with a test and will open a PR.

Dominant language
Python
Stars
160k
Forks
25.3k
Avg merge
21h 9m
Merged PRs (30d)
614

Getting set up

Open in Codespaces

Starts the project's dev container in your browser, under your own GitHub account.

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from langgenius/dify

All issues in langgenius/dify

Similar issues

More Python issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.