{"record":{"id":"1e9ec27f4f3b9695","repo":"jamiepine/voicebox","slug":"str-e","errorCode":null,"errorMessage":"{str(e)}","messagePattern":"\\{str\\(e\\)\\}","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"warning","filePath":"backend/routes/profiles.py","lineNumber":34,"sourceCode":"from ..database import VoiceProfile as DBVoiceProfile, get_db\nfrom ..services import channels, export_import, personality, profiles\nfrom ..services.profiles import _profile_to_response\n\nlogger = logging.getLogger(__name__)\n\nrouter = APIRouter()\n\n\n@router.post(\"/profiles\", response_model=models.VoiceProfileResponse)\nasync def create_profile(\n    data: models.VoiceProfileCreate,\n    db: Session = Depends(get_db),\n):\n    \"\"\"Create a new voice profile.\"\"\"\n    try:\n        return await profiles.create_profile(data, db)\n    except ValueError as e:\n        raise HTTPException(status_code=400, detail=str(e))\n    except Exception as e:\n        raise HTTPException(status_code=400, detail=str(e))\n\n\n@router.get(\"/profiles\", response_model=list[models.VoiceProfileResponse])\nasync def list_profiles(db: Session = Depends(get_db)):\n    \"\"\"List all voice profiles.\"\"\"\n    return await profiles.list_profiles(db)\n\n\n@router.post(\"/profiles/import\", response_model=models.VoiceProfileResponse)\nasync def import_profile(\n    file: UploadFile = File(...),\n    db: Session = Depends(get_db),\n):\n    \"\"\"Import a voice profile from a ZIP archive.\"\"\"\n    MAX_FILE_SIZE = 100 * 1024 * 1024\n","sourceCodeStart":16,"sourceCodeEnd":52,"githubUrl":"https://github.com/jamiepine/voicebox/blob/51f49dea198384b4eb6087b72c17057c6eb1c1cd/backend/routes/profiles.py#L16-L52","documentation":"400 from POST /profiles. profiles.create_profile raises ValueError for expected business-rule failures — most commonly 'A profile with the name ... already exists' (duplicate-name guard) and schema-level validation errors produced while building the profile. The route maps ValueError to HTTPException(400, str(e)), so the human-readable reason is forwarded.","triggerScenarios":"POST /profiles with a name that already exists; language/voice_type/preset fields that fail internal consistency checks (e.g. preset profile missing preset_engine, designed profile missing design_prompt, cloned profile on an engine that doesn't support cloning); reference-text or design_prompt validation failing inside create_profile.","commonSituations":"User submits the same profile name twice; client didn't refresh the profile list before creating; switching voice_type without supplying the required companion fields (preset_engine + preset_voice_id, or design_prompt); frontend default values violate a server-side rule.","solutions":["Read detail — it names the exact violated rule (duplicate name, missing preset metadata, etc.).","For duplicate names, choose a unique name or PUT /profiles/{id} to update the existing one instead.","For voice_type='preset', include preset_engine and preset_voice_id; for 'designed', include design_prompt.","GET /profiles to reconcile client state with the server before retrying."],"exampleFix":"// before\nPOST /profiles {\"name\":\"Morgan\",\"voice_type\":\"preset\"}  // missing preset fields\n// after\nPOST /profiles {\"name\":\"Morgan\",\"voice_type\":\"preset\",\"preset_engine\":\"kokoro\",\"preset_voice_id\":\"af_heart\"}","handlingStrategy":"validation","validationCode":"async function createProfile(input) {\n  const list = await (await fetch('/profiles')).json();\n  if (list.some(p => p.name.toLowerCase() === input.name.toLowerCase())) {\n    throw new Error(`A profile named '${input.name}' already exists`);\n  }\n  if (input.voice_type === 'preset' && (!input.preset_engine || !input.preset_voice_id)) {\n    throw new Error('preset profiles require preset_engine and preset_voice_id');\n  }\n  if (input.voice_type === 'designed' && !input.design_prompt) {\n    throw new Error('designed profiles require design_prompt');\n  }\n  return await fetch('/profiles', {method:'POST', body: JSON.stringify(input)});\n}","typeGuard":null,"tryCatchPattern":"try {\n  await fetch('/profiles', {method:'POST', body: JSON.stringify(input)});\n} catch (e) {\n  if (e.response?.status === 400) {\n    // detail explains the violated rule (duplicate name, missing preset fields)\n    showUserError(e.response.detail);\n  } else throw e;\n}","preventionTips":["Refresh GET /profiles before creating to avoid duplicate-name rejections.","Send all fields required for the chosen voice_type (preset needs engine+voice_id; designed needs design_prompt).","Validate language against the schema's allow-list before submit."],"tags":["profiles","validation","duplicate","http-400","voice-type"],"backgroundTag":null,"analyzedSha":"51f49dea198384b4eb6087b72c17057c6eb1c1cd","analyzedAt":"2026-08-12T16:51:42.824Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}