{"record":{"id":"bb3ee5c82a49d059","repo":"HKUDS/DeepTutor","slug":"action-failed-with-http-status-code-detail","errorCode":null,"errorMessage":"{action} failed with HTTP {status_code}: {detail}","messagePattern":"(.+?) failed with HTTP (.+?): (.+?)","errorType":"http","errorClass":"VoiceProviderHTTPError","httpStatus":null,"severity":"error","filePath":"deeptutor/services/voice/adapters/openai_compat.py","lineNumber":69,"sourceCode":"    \"nova\",\n    \"onyx\",\n    \"sage\",\n    \"shimmer\",\n    \"verse\",\n}\n\n\ndef _provider_error_message(action: str, status_code: int, body: str = \"\") -> str:\n    detail = (body or \"\").strip()[:400]\n    return f\"{action} failed with HTTP {status_code}\" + (f\": {detail}\" if detail else \".\")\n\n\ndef _raise_for_provider(resp: httpx.Response, action: str) -> None:\n    \"\"\"Surface a provider error with a trimmed body for diagnostics.\"\"\"\n    if resp.status_code < 400:\n        return\n    body = resp.text or \"\"\n    raise VoiceProviderHTTPError(\n        _provider_error_message(action, resp.status_code, body),\n        status_code=resp.status_code,\n        body=body,\n    )\n\n\ndef _join_api_path(base_url: str, suffix: str) -> str:\n    \"\"\"Append a generic API path to ``base_url`` while preserving query strings.\"\"\"\n    base = (base_url or \"\").strip()\n    if not base:\n        raise VoiceProviderError(\"No endpoint URL configured for this provider.\")\n    head, sep, query = base.partition(\"?\")\n    suffix = suffix.strip(\"/\")\n    if head.rstrip(\"/\").endswith(f\"/{suffix}\"):\n        return base\n    joined = f\"{head.rstrip('/')}/{suffix}\"\n    return f\"{joined}?{query}\" if sep else joined\n","sourceCodeStart":51,"sourceCodeEnd":87,"githubUrl":"https://github.com/HKUDS/DeepTutor/blob/3e82f130422a813cdd73c10b21a44e9325f5821a/deeptutor/services/voice/adapters/openai_compat.py#L51-L87","documentation":"_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.","triggerScenarios":"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.","commonSituations":"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).","solutions":["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","Inspect the trimmed body in the exception for the provider's own error message","Confirm the model name is valid for the configured provider","If persistent, test the same request with curl against base_url"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"from deeptutor.services.voice.exceptions import VoiceProviderHTTPError\n\ntry:\n    ...\nexcept VoiceProviderHTTPError as exc:\n    if exc.status_code in (429, 500, 502, 503):\n        await asyncio.sleep(backoff); retry()\n    elif exc.status_code in (401, 403):\n        invalidate_credentials()\n    else:\n        raise","preventionTips":["Branch on status_code, not message text, when you catch VoiceProviderHTTPError","Monitor 429s to pace request rates","Smoke-test base_url with a curl call when adding a new provider"],"tags":["voice","http-status","provider","tts","stt"],"backgroundTag":"provider-http-error","analyzedSha":"3e82f130422a813cdd73c10b21a44e9325f5821a","analyzedAt":"2026-08-27T06:57:25.364Z","schemaVersion":2},"datasetVersion":"2026-08-27T08:17:20.692Z"}