n8n-io/n8n · error · ResponseError

ESTATUS

ESTATUS

Error message

HTTP status ${status}

What it means

ResponseError (code ESTATUS) thrown by ClientOAuth2.accessTokenRequest when the token endpoint returns status >= 400 (after getAuthError finds no standard OAuth2 error body) or status >= 300. It carries the HTTP status and raw response data; ESTATUS is the library's stable code for 'unexpected token endpoint status'.

Source

Thrown at packages/@n8n/client-oauth2/src/client-oauth2.ts:165

		if (options.ignoreSSLIssues || lookup) {
			requestConfig.httpsAgent = createHttpsProxyAgent(url, undefined, {
				...(options.ignoreSSLIssues ? { rejectUnauthorized: false } : {}),
				...(lookup ? { lookup } : {}),
			});
		}

		if (lookup) {
			requestConfig.httpAgent = createHttpProxyAgent(url, undefined, { lookup });
		}

		const response = await axios.request(requestConfig);

		if (response.status >= 400) {
			const body = this.parseResponseBody<OAuth2AccessTokenErrorResponse>(response);
			const authErr = getAuthError(body);

			if (authErr) throw authErr;
			else throw new ResponseError(response.status, response.data);
		}

		if (response.status >= 300) {
			throw new ResponseError(response.status, response.data);
		}

		return this.parseResponseBody<ClientOAuth2TokenData>(response);
	}

	/**
	 * Attempt to parse response body based on the content type.
	 */
	private parseResponseBody<T extends object>(response: AxiosResponse<unknown>): T {
		const contentType = (response.headers['content-type'] as string) ?? '';
		const body = response.data as string;

		if (contentType.startsWith('application/x-www-form-urlencoded')) {
			return qs.parse(body) as T;

View on GitHub (pinned to 5ac6606e81)

Solutions

  1. Read the embedded status: 401 -> invalid_client (check id/secret), 400 -> invalid_grant (re-auth), 5xx -> provider issue.
  2. Inspect the response data on the ResponseError for provider-specific error text.
  3. Verify the accessTokenUri is correct and not behind a redirect.
  4. Ensure the client_id, client_secret, scopes, and redirect_uri match the provider app registration.
Defensive patterns

Strategy: try-catch

Validate before calling

// Pre-flight: confirm the token endpoint is reachable and returns 2xx for a known request
// (operational smoke check, not in-process)

Type guard

import { ResponseError } from './client-oauth2';
const isOAuthStatusError = (e: unknown): boolean =>
  e instanceof ResponseError && (e as any).code === 'ESTATUS';

Try / catch

try {
  return await client.accessTokenRequest(requestOptions);
} catch (e) {
  if (e instanceof ResponseError && e.status === 401) {
    // invalid_client — fix id/secret
  } else if (e instanceof ResponseError && e.status === 400) {
    // invalid_grant — re-authorize
  } else if (e instanceof ResponseError && e.status >= 500) {
    // retry token endpoint with backoff
  } else {
    throw e;
  }
}

Prevention

When it happens

Trigger: axios.request to the OAuth2 accessTokenUri returns a non-3xx-non-2xx status. If the body parses to a standard OAuth2 error (invalid_grant, invalid_client, etc.) getAuthError throws that instead; otherwise this generic ResponseError is thrown. 3xx (unsupported redirect on a POST token endpoint) also triggers it.

Common situations: invalid_client (wrong client_id/secret); the token endpoint URL is wrong and returns an HTML 404; a provider returns a non-standard error body; network middleware returns a 502/504 on the token endpoint; an expired authorization code being exchanged late.

Related errors


AI-assisted analysis of n8n-io/n8n@5ac6606e81 (2026-08-12). Data as JSON: /api/errors/40e0796df4f2394f. Report an issue: GitHub.