Repository: BerriAI/litellm

Issue:
[Bug]: error in firecrawl-search "Invalid request body"
### What happened?

### my config: 

```yaml
search_tools:
  - search_tool_name: searxng-search
    litellm_params:
      search_provider: searxng
      api_base: http://search.partco.ir/

  - search_tool_name: firecrawl-search
    litellm_params:
      search_provider: firecrawl
      api_base: http://crawl.partco.ir/v2
      api_key: fc-YOUR_API_KEY
```

### my request

```bash
 curl http://llm-test.partco.ir/v1/search/firecrawl-search \
  -H "Authorization: Bearer sk-kElxx" \
  -H "Content-Type: application/json" \
  -d '{"query":"latest ai developments", "max_results":5, "scrapeOptions":{"formats":["markdown"]}}' | hq
```

### response error

```bash
{
  "error": {
    "message": "litellm.APIConnectionError: FirecrawlException - {\"success\":false,\"error\":\"Invalid request body\",\"details\":[{\"code\":\"unrecognized_keys\",\"keys\":[\"search_tool_name\"],\"path\":[],\"message\":\"Unrecognized key in body -- please review the v1 API documentation for request body changes\"}]}. Received Model Group=firecrawl-search\nAvailable Model Group Fallbacks=None",
    "type": null,
    "param": null,
    "code": "500"
  }
}
```

### console logs 

