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

  1. Log the full parsed data object before throwing to see exactly what ZoomInfo returned.
  2. Confirm BASE_URL matches the account's correct ZoomInfo region/endpoint.
  3. 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

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


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