actualbudget/actual · error

Authorization failed: missing code.

Error message

Authorization failed: missing code.

What it means

The Enable Banking OAuth callback endpoint requires the authorization server to append a `code` query parameter to the redirect URL. When the callback request arrives without it, the handler immediately returns HTTP 400 with this HTML page instead of exchanging the code for tokens. It signals that the OAuth redirect never carried an authorization code, so the flow cannot continue.

Source

Thrown at packages/sync-server/src/app-enablebanking/app-enablebanking.ts:107

  );

  return {
    session_id: session.session_id,
    accounts: accountsWithBalances,
    aspsp: session.aspsp,
  };
}

// Auth callback from bank redirect — must be before validateSessionMiddleware
// since the bank redirects here directly (no auth token available)
app.get('/auth_callback', async (req: Request, res: Response) => {
  const code = typeof req.query.code === 'string' ? req.query.code : undefined;
  const state =
    typeof req.query.state === 'string' ? req.query.state : undefined;

  if (!code) {
    res
      .status(400)
      .send(
        '<html><body><p>Authorization failed: missing code.</p></body></html>',
      );
    return;
  }

  if (!state) {
    res
      .status(400)
      .send(
        '<html><body><p>Authorization failed: missing state parameter.</p></body></html>',
      );
    return;
  }

  try {
    const session = await enableBankingService.createSession(code);
    debug(

View on GitHub (pinned to d4334cb6e6)

Solutions

  1. Restart the OAuth flow from the beginning (initiate a new authorization request) instead of reusing/reloading the callback URL
  2. Verify the authorization request URL and redirect_uri registered with Enable Banking match exactly, so the provider returns a code
  3. Check whether the provider redirected with an `error` query param and surface that to the user
  4. Inspect any reverse proxy / URL rewriting in front of the sync-server that could strip query parameters

Example fix

// before (reloading stale callback URL)
GET https://server/enablebanking-handler  -> 400 missing code
// after (restart flow)
GET https://server/enablebanking-handler/init → redirect to bank → callback includes ?code=...&state=...
Defensive patterns

Strategy: validation

Validate before calling

const url = new URL(callbackUrl);
if (!url.searchParams.get('code')) {
  throw new Error('callback URL has no code param; restart the OAuth flow');
}

Type guard

function hasCode(q: Record<string, unknown>): q is { code: string } {
  return typeof q.code === 'string' && q.code.length > 0;
}

Try / catch

null

Prevention

When it happens

Trigger: The bank/ASPSP redirected the user to /enablebanking-handler (callback) without a `code` query parameter — e.g. the user hit the callback URL directly, the authorization request was malformed, or the provider errored before issuing a code.

Common situations: Developers bookmarking or re-opening the callback URL manually; misconfigured redirect URIs where the provider strips query params; Enable Banking authorization failing at the provider side and redirecting back with only an error parameter; proxies rewriting the URL and dropping the query string.

Related errors


AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29). Data as JSON: /api/errors/a96f7c2b2c520e0d. Report an issue: GitHub.