Discussions

Ask a Question

Keeping the avatar look the same in all videos

We have an app in which we create avatars & videos using Heygen

Answered

I want to switch from tariff 29 to 99. Can you tell me if I will be paying for two tariffs?

I want to switch from tariff 29 to 99. Can you tell me if I will be paying for two tariffs?

Answered

Video agent limit

Hey Heygen development team.

Answered

v2/video/generate status 200 still shows error on panel

curl --location 'https://api.heygen.com/v2/video/generate'
--header 'accept: application/json'
--header 'content-type: application/json'
--header 'x-api-key: abcd'
--data '{
"video_inputs": [
{
"character": {
"type": "avatar",
"avatar_id": "1ee71d11eb11400a996b5861e412aed0",
"avatar_style": "normal"
},
"voice": {
"type": "text",
"input_text": "Hello how are you?",
"voice_id": "5d2fe6fd8dad41949db24f4321b6926a",
"speed": 1.1
}
}
],
"dimension": {
"width": 1280,
"height": 1920
}
}'

Need exact Video Agent generate payload to lock avatar identity + use asset_id files[]

Hi HeyGen Engineering Team,

API/ELevenLabs

Hello,
I’m on the Creator Plan with a 1-member Workspace.
My API key is valid but HeyGen returns: “missing user-read to execute this operation.”
It appears my Workspace API key does not include the user:read scope.
Can you enable full API scope permissions for my Workspace so I can integrate with HeyGen?

DIFFICULTIES TO GET GENERATED VIDEOS URL BACK IN ORDER TO DISPLAY THEM ON MY WEB APP

