Comfy-Org/ComfyUI · error · ValueError

HeyGen did not return an audio_url: {response}

Error message

HeyGen did not return an audio_url: {response}

What it means

Thrown by HeyGen text-to-speech when the POST to the speech endpoint returns no data.audio_url. The request (text/ssml, voice_id, speed) was accepted HTTP-wise but the response lacks the synthesizable artifact URL; the whole response is embedded in the error for diagnosis.

Source

Thrown at comfy_api_nodes/nodes_heygen.py:781

        ssml: bool = False,
        seed: int = 0,
    ) -> IO.NodeOutput:
        validate_string(text, strip_whitespace=True, min_length=1, max_length=5000)
        payload = {
            "text": text,
            "voice_id": custom_voice_id.strip() or HEYGEN_VOICE_TTS_MAP[voice],
            "speed": round(speed, 2),
        }
        if ssml:
            payload["input_type"] = "ssml"
        response = await sync_op_raw(
            cls,
            ApiEndpoint(path=_SPEECH_PATH, method="POST"),
            data=payload,
        )
        audio_url = (response.get("data") or {}).get("audio_url")
        if not audio_url:
            raise ValueError(f"HeyGen did not return an audio_url: {response}")
        audio_bytes = await download_url_as_bytesio(audio_url)
        return IO.NodeOutput(audio_bytes_to_audio_input(audio_bytes.getvalue()))


class HeyGenExtension(ComfyExtension):
    @override
    async def get_node_list(self) -> list[type[IO.ComfyNode]]:
        return [
            HeyGenTalkingPhotoNode,
            HeyGenAvatarVideoNode,
            HeyGenCreateAvatarNode,
            HeyGenVideoTranslateNode,
            HeyGenTextToSpeechNode,
        ]


async def comfy_entrypoint() -> HeyGenExtension:
    return HeyGenExtension()

View on GitHub (pinned to 1c6d8d45b3)

Solutions

  1. Read the response body in the message — HeyGen's error text names the bad field
  2. Use a voice from the dropdown or verify the custom voice_id exists in your workspace
  3. Validate SSML against HeyGen's documented subset, or disable ssml mode for plain text
  4. Check account TTS credits
Defensive patterns

Strategy: try-catch

Validate before calling

def tts_payload_ok(voice: str, custom_voice_id: str, ssml: bool) -> bool:
    return bool(custom_voice_id.strip() or voice in HEYGEN_VOICE_TTS_MAP)

Try / catch

try:
    audio = await tts(...)
except ValueError as e:
    if 'did not return an audio_url' in str(e):
        # check embedded response: bad voice_id / SSML / credits
        raise

Prevention

When it happens

Trigger: HeyGen returns an error payload: invalid voice_id (custom id typo, or voice missing from HEYGEN_VOICE_TTS_MAP mapping), malformed SSML when input_type=ssml, quota/auth failure, or schema change moving audio_url.

Common situations: Custom voice ids pasted with whitespace or from a different HeyGen workspace; SSML that does not comply with HeyGen's Starfish TTS dialect; expired credentials.

Related errors


AI-assisted analysis of Comfy-Org/ComfyUI@1c6d8d45b3 (2026-08-14). Data as JSON: /api/errors/6f78316e7d5fc1a7. Report an issue: GitHub.