coreyhaines31/marketingskills · error · Error
Authentication failed: ${text}
Error message
Authentication failed: ${text} What it means
The /authenticate call returned a 2xx status but res.text() could not be parsed as JSON (JSON.parse threw SyntaxError, which is then re-wrapped here; the 'No JWT in response' case is re-thrown untouched). It means the success-status body was not JSON at all -- an HTML gateway page, an empty body, or plain text.
Source
Thrown at tools/clis/zoominfo.js:35
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}`)
}
}
async function api(method, path, body) {
if (args['dry-run']) {
return { _dry_run: true, method, url: `${BASE_URL}${path}`, headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ***' }, body }
}
const token = await authenticate()
const res = await fetch(`${BASE_URL}${path}`, {
method,
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`,
},
body: body ? JSON.stringify(body) : undefined,
})
const text = await res.text()
try {View on GitHub (pinned to 7868cb9251)
Solutions
- Inspect the text value embedded in the message to identify the actual body (HTML title, proxy banner, etc.).
- Verify direct connectivity with a raw request: `curl -i https://api.zoominfo.com/authenticate` and confirm a JSON content-type.
- Check for an intercepting proxy, VPN, or DNS override rerouting api.zoominfo.com.
Defensive patterns
Strategy: try-catch
Try / catch
let data
try {
data = JSON.parse(text)
} catch (e) {
const looksLikeHtml = /^\s*<(?:!doctype|html|body)/i.test(text)
if (looksLikeHtml) {
throw new Error('ZoomInfo auth returned an HTML page -- likely a proxy/VPN block page (check connectivity)')
}
throw new Error(`ZoomInfo auth response was not valid JSON (first 200 chars): ${text.slice(0, 200)}`)
} Prevention
- Whitelist api.zoominfo.com on any corporate proxy/VPN to avoid injected HTML block pages.
- Verify the response Content-Type is application/json before parsing.
- Confirm BASE_URL has no typo that could reach a non-API host serving HTML.
When it happens
Trigger: A 200 response carrying an HTML block/error page from a corporate proxy or CDN, an empty response body, a plain-text token without a JSON wrapper, or a BASE_URL typo that reaches a different host returning HTML.
Common situations: Corporate proxy/VPN injecting an HTML captive-portal or block page with HTTP 200, a ZoomInfo maintenance page served as HTML, or a misconfigured BASE_URL hitting a web frontend rather than the API host.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- Authentication failed (${res.status}): ${text}
- Failed to obtain access token
- ZOOMINFO_USERNAME and ZOOMINFO_PRIVATE_KEY required for auth
- No JWT in response
- Failed to obtain access token
AI-assisted analysis of coreyhaines31/marketingskills@7868cb9251 (2026-08-13).
Data as JSON: /api/errors/fd02cc32f3eb0b4d.
Report an issue: GitHub.