Playbook: RADEK_BRAND_HEYGEN_API_KEY Rotation

Service: HeyGen (https://app.heygen.com) — AI video generation Secret: RADEK_BRAND_HEYGEN_API_KEY SOPS file: radekkonarski-personal-brand/secrets/radekkonarski-brand.env.sops Rotation frequency: 90 days (or immediately on suspected exposure) Last rotated: 2026-06-27 Next due: 2026-09-27

Key naming clarification: The real HeyGen API key is RADEK_BRAND_HEYGEN_API_KEY, stored in the radekkonarski-personal-brand repo SOPS. A stale 4-char placeholder called HEYGEN_API_KEY was removed from vps-h1.env.sops on 2026-06-27 (PR #1723). No services on vps-h1 use HeyGen directly.


What uses this credential

ConsumerHowEffect if missing
n8n workflows on bms-4RADEK_BRAND_HEYGEN_API_KEY env var injected via bms-4/docker-compose.yml → HTTP Request nodesVideo generation workflows fail with 401
mcp__heygen__* MCP tools in Claude CodeBearer token auth to HeyGen APIMCP tool calls fail with 401
radekkonarski-personal-brand automationVideo creation pipeline via n8n on bms-4Personal brand video automation stops

Where stored

LocationKey nameHow to update
radekkonarski-personal-brand/secrets/radekkonarski-brand.env.sopsRADEK_BRAND_HEYGEN_API_KEYSOPS write pattern (see below)
bms-4 docker-compose envRADEK_BRAND_HEYGEN_API_KEY via compose env blockAuto-synced when bms-4 stack is redeployed
.env.local on dev workstationRADEK_BRAND_HEYGEN_API_KEYUpdate manually (emergency fallback only)

Path to SOPS file (local): d:\code_2026\radekkonarski-personal-brand\secrets\radekkonarski-brand.env.sops


Login credentials


Automation status

Playwright-automatable — Claude can rotate this via the HeyGen web UI. HeyGen provides API key management under Account → API. Spawn a Playwright agent with the prompt below.


Rotation steps

Option A — Playwright agent (preferred)

Spawn a background agent with this prompt:

You are rotating RADEK_BRAND_HEYGEN_API_KEY for the HeyGen video generation service at https://app.heygen.com.

IMPORTANT: Never display any secret value in your response. Reference key names only.

1. Use Playwright to navigate to https://app.heygen.com
   - Log in with email: radieu@gmail.com
   - Password: the radieu@gmail.com Google account password
     (read from .env.local: Get-Content "d:\code_2026\p24-infra\.env.local" | Select-String "^GOOGLE_ACCOUNT_PASSWORD=")
   - Navigate to Account (top-right avatar) → API
   - Find the existing API key — note its label (never log the value)
   - Click "Regenerate" or delete and create a new API key
   - Store the new key value in $env:NEW_KEY (never print it)

2. Update radekkonarski-personal-brand/secrets/radekkonarski-brand.env.sops:
   $env:SOPS_AGE_KEY_FILE = "C:\Users\konar\.age\p24-infra-keys.txt"
   $sopsPath = "d:\code_2026\radekkonarski-personal-brand\secrets\radekkonarski-brand.env.sops"
   $tempPath = "d:\code_2026\radekkonarski-personal-brand\secrets\radekkonarski-brand-edit.env.sops"
   $plain = sops --decrypt --input-type dotenv --output-type dotenv $sopsPath
   $updated = $plain -replace "^RADEK_BRAND_HEYGEN_API_KEY=.*", "RADEK_BRAND_HEYGEN_API_KEY=$env:NEW_KEY"
   [System.IO.File]::WriteAllText($tempPath, ($updated -join "`n") + "`n", [System.Text.UTF8Encoding]::new($false))
   $enc = sops --encrypt --input-type dotenv --output-type dotenv $tempPath
   [System.IO.File]::WriteAllText($sopsPath, ($enc -join "`n") + "`n", [System.Text.UTF8Encoding]::new($false))
   # Canary
   sops --decrypt --input-type dotenv --output-type dotenv $sopsPath | Out-Null
   if ($LASTEXITCODE -ne 0) { throw "SOPS corrupt — do NOT commit" }
   [System.IO.File]::Delete($tempPath)
   $env:NEW_KEY = ""

3. Commit and open PR targeting dev in the radekkonarski-personal-brand repo:
   cd d:\code_2026\radekkonarski-personal-brand
   git checkout -b fix/rotate-heygen-api-key origin/dev
   git add secrets/radekkonarski-brand.env.sops
   git commit -m "chore: rotate RADEK_BRAND_HEYGEN_API_KEY (scheduled 90d rotation)"
   git push -u origin fix/rotate-heygen-api-key
   gh pr create --base dev --title "chore: rotate RADEK_BRAND_HEYGEN_API_KEY" --repo radieu/radekkonarski-personal-brand

4. Append to p24-infra/docs/secrets-rotation-log.md:
   | <YYYY-MM-DD> | RADEK_BRAND_HEYGEN_API_KEY | scheduled 90d rotation | AI-agent | SOPS (radekkonarski-brand) |

Option B — Manual (fallback)

Step 1 — Log into HeyGen
  URL: https://app.heygen.com
  Email: radieu@gmail.com
  Password: radieu@gmail.com Google account password

Step 2 — Navigate to API settings
  → Click avatar / account menu (top right)
  → Account → API
  (or direct URL: https://app.heygen.com/account?action=api)

Step 3 — Regenerate API key
  → Click "Regenerate API Key" or delete old → Create new
  → Copy the new key value immediately (shown once)

Step 4 — Update radekkonarski-personal-brand SOPS
  $env:SOPS_AGE_KEY_FILE = "C:\Users\konar\.age\p24-infra-keys.txt"
  $sopsPath = "d:\code_2026\radekkonarski-personal-brand\secrets\radekkonarski-brand.env.sops"
  $tempPath = "d:\code_2026\radekkonarski-personal-brand\secrets\radekkonarski-brand-edit.env.sops"
  $plain = sops --decrypt --input-type dotenv --output-type dotenv $sopsPath
  $env:NEW_KEY = "<paste-from-step-3>"
  $updated = $plain -replace "^RADEK_BRAND_HEYGEN_API_KEY=.*", "RADEK_BRAND_HEYGEN_API_KEY=$env:NEW_KEY"
  [System.IO.File]::WriteAllText($tempPath, ($updated -join "`n") + "`n",
    [System.Text.UTF8Encoding]::new($false))
  $enc = sops --encrypt --input-type dotenv --output-type dotenv $tempPath
  [System.IO.File]::WriteAllText($sopsPath, ($enc -join "`n") + "`n",
    [System.Text.UTF8Encoding]::new($false))
  # Canary
  sops --decrypt --input-type dotenv --output-type dotenv $sopsPath | Out-Null
  if ($LASTEXITCODE -ne 0) { throw "SOPS corrupt — do NOT commit" }
  [System.IO.File]::Delete($tempPath); $env:NEW_KEY = ""

Step 5 — Commit and push in the radekkonarski-personal-brand repo
  cd d:\code_2026\radekkonarski-personal-brand
  git checkout -b fix/rotate-heygen-api-key origin/dev
  git add secrets/radekkonarski-brand.env.sops
  git commit -m "chore: rotate RADEK_BRAND_HEYGEN_API_KEY (scheduled 90d rotation)"
  git push -u origin fix/rotate-heygen-api-key
  gh pr create --base dev --title "chore: rotate RADEK_BRAND_HEYGEN_API_KEY" --repo radieu/radekkonarski-personal-brand

Step 6 — Redeploy bms-4 n8n stack to pick up new key
  ssh root@54.36.123.110 "cd /opt/p24-infra/bms-4 && docker compose up -d --no-deps n8n n8n-worker-1 n8n-worker-2 n8n-worker-3"

Step 7 — Append to p24-infra/docs/secrets-rotation-log.md
  | <YYYY-MM-DD> | RADEK_BRAND_HEYGEN_API_KEY | scheduled 90d rotation | radieu | SOPS (radekkonarski-brand) |

Step 8 — Clear env
  $env:NEW_KEY = ""

Verification

# 1. Confirm SOPS updated (length should be >= 32 for a real HeyGen API key)
$env:SOPS_AGE_KEY_FILE = "C:\Users\konar\.age\p24-infra-keys.txt"
$line = sops --decrypt --input-type dotenv --output-type dotenv `
    "d:\code_2026\radekkonarski-personal-brand\secrets\radekkonarski-brand.env.sops" |
    Select-String "^RADEK_BRAND_HEYGEN_API_KEY="
$val = $line.ToString().Split('=', 2)[1]
Write-Host "RADEK_BRAND_HEYGEN_API_KEY length: $($val.Length)"   # expected: >= 32
 
# 2. Quick API health check (do NOT print the key value)
$env:K = $val
$r = Invoke-RestMethod "https://api.heygen.com/v1/user/remaining_quota" `
    -Headers @{ "X-Api-Key" = $env:K }
Write-Host "HeyGen quota remaining: $($r.data.remaining_quota)"
$env:K = ""; $env:SOPS_AGE_KEY_FILE = ""
 
# 3. Confirm n8n on bms-4 can reach HeyGen
#    Run a test workflow execution via n8n UI:
#    https://n8n.bms-4.infra.zintegrowana.online

Escalation

SymptomAction
HeyGen UI has moved API settingsTry https://app.heygen.com/settings or search for “API” in account settings
Login fails (Google SSO)Use “Sign in with Google” button if password login is unavailable
n8n workflows on bms-4 still fail after rotationRedeploy n8n stack: docker compose up -d --no-deps n8n n8n-worker-1 n8n-worker-2 n8n-worker-3 on bms-4
MCP HeyGen tools fail with 401Restart Claude Code session; the session loads the key from SOPS at startup
SOPS temp file encrypt failsConfirm path_regex in .sops.yaml covers secrets/radekkonarski-brand-edit.env.sops

Prevention

  • The credential-rotation.yml GH Actions workflow (Monday 06:00 UTC) opens a human-action issue when next_due passes.
  • HeyGen does not send expiry warnings for API keys — only the calendar reminder and GH Actions check trigger rotation.
  • After rotation, update Last rotated and Next due at the top of this playbook.

Audit Log — Log to infra_operations

After this operation completes, log it to the infra_operations audit table.

Python (Linux server — bms-4, vps-i1, vps-h1, or similar):

import sys
sys.path.insert(0, '/opt/p24-infra')
from scripts.lib.log_op import log_op
 
log_op(
    actor="claude",  # "radieu" for manual human ops, "claude" for agent
    op_type="credential_rotation",
    resource="HEYGEN_API_KEY",
    result="success",  # "success" | "failed" | "skipped"
    detail="Scheduled rotation — HeyGen API key regenerated and SOPS n8n-bms4 updated",
    env="bms-4",
    gh_issue=2730,
)

PowerShell (Windows dev machine):

$env:SUPABASE_URL = (Get-Content "C:\code_2026\p24-infra\.env.local" | Select-String "^SUPABASE_URL=").ToString().Split("=",2)[1].Trim()
$env:SUPABASE_SERVICE_KEY = (Get-Content "C:\code_2026\p24-infra\.env.local" | Select-String "^SUPABASE_SERVICE_KEY=").ToString().Split("=",2)[1].Trim()
python -c "
import os, sys
sys.path.insert(0, 'C:/code_2026/p24-infra')
from scripts.lib.log_op import log_op
log_op('claude', 'credential_rotation', 'HEYGEN_API_KEY', 'success', 'Scheduled rotation — HeyGen API key regenerated and SOPS n8n-bms4 updated', 'bms-4')
"
$env:SUPABASE_URL = ''; $env:SUPABASE_SERVICE_KEY = ''