{"record":{"id":"e5442ac781e35f7a","repo":"lfnovo/open-notebook","slug":"invalid-model-type-must-be-one-of-valid-types","errorCode":null,"errorMessage":"Invalid model type. Must be one of: {valid_types}","messagePattern":"Invalid model type\\. Must be one of: (.+?)","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"warning","filePath":"api/routers/models.py","lineNumber":212,"sourceCode":"            for model in models\n        ]\n    except HTTPException:\n        raise\n    except OpenNotebookError:\n        raise\n    except Exception as e:\n        logger.error(f\"Error fetching models: {str(e)}\")\n        raise HTTPException(status_code=500, detail=f\"Error fetching models: {str(e)}\")\n\n\n@router.post(\"/models\", response_model=ModelResponse)\nasync def create_model(model_data: ModelCreate):\n    \"\"\"Create a new model configuration.\"\"\"\n    try:\n        # Validate model type\n        valid_types = [\"language\", \"embedding\", \"text_to_speech\", \"speech_to_text\"]\n        if model_data.type not in valid_types:\n            raise HTTPException(\n                status_code=400,\n                detail=f\"Invalid model type. Must be one of: {valid_types}\",\n            )\n\n        # Check for duplicate model name under the same provider and type (case-insensitive)\n        from open_notebook.database.repository import repo_query\n\n        existing = await repo_query(\n            \"SELECT * FROM model WHERE string::lowercase(provider) = $provider AND string::lowercase(name) = $name AND string::lowercase(type) = $type LIMIT 1\",\n            {\n                \"provider\": model_data.provider.lower(),\n                \"name\": model_data.name.lower(),\n                \"type\": model_data.type.lower(),\n            },\n        )\n        if existing:\n            raise HTTPException(\n                status_code=400,","sourceCodeStart":194,"sourceCodeEnd":230,"githubUrl":"https://github.com/lfnovo/open-notebook/blob/a7de90d38aaf18ee85fd661854d35c11e44613e2/api/routers/models.py#L194-L230","documentation":"400 raised by POST /api/v1/models when model_data.type is not one of the four allowed values: language, embedding, text_to_speech, speech_to_text. The type field is validated inline in the router before any duplicate check or DB write.","triggerScenarios":"POST /models with body type: 'chat', 'llm', 'tts', 'transcription', or any casing/typo variant; the comparison is case-sensitive so 'Language' also fails.","commonSituations":"Frontend sending free-text or legacy type names; scripts written against older API versions; casing mismatches.","solutions":["Set type to exactly one of language, embedding, text_to_speech, speech_to_text (lowercase)","Validate/normalize the type in the client before POSTing","Update stale scripts or SDK helpers that use old type names"],"exampleFix":"// before\n{\"name\":\"gpt-4o\",\"provider\":\"openai\",\"type\":\"chat\"}\n// after\n{\"name\":\"gpt-4o\",\"provider\":\"openai\",\"type\":\"language\"}","handlingStrategy":"validation","validationCode":"const VALID = ['language','embedding','text_to_speech','speech_to_text'];\nif (!VALID.includes(model.type)) throw new Error(`type must be one of ${VALID.join(', ')}`);","typeGuard":"const isModelType = (t: string): t is 'language'|'embedding'|'text_to_speech'|'speech_to_text' =>\n  ['language','embedding','text_to_speech','speech_to_text'].includes(t);","tryCatchPattern":"try { await api.createModel(data); } catch (e) { if (e.status === 400 && /Invalid model type/.test(e.detail)) fixType(); }","preventionTips":["Use a constrained dropdown/enum in forms","Normalize to lowercase before submit","Pin the allowed list from the API docs"],"tags":["models","validation","http-400"],"backgroundTag":"request-validation-failed","analyzedSha":"a7de90d38aaf18ee85fd661854d35c11e44613e2","analyzedAt":"2026-08-27T02:39:58.166Z","schemaVersion":2},"datasetVersion":"2026-08-27T03:17:27.898Z"}