```bash
{
  "message": "litellm.proxy.proxy_server._handle_llm_api_exception(): Exception occured - litellm.APIConnectionError: FirecrawlException - {\"success\":false,\"error\":\"Invalid request body\",\"details\":[{\"code\":\"unrecognized_keys\",\"keys\":[\"search_tool_name\"],\"path\":[],\"message\":\"Unrecognized key in body -- please review the v1 API documentation for request body changes\"}]}. Received Model Group=firecrawl-search\nAvailable Model Group Fallbacks=None",
  "level": "ERROR",
  "timestamp": "2025-11-15T13:59:02.483342",
  "stacktrace": "Traceback (most recent call last):\n  File \"/usr/lib/python3.13/site-packages/litellm/llms/custom_httpx/llm_http_handler.py\", line 1736, in async_search\n    response = await async_httpx_client.post(\n               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n    ...<4 lines>...\n    )\n    ^\n  File \"/usr/lib/python3.13/site-packages/litellm/litellm_core_utils/logging_utils.py\", line 190, in async_wrapper\n    result = await func(*args, **kwargs)\n             ^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File \"/usr/lib/python3.13/site-packages/litellm/llms/custom_httpx/http_handler.py\", line 403, in post\n    raise e\n  File \"/usr/lib/python3.13/site-packages/litellm/llms/custom_httpx/http_handler.py\", line 359, in post\n    response.raise_for_status()\n    ~~~~~~~~~~~~~~~~~~~~~~~~~^^\n  File \"/usr/lib/python3.13/site-packages/httpx/_models.py\", line 829, in raise_for_status\n    raise HTTPStatusError(message, request=request, response=self)\nhttpx.HTTPStatusError: Client error '400 Bad Request' for url 'http://crawl.partcorp.ir/***'\nFor more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400\n\nDuring handling of the above exception, another exception occurred:\n\nTraceback (most recent call last):\n  File \"/usr/lib/python3.13/site-packages/litellm/search/main.py\", line 141, in asearch\n    response = await init_response\n               ^^^^^^^^^^^^^^^^^^^\n  File \"/usr/lib/python3.13/site-packages/litellm/llms/custom_httpx/llm_http_handler.py\", line 1743, in async_search\n    raise self._handle_error(e=e, provider_config=provider_config)\n          ~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File \"/usr/lib/python3.13/site-packages/litellm/llms/custom_httpx/llm_http_handler.py\", line 3569, in _handle_error\n    raise provider_config.get_error_class(\n    ...<3 lines>...\n    )\nlitellm.llms.base_llm.chat.transformation.BaseLLMException: {\"success\":false,\"error\":\"Invalid request body\",\"details\":[{\"code\":\"unrecognized_keys\",\"keys\":[\"search_tool_name\"],\"path\":[],\"message\":\"Unrecognized key in body -- please review the v1 API documentation for request body changes\"}]}\n\nDuring handling of the above exception, another exception occurred:\n\nTraceback (most recent call last):\n  File \"/usr/lib/python3.13/site-packages/litellm/proxy/search_endpoints/endpoints.py\", line 137, in search\n    return await processor.base_process_llm_request(\n           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n    ...<16 lines>...\n    )\n    ^\n  File \"/usr/lib/python3.13/site-packages/litellm/proxy/common_request_processing.py\", line 502, in base_process_llm_request\n    responses = await llm_responses\n                ^^^^^^^^^^^^^^^^^^^\n  File \"/usr/lib/python3.13/site-packages/litellm/router.py\", line 3682, in async_wrapper\n    return await self._asearch_with_fallbacks(\n           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n    ...<2 lines>...\n    )\n    ^\n  File \"/usr/lib/python3.13/site-packages/litellm/router.py\", line 2706, in _asearch_with_fallbacks\n    return await SearchAPIRouter.async_search_with_fallbacks(\n           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n    ...<3 lines>...\n    )\n    ^\n  File \"/usr/lib/python3.13/site-packages/litellm/router_utils/search_api_router.py\", line 149, in async_search_with_fallbacks\n    raise e\n  File \"/usr/lib/python3.13/site-packages/litellm/router_utils/search_api_router.py\", line 135, in async_search_with_fallbacks\n    response = await router_instance.async_function_with_fallbacks(**kwargs)\n               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File \"/usr/lib/python3.13/site-packages/litellm/router.py\", line 4037, in async_function_with_fallbacks\n    return await self.async_function_with_fallbacks_common_utils(\n           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n    ...<8 lines>...\n    )\n    ^\n  File \"/usr/lib/python3.13/site-packages/litellm/router.py\", line 3995, in async_function_with_fallbacks_common_utils\n    raise original_exception\n  File \"/usr/lib/python3.13/site-packages/litellm/router.py\", line 4029, in async_function_with_fallbacks\n    response = await self.async_function_with_retries(*args, **kwargs)\n               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File \"/usr/lib/python3.13/site-packages/litellm/router.py\", line 4151, in async_function_with_retries\n    self.should_retry_this_error(\n    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^\n        error=e,\n        ^^^^^^^^\n    ...<4 lines>...\n        content_policy_fallbacks=content_policy_fallbacks,\n        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n    )\n    ^\n  File \"/usr/lib/python3.13/site-packages/litellm/router.py\", line 4350, in should_retry_this_error\n    raise error\n  File \"/usr/lib/python3.13/site-packages/litellm/router.py\", line 4125, in async_function_with_retries\n    response = await self.make_call(original_function, *args, **kwargs)\n               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File \"/usr/lib/python3.13/site-packages/litellm/router.py\", line 4245, in make_call\n    response = await response\n               ^^^^^^^^^^^^^^\n  File \"/usr/lib/python3.13/site-packages/litellm/router_utils/search_api_router.py\", line 211, in async_search_with_fallbacks_helper\n    raise e\n  File \"/usr/lib/python3.13/site-packages/litellm/router_utils/search_api_router.py\", line 198, in async_search_with_fallbacks_helper\n    response = await original_generic_function(\n               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n    ...<4 lines>...\n    )\n    ^\n  File \"/usr/lib/python3.13/site-packages/litellm/utils.py\", line 1638, in wrapper_async\n    raise e\n  File \"/usr/lib/python3.13/site-packages/litellm/utils.py\", line 1484, in wrapper_async\n    result = await original_function(*args, **kwargs)\n             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File \"/usr/lib/python3.13/site-packages/litellm/search/main.py\", line 153, in asearch\n    raise litellm.exception_type(\n          ~~~~~~~~~~~~~~~~~~~~~~^\n        model=model_name,\n        ^^^^^^^^^^^^^^^^^\n    ...<3 lines>...\n        extra_kwargs=kwargs,\n        ^^^^^^^^^^^^^^^^^^^^\n    )\n    ^\n  File \"/usr/lib/python3.13/site-packages/litellm/litellm_core_utils/exception_mapping_utils.py\", line 2323, in exception_type\n    raise e\n  File \"/usr/lib/python3.13/site-packages/litellm/litellm_core_utils/exception_mapping_utils.py\", line 2292, in exception_type\n    raise APIConnectionError(\n    ...<4 lines>...\n    )\nlitellm.exceptions.APIConnectionError: litellm.APIConnectionError: FirecrawlException - {\"success\":false,\"error\":\"Invalid request body\",\"details\":[{\"code\":\"unrecognized_keys\",\"keys\":[\"search_tool_name\"],\"path\":[],\"message\":\"Unrecognized key in body -- please review the v1 API documentation for request body changes\"}]}. Received Model Group=firecrawl-search\nAvailable Model Group Fallbacks=None"
}
```

