coreyhaines31/marketingskills · error · Error
No JWT in response
Error message
No JWT in response
What it means
The /authenticate call returned a 2xx status and the body parsed as valid JSON, but the expected jwt field is absent or falsy. ZoomInfo's success contract is { jwt: "..." }; reaching this throw means the response was success-shaped but did not conform -- typically API/version drift, a maintenance page with 200 status, or an account lacking JWT issuance permission.
Source
Thrown at tools/clis/zoominfo.js:30
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}`)
}
}
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}`,View on GitHub (pinned to 7868cb9251)
Solutions
- Log the full parsed data object before throwing to see exactly what ZoomInfo returned.
- Confirm BASE_URL matches the account's correct ZoomInfo region/endpoint.
- Check ZoomInfo release notes or contact support for an authenticate-response schema change.
Example fix
// before
if (!data.jwt) throw new Error('No JWT in response')
// after
if (!data.jwt) throw new Error(`No JWT in response: ${JSON.stringify(data)}`) Defensive patterns
Strategy: type-guard
Type guard
function isZoominfoAuthResponse(v) {
return v != null && typeof v === 'object' && typeof v.jwt === 'string' && v.jwt.length > 0
} Try / catch
const data = JSON.parse(text)
if (!isZoominfoAuthResponse(data)) {
throw new Error(`Unexpected ZoomInfo auth response shape: ${JSON.stringify(data)}`)
} Prevention
- Pin to a known BASE_URL for your ZoomInfo tenant rather than a guessable host.
- Log the full response body once during integration to lock in the expected jwt contract.
- Subscribe to ZoomInfo API changelog notices to catch token-field renames early.
When it happens
Trigger: A 2xx response whose JSON lacks a jwt key: ZoomInfo API version change renaming the token field, BASE_URL pointed at a tenant/region that returns a different shape, a partial response during maintenance, or an account plan without JWT access.
Common situations: BASE_URL (https://api.zoominfo.com) redirected or pointed at the wrong region, a recent ZoomInfo release that altered the authenticate response, or a proxy returning a 200 with a wrapped/extra-keyed body.
Related errors
- ZOOMINFO_USERNAME and ZOOMINFO_PRIVATE_KEY required for auth
- Authentication failed (${res.status}): ${text}
- Authentication failed: ${text}
- Failed to obtain access token
- Failed to obtain access token
AI-assisted analysis of coreyhaines31/marketingskills@7868cb9251 (2026-08-13).
Data as JSON: /api/errors/d7910cc9a2f54bbf.
Report an issue: GitHub.