tinyhumansai/openhuman · error · Error

Endpoint must start with http:// or https://

Error message

Endpoint must start with http:// or https://

What it means

Thrown while saving a local-runtime provider in AI settings: the endpoint (endpointOverride for endpoint_key runtimes like OMLX, otherwise the trimmed field value) is parsed with new URL(rawEndpoint), and if it parses but the protocol is not http: or https:, this error fires. Note the ordering: a malformed URL (no scheme at all, e.g. 'localhost:1234' parsed as host 'localhost' with scheme mismatch, or 'foo bar') throws 'Invalid URL' from the URL constructor earlier - this specific message means a well-formed URL with the wrong scheme (file://, ws://, ftp://). On success an empty/'/' pathname is normalized to /v1 and a trailing slash is stripped.

Solutions

  1. Use a full absolute URL with http:// or https://, e.g. http://127.0.0.1:8080 (the /v1 path is appended automatically if omitted)
  2. For OMLX-style endpoint_key runtimes, put the URL in the dialog's endpoint field (endpointOverride), not in the API-key value
  3. If you really meant a local file, that provider type is not a local runtime - use the non-local path where defaultEndpointFor(slug) applies

Example fix

// before
const rawEndpoint = 'file:///opt/models-server';

// after
const rawEndpoint = 'http://127.0.0.1:8080'; // empty pathname normalizes to /v1
Defensive patterns

Strategy: validation

Validate before calling

// Validate before the save handler runs
function isValidHttpEndpoint(raw: string): boolean {
  try {
    const u = new URL(raw.trim());
    return u.protocol === 'http:' || u.protocol === 'https:';
  } catch {
    return false; // no scheme / malformed - new URL() would throw Invalid URL
  }
}
// disable Save: disabled={!isValidHttpEndpoint(endpointOverride ?? '')}

Prevention

When it happens

Trigger: Pasting a URL with a non-HTTP scheme into the endpoint field of a local runtime (isLocalRuntime) - file:///path/to/model-server, ws://host:port, ftp://... - or a scheme typo that still parses as a URL.

Common situations: Copying a file path from Finder/Explorer and pasting it as the endpoint; users assuming 'localhost:8080' needs no scheme (that case throws Invalid URL from new URL instead, since localhost is treated as the scheme); pointing at a websocket endpoint of a server that also exposes HTTP.

Related errors


AI-assisted analysis of tinyhumansai/openhuman@7491200858 (2026-08-17). Data as JSON: /api/errors/0005fcf9ab0e4cb0. Report an issue: GitHub.

Appendix: source

Thrown at app/src/components/settings/panels/AIPanel.tsx:2987

      // /models probe is made — auth + execution both go through the local
      // `claude` CLI. Mirrors the Codex skip, but also skips model listing.
      const isCliLogin = credentialMode === 'cli_login';
      setBusyAction(`toggle-${localLabel ? localLabel.toLowerCase().replace(/\s/g, '') : slug}`);
      // A fresh attempt on THIS provider clears only its own prior advisory —
      // an advisory about a different provider must survive (#5341).
      setProviderSaveNotice(prev => (prev?.slug === slug ? null : prev));

      try {
        const trimmed = value.trim();
        // For `endpoint_key` (OMLX), the endpoint URL arrives via `endpointOverride`
        // (the dialog's endpoint field) and `trimmed` is the API key. For plain
        // `endpoint` runtimes, `trimmed` itself is the endpoint URL.
        const rawEndpoint = isEndpointKey ? (endpointOverride ?? '').trim() : trimmed;
        const endpoint = isLocalRuntime
          ? (() => {
              const url = new URL(rawEndpoint);
              if (!/^https?:$/.test(url.protocol)) {
                throw new Error('Endpoint must start with http:// or https://');
              }
              if (url.pathname === '' || url.pathname === '/') {
                url.pathname = '/v1';
              }
              return url.toString().replace(/\/$/, '');
            })()
          : defaultEndpointFor(slug);

        const upserted: CloudProvider = {
          id: `p_${slug}_${Math.random().toString(36).slice(2, 7)}`,
          slug,
          label: localLabel ?? BUILTIN_PROVIDER_META[slug]?.label ?? slug,
          endpoint,
          authStyle: authStyleForSlug(slug),
          // CLI-login providers hold no API key — reflect that honestly so
          // the entry matches its reloaded (has_api_key === false) shape.
          maskedKey: maskKeyLabel(!isCliLogin),
        };

View on GitHub (pinned to 7491200858)