### Relevant log output

```shell

```

### Are you a ML Ops Team?

No

### What LiteLLM version are you on ?

v1.79.3

### Twitter / LinkedIn details

_No response_

Discussion hints:

Solution patch:
diff --git a/litellm/proxy/proxy_config.yaml b/litellm/proxy/proxy_config.yaml
index 2e58d8554c6d..014bcdc1670c 100644
--- a/litellm/proxy/proxy_config.yaml
+++ b/litellm/proxy/proxy_config.yaml
@@ -26,10 +26,10 @@ search_tools:
     litellm_params:
       search_provider: perplexity
       api_key: os.environ/PERPLEXITYAI_API_KEY
-  - search_tool_name: exa-search
+  - search_tool_name: firecrawl-search
     litellm_params:
-      search_provider: exa_ai
-      api_key: os.environ/EXA_API_KEY
+      search_provider: firecrawl
+      api_key: os.environ/FIRECRAWL_API_KEY
   
 
 litellm_settings:
diff --git a/litellm/types/utils.py b/litellm/types/utils.py
index 6e34bbca3aa9..10b7e42b5d2b 100644
--- a/litellm/types/utils.py
+++ b/litellm/types/utils.py
@@ -2478,6 +2478,7 @@ class CustomPricingLiteLLMParams(BaseModel):
         "use_litellm_proxy",
         "prompt_label",
         "shared_session",
+        "search_tool_name",
     ]
     + list(StandardCallbackDynamicParams.__annotations__.keys())
     + list(CustomPricingLiteLLMParams.model_fields.keys())


Test patch:
diff --git a/tests/search_tests/test_search_tool_name_filtering.py b/tests/search_tests/test_search_tool_name_filtering.py
new file mode 100644
index 000000000000..2cfe1177b43c
--- /dev/null
+++ b/tests/search_tests/test_search_tool_name_filtering.py
@@ -0,0 +1,50 @@
+"""
+Test that search_tool_name is properly filtered out from search requests.
+
+The search_tool_name parameter is used internally by LiteLLM to identify
+which search tool configuration to use, but should not be sent to external
+search provider APIs.
+"""
+import sys
+import os
+
+sys.path.insert(0, os.path.abspath("../.."))
+
+from litellm.types.utils import all_litellm_params
+from litellm.utils import filter_out_litellm_params
+
+
+def test_search_tool_name_in_all_litellm_params():
+    """
+    Test that search_tool_name is in all_litellm_params.
+    
+    If missing, it gets passed to provider APIs causing errors.
+    """
+    assert "search_tool_name" in all_litellm_params
+
+
+def test_filter_out_search_tool_name():
+    """
+    Test that filter_out_litellm_params correctly filters search_tool_name.
+    """
+    kwargs = {
+        "query": "latest ai developments",
+        "max_results": 5,
+        "scrapeOptions": {"formats": ["markdown"]},
+        "search_tool_name": "firecrawl-search",
+        "metadata": {"user": "test"},
+        "litellm_call_id": "test-123"
+    }
+    
+    filtered = filter_out_litellm_params(kwargs=kwargs)
+    
+    assert "search_tool_name" not in filtered
+    assert "metadata" not in filtered
+    assert "litellm_call_id" not in filtered
+    
+    assert "query" in filtered
+    assert "max_results" in filtered
+    assert "scrapeOptions" in filtered
+    assert filtered["query"] == "latest ai developments"
+    assert filtered["max_results"] == 5
+