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

  1. Use a known profile id from GET /profiles rather than a free-text name.
  2. Confirm exact casing and spelling of the profile name against the stored value.
  3. In the client, surface the unresolved name in the error so the user can re-pick.
  4. 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

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


AI-assisted analysis of jamiepine/voicebox@51f49dea19 (2026-08-12). Data as JSON: /api/errors/1d3bc3616df35cf2. Report an issue: GitHub.