sipeed/picoclaw · error

No default API base for provider %q

Error message

No default API base for provider %q

What it means

Returned by POST /api/models/fetch when api_base was not supplied AND providers.DefaultAPIBaseForProtocol(provider) returns "" for that provider. The handler refuses to guess an endpoint: without an api_base it cannot build the upstream /models URL, so it returns 400 naming the provider.

Source

Thrown at web/backend/api/models.go:676

	if !providers.IsModelProviderFetchable(req.Provider) {
		http.Error(w, fmt.Sprintf("provider %q does not support model listing", req.Provider), http.StatusBadRequest)
		return
	}

	apiKey := strings.TrimSpace(req.APIKey)
	apiBase := strings.TrimSpace(req.APIBase)

	if apiKey == "" && req.ModelIndex != nil {
		if stored := h.lookupStoredAPIKey(*req.ModelIndex, req.Provider, apiBase); stored != "" {
			apiKey = stored
		}
	}

	if apiBase == "" {
		apiBase = providers.DefaultAPIBaseForProtocol(req.Provider)
	}
	if apiBase == "" {
		http.Error(w, fmt.Sprintf("No default API base for provider %q", req.Provider), http.StatusBadRequest)
		return
	}

	ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
	defer cancel()

	models, err := fetchUpstreamModels(ctx, req.Provider, apiBase, apiKey)
	if err != nil {
		http.Error(w, fmt.Sprintf("Failed to fetch models: %v", err), http.StatusBadGateway)
		return
	}

	// Auto-save fetched models to catalog
	catalogModels := make([]CatalogModel, len(models))
	for i, m := range models {
		catalogModels[i] = CatalogModel{ID: m.ID, OwnedBy: m.OwnedBy}
	}
	if saveErr := SaveCatalog(req.Provider, apiBase, apiKey, catalogModels); saveErr != nil {

View on GitHub (pinned to 49183d7e8d)

Solutions

  1. Send an explicit api_base: {"provider": "...", "api_base": "http://localhost:11434/v1"}
  2. Verify the api_base value is non-empty after trimming — whitespace-only fails
  3. Confirm the provider id is the one that actually has a default base (e.g. plain "openai"), not a custom alias

Example fix

// before
body: JSON.stringify({ provider: 'custom', api_key: key })

// after
body: JSON.stringify({ provider: 'custom', api_base: 'http://localhost:11434/v1', api_key: key })
Defensive patterns

Strategy: validation

Validate before calling

function effectiveApiBase(provider: string, apiBase: string | undefined, defaults: Record<string,string>): string {
  const base = (apiBase ?? '').trim();
  if (base) return base;
  const def = defaults[provider];
  if (!def) throw new Error(`no default API base for ${provider} — api_base is required`);
  return def;
}

Type guard

function hasApiBase(v: unknown): v is { api_base: string } {
  return typeof v === 'object' && v !== null && typeof (v as any).api_base === 'string' && (v as any).api_base.trim() !== '';
}

Try / catch

try {
  const res = await fetch('/api/models/fetch', {...});
  if (res.status === 400 && (await res.text()).includes('No default API base')) {
    /* mark api_base required in the form for this provider and block submit */
  }
} catch (e) { /* network */ }

Prevention

When it happens

Trigger: POST /api/models/fetch with {"provider": "custom-openai-compatible"} (a provider type with no well-known default base) and no api_base; api_base sent as "" or only whitespace after trimming; a proxy provider that requires an explicit endpoint.

Common situations: Self-hosted LLM setups (ollama, vllm, llama.cpp) behind a custom provider id; frontend omits the api_base field for providers where it assumed a default exists; provider added via config with empty api_base.

Related errors


AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15). Data as JSON: /api/errors/7771d3f4565ee9a6. Report an issue: GitHub.