decolua/9router · error · Error

`Failed to fetch user info: ${errorText}`

Error message

`Failed to fetch user info: ${errorText}`

What it means

The iFlow user-info request (issued inside postExchange after a successful token exchange) returned a non-2xx HTTP status. The raw response body is surfaced as the error text. This endpoint is mandatory for iFlow because it returns the apiKey needed for subsequent API calls.

Source

Thrown at src/lib/oauth/providers/iflow.js:58

      throw new Error(`Token exchange failed: ${error}`);
    }

    return await response.json();
  },
  postExchange: async (tokens) => {
    // Fetch user info (MUST succeed to get API key)
    const userInfoRes = await fetch(
      `${IFLOW_CONFIG.userInfoUrl}?accessToken=${encodeURIComponent(tokens.access_token)}`,
      {
        headers: {
          Accept: "application/json",
        },
      }
    );

    if (!userInfoRes.ok) {
      const errorText = await userInfoRes.text();
      throw new Error(`Failed to fetch user info: ${errorText}`);
    }

    const result = await userInfoRes.json();
    if (!result.success) {
      throw new Error(`User info request failed: ${result.message || 'Unknown error'}`);
    }

    const userInfo = result.data || {};

    // Validate API key (critical for iFlow)
    if (!userInfo.apiKey || userInfo.apiKey.trim() === "") {
      throw new Error("Empty API key returned from iFlow");
    }

    // Validate email/phone
    const email = userInfo.email?.trim() || userInfo.phone?.trim();
    if (!email) {
      throw new Error("Missing account email/phone in user info");

View on GitHub (pinned to 90b52e06ff)

Solutions

  1. Confirm tokens.access_token is a non-empty string before calling postExchange; if undefined, the token endpoint's response schema changed — inspect the raw exchange response.
  2. Verify IFLOW_CONFIG.userInfoUrl matches the environment of the token endpoint (staging vs production hosts are usually not interchangeable).
  3. Re-run the OAuth flow to obtain a fresh access token — a 401 here shortly after exchange means the token was rejected server-side.
  4. Check network/proxy configuration if the status is 5xx, and retry the whole flow.

Example fix

// before
const userInfoRes = await fetch(`${IFLOW_CONFIG.userInfoUrl}?accessToken=${encodeURIComponent(tokens.access_token)}`, {...});
// after: fail fast on a missing token instead of sending 'undefined'
if (!tokens?.access_token) {
  throw new Error(`iFlow token response missing access_token: ${JSON.stringify(tokens)}`);
}
const userInfoRes = await fetch(`${IFLOW_CONFIG.userInfoUrl}?accessToken=${encodeURIComponent(tokens.access_token)}`, {...});
Defensive patterns

Strategy: try-catch

Validate before calling

// before calling postExchange, verify the token payload shape
function validateTokensForUserInfo(tokens) {
  if (!tokens || typeof tokens.access_token !== 'string' || tokens.access_token.length === 0) {
    throw new Error('access_token missing from iFlow token exchange response — cannot fetch user info');
  }
}

Type guard

function hasAccessToken(t) {
  return typeof t === 'object' && t !== null && typeof t.access_token === 'string' && t.access_token.length > 0;
}

Try / catch

try {
  const { userInfo } = await provider.postExchange(tokens);
} catch (e) {
  if (String(e.message).startsWith('Failed to fetch user info:')) {
    const body = e.message.slice('Failed to fetch user info:'.length);
    if (/401|Unauthorized/i.test(body)) {
      throw new Error('iFlow rejected the access token; restart OAuth flow for a fresh token');
    }
    if (/^[45]\d\d/.test(body)) {
      // transient provider/network error — safe to retry once
      return retryPostExchange(tokens, 1);
    }
  }
  throw e;
}

Prevention

When it happens

Trigger: GET `${userInfoUrl}?accessToken=<access_token>` returns userInfoRes.ok === false — most commonly HTTP 401 because the freshly minted access_token is invalid/expired/for the wrong environment, or the accessToken query param is empty because tokens.access_token was missing from the token response.

Common situations: Token endpoint returned a 200 with an unexpected body shape (no access_token) so the query param is 'undefined'; access token already revoked; iFlow user-info base URL changed or points at a different environment than the token endpoint; corporate proxy blocking the request (5xx).

Related errors


AI-assisted analysis of decolua/9router@90b52e06ff (2026-08-30). Data as JSON: /api/errors/d9e1f8e03412f524. Report an issue: GitHub.