unclecode/crawl4ai · error · HTTPException

str(e)

Error message

str(e)

What it means

A 400 raised by POST /config/dump when _config_from_json() raises TypeError or ValueError while parsing the submitted config dict. The detail is the underlying exception message, so the real cause (bad key, wrong type, malformed value) is embedded in str(e).

Source

Thrown at deploy/docker/server.py:560

            "Token issuance is disabled: no api_token is configured on the server.",
        )
    if not req.api_token or not constant_time_eq(req.api_token, expected_token):
        raise HTTPException(401, "Invalid or missing api_token")
    if not verify_email_domain(req.email):
        raise HTTPException(400, "Invalid email domain")
    token = create_access_token({"sub": req.email})
    return {"email": req.email, "access_token": token, "token_type": "bearer"}


@app.post("/config/dump")
async def config_dump(
    data: dict,
    _td: Dict = Depends(token_dep),
):
    try:
        return JSONResponse(_config_from_json(data))
    except (TypeError, ValueError) as e:
        raise HTTPException(400, str(e))


@app.post("/md")
@limiter.limit(config["rate_limiting"]["default_limit"])
@mcp_tool("md")
async def get_markdown(
    request: Request,
    body: MarkdownRequest,
    _td: Dict = Depends(token_dep),
):
    """
    Convert a web page into Markdown format.

    Supports multiple extraction modes:
    - fit (default): Readability-based extraction for clean content
    - raw: Direct DOM to Markdown conversion
    - bm25: BM25 relevance ranking with optional query
    - llm: LLM-based summarization with optional query

View on GitHub (pinned to 7e80152142)

Solutions

  1. Read the str(e) detail in the 400 response — it names the offending key/value.
  2. Validate your payload against the current schema first: GET /schema returns the canonical BrowserConfig/CrawlerRunConfig dump.
  3. Regenerate the config from a working crawl instead of hand-writing it.
  4. Match the crawl4ai version between the client that produced the config and the server that consumes it.

Example fix

# before
requests.post(f'{base}/config/dump', json={'browser': {'headless': 'yes'}})  # str instead of bool
# after
schema = requests.get(f'{base}/schema').json()
requests.post(f'{base}/config/dump', json={'browser': {'headless': True}})
Defensive patterns

Strategy: validation

Validate before calling

import requests

schema = requests.get(f'{BASE}/schema').json()  # canonical config shape

def check_config(cfg: dict) -> None:
    def walk(node, template, path='config'):
        if isinstance(template, dict):
            for k, v in node.items():
                if k not in template:
                    raise ValueError(f'unknown key {path}.{k}')
                walk(v, template[k], f'{path}.{k}')
        elif isinstance(template, bool):
            if not isinstance(node, bool):
                raise ValueError(f'{path} must be bool, got {type(node).__name__}')
    walk(cfg, schema)

Try / catch

resp = requests.post(f'{BASE}/config/dump', json=cfg, headers=hdrs)
if resp.status_code == 400:
    raise ValueError(f'config rejected: {resp.json()["detail"]}')  # detail carries the offending key

Prevention

When it happens

Trigger: POST /config/dump with a JSON body that is not a valid CrawlerRunConfig/BrowserConfig shape: unknown/misspelled keys, wrong value types (string where a number is expected), or nested structures that fail pydantic/manual parsing. Auth via token_dep must already have passed.

Common situations: Client sends a config dumped from an older/newer crawl4ai version whose schema differs; hand-editing a config dump and introducing typos; passing {'crawler': {...}} vs the flat dict the endpoint expects.

Related errors


AI-assisted analysis of unclecode/crawl4ai@7e80152142 (2026-08-14). Data as JSON: /api/errors/22215011de4f3c62. Report an issue: GitHub.