{"record":{"id":"3306d6d5a27dc66e","repo":"coreyhaines31/marketingskills","slug":"authentication-failed-res-status-text","errorCode":null,"errorMessage":"Authentication failed (${res.status}): ${text}","messagePattern":"Authentication failed \\((.+?)\\): (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"tools/clis/zoominfo.js","lineNumber":26,"sourceCode":"  console.error(JSON.stringify({ error: 'ZOOMINFO_ACCESS_TOKEN or ZOOMINFO_USERNAME + ZOOMINFO_PRIVATE_KEY environment variables required' }))\n  process.exit(1)\n}\n\nasync function authenticate() {\n  if (ACCESS_TOKEN) return ACCESS_TOKEN\n  const username = process.env.ZOOMINFO_USERNAME\n  const password = process.env.ZOOMINFO_PRIVATE_KEY\n  if (!username || !password) {\n    throw new Error('ZOOMINFO_USERNAME and ZOOMINFO_PRIVATE_KEY required for authentication')\n  }\n  const res = await fetch(`${BASE_URL}/authenticate`, {\n    method: 'POST',\n    headers: { 'Content-Type': 'application/json' },\n    body: JSON.stringify({ username, password }),\n  })\n  const text = await res.text()\n  if (!res.ok) {\n    throw new Error(`Authentication failed (${res.status}): ${text}`)\n  }\n  try {\n    const data = JSON.parse(text)\n    if (!data.jwt) throw new Error('No JWT in response')\n    ACCESS_TOKEN = data.jwt\n    return ACCESS_TOKEN\n  } catch (e) {\n    if (e.message === 'No JWT in response') throw e\n    throw new Error(`Authentication failed: ${text}`)\n  }\n}\n\nasync function api(method, path, body) {\n  if (args['dry-run']) {\n    return { _dry_run: true, method, url: `${BASE_URL}${path}`, headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ***' }, body }\n  }\n  const token = await authenticate()\n  const res = await fetch(`${BASE_URL}${path}`, {","sourceCodeStart":8,"sourceCodeEnd":44,"githubUrl":"https://github.com/coreyhaines31/marketingskills/blob/7868cb9251fad80a73d26e488a5ad5f6c4a9f335/tools/clis/zoominfo.js#L8-L44","documentation":"The POST to https://api.zoominfo.com/authenticate returned a non-2xx HTTP status. The error embeds both the status code and the raw response body, so ZoomInfo's reason is visible in the message. 401 means bad credentials, 400 a malformed request body, 403 an IP/permission issue, and 5xx a ZoomInfo-side fault.","triggerScenarios":"Wrong username/private-key pair, expired or locked credentials, an IP not allowlisted by the account, a malformed JSON body (e.g. a private key with literal backslash-n instead of real newlines), or ZoomInfo rate limiting / outage.","commonSituations":"Private key pasted with escaped backslash-n instead of real line breaks, credentials from a different ZoomInfo environment/region, account throttled after repeated automated calls, or a stale key after rotation.","solutions":["Read the status and text already embedded in the message -- ZoomInfo states the exact problem (e.g. 'Invalid username or password').","For 401/403, regenerate the username and private key in ZoomInfo and ensure the calling IP is allowlisted.","Preserve real newlines in the private key by exporting it single-quoted in the shell, or load it from a file to avoid shell mangling."],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"async function authenticateWithRetry(maxAttempts = 3) {\n  for (let attempt = 1; attempt <= maxAttempts; attempt++) {\n    try {\n      return await authenticate()\n    } catch (e) {\n      const status = /\\((\\d{3})\\)/.exec(e.message)?.[1]\n      const transient = status && Number(status) >= 500\n      if (!transient || attempt === maxAttempts) throw e\n      await new Promise(r => setTimeout(r, 500 * 2 ** attempt))\n    }\n  }\n}","preventionTips":["Parse the status code from the message to distinguish 4xx (fix credentials) from 5xx (retry).","Keep the private key's real newlines intact to avoid 400-level body rejections.","Ensure the calling IP is allowlisted in ZoomInfo to prevent intermittent 403s."],"tags":["authentication","http","zoominfo","network"],"backgroundTag":null,"analyzedSha":"7868cb9251fad80a73d26e488a5ad5f6c4a9f335","analyzedAt":"2026-08-13T03:33:17.303Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}