{"record":{"id":"ef3c70ef6dff7b1d","repo":"ToolJet/ToolJet","slug":"access-token-not-found-in-xero-response","errorCode":null,"errorMessage":"Access token not found in Xero response","messagePattern":"Access token not found in Xero response","errorType":"exception","errorClass":"QueryError","httpStatus":null,"severity":"error","filePath":"marketplace/plugins/xero/lib/index.ts","lineNumber":180,"sourceCode":"        method: 'post',\n        form: data,\n        responseType: 'json',\n      });\n\n      const result = response.body as { access_token?: string; refresh_token?: string };\n\n      if (result.access_token) {\n        return {\n          access_token: result.access_token,\n          refresh_token: result.refresh_token,\n        };\n      } else {\n        const errorMessage = 'Access token not found in Xero response';\n        const errorDetails = {\n          response: result,\n          status: response.statusCode,\n        };\n        throw new QueryError('XeroTokenError', errorMessage, errorDetails);\n      }\n    } catch (error: any) {\n      let parsed: any;\n\n      try {\n        parsed = error?.response?.body ? JSON.parse(error.response.body) : error;\n      } catch {\n        parsed = error?.response?.body || error;\n      }\n\n      const errorMessage =\n        parsed?.Title || parsed?.error_description || parsed?.error || error?.message || 'Xero token refresh failed';\n\n      const errorDetails = {\n        status: error?.response?.statusCode || null,\n        response: parsed,\n      };\n      throw new QueryError('XeroTokenRefreshError', errorMessage, errorDetails);","sourceCodeStart":162,"sourceCodeEnd":198,"githubUrl":"https://github.com/ToolJet/ToolJet/blob/20602a8e101f2e59686c9afde0d1402aac2c8871/marketplace/plugins/xero/lib/index.ts#L162-L198","documentation":"Thrown by the refresh path (index.ts:180) when the token endpoint returned an HTTP success (got did not throw) but the parsed body lacks access_token. It is a defensive check: Xero answered 2xx but the payload was not a usable token response. errorDetails carries the raw response and statusCode; thrown as QueryError 'XeroTokenError'.","triggerScenarios":"Xero identity endpoint returning 200 with an unexpected/empty body, a man-in-the-middle or proxy returning a 200 HTML page, an API contract change where the field was renamed, or a partial/malformed JSON body parsed without access_token.","commonSituations":"Corporate proxy intercepting identity.xero.com with a captive page, Xero temporarily serving a degraded response, an incorrect endpoint URL returning success for unrelated content.","solutions":["Inspect errorDetails.response to see what Xero actually returned.","Confirm network egress to identity.xero.com is unproxied/unintercepted.","Retry; if persistent, verify the grant_type/refresh_token sent were valid (a bad refresh_token usually yields non-2xx, but some proxies normalise to 200).","Check for an Xero API status incident."],"exampleFix":"// before: (no caller-side fix; server-side response is malformed)\n\n// after: surface the raw response and surface a clearer error\nif (!result.access_token) {\n  throw new QueryError('XeroTokenError', 'No access_token in 2xx response', { response: result, status: response.statusCode });\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":"function hasAccessToken(body: any): boolean {\n  return typeof body?.access_token === 'string' && body.access_token.length > 0;\n}","tryCatchPattern":"try {\n  const tokens = await refreshToken(sourceOptions, userId);\n  if (!tokens.access_token) throw new Error('Xero returned 2xx with no access_token');\n} catch (e) {\n  if (e instanceof QueryError && /access token not found/i.test(e.description)) {\n    logger.error({ response: e.data?.response }, 'Xero returned a non-token 2xx body — check for proxy interception');\n  }\n  throw e;\n}","preventionTips":["Ensure egress to identity.xero.com is not intercepted by a captive proxy.","Log the raw response body when this fires to diagnose the shape.","Retry once; persistent failures usually indicate infra interception."],"tags":["xero","oauth","token-refresh","unexpected-response","tooljet-plugin"],"backgroundTag":null,"analyzedSha":"20602a8e101f2e59686c9afde0d1402aac2c8871","analyzedAt":"2026-08-13T05:58:54.221Z","schemaVersion":2},"datasetVersion":"2026-08-13T09:17:06.757Z"}