{"record":{"id":"b8751c8356cec6e7","repo":"coreyhaines31/marketingskills","slug":"failed-to-obtain-access-token","errorCode":null,"errorMessage":"Failed to obtain access token","messagePattern":"Failed to obtain access token","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"tools/clis/hotjar.js","lineNumber":24,"sourceCode":"const BASE_URL = 'https://api.hotjar.io/v2'\n\nif (!CLIENT_ID || !CLIENT_SECRET) {\n  console.error(JSON.stringify({ error: 'HOTJAR_CLIENT_ID and HOTJAR_CLIENT_SECRET environment variables required' }))\n  process.exit(1)\n}\n\nlet cachedToken = null\n\nasync function getToken() {\n  if (cachedToken) return cachedToken\n  const res = await fetch(`${OAUTH_URL}/oauth/token`, {\n    method: 'POST',\n    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n    body: `grant_type=client_credentials&client_id=${encodeURIComponent(CLIENT_ID)}&client_secret=${encodeURIComponent(CLIENT_SECRET)}`,\n  })\n  const data = await res.json()\n  if (!data.access_token) {\n    throw new Error(data.error_description || data.error || 'Failed to obtain access token')\n  }\n  cachedToken = data.access_token\n  return cachedToken\n}\n\nasync function api(method, path) {\n  if (args['dry-run']) {\n    return { _dry_run: true, method, url: `${BASE_URL}${path}`, headers: { Authorization: '***', 'Content-Type': 'application/json', Accept: 'application/json' } }\n  }\n  const token = await getToken()\n  const res = await fetch(`${BASE_URL}${path}`, {\n    method,\n    headers: {\n      'Authorization': `Bearer ${token}`,\n      'Content-Type': 'application/json',\n      'Accept': 'application/json',\n    },\n  })","sourceCodeStart":6,"sourceCodeEnd":42,"githubUrl":"https://github.com/coreyhaines31/marketingskills/blob/7868cb9251fad80a73d26e488a5ad5f6c4a9f335/tools/clis/hotjar.js#L6-L42","documentation":"Hotjar's OAuth2 client-credentials flow: the script POSTs grant_type=client_credentials to https://api.hotjar.io/oauth/token and expects an access_token in the JSON response. This error is the FINAL fallback, raised only when the parsed body has no access_token AND no error_description/error fields to report. Because the code never checks res.ok before parsing, a non-2xx status with an atypical JSON body also lands here with an uninformative generic message.","triggerScenarios":"POST to /oauth/token returns JSON without access_token: invalid/expired/revoked HOTJAR_CLIENT_ID or HOTJAR_CLIENT_SECRET, the Hotjar app lacking the requested scope/resources, a Hotjar-side error whose body omits the standard error fields, or any non-2xx status (the script does not gate on res.ok).","commonSituations":"Credentials copied with trailing whitespace or surrounding quotes, env vars exported in a different shell than the one running the CLI, a stale .env after rotating Hotjar keys, an app still pending Hotjar approval, or the script invoked without sourcing the .env file (it only reads process.env).","solutions":["Confirm the env vars are present and non-empty in the executing shell: `node -e \"console.log(!!process.env.HOTJAR_CLIENT_ID, !!process.env.HOTJAR_CLIENT_SECRET)\"`","Surface Hotjar's real response by checking res.ok and logging res.status plus the raw body before throwing (the generic message hides the cause).","Re-issue the client credentials in the Hotjar admin and confirm the app has the required resources/scopes, then re-export HOTJAR_CLIENT_ID and HOTJAR_CLIENT_SECRET."],"exampleFix":"// before\nconst data = await res.json()\nif (!data.access_token) {\n  throw new Error(data.error_description || data.error || 'Failed to obtain access token')\n}\n\n// after\nconst data = await res.json()\nif (!res.ok || !data.access_token) {\n  throw new Error(`Hotjar token request failed (${res.status}): ${JSON.stringify(data)}`)\n}","handlingStrategy":"try-catch","validationCode":"function validateHotjarCreds() {\n  const missing = ['HOTJAR_CLIENT_ID', 'HOTJAR_CLIENT_SECRET'].filter(k => !process.env[k])\n  if (missing.length) throw new Error(`Missing env vars: ${missing.join(', ')}`)\n}","typeGuard":"function hasAccessToken(v) {\n  return v != null && typeof v.access_token === 'string' && v.access_token.length > 0\n}","tryCatchPattern":"try {\n  await hotjarApi('GET', '/sites')\n} catch (e) {\n  if (/access token|token request/i.test(e.message)) {\n    throw new Error(`Hotjar auth failed -- verify HOTJAR_CLIENT_ID/CLIENT_SECRET: ${e.message}`)\n  }\n  throw e\n}","preventionTips":["Run the CLI with `--dry-run` first to confirm env vars load before any network call.","Source the .env file explicitly in the same shell that runs the tool (the script only reads process.env).","Rotate Hotjar credentials in one place and update every environment the same day to avoid drift."],"tags":["oauth","authentication","hotjar","configuration","network"],"backgroundTag":null,"analyzedSha":"7868cb9251fad80a73d26e488a5ad5f6c4a9f335","analyzedAt":"2026-08-13T03:33:17.303Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}