coreyhaines31/marketingskills · error · Error
Failed to obtain access token
Error message
Failed to obtain access token
What it means
Snov.io access-token endpoint: the script POSTs grant_type=client_credentials to https://api.snov.io/v1/oauth/access_token and expects an access_token. This is the fallback message, surfaced only when the parsed JSON has no access_token and Snov also omitted error/error_description. Like the hotjar tool, the code parses res.json() unconditionally without checking res.ok.
Source
Thrown at tools/clis/snov.js:23
const BASE_URL = 'https://api.snov.io/v1'
if (!CLIENT_ID || !CLIENT_SECRET) {
console.error(JSON.stringify({ error: 'SNOV_CLIENT_ID and SNOV_CLIENT_SECRET environment variables required' }))
process.exit(1)
}
let cachedToken = null
async function getToken() {
if (cachedToken) return cachedToken
const res = await fetch('https://api.snov.io/v1/oauth/access_token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ grant_type: 'client_credentials', client_id: CLIENT_ID, client_secret: CLIENT_SECRET }),
})
const data = await res.json()
if (!data.access_token) {
throw new Error(data.error_description || data.error || 'Failed to obtain access token')
}
cachedToken = data.access_token
return cachedToken
}
async function api(method, path, body) {
if (args['dry-run']) {
return { _dry_run: true, method, url: `${BASE_URL}${path}`, headers: { Authorization: '***', 'Content-Type': 'application/json', Accept: 'application/json' }, body: body || undefined }
}
const token = await getToken()
const opts = {
method,
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
'Accept': 'application/json',
},
}View on GitHub (pinned to 7868cb9251)
Solutions
- Confirm the env vars are set in the executing shell: `node -e "console.log(!!process.env.SNOV_CLIENT_ID, !!process.env.SNOV_CLIENT_SECRET)"`
- Log res.status and the full data object before throwing to expose Snov's actual error reason.
- Regenerate the API credentials in Snov -> Settings -> API users and re-export SNOV_CLIENT_ID and SNOV_CLIENT_SECRET.
Example fix
// before
const data = await res.json()
if (!data.access_token) {
throw new Error(data.error_description || data.error || 'Failed to obtain access token')
}
// after
const data = await res.json()
if (!res.ok || !data.access_token) {
throw new Error(`Snov token request failed (${res.status}): ${JSON.stringify(data)}`)
} Defensive patterns
Strategy: try-catch
Validate before calling
function validateSnovCreds() {
const missing = ['SNOV_CLIENT_ID', 'SNOV_CLIENT_SECRET'].filter(k => !process.env[k])
if (missing.length) throw new Error(`Missing env vars: ${missing.join(', ')}`)
} Type guard
function hasAccessToken(v) {
return v != null && typeof v.access_token === 'string' && v.access_token.length > 0
} Try / catch
try {
await snovApi('GET', '/get-user-info')
} catch (e) {
if (/access token|token request/i.test(e.message)) {
throw new Error(`Snov auth failed -- verify SNOV_CLIENT_ID/CLIENT_SECRET: ${e.message}`)
}
throw e
} Prevention
- Load .env before invoking (the tool has no built-in dotenv loader), e.g. `set -a; . ./.env; set +a`.
- Confirm the Snov account is active and not throttled before long-running bulk operations.
- Keep API credentials in a secrets manager rather than pasting them into shell history.
When it happens
Trigger: POST to /oauth/access_token returns JSON without access_token: wrong/revoked SNOV_CLIENT_ID or SNOV_CLIENT_SECRET, a deactivated Snov account, a rate-limited token endpoint returning a non-standard body, or any non-2xx status (no res.ok gate).
Common situations: SNOV_CLIENT_ID/SECRET not loaded because the script only reads process.env and there is no dotenv loader, free-tier quotas blocking token issuance, credentials rotated in the Snov UI but not updated locally, or env vars set under a different name.
Related errors
- Failed to obtain access token
- ZOOMINFO_USERNAME and ZOOMINFO_PRIVATE_KEY required for auth
- Authentication failed (${res.status}): ${text}
- No JWT in response
- Authentication failed: ${text}
AI-assisted analysis of coreyhaines31/marketingskills@7868cb9251 (2026-08-13).
Data as JSON: /api/errors/78bd6a4b93d21f4a.
Report an issue: GitHub.