{"record":{"id":"6f78316e7d5fc1a7","repo":"Comfy-Org/ComfyUI","slug":"heygen-did-not-return-an-audio-url-response","errorCode":null,"errorMessage":"HeyGen did not return an audio_url: {response}","messagePattern":"HeyGen did not return an audio_url: (.+?)","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"comfy_api_nodes/nodes_heygen.py","lineNumber":781,"sourceCode":"        ssml: bool = False,\n        seed: int = 0,\n    ) -> IO.NodeOutput:\n        validate_string(text, strip_whitespace=True, min_length=1, max_length=5000)\n        payload = {\n            \"text\": text,\n            \"voice_id\": custom_voice_id.strip() or HEYGEN_VOICE_TTS_MAP[voice],\n            \"speed\": round(speed, 2),\n        }\n        if ssml:\n            payload[\"input_type\"] = \"ssml\"\n        response = await sync_op_raw(\n            cls,\n            ApiEndpoint(path=_SPEECH_PATH, method=\"POST\"),\n            data=payload,\n        )\n        audio_url = (response.get(\"data\") or {}).get(\"audio_url\")\n        if not audio_url:\n            raise ValueError(f\"HeyGen did not return an audio_url: {response}\")\n        audio_bytes = await download_url_as_bytesio(audio_url)\n        return IO.NodeOutput(audio_bytes_to_audio_input(audio_bytes.getvalue()))\n\n\nclass HeyGenExtension(ComfyExtension):\n    @override\n    async def get_node_list(self) -> list[type[IO.ComfyNode]]:\n        return [\n            HeyGenTalkingPhotoNode,\n            HeyGenAvatarVideoNode,\n            HeyGenCreateAvatarNode,\n            HeyGenVideoTranslateNode,\n            HeyGenTextToSpeechNode,\n        ]\n\n\nasync def comfy_entrypoint() -> HeyGenExtension:\n    return HeyGenExtension()","sourceCodeStart":763,"sourceCodeEnd":799,"githubUrl":"https://github.com/Comfy-Org/ComfyUI/blob/1c6d8d45b3693bfbb32385b410d813a7fd6be216/comfy_api_nodes/nodes_heygen.py#L763-L799","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Read the response body in the message — HeyGen's error text names the bad field","Use a voice from the dropdown or verify the custom voice_id exists in your workspace","Validate SSML against HeyGen's documented subset, or disable ssml mode for plain text","Check account TTS credits"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"def tts_payload_ok(voice: str, custom_voice_id: str, ssml: bool) -> bool:\n    return bool(custom_voice_id.strip() or voice in HEYGEN_VOICE_TTS_MAP)","typeGuard":null,"tryCatchPattern":"try:\n    audio = await tts(...)\nexcept ValueError as e:\n    if 'did not return an audio_url' in str(e):\n        # check embedded response: bad voice_id / SSML / credits\n        raise","preventionTips":["Use dropdown voices or workspace-verified custom voice ids","Test SSML snippets with plain text first to isolate dialect errors"],"tags":["heygen","tts","api-contract","voice-id"],"backgroundTag":null,"analyzedSha":"1c6d8d45b3693bfbb32385b410d813a7fd6be216","analyzedAt":"2026-08-14T19:37:18.893Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}