calcom/cal.diy · error · OAuth2HttpException

${reason}

Error message

${reason}

What it means

Thrown by OAuth2ErrorService.handleAuthorizeError when the underlying error is an ErrorWithCode whose data.reason is one of the NON_REDIRECTABLE_REASONS (client_not_found, client_not_approved, client_rejected, redirect_uri_mismatch). Instead of redirecting the user back to the redirect_uri, the server responds directly with an OAuth2HttpException carrying error=err.message and error_description=reason. This is the OAuth2 /authorize endpoint's 'fatal client misconfiguration' path where redirecting would be unsafe or impossible.

Source

Thrown at apps/api/v2/src/modules/auth/oauth2/services/oauth2-error.service.ts:26

  "client_not_found",
  "client_not_approved",
  "client_rejected",
  "redirect_uri_mismatch",
]);

@Injectable()
export class OAuth2ErrorService {
  private readonly logger = new Logger("OAuth2ErrorService");

  constructor(private readonly oAuthService: OAuthService) {}

  handleAuthorizeError(err: unknown, redirectUri: string, state?: string): never {
    if (err instanceof ErrorWithCode) {
      const reason = err.data?.["reason"] as string | undefined;

      if (reason && NON_REDIRECTABLE_REASONS.has(reason)) {
        const statusCode = getHttpStatusCode(err);
        throw new OAuth2HttpException(
          {
            error: err.message,
            error_description: reason,
          },
          statusCode
        );
      }
    }

    const errorRedirectUrl = this.oAuthService.buildErrorRedirectUrl(redirectUri, err, state);
    throw new OAuth2RedirectException(errorRedirectUrl);
  }

  handleTokenError(err: unknown): never {
    if (err instanceof ErrorWithCode) {
      const statusCode = getHttpStatusCode(err);
      const reason = err.data?.["reason"] as string | undefined;
      throw new OAuth2HttpException(

View on GitHub (pinned to 176037d0af)

Solutions

  1. Verify the clientId in the authorize URL matches the value shown at https://app.cal.com/settings/platform for the target environment.
  2. Confirm the client is in APPROVED state in the platform OAuth settings; re-request approval if it is PENDING or REJECTED.
  3. Make the redirect_uri query parameter exactly match one of the URIs registered under the client's 'Redirect uris' list (scheme, host, port, and path must all match).
  4. Inspect the error_description field in the response; it will be the literal reason (client_not_found / client_not_approved / client_rejected / redirect_uri_mismatch) telling you which condition failed.

Example fix

// before
const url = `https://api.cal.com/v2/oauth/${clientId}/authorize?redirect_uri=http://localhost:3000&state=xyz`;

// after — redirect_uri must match a registered URI exactly
const url = `https://api.cal.com/v2/oauth/${clientId}/authorize?redirect_uri=http://localhost:3000/auth/callback&state=xyz`;
Defensive patterns

Strategy: validation

Validate before calling

// Before redirecting to /authorize, validate the client config you control
const CLIENT_ID = process.env.CAL_OAUTH_CLIENT_ID!;
const REDIRECT_URI = process.env.CAL_REDIRECT_URI!; // must be registered exactly
const APPROVED_STATES = new Set(['APPROVED']);

async function preflightAuthorize() {
  const client = await fetch(`/v2/oauth-clients/${CLIENT_ID}`, {
    headers: { Authorization: `Bearer ${process.env.CAL_ADMIN_API_KEY}` }
  }).then(r => r.ok ? r.json() : null);
  if (!client) throw new Error('client_not_found: check CAL_OAUTH_CLIENT_ID');
  if (!APPROVED_STATES.has(client.approvalStatus)) throw new Error(`client_not_approved: status=${client.approvalStatus}`);
  if (!client.redirectUris.includes(REDIRECT_URI)) throw new Error(`redirect_uri_mismatch: register ${REDIRECT_URI}`);
}

Type guard

import { ErrorWithCode } from '@calcom/platform-libraries/errors';

const NON_REDIRECTABLE_REASONS = new Set(['client_not_found','client_not_approved','client_rejected','redirect_uri_mismatch']);

function isNonRedirectableAuthorizeError(err: unknown): err is ErrorWithCode {
  return err instanceof ErrorWithCode
    && NON_REDIRECTABLE_REASONS.has((err as any).data?.['reason']);
}

Try / catch

try {
  await driveAuthorizeFlow();
} catch (err) {
  if (isNonRedirectableAuthorizeError(err)) {
    // surfaced as HTTP error, not redirect — show config guidance to the integrator
    renderClientConfigError((err as any).data.reason);
  } else {
    // redirect-based error — handle in the redirect_uri callback
  }
}

Prevention

When it happens

Trigger: A GET to the /v2/oauth/{clientId}/authorize endpoint where: (a) clientId does not match any OAuth client row (client_not_found); (b) the client exists but its approval state is not approved (client_not_approved / client_rejected); (c) the redirect_uri query param does not match any registered redirect URI for the client (redirect_uri_mismatch). The error surfaces as a JSON HTTP error response rather than a 302 redirect.

Common situations: Developer copy-pastes the wrong client id from settings; client is still in PENDING approval after creation; redirect_uri in the request uses http://localhost:3000 while the registered URI is http://localhost:3000/callback (trailing path or scheme mismatch); staging client id used against production, or vice versa.

Related errors


AI-assisted analysis of calcom/cal.diy@176037d0af (2026-08-12). Data as JSON: /api/errors/47a556a702ac794b. Report an issue: GitHub.