jamiepine/voicebox · error · HTTPException

No voice profile resolved. Pass `profile` (name or id), or…

Error message

No voice profile resolved. Pass `profile` (name or id), or configure a default in Voicebox → Settings → MCP.

What it means

Returned by POST /speak when resolve_profile returns None AND data.profile is empty/None (HTTP 400). The handler treats 'no identifier supplied and no default resolvable' as a client error distinct from a not-found. The message directs the user to either pass profile or set an MCP default in settings.

Solutions

  1. Always pass profile (name or id) in the request body when no default is configured.
  2. Configure a default profile in Voicebox → Settings → MCP for the given client.
  3. Ensure the X-Voicebox-Client-Id header is sent so the binding lookup can resolve a default.
  4. In the client, detect 'no profile' state and prompt selection before calling /speak.

Example fix

// before
{ "text": "hi" }  // profile omitted, no default

// after
{ "text": "hi", "profile": "default-narrator" }
Defensive patterns

Strategy: validation

Validate before calling

function speakPayloadOk(profile, clientId) {
  if (profile) return true;
  return !!clientId; // default binding only resolvable when client_id is sent
}

Type guard

function hasSpeakTarget(profile, clientId) { return !!profile || !!clientId; }

Try / catch

try { await api.post('/speak', { text }); }
catch (e) { if (e.response?.status === 400) promptForProfile(); throw e; }

Prevention

When it happens

Trigger: POST /speak with profile omitted/null and no MCP default configured for the client. First-time use before any default is set, or after the default binding was cleared.

Common situations: New Voicebox client that has not configured a default profile. Client_id not sent (no X-Voicebox-Client-Id header) so no binding could be looked up, and profile omitted.

Related errors


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

Appendix: source

Thrown at backend/routes/speak.py:47

    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()
        )

    # Resolve per-client personality default when the caller didn't pin it.
    personality_flag = data.personality
    if personality_flag is None and binding is not None:

View on GitHub (pinned to 51f49dea19)