coreyhaines31/marketingskills · error · Error

ZOOMINFO_USERNAME and ZOOMINFO_PRIVATE_KEY required for auth

Error message

ZOOMINFO_USERNAME and ZOOMINFO_PRIVATE_KEY required for authentication

What it means

ZoomInfo's authenticate() requires both ZOOMINFO_USERNAME and ZOOMINFO_PRIVATE_KEY when no pre-supplied ZOOMINFO_ACCESS_TOKEN exists. This in-function guard is reachable because the top-level startup guard only checks for ZOOMINFO_USERNAME (not ZOOMINFO_PRIVATE_KEY), so the process passes startup validation and then fails on the first API call.

Source

Thrown at tools/clis/zoominfo.js:17

#!/usr/bin/env node

const BASE_URL = 'https://api.zoominfo.com'

let ACCESS_TOKEN = process.env.ZOOMINFO_ACCESS_TOKEN

if (!ACCESS_TOKEN && !process.env.ZOOMINFO_USERNAME) {
  console.error(JSON.stringify({ error: 'ZOOMINFO_ACCESS_TOKEN or ZOOMINFO_USERNAME + ZOOMINFO_PRIVATE_KEY environment variables required' }))
  process.exit(1)
}

async function authenticate() {
  if (ACCESS_TOKEN) return ACCESS_TOKEN
  const username = process.env.ZOOMINFO_USERNAME
  const password = process.env.ZOOMINFO_PRIVATE_KEY
  if (!username || !password) {
    throw new Error('ZOOMINFO_USERNAME and ZOOMINFO_PRIVATE_KEY required for authentication')
  }
  const res = await fetch(`${BASE_URL}/authenticate`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ username, password }),
  })
  const text = await res.text()
  if (!res.ok) {
    throw new Error(`Authentication failed (${res.status}): ${text}`)
  }
  try {
    const data = JSON.parse(text)
    if (!data.jwt) throw new Error('No JWT in response')
    ACCESS_TOKEN = data.jwt
    return ACCESS_TOKEN
  } catch (e) {
    if (e.message === 'No JWT in response') throw e
    throw new Error(`Authentication failed: ${text}`)

View on GitHub (pinned to 7868cb9251)

Solutions

  1. Set both ZOOMINFO_USERNAME and ZOOMINFO_PRIVATE_KEY in the environment (or supply ZOOMINFO_ACCESS_TOKEN directly to skip the authenticate() path).
  2. Tighten the top-level guard to require the pair together so the CLI fails fast at startup instead of mid-run: `if (!ACCESS_TOKEN && (!process.env.ZOOMINFO_USERNAME || !process.env.ZOOMINFO_PRIVATE_KEY))`.
  3. Verify with `node -e "console.log(!!process.env.ZOOMINFO_USERNAME, !!process.env.ZOOMINFO_PRIVATE_KEY)"` before running the command.

Example fix

// before
if (!ACCESS_TOKEN && !process.env.ZOOMINFO_USERNAME) {
  console.error(JSON.stringify({ error: 'ZOOMINFO_ACCESS_TOKEN or ZOOMINFO_USERNAME + ZOOMINFO_PRIVATE_KEY environment variables required' }))
  process.exit(1)
}

// after
if (!ACCESS_TOKEN && (!process.env.ZOOMINFO_USERNAME || !process.env.ZOOMINFO_PRIVATE_KEY)) {
  console.error(JSON.stringify({ error: 'ZOOMINFO_ACCESS_TOKEN or ZOOMINFO_USERNAME + ZOOMINFO_PRIVATE_KEY environment variables required' }))
  process.exit(1)
}
Defensive patterns

Strategy: validation

Validate before calling

function validateZoominfoCreds() {
  const token = process.env.ZOOMINFO_ACCESS_TOKEN
  const user = process.env.ZOOMINFO_USERNAME
  const key = process.env.ZOOMINFO_PRIVATE_KEY
  if (token) return
  if (!user || !key) {
    const missing = [!user && 'ZOOMINFO_USERNAME', !key && 'ZOOMINFO_PRIVATE_KEY'].filter(Boolean)
    throw new Error(`ZoomInfo requires ACCESS_TOKEN or both USERNAME+PRIVATE_KEY. Missing: ${missing.join(', ')}`)
  }
}

Prevention

When it happens

Trigger: ZOOMINFO_ACCESS_TOKEN is unset, ZOOMINFO_USERNAME is set, but ZOOMINFO_PRIVATE_KEY is unset or empty -- authenticate() proceeds past the ACCESS_TOKEN check and hits this throw before any network call.

Common situations: Partial credentials pasted into .env (username only), ZOOMINFO_PRIVATE_KEY line-wrapped or truncated by the shell, a CI secret configured for username but not the private key, or copy-paste of the public-style username without its paired key.

Understand the failure class

Related errors


AI-assisted analysis of coreyhaines31/marketingskills@7868cb9251 (2026-08-13). Data as JSON: /api/errors/61ee5e9820b33e7b. Report an issue: GitHub.