harry0703/MoneyPrinterTurbo · error · SoniloError

Sonilo returned an invalid service response

Error message

Sonilo returned an invalid service response

What it means

Raised by sonilo.test_connection() when response.json() throws ValueError on the 2xx services response — the body is not valid JSON (HTML error page, empty body, gzip/proxy corruption). The original parse error is chained with `from exc`.

Source

Thrown at app/services/sonilo.py:107

    if not api_key:
        raise SoniloError("Sonilo API key is required")
    try:
        response = requests.get(
            f"{_base_url()}{SERVICES_PATH}",
            headers={"Authorization": f"Bearer {api_key}"},
            timeout=(15, 30),
        )
    except requests.RequestException as exc:
        raise SoniloError(f"failed to connect to Sonilo: {exc}") from exc
    if not response.ok:
        raise SoniloError(
            f"Sonilo connection failed ({response.status_code}): "
            f"{_safe_response_error(response)}"
        )
    try:
        payload = response.json()
    except ValueError as exc:
        raise SoniloError("Sonilo returned an invalid service response") from exc
    if not isinstance(payload, dict):
        raise SoniloError("Sonilo returned an unexpected service response")
    available_services = payload.get("available_services")
    if not isinstance(available_services, list) or not all(
        isinstance(service_id, str) for service_id in available_services
    ):
        raise SoniloError("Sonilo returned an invalid service list")
    normalized_services = {
        _normalize_service_id(service_id) for service_id in available_services
    }
    if VIDEO_TO_MUSIC_SERVICE_ID not in normalized_services:
        raise SoniloError("Sonilo video-to-music service is not available for this key")
    logger.info("Sonilo connection test succeeded")
    return payload


def _remove_file(file_path: str) -> None:
    """尽力清理 Sonilo 中间文件,不覆盖调用方正在处理的原始异常。"""

View on GitHub (pinned to 1f9f19c202)

Solutions

  1. Check sonilo_base_url — pointing at a web page host instead of the API host is the most common cause
  2. Inspect the raw body: log response.headers.get('content-type') and response.text[:200] in a debug run to see HTML vs JSON
  3. Bypass the captive/transparent proxy or fix proxy SSL/encoding handling
  4. If content-type is consistently non-JSON from the API itself, the endpoint/path is wrong — use the default base_url

Example fix

# debug snippet to identify the offending body
resp = requests.get(f"{_base_url()}{SERVICES_PATH}", headers=headers, timeout=(15, 30))
print(resp.status_code, resp.headers.get("content-type"), resp.text[:200])
Defensive patterns

Strategy: validation

Validate before calling

# pre-check that the endpoint returns JSON before parsing
resp = requests.get(url, headers=headers, timeout=(15, 30))
if 'application/json' not in (resp.headers.get('content-type') or ''):
    raise SoniloError(f"non-JSON content-type: {resp.headers.get('content-type')}")

Try / catch

except SoniloError as e: if 'invalid service response' in str(e): non-retryable in place — inspect content-type/body; if a proxy caused it, fix the proxy then retry

Prevention

When it happens

Trigger: requests.get to /v1/account/services returns 200 but body is HTML (captive portal, CDN error page), truncated (proxy buffering issue), or BOM-prefixed/plain text; response.json() then raises requests.exceptions.JSONDecodeError (a ValueError subclass).

Common situations: Captive portals / hotel WiFi injecting HTML on 200; transparent proxies mangling gzip encoding; Sonilo CDN edge returning an HTML error with status 200; sonilo_base_url pointing at a web UI host rather than the API host.

Related errors


AI-assisted analysis of harry0703/MoneyPrinterTurbo@1f9f19c202 (2026-08-14). Data as JSON: /api/errors/c40e8fa6a2c6af70. Report an issue: GitHub.