jamiepine/voicebox · error · HTTPException
Voice profile '{data.profile}' not found.
Error message
Voice profile '{data.profile}' not found. What it means
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.
Source
Thrown at backend/routes/speak.py:43
@router.post("/speak", response_model=models.GenerationResponse)
async def speak(
data: models.SpeakRequest,
request: Request,
db: Session = Depends(get_db),
):
"""Speak text in a voice profile. Mirrors voicebox.speak (MCP).
Response shape matches POST /generate — a ``GenerationResponse`` with
``status="generating"`` and an ``id`` the caller polls at
``GET /generate/{id}/status``.
"""
client_id = request.headers.get("X-Voicebox-Client-Id")
profile = resolve_profile(data.profile, client_id, db)
if profile is None:
if data.profile:
raise HTTPException(
status_code=404,
detail=f"Voice profile '{data.profile}' not found.",
)
raise HTTPException(
status_code=400,
detail=(
"No voice profile resolved. Pass `profile` (name or id), "
"or configure a default in Voicebox → Settings → MCP."
),
)
binding = None
if client_id:
binding = (
db.query(MCPClientBinding)
.filter(MCPClientBinding.client_id == client_id)
.first()
)View on GitHub (pinned to 51f49dea19)
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).
Example fix
// before
{ "text": "hi", "profile": "Narrator" }
// after (use id resolved from the profiles list)
const p = (await getProfiles()).find(x => x.name === 'Narrator');
{ "text": "hi", "profile": p.id } Defensive patterns
Strategy: validation
Validate before calling
async function resolveProfileId(name) {
const list = await (await fetch('/api/profiles')).json();
const hit = list.find(p => p.name === name || p.id === name);
return hit ? hit.id : null;
} Type guard
function isProfileNameOrId(v) { return typeof v === 'string' && v.trim().length > 0; } Try / catch
try { await api.post('/speak', { text, profile }); }
catch (e) { if (e.response?.status === 404) promptProfileRepick(profile); throw e; } Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- No voice profile resolved. Pass `profile` (name or id), or c
- Profile not found
- {error}
- No ROCm backend found to delete
- Story not found
AI-assisted analysis of jamiepine/voicebox@51f49dea19 (2026-08-12).
Data as JSON: /api/errors/1d3bc3616df35cf2.
Report an issue: GitHub.