harry0703/MoneyPrinterTurbo · error · SoniloError
Sonilo returned an unexpected service response
Error message
Sonilo returned an unexpected service response
What it means
Raised by sonilo.test_connection() when the JSON parses successfully but the top-level value is not a dict — the code expects an object with an 'available_services' key, so a list, string, number, or null payload means the response schema does not match the expected Sonilo services contract.
Source
Thrown at app/services/sonilo.py:109
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 中间文件,不覆盖调用方正在处理的原始异常。"""
if not file_path or not os.path.exists(file_path):
returnView on GitHub (pinned to 1f9f19c202)
Solutions
- Log/inspect the parsed payload in a debug run to see its actual shape (list vs dict vs null)
- Drop custom sonilo_base_url to use the default https://api.sonilo.com so the expected schema is served
- If the API legitimately changed, update the parsing in app/services/sonilo.py test_connection() to accept the new envelope
- In tests, make the mock return {"available_services": ["video_to_music"]}
Example fix
# before (test mock)
requests.get.return_value.json.return_value = ["video_to_music"] # list -> SoniloError
# after
requests.get.return_value.json.return_value = {"available_services": ["video_to_music"]} Defensive patterns
Strategy: type-guard
Type guard
def is_sonilo_services_payload(payload: object) -> bool:
return (
isinstance(payload, dict)
and isinstance(payload.get("available_services"), list)
and all(isinstance(s, str) for s in payload["available_services"])
) Try / catch
except SoniloError as e: if 'unexpected service response' in str(e): non-retryable — log the payload shape, revert to default base_url or update the parser to the new API envelope
Prevention
- Type-check third-party JSON payloads before field access
- Pin the API version via base_url when the provider offers versioned hosts
- Update mocks to match the real {available_services: [...]} envelope
When it happens
Trigger: response.json() returning e.g. ["video_to_music"] (bare array), "ok" (string), or null from /v1/account/services — API version change returning a different envelope, gateway transforming the payload, or a mock/test server returning a list.
Common situations: Sonilo API schema evolution (payload wrapped/unwrapped between versions); custom sonilo_base_url hitting a different API version; API gateways normalizing responses; test doubles returning a bare list of services.
Related errors
- Sonilo returned an invalid service list
- Sonilo returned an invalid service response
- Sonilo returned malformed streaming data
- ElevenLabs returned an invalid subscription response
- ElevenLabs returned an unexpected subscription response
AI-assisted analysis of harry0703/MoneyPrinterTurbo@1f9f19c202 (2026-08-14).
Data as JSON: /api/errors/fa7f6d854d6ac78f.
Report an issue: GitHub.