calesthio/OpenMontage · error · ValueError

Kling TTS response item contained no downloadable URL

Error message

Kling TTS response item contained no downloadable URL: {item}

What it means

Raised as ValueError by the static _output_url helper when a single audios entry has none of the known URL keys: url, audio_url, resource_url, or resource.url. It is a per-item shape check with the entire item embedded in the message, so the offending object is visible in the error.

Solutions

  1. Inspect the embedded item in the error message to find the new key name
  2. Extend _output_url's key chain (url → audio_url → resource_url → resource.url) with the new key
  3. If the item holds an id/path, add a follow-up fetch to resolve it to a URL

Example fix

// before
url = item.get("url") or item.get("audio_url") or item.get("resource_url")

// after
url = (
    item.get("url")
    or item.get("audio_url")
    or item.get("resource_url")
    or item.get("file_url")  # new upstream key
)
Defensive patterns

Strategy: validation

Validate before calling

def item_has_url(item: dict) -> bool:
    return bool(
        item.get("url") or item.get("audio_url") or item.get("resource_url")
        or (isinstance(item.get("resource"), dict) and item["resource"].get("url"))
    )

Type guard

def is_downloadable_item(item: Any) -> bool:
    if not isinstance(item, dict):
        return False
    resource = item.get("resource")
    return bool(
        item.get("url") or item.get("audio_url") or item.get("resource_url")
        or (isinstance(resource, dict) and resource.get("url"))
    )

Try / catch

try:
    paths = kling_tts_tool._download_audios(client, outputs, inputs)
except ValueError as e:
    if "no downloadable URL" in str(e):
        logger.error("kling audio item schema changed: %s", e)
    raise

Prevention

When it happens

Trigger: Kling changes the per-audio object schema (new key like 'file_url'); item contains only a task-relative path or an id requiring a second fetch; gateway strips URL fields.

Common situations: API version drift adding/renameing URL fields; items that reference a resource endpoint instead of a direct URL; test fixtures built from outdated docs.

Related errors


AI-assisted analysis of calesthio/OpenMontage@95e1c3d0ab (2026-08-15). Data as JSON: /api/errors/7504cab68acc18db. Report an issue: GitHub.

Appendix: source

Thrown at tools/audio/kling_tts.py:302

        base_path = Path(inputs.get("output_path", "kling_tts.mp3"))
        paths: list[Path] = []
        for index, item in enumerate(outputs):
            url = self._output_url(item)
            suffix = extension_from_url(url, ".mp3")
            output_path = numbered_output_path(output_path_with_suffix(base_path, suffix), index, suffix)
            client.download(url, output_path)
            paths.append(output_path)
        return paths

    @staticmethod
    def _output_url(item: dict[str, Any]) -> str:
        url = item.get("url") or item.get("audio_url") or item.get("resource_url")
        if url:
            return str(url)
        resource = item.get("resource") or {}
        if isinstance(resource, dict) and resource.get("url"):
            return str(resource["url"])
        raise ValueError(f"Kling TTS response item contained no downloadable URL: {item}")

    @staticmethod
    def _copy_common_task_fields(inputs: dict[str, Any], payload: dict[str, Any]) -> None:
        callback_url = validate_callback_url(inputs.get("callback_url"))
        if callback_url:
            payload["callback_url"] = callback_url
        if inputs.get("external_task_id"):
            payload["external_task_id"] = inputs["external_task_id"]

    @staticmethod
    def _callback_result_data(inputs: dict[str, Any], task_id: str) -> dict[str, Any]:
        callback_url = inputs.get("callback_url")
        if not callback_url:
            return {}
        return {
            "callback_url": str(callback_url),
            "callback_requested": True,
            "polling_used": True,

View on GitHub (pinned to 95e1c3d0ab)