{"record":{"id":"5b1d6608569497d4","repo":"siyuan-note/siyuan","slug":"provider-base-url-is-required","errorCode":null,"errorMessage":"provider base URL is required","messagePattern":"provider base URL is required","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/api/ai.go","lineNumber":69,"sourceCode":"\t}\n\treturn nil\n}\n\nfunc resolveAIProvider(request apicontract.AIProviderRequest) (*conf.Provider, error) {\n\tif err := request.ProviderError(); err != nil {\n\t\treturn nil, err\n\t}\n\tif providerConfig := request.ProviderConfig; providerConfig != nil {\n\t\tdata, err := gulu.JSON.MarshalJSON(providerConfig)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tprovider := &conf.Provider{}\n\t\tif err = gulu.JSON.UnmarshalJSON(data, provider); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif strings.TrimSpace(provider.BaseURL) == \"\" {\n\t\t\treturn nil, errors.New(\"provider base URL is required\")\n\t\t}\n\t\tif err = util.ValidateAIProviderHeaders(provider.Headers); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tai := &conf.AI{Providers: []*conf.Provider{provider}}\n\t\tai.Normalize()\n\t\tif len(ai.Providers) != 1 {\n\t\t\treturn nil, errors.New(\"invalid provider config\")\n\t\t}\n\t\treturn ai.Providers[0], nil\n\t}\n\n\tproviderID := request.Provider\n\tfor _, provider := range model.Conf.AI.Providers {\n\t\tif provider != nil && provider.ID == providerID {\n\t\t\treturn provider, nil\n\t\t}\n\t}","sourceCodeStart":51,"sourceCodeEnd":87,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/api/ai.go#L51-L87","documentation":"resolveAIProvider, when given an inline providerConfig, requires a non-blank BaseURL on the provider. After unmarshalling the supplied config into conf.Provider, an empty base URL means no API endpoint is known, so the request is refused before any network call.","triggerScenarios":"Calling an AI endpoint (e.g. the test-model or list-models contract APIs) with request.ProviderConfig whose baseURL/baseUrl field is empty, whitespace-only, or omitted entirely.","commonSituations":"User adds a custom AI provider in settings but leaves the API endpoint field blank, an API client builds ProviderConfig without baseURL, or migrating configs where the URL field was renamed and no longer maps.","solutions":["Set the provider's baseURL to the full API endpoint (e.g. https://api.openai.com) and retry","Check field-name casing in the JSON payload — it must map to conf.Provider's BaseURL tag","If switching from providerID reference to inline config, copy the BaseURL from the existing saved provider","Verify with a request containing only baseURL to isolate the field mapping"],"exampleFix":"// before\n{\"providerConfig\": {\"name\": \"my-openai\", \"key\": \"sk-...\"}}\n// after\n{\"providerConfig\": {\"name\": \"my-openai\", \"baseURL\": \"https://api.openai.com\", \"key\": \"sk-...\"}}","handlingStrategy":"validation","validationCode":"if strings.TrimSpace(provider.BaseURL) == \"\" {\n    return errors.New(\"provider base URL is required\")\n}","typeGuard":"func hasBaseURL(p *conf.Provider) bool { return p != nil && strings.TrimSpace(p.BaseURL) != \"\" }","tryCatchPattern":"provider, err := resolveAIProvider(req)\nif err != nil {\n    return contractFailure[string](gulu.Ret.NewResultWithMsg(err.Error()))\n}","preventionTips":["Require the API endpoint field in provider settings UI before saving","Validate baseURL client-side before sending ProviderConfig","Match JSON field names to conf.Provider's tags exactly"],"tags":["ai","config","api","validation"],"backgroundTag":"missing-required-config-field","analyzedSha":"9f775e8a12daef8255556097396f9b2739078892","analyzedAt":"2026-09-19T03:17:15.984Z","contentChangedAt":"2026-09-19T03:17:15.984Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}