siyuan-note/siyuan · error

provider base URL is required

Error message

provider base URL is required

What it means

resolveAIProvider (kernel/api/ai.go:41-60) resolves the AI provider for a request either from saved config (by provider id) or from an inline providerConfig object. When an inline providerConfig is supplied, its baseURL must be a non-empty string after trimming; otherwise the request is rejected before any network call. This is deliberate: conf.AI.Normalize() would silently default an empty BaseURL to https://api.openai.com/v1 (conf/ai.go:593-596), so the API layer refuses blank URLs rather than let a misconfigured request hit OpenAI by accident.

Source

Thrown at kernel/api/ai.go:52

	TaskID  string                  `json:"taskID"`
	IDs     []string                `json:"ids"`
	Input   string                  `json:"input"`
	Action  string                  `json:"action"`
	History []model.AIEditorMessage `json:"history"`
}

func resolveAIProvider(arg map[string]any) (*conf.Provider, error) {
	if providerConfig, ok := arg["providerConfig"]; ok && providerConfig != nil {
		data, err := gulu.JSON.MarshalJSON(providerConfig)
		if err != nil {
			return nil, err
		}
		provider := &conf.Provider{}
		if err = gulu.JSON.UnmarshalJSON(data, provider); err != nil {
			return nil, err
		}
		if strings.TrimSpace(provider.BaseURL) == "" {
			return nil, errors.New("provider base URL is required")
		}
		ai := &conf.AI{Providers: []*conf.Provider{provider}}
		ai.Normalize()
		if len(ai.Providers) != 1 {
			return nil, errors.New("invalid provider config")
		}
		return ai.Providers[0], nil
	}

	providerID, _ := arg["provider"].(string)
	for _, provider := range model.Conf.AI.Providers {
		if provider != nil && provider.ID == providerID {
			return provider, nil
		}
	}
	return nil, errors.New("provider not found")
}

View on GitHub (pinned to afa823b6b4)

Solutions

  1. Set baseURL explicitly, including scheme and version path, e.g. https://api.openai.com/v1 or http://127.0.0.1:11434/v1 for local gateways
  2. Check the exact JSON key: the Provider struct binds baseURL (case-insensitive JSON match, but 'url' will not bind)
  3. If you want to use an already-configured provider, send its provider id instead of an inline providerConfig object
  4. Trim-test the value client-side before posting

Example fix

// before
{"providerConfig": {"name": "local-llm", "apiKey": "sk-x"}}

// after
{"providerConfig": {"name": "local-llm", "baseURL": "http://127.0.0.1:11434/v1", "apiKey": "sk-x"}}
Defensive patterns

Strategy: validation

Validate before calling

const okProvider = (p: any) =>
  !!p && typeof p.baseURL === 'string' && p.baseURL.trim().length > 0;
if (!okProvider(req.providerConfig)) throw new Error('provider base URL is required');

Type guard

const hasBaseURL = (p: unknown): p is {baseURL: string} =>
  !!p && typeof (p as any).baseURL === 'string' && (p as any).baseURL.trim() !== '';

Try / catch

null

Prevention

When it happens

Trigger: Any AI API taking a providerConfig object (e.g. /api/ai/ chat endpoints and agent provider selection) with baseURL omitted, empty, or whitespace-only — for example a custom provider meant for a local gateway (Ollama/vLLM) sent as {"name": "local", "apiKey": "..."} with no baseURL.

Common situations: Frontend forms adding a provider but not sending the URL field; JSON field-name mismatch (url vs baseUrl vs baseURL — the struct tag is baseURL); users assuming the saved default applies to inline configs; copy-pasted configs with the URL line deleted.

Related errors


AI-assisted analysis of siyuan-note/siyuan@afa823b6b4 (2026-08-18). Data as JSON: /api/errors/5b1d6608569497d4. Report an issue: GitHub.