{"record":{"id":"1d3bc3616df35cf2","repo":"jamiepine/voicebox","slug":"voice-profile-data-profile-not-found","errorCode":null,"errorMessage":"Voice profile '{data.profile}' not found.","messagePattern":"Voice profile '(.+?)' not found\\.","errorType":"http","errorClass":"HTTPException","httpStatus":404,"severity":"error","filePath":"backend/routes/speak.py","lineNumber":43,"sourceCode":"\n\n@router.post(\"/speak\", response_model=models.GenerationResponse)\nasync def speak(\n    data: models.SpeakRequest,\n    request: Request,\n    db: Session = Depends(get_db),\n):\n    \"\"\"Speak text in a voice profile. Mirrors voicebox.speak (MCP).\n\n    Response shape matches POST /generate — a ``GenerationResponse`` with\n    ``status=\"generating\"`` and an ``id`` the caller polls at\n    ``GET /generate/{id}/status``.\n    \"\"\"\n    client_id = request.headers.get(\"X-Voicebox-Client-Id\")\n    profile = resolve_profile(data.profile, client_id, db)\n    if profile is None:\n        if data.profile:\n            raise HTTPException(\n                status_code=404,\n                detail=f\"Voice profile '{data.profile}' not found.\",\n            )\n        raise HTTPException(\n            status_code=400,\n            detail=(\n                \"No voice profile resolved. Pass `profile` (name or id), \"\n                \"or configure a default in Voicebox → Settings → MCP.\"\n            ),\n        )\n\n    binding = None\n    if client_id:\n        binding = (\n            db.query(MCPClientBinding)\n            .filter(MCPClientBinding.client_id == client_id)\n            .first()\n        )","sourceCodeStart":25,"sourceCodeEnd":61,"githubUrl":"https://github.com/jamiepine/voicebox/blob/51f49dea198384b4eb6087b72c17057c6eb1c1cd/backend/routes/speak.py#L25-L61","documentation":"Returned by POST /speak when resolve_profile(data.profile, client_id, db) returns None but data.profile was provided (HTTP 404). The handler distinguishes 'a name/id was supplied but did not resolve' from 'nothing was supplied'. It means the supplied profile identifier matches no profile by name or id.","triggerScenarios":"POST /speak with a profile field set to a name or id that does not exist, is misspelled, or belongs to a different client/database. Also when name resolution is case-sensitive and the casing differs.","commonSituations":"Client passes a display name that doesn't match the stored profile name exactly. Profile deleted between sessions. Multi-tenant: profile exists but is not visible to the resolved scope.","solutions":["Use a known profile id from GET /profiles rather than a free-text name.","Confirm exact casing and spelling of the profile name against the stored value.","In the client, surface the unresolved name in the error so the user can re-pick.","If relying on a default, omit profile and configure the MCP default instead (see error 188)."],"exampleFix":"// before\n{ \"text\": \"hi\", \"profile\": \"Narrator\" }\n\n// after (use id resolved from the profiles list)\nconst p = (await getProfiles()).find(x => x.name === 'Narrator');\n{ \"text\": \"hi\", \"profile\": p.id }","handlingStrategy":"validation","validationCode":"async function resolveProfileId(name) {\n  const list = await (await fetch('/api/profiles')).json();\n  const hit = list.find(p => p.name === name || p.id === name);\n  return hit ? hit.id : null;\n}","typeGuard":"function isProfileNameOrId(v) { return typeof v === 'string' && v.trim().length > 0; }","tryCatchPattern":"try { await api.post('/speak', { text, profile }); }\ncatch (e) { if (e.response?.status === 404) promptProfileRepick(profile); throw e; }","preventionTips":["Prefer the profile id resolved from GET /profiles over a free-text name.","Match name casing exactly against the stored value.","When name resolution fails, fall back to letting the user pick from the list."],"tags":["fastapi","http","not-found","speak","profiles","voice","rest"],"backgroundTag":null,"analyzedSha":"51f49dea198384b4eb6087b72c17057c6eb1c1cd","analyzedAt":"2026-08-12T16:51:42.824Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}