{"record":{"id":"7f8950aa181ac4ae","repo":"musistudio/claude-code-router","slug":"provider-base-url-must-use-http-or-https","errorCode":null,"errorMessage":"Provider Base URL must use http or https.","messagePattern":"Provider Base URL must use http or https\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/core/src/contracts/deep-link.ts","lineNumber":471,"sourceCode":"    }\n  }\n  return undefined;\n}\n\nfunction boundedString(value: string | undefined, maxLength: number, label: string): string | undefined {\n  if (!value) {\n    return undefined;\n  }\n  if (value.length > maxLength) {\n    throw new Error(`${label} is too long.`);\n  }\n  return value;\n}\n\nfunction validateProviderBaseUrl(value: string): void {\n  const url = new URL(providerUrlWithDefaultScheme(value));\n  if (![\"http:\", \"https:\"].includes(url.protocol)) {\n    throw new Error(\"Provider Base URL must use http or https.\");\n  }\n  if (!url.hostname) {\n    throw new Error(\"Provider Base URL is invalid.\");\n  }\n}\n\nfunction validateManifestUrl(value: string): void {\n  const url = new URL(value);\n  if (url.protocol !== \"https:\") {\n    throw new Error(\"Provider manifest URL must use https.\");\n  }\n  if (url.username || url.password) {\n    throw new Error(\"Provider manifest URL cannot include credentials.\");\n  }\n  if (!url.hostname) {\n    throw new Error(\"Provider manifest URL is invalid.\");\n  }\n}","sourceCodeStart":453,"sourceCodeEnd":489,"githubUrl":"https://github.com/musistudio/claude-code-router/blob/99f24806c6a2c660b16e53e95211c517448a6c90/packages/core/src/contracts/deep-link.ts#L453-L489","documentation":"validateProviderBaseUrl normalizes the base URL (adding a default scheme when missing via providerUrlWithDefaultScheme) and requires the resulting protocol to be http: or https:. Any other scheme — ftp://, ws://, file:// — is rejected because the gateway can only call HTTP endpoints.","triggerScenarios":"Passing base_url=\"ftp://host\", \"ws://host\", or a scheme like \"localhost:3000/v1\" where the default-scheme logic still yields a non-http protocol.","commonSituations":"Typos in scheme; copying an ws:// websocket endpoint from provider docs; file:// paths pasted as base URL; custom scheme prefixes like gws://.","solutions":["Set base_url to start with http:// or https:// (e.g. https://api.provider.com/v1)","For localhost dev use http://localhost:3000/v1 explicitly","Double-check the provider docs endpoint — it is an HTTP REST endpoint, not websockets"],"exampleFix":"// before\n{\"base_url\":\"ws://api.acme.dev/v1\"}\n// after\n{\"base_url\":\"https://api.acme.dev/v1\"}","handlingStrategy":"validation","validationCode":"const u = new URL(baseUrl.includes(\"://\") ? baseUrl : `https://${baseUrl}`); if (u.protocol !== \"https:\" && u.protocol !== \"http:\") return fixScheme();","typeGuard":"const isHttpBaseUrl = (v: string) => {\n  try { const p = new URL(/^\\w+:\\/\\//.test(v) ? v : `https://${v}`).protocol; return p === \"http:\" || p === \"https:\"; } catch { return false; }\n};","tryCatchPattern":"try { validateProviderBaseUrl(baseUrl); } catch (e) { if (e instanceof Error && e.message.includes(\"http or https\")) return coerceToHttps(baseUrl); throw e; }","preventionTips":["Default to https:// when building base URLs","Never copy ws:// endpoints as base_url"],"tags":["base-url","validation","protocol"],"backgroundTag":"invalid-url-scheme","analyzedSha":"99f24806c6a2c660b16e53e95211c517448a6c90","analyzedAt":"2026-08-27T04:11:01.184Z","schemaVersion":2},"datasetVersion":"2026-08-27T08:17:20.692Z"}