router-for-me/CLIProxyAPI · error
model_not_found
model_not_found
Error message
unknown provider for model <modelName>
What it means
Routing error from the base API handler: after resolving the requested model name (including any thinking suffix stripped), no provider in the registry is registered to serve it, so the request cannot be dispatched. The handler deliberately returns HTTP 400 with OpenAI-style code model_not_found (rather than 404) to distinguish 'route missing' from 'model unknown', and builds the message via sjson because the model name is client-supplied and must not corrupt or inject into the JSON body.
Source
Thrown at sdk/api/handlers/handlers_routing.go:217
providers = util.GetProviderName(resolvedModelName)
}
if len(providers) == 0 {
// The client asked for a model this proxy cannot route. Report it as a request
// error so streaming clients receive an actionable message instead of a
// gateway failure they would keep retrying. 400 is used rather than 404 to keep
// it distinguishable from an unregistered HTTP route.
// The model name is client supplied, so it is inserted through sjson rather
// than formatted into the JSON literal: an unescaped quote would otherwise
// corrupt the body or let the caller overwrite the error code.
body := `{"error":{"message":"","type":"invalid_request_error","code":"model_not_found","param":"model"}}`
body, errSet := sjson.Set(body, "error.message", "unknown provider for model "+modelName)
if errSet != nil {
body = `{"error":{"message":"unknown provider for model","type":"invalid_request_error","code":"model_not_found","param":"model"}}`
}
return nil, "", &interfaces.ErrorMessage{
StatusCode: http.StatusBadRequest,
Error: errors.New(body),
}
}
// The thinking suffix is preserved in the model name itself, so no
// metadata-based configuration passing is needed.
return providers, resolvedModelName, nil
}
func (h *BaseAPIHandler) validateImageOnlyModel(modelName string, allowImageModel bool) *interfaces.ErrorMessage {
baseModel := strings.TrimSpace(thinking.ParseSuffix(modelName).ModelName)
if baseModel == "" {
baseModel = strings.TrimSpace(modelName)
}
if isOpenAIImageOnlyModel(baseModel) && !allowImageModel {
return &interfaces.ErrorMessage{
StatusCode: http.StatusServiceUnavailable,
Error: fmt.Errorf("model %s is only supported on /v1/images/generations and /v1/images/edits", routeModelBaseName(baseModel)),
}View on GitHub (pinned to 78f0c4079e)
Solutions
- List available models via the models endpoint and use an exact registered name
- Verify the provider that should serve the model has valid auth configured (auths/ directory or management API) and is not disabled
- If the model should exist, check the registry/updater state — run with --local-model off or refresh the model list
- Fix typos including thinking suffixes (e.g. model:high vs model-high conventions)
Example fix
// before
curl -X POST http://localhost:8000/v1/chat/completions -d '{"model":"gpt-5-turbo","messages":[...]}'
// after (use a model the registry actually serves)
curl http://localhost:8000/v1/models
curl -X POST http://localhost:8000/v1/chat/completions -d '{"model":"gpt-5","messages":[...]}' Defensive patterns
Strategy: validation
Validate before calling
// Validate the model name against the served list before sending the request
models, _ := client.Models(ctx)
served := map[string]bool{}
for _, m := range models { served[m.ID] = true }
if !served[req.Model] {
return fmt.Errorf("model %q is not served; pick from %d registered models", req.Model, len(served))
} Type guard
func isServedModel(want string, served []string) bool {
want = strings.TrimSpace(want)
for _, m := range served {
if m == want {
return true
}
}
return false
} Try / catch
resp, err := client.Do(req)
if err == nil && resp.StatusCode == http.StatusBadRequest {
var e struct{ Error struct{ Code, Message string } }
_ = json.NewDecoder(resp.Body).Decode(&e)
if e.Error.Code == "model_not_found" {
return fmt.Errorf("model %q unknown to this gateway; fetch /v1/models", model)
}
}
// note: 400 (not 404) signals unknown model vs unknown route Prevention
- Fetch the models endpoint at client startup and validate all hardcoded model names
- Keep provider auths healthy — models with zero available credentials drop out of routing
- After registry updates, re-validate model lists in config and clients
When it happens
Trigger: POST /v1/chat/completions (or any protocol route) with a model field that matches no registered provider entry — unknown name, typo, model requiring an auth/provider that is not configured or has no available credentials, or a model removed after a registry update.
Common situations: Model registry updated and a model renamed/removed; provider auths missing or all exhausted so the model resolves to no provider; client hardcodes a model name from a different deployment; thinking-suffix typo making the base name unmatched.
Related errors
- target executor plugin id is required
- plugin %s does not declare an executor
- plugin executor %s not found
AI-assisted analysis of router-for-me/CLIProxyAPI@78f0c4079e (2026-08-15).
Data as JSON: /api/errors/d57d0478198d5f51.
Report an issue: GitHub.