ToolJet/ToolJet · error · QueryError

access_token not found in the response

Error message

access_token not found in the response

What it means

Thrown after a successful (2xx) refresh-token response when the parsed body has no access_token field. The data payload includes the responseObject and responseHeaders so the caller can see exactly what the IdP returned. It indicates the IdP accepted the request but returned a body shape this code does not recognize as a valid token response.

Source

Thrown at marketplace/plugins/common/lib/oauth.ts:344

  if (!(response.statusCode >= 200 || response.statusCode < 300)) {
    throw new QueryError(
      'could not connect to Oauth server. status code',
      JSON.stringify({ statusCode: response.statusCode }),
      {
        responseObject: {
          statusCode: response.statusCode,
          responseBody: response.body,
        },
        responseHeaders: response.headers,
      }
    );
  }

  if (result['access_token']) {
    accessTokenDetails['access_token'] = result['access_token'];
    accessTokenDetails['refresh_token'] = result['refresh_token'] || refreshToken;
  } else {
    throw new QueryError(
      'access_token not found in the response',
      {},
      {
        responseObject: {
          statusCode: response.statusCode,
          responseBody: response.body,
        },
        responseHeaders: response.headers,
      }
    );
  }
  return accessTokenDetails;
};

View on GitHub (pinned to 20602a8e10)

Solutions

  1. Inspect error.data.responseObject.responseBody to see the actual IdP response body and compare it to the expected OAuth2 token shape.
  2. If the token is nested (e.g., under data or result), the IdP is non-compliant; you need adapter code or the correct endpoint.
  3. On the IdP admin side, enable the refresh_token grant and the offline_access equivalent scope for this app.
  4. Confirm access_token_url points to the token endpoint, not the authorize or userinfo endpoint.
  5. Re-authorize if the IdP requires periodic re-consent and returned a 200 'consent_required' envelope.
Defensive patterns

Strategy: type-guard

Validate before calling

function hasAccessToken(body: any): boolean {
  return body != null && typeof body.access_token === 'string' && body.access_token.length > 0;
}
// after the refresh response, before trusting it:
if (!hasAccessToken(result)) { /* surface IdP response body for diagnosis */ }

Type guard

function isTokenResponse(o: any): o is { access_token: string; refresh_token?: string } {
  return o != null && typeof o.access_token === 'string' && o.access_token.length > 0;
}

Try / catch

try { await getRefreshedToken(...); }
catch (e) {
  if (e instanceof QueryError && e.message === 'access_token not found in the response') {
    // e.data.responseObject.responseBody shows what the IdP actually returned; likely needs IdP config fix
  }
  throw e;
}

Prevention

When it happens

Trigger: result = JSON.parse(response.body) succeeds but result['access_token'] is falsy. Causes: the IdP returned an error envelope with 200 status; the refresh_token grant is not enabled on the app registration; the response nests the token under a different key (e.g., data.access_token); the response is a non-token JSON object (rate-limit notice, consent required).

Common situations: Custom/private IdPs with non-standard token envelopes; app registration missing the refresh_token grant type; the consent screen was skipped and the IdP returned a 200 with an error body; the wrong access_token_url was used (hit a different endpoint that returns JSON without access_token).

Related errors


AI-assisted analysis of ToolJet/ToolJet@20602a8e10 (2026-08-13). Data as JSON: /api/errors/95d173ad2ded694a. Report an issue: GitHub.