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
- 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)
- For OMLX-style endpoint_key runtimes, put the URL in the dialog's endpoint field (endpointOverride), not in the API-key value
- 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
- Always paste absolute URLs with an explicit http:// or https:// scheme - bare hosts fail earlier in new URL()
- For local runtimes include the port, e.g. http://127.0.0.1:8080; /v1 is appended automatically when the path is empty
- For OMLX-style endpoint_key runtimes, the URL belongs in the endpoint field, not the API-key field
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
- Could not reach
- settings.ai.slugMissingError|settings.ai.slugInUseError|sett…
- OpenHuman uses the session JWT — keys are not configurable…
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)