HKUDS/DeepTutor · error · VoiceProviderHTTPError

{action} failed with HTTP {status_code}: {detail}

Error message

{action} failed with HTTP {status_code}: {detail}

What it means

_raise_for_provider inspects the provider HTTP response; any status >= 400 is converted into a VoiceProviderHTTPError whose message is built by _provider_error_message as "{action} failed with HTTP {status_code}: {detail}" with a trimmed response body. This is the central HTTP-status-to-exception bridge for TTS/STT calls.

Source

Thrown at deeptutor/services/voice/adapters/openai_compat.py:69

    "nova",
    "onyx",
    "sage",
    "shimmer",
    "verse",
}


def _provider_error_message(action: str, status_code: int, body: str = "") -> str:
    detail = (body or "").strip()[:400]
    return f"{action} failed with HTTP {status_code}" + (f": {detail}" if detail else ".")


def _raise_for_provider(resp: httpx.Response, action: str) -> None:
    """Surface a provider error with a trimmed body for diagnostics."""
    if resp.status_code < 400:
        return
    body = resp.text or ""
    raise VoiceProviderHTTPError(
        _provider_error_message(action, resp.status_code, body),
        status_code=resp.status_code,
        body=body,
    )


def _join_api_path(base_url: str, suffix: str) -> str:
    """Append a generic API path to ``base_url`` while preserving query strings."""
    base = (base_url or "").strip()
    if not base:
        raise VoiceProviderError("No endpoint URL configured for this provider.")
    head, sep, query = base.partition("?")
    suffix = suffix.strip("/")
    if head.rstrip("/").endswith(f"/{suffix}"):
        return base
    joined = f"{head.rstrip('/')}/{suffix}"
    return f"{joined}?{query}" if sep else joined

View on GitHub (pinned to 3e82f13042)

Solutions

  1. Match on the embedded status code: 401/403 → fix API key/entitlements; 404 → fix base_url; 429 → back off / check quota; 5xx → retry with backoff
  2. Inspect the trimmed body in the exception for the provider's own error message
  3. Confirm the model name is valid for the configured provider
  4. If persistent, test the same request with curl against base_url
Defensive patterns

Strategy: try-catch

Try / catch

from deeptutor.services.voice.exceptions import VoiceProviderHTTPError

try:
    ...
except VoiceProviderHTTPError as exc:
    if exc.status_code in (429, 500, 502, 503):
        await asyncio.sleep(backoff); retry()
    elif exc.status_code in (401, 403):
        invalidate_credentials()
    else:
        raise

Prevention

When it happens

Trigger: The provider returns 401 (bad key), 403, 404 (wrong base_url/path), 429 (rate limit), or 5xx during TTS synthesis, OpenRouter chat audio synthesis, or transcription.

Common situations: Expired or wrong API key (401), model not entitled (403), wrong endpoint path from a mis-pasted base_url (404), quota exhaustion (429), or transient provider outage (5xx).

Related errors


AI-assisted analysis of HKUDS/DeepTutor@3e82f13042 (2026-08-27). Data as JSON: /api/errors/bb3ee5c82a49d059. Report an issue: GitHub.