odysseus-dev/odysseus · error · HTTPException

detail

Error message

detail

What it means

HTTP 400 from POST /v1/chat Case 2 (direct api_key): the caller-supplied base_url failed validate_public_http_url() in src/url_security.py. The detail is the ValueError message with the first 'URL' replaced by 'base_url': either 'base_url is too long' or 'base_url must point to a public HTTP(S) endpoint'. The check enforces http(s) scheme, resolvable DNS, and no private/blocked IPs — fail-closed on DNS errors.

Source

Thrown at routes/webhook/webhook_routes.py:292

            # chat-scoped token.
            _sess_owner = getattr(sess, "owner", None)
            if not _caller_owns_session(_sess_owner, _tok_user):
                raise HTTPException(404, "Session not found")

        # --- Case 2: Direct API key + model (no pre-configured endpoint needed) ---
        if not sess and body.api_key:
            api_key = body.api_key.strip()
            model = body.model or "deepseek-chat"

            # Validate only token-supplied direct base_url; auto-resolved known-provider
            # URLs are not subject to extra local/LAN blocking beyond existing provider logic.
            direct_base_url = body.base_url.strip().rstrip("/") if body.base_url else None
            if direct_base_url:
                try:
                    base_url = validate_public_http_url(direct_base_url)
                except ValueError as e:
                    detail = str(e).replace("URL", "base_url", 1)
                    raise HTTPException(400, detail)
            else:
                base_url = _resolve_base_url(model, body.provider)
            if not base_url:
                raise HTTPException(400,
                    "Could not auto-detect provider. Pass base_url (e.g. 'https://api.deepseek.com/v1') "
                    "or provider ('deepseek', 'openai', 'groq', etc.)")
            base_url = normalize_base(base_url)
            endpoint_url = build_chat_url(base_url)

            if not session_manager:
                raise HTTPException(500, "Session manager not available")

            sid = str(uuid.uuid4())
            sess = session_manager.create_session(
                session_id=sid, name="API Chat", endpoint_url=endpoint_url,
                model=model, owner=token_owner,
            )
            sess.headers = build_headers(api_key, base_url)

View on GitHub (pinned to f9235ebbf1)

Solutions

  1. Point base_url at a publicly reachable https endpoint (expose the model server via a tunnel or host it publicly)
  2. If the model server is trusted infra, have an admin configure it as a ModelEndpoint (Case 3 path) instead of passing base_url per-request
  3. Verify the hostname resolves publicly from the server before using it

Example fix

// before
{"message":"hi","api_key":"sk-...","model":"llama3","base_url":"http://192.168.1.50:11434/v1"}
// -> 400 "base_url must point to a public HTTP(S) endpoint"

// after: omit base_url and use a known provider, or a public endpoint
{"message":"hi","api_key":"sk-...","model":"deepseek-chat"}
Defensive patterns

Strategy: validation

Validate before calling

from src.url_security import validate_public_http_url
try:
    base = validate_public_http_url(candidate_base_url)
except ValueError as e:
    inform_user(str(e).replace("URL", "base_url", 1))  # do not send

Try / catch

if resp.status_code == 400 and 'base_url' in detail:
    # private/unresolvable base_url: switch to public endpoint or drop base_url

Prevention

When it happens

Trigger: Passing base_url=http://192.168.1.50:11434/v1 (local Ollama), base_url=http://localhost:8000, an ftp:// URL, a >2048-char URL, or a hostname that does not resolve from the server.

Common situations: Trying to use a self-hosted LAN model server (vLLM/Ollama/LM Studio) through the token-facing chat API — blocked because API-token callers are untrusted and private targets are SSRF surfaces; typos in domains; split-horizon DNS where the name resolves internally only.

Related errors


AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14). Data as JSON: /api/errors/e5a5dc77dcedcefb. Report an issue: GitHub.