crewAIInc/crewAI · error · ValueError

url is required either in constructor or method call

Error message

url is required either in constructor or method call

What it means

Raised by BrightDataUnlockerTool._run when the url parameter is falsy after fallback (url = url or self.url). The unlocker fetches exactly one page per call, and its payload is built around that URL, so no default or inference exists.

Source

Thrown at lib/crewai-tools/src/crewai_tools/tools/brightdata_tool/brightdata_unlocker.py:118

        self.zone = os.getenv("BRIGHT_DATA_ZONE") or ""
        if not self.api_key:
            raise ValueError("BRIGHT_DATA_API_KEY environment variable is required.")
        if not self.zone:
            raise ValueError("BRIGHT_DATA_ZONE environment variable is required.")

    def _run(
        self,
        url: str | None = None,
        format: str | None = None,
        data_format: str | None = None,
        **kwargs: Any,
    ) -> Any:
        url = url or self.url
        format = format or self.format
        data_format = data_format or self.data_format

        if not url:
            raise ValueError("url is required either in constructor or method call")

        payload = {
            "url": url,
            "zone": self.zone,
            "format": format,
        }
        valid_data_formats = {"html", "markdown"}
        if data_format not in valid_data_formats:
            raise ValueError(
                f"Unsupported data format: {data_format}. Must be one of {', '.join(valid_data_formats)}."
            )

        if data_format == "markdown":
            payload["data_format"] = "markdown"

        headers = {
            "Authorization": f"Bearer {self.api_key}",
            "Content-Type": "application/json",

View on GitHub (pinned to 754d7323be)

Solutions

  1. Pass the URL per call: tool.run(url='https://example.com/pricing').
  2. Or bind it in the constructor: BrightDataUnlockerTool(url='https://example.com/pricing').
  3. Ensure the value is a non-empty http(s) URL string.

Example fix

# before
result = tool.run(data_format='markdown')  # ValueError: url is required

# after
result = tool.run(url='https://example.com/pricing', data_format='markdown')
Defensive patterns

Strategy: validation

Validate before calling

def validate_unlock_url(url: str | None) -> str:
    if not url or not url.startswith(("http://", "https://")):
        raise ValueError("A fully qualified URL is required for BrightDataUnlockerTool")
    return url

result = tool.run(url=validate_unlock_url(url), data_format='markdown')

Type guard

def is_unlocker_call_ready(kwargs: dict, tool) -> bool:
    return bool(kwargs.get("url") or tool.url)

Try / catch

try:
    result = tool.run(data_format='markdown')
except ValueError as e:
    if "url is required" in str(e):
        result = tool.run(url=get_url_from_context(), data_format='markdown')
    else:
        raise

Prevention

When it happens

Trigger: Constructing BrightDataUnlockerTool() with no url and calling tool.run(format='markdown'); passing url=None/'' at both constructor and call; agents omitting the url argument.

Common situations: Tool instantiated generically for reuse with the URL expected per call but forgotten, empty URL from prompt interpolation, config templates leaving url blank.

Related errors


AI-assisted analysis of crewAIInc/crewAI@754d7323be (2026-08-15). Data as JSON: /api/errors/fbe0ed7c4f7adca8. Report an issue: GitHub.