'Annie_expressive4_public', 'teaching' => 'Annie_expressive4_public', 'project_management'=> 'Annie_expressive4_public', 'pharmacy' => 'Annie_expressive4_public', 'agro' => 'Annie_expressive4_public', ]; $VOICE_ID = '0b41c487c6da4f5ba5782bbe462958e8'; // === UTILS === function logMessage($msg) { $logFile = __DIR__ . '/logs/avatar_gen.log'; if (!is_dir(dirname($logFile))) mkdir(dirname($logFile), 0755, true); $timestamp = date('Y-m-d H:i:s'); error_log("[$timestamp] [AvatarGen] $msg\n", 3, $logFile); echo "[$timestamp] $msg\n"; } function heyGenRequest($method, $url, $payload = null) { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_TIMEOUT, 60); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Content-Type: application/json', 'Accept: application/json', 'X-Api-Key: ' . HEYGEN_API_KEY ]); if ($method === 'POST' && $payload) { curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload)); } $response = curl_exec($ch); $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); $error = curl_error($ch); curl_close($ch); if ($error) { throw new Exception("cURL error: $error"); } // 🔍 Debug temporaire if ($http_code !== 200) { logMessage("DEBUG HTTP $http_code - URL: $url"); logMessage("DEBUG Response: " . substr($response, 0, 200)); } return [$http_code, json_decode($response, true)]; } // === ÉTAPE 1 : Lancer la génération === function generateVideo($text, $user_group) { global $AVATAR_MAP, $VOICE_ID; $avatar_id = $AVATAR_MAP[$user_group] ?? $AVATAR_MAP['default']; $payload = [ "video_inputs" => [[ "character" => [ "type" => "avatar", "avatar_id" => $avatar_id, "scale" => 1 ], "voice" => [ "type" => "text", "input_text" => $text, "voice_id" => $VOICE_ID ], "background" => ["type" => "color", "value" => "#f8fafc"] ]], "dimension" => ["width" => 1920, "height" => 1080], "test" => false, "caption" => false ]; for ($attempt = 1; $attempt <= MAX_RETRIES; $attempt++) { try { [$code, $data] = heyGenRequest('POST', HEYGEN_API_URL, $payload); if ($code === 200 && !empty($data['data']['video_id'])) { return $data['data']['video_id']; } else { $msg = "HeyGen error (attempt $attempt): HTTP $code - " . ($data['message'] ?? json_encode($data)); logMessage($msg); if ($attempt < MAX_RETRIES) sleep(RETRY_DELAY_SEC); } } catch (Exception $e) { logMessage("Exception (attempt $attempt): " . $e->getMessage()); if ($attempt < MAX_RETRIES) sleep(RETRY_DELAY_SEC); } } throw new Exception("Échec après " . MAX_RETRIES . " tentatives."); } // === ÉTAPE 2 : Attendre la fin de la génération === function waitForVideoCompletion($video_id) { $elapsed = 0; $check_interval = 5; $max_checks = MAX_WAIT_SEC / $check_interval; logMessage("⏳ Attente de la complétion de la vidéo (max " . MAX_WAIT_SEC . " sec)..."); logMessage("🔍 Video ID: $video_id"); // Délai initial plus long (HeyGen met du temps à indexer) sleep(25); for ($check = 1; $check <= $max_checks; $check++) { try { // ✅ CORRECTION DÉFINITIVE : POST avec video_id dans le body JSON (API v1) $payload = ['video_id' => $video_id]; [$code, $data] = heyGenRequest('POST', HEYGEN_STATUS_URL, $payload); if ($code === 200 && !empty($data['data'])) { $status = $data['data']['status'] ?? 'unknown'; logMessage("CallCheck #$check (elapsed: {$elapsed}s) - Statut: $status"); if ($status === 'completed') { $video_url = $data['data']['video_url'] ?? null; if ($video_url) { logMessage("✅ URL CDN trouvée : $video_url"); return $video_url; } // Recherche alternative dans assets $assets = $data['data']['assets'] ?? []; foreach ($assets as $asset) { if (isset($asset['type']) && $asset['type'] === 'video' && isset($asset['url'])) { $video_url = $asset['url']; logMessage("✅ URL trouvée dans assets : $video_url"); return $video_url; } } throw new Exception("Vidéo complétée mais URL non trouvée"); } elseif ($status === 'failed') { $reason = $data['data']['reason'] ?? 'Unknown'; throw new Exception("Échec HeyGen : $reason"); } } else { $error_msg = $data['message'] ?? json_encode($data); logMessage("⚠️ CheckCall #$check - HTTP $code - $error_msg"); } sleep($check_interval); $elapsed += $check_interval; } catch (Exception $e) { logMessage("⚠️ CheckCall #$check erreur: " . $e->getMessage()); sleep($check_interval); $elapsed += $check_interval; } } throw new Exception("Timeout après " . MAX_WAIT_SEC . " secondes"); } // === ÉTAPE 3 : Mettre à jour la base === function updateMediaUrl($conn, $content_date, $user_group, $video_url) { logMessage("💾 Mise à jour de la base de données..."); $stmt = $conn->prepare(" UPDATE daily_content SET media_url = :url, media_type = 'video' WHERE content_date = :date AND user_group = :group "); $stmt->execute([ ':url' => $video_url, ':date' => $content_date, ':group' => $user_group ]); $rows = $stmt->rowCount(); logMessage("✅ Base mise à jour : $rows ligne(s) modifiée(s) - $user_group / $content_date → $video_url"); // Vérification $verify = $conn->prepare(" SELECT media_url, media_type FROM daily_content WHERE content_date = :date AND user_group = :group "); $verify->execute([':date' => $content_date, ':group' => $user_group]); $result = $verify->fetch(PDO::FETCH_ASSOC); if ($result && !empty($result['media_url'])) { logMessage("🔍 Vérification BDD : OK → " . substr($result['media_url'], 0, 60) . "..."); } else { logMessage("⚠️ Vérification BDD : ÉCHOUÉE"); } } // === SCRIPT PRINCIPAL === try { logMessage("╔════════════════════════════════════════════════════════════╗"); logMessage("║ DÉMARRAGE GÉNÉRATION VIDÉO HEYGEN v2 ║"); logMessage("╚════════════════════════════════════════════════════════════╝"); $stmt = $conn->prepare(" SELECT id, content_date, user_group, reading_text FROM daily_content WHERE content_date = :today AND (media_url IS NULL OR media_url = '' OR media_type != 'video') AND reading_text IS NOT NULL AND TRIM(reading_text) != '' "); $stmt->execute([':today' => date('Y-m-d')]); $contents = $stmt->fetchAll(PDO::FETCH_ASSOC); if (empty($contents)) { logMessage("ℹ️ Aucun contenu à traiter aujourd'hui."); exit(0); } logMessage("📋 " . count($contents) . " contenu(s) trouvé(s) à traiter"); foreach ($contents as $row) { $text = trim($row['reading_text']); $user_group = $row['user_group']; $content_date = $row['content_date']; $content_id = $row['id']; if (empty($text)) continue; logMessage("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"); logMessage("🎬 Traitement : $user_group / $content_date (ID: $content_id)"); logMessage("📝 Texte (premiers 50 caractères): " . substr($text, 0, 50) . "..."); try { $video_id = generateVideo($text, $user_group); logMessage("⏳ Job HeyGen lancé : $video_id"); logMessage("🌐 Suivre sur : https://app.heygen.com/videos/$video_id"); $video_url = waitForVideoCompletion($video_id); if (empty($video_url)) { throw new Exception("URL de vidéo vide"); } updateMediaUrl($conn, $content_date, $user_group, $video_url); logMessage("✅ VIDÉO GÉNÉRÉE ET STOCKÉE !"); logMessage("🎬 URL publique : $video_url"); } catch (Exception $e) { logMessage("❌ ÉCHEC pour $user_group : " . $e->getMessage()); } } logMessage("╔════════════════════════════════════════════════════════════╗"); logMessage("║ GÉNÉRATION TERMINÉE ║"); logMessage("╚════════════════════════════════════════════════════════════╝"); logMessage("💡 Rechargez enregistrement.php pour voir la vidéo !"); } catch (Exception $e) { logMessage("🔥 ERREUR CRITIQUE : " . $e->getMessage()); exit(1); } ?>

Requests

  • Let Video Agent API to retrieve CC (subtitles) or allow for burning it into video
  • Let Video Agent API to give another prompt to edit the video
  • Allow to edit the plan with Video Agent API.

Personal Avatars, Voices, Through Video Agent API

Right now when I send a prompt to the video agent API it replies it can't access personal avatars, voices, templates, etc... Do you have any info about when that's going to be available? That would be a game-changer and I'd consider upgrading to the paid plan to be able to use that.