Documentsdify
Custom Tool Authentication
Custom Tool Authentication
Type
Topic
Status
Published
Created
Jul 9, 2026
Updated
Jul 9, 2026
Created by
Dosu Bot
Updated by
Dosu Bot

Custom Tool Authentication#

Custom tools in Dify (called ApiTool) support three API key authentication modes, all handled in assembling_request() before each HTTP call:

auth_type valueBehavior
api_key / api_key_headerAdds an Authorization (or custom) header, optionally prefixed with Bearer or Basic
api_key_queryAppends the API key as a query parameter; no header added
custom prefixValue written to header exactly as-is

The header name defaults to Authorization but can be overridden via credentials["api_key_header"] . The prefix is controlled by credentials["api_key_header_prefix"] ("bearer", "basic", or "custom") .

Entry point: ApiTool._invoke()assembling_request()do_http_request().

Credential Lifecycle#

Credentials live in ToolRuntime.credentials — a plain dict[str, Any] on a Pydantic BaseModel. A fresh ToolRuntime with decrypted credentials is constructed by ToolManager.get_tool_runtime() and passed into ApiTool.fork_tool_runtime().

The fork_tool_runtime() override deep-copies api_bundle via model_copy() but passes the runtime object by reference. This means the forked tool and its parent share the same ToolRuntime — and crucially, the same credentials dict.

Prefix Accumulation Bug#

Symptom: Repeated invocations of the same custom tool (in agent loops, workflow retries, or parallel branches) produce escalating Authorization header values:

Call #Header value sent
1Bearer my-token
2Bearer Bearer my-token
3Bearer Bearer Bearer my-token

This causes 401/403 failures on every call after the first .

Root cause: Before the fix, assembling_request() wrote the prefixed value back into the shared credentials dict :

# buggy — mutates the shared dict
credentials["api_key_value"] = f"Bearer {credentials['api_key_value']}"

Because credentials is a mutable dict passed by reference through fork_tool_runtime(), and ToolRuntime instances can be reused across invocations, each call found an already-prefixed value and prepended again .

Fix (PR #38616, merged): Store the original api_key_value in a local variable, apply the prefix transformation to the local variable, and use it for the header — leaving credentials unchanged :

# correct — local variable, no mutation
api_key_value = credentials["api_key_value"]
if api_key_header_prefix == "basic" and api_key_value:
    api_key_value = f"Basic {api_key_value}"
elif api_key_header_prefix == "bearer" and api_key_value:
    api_key_value = f"Bearer {api_key_value}"
headers[api_key_header] = api_key_value

Earlier parallel fixes were also proposed in PR #36350 (with a double-prefix guard) and PR #35997 (local-variable approach) .

Key Files and References#

ResourcePurpose
api/core/tools/custom_tool/tool.pyApiTool class — auth header assembly, HTTP dispatch, fork logic
api/core/tools/__base/tool_runtime.pyToolRuntime — holds the credentials dict
PR #38616fix: fix auth prefix duplicateMerged fix preventing credential mutation
PR #36350fix(custom tool): prevent Authorization prefix accumulationEarlier fix with double-prefix guard
PR #35997fix: stop auth header prefix accumulating on repeated Custom Tool callsParallel fix (local variable only)

Tests: Regression tests for the fix live in api/tests/unit_tests/core/tools/test_custom_tool.py. They call assembling_request() multiple times sequentially and assert that the credentials["api_key_value"] remains unchanged (e.g. "abc") without accumulating prefixes, while the header value is correctly prefixed on each call.