calcom/cal.diy · error · BadRequestException

OAuth client with ID '${clientId}' not found

Error message

OAuth client with ID '${clientId}' not found

What it means

Thrown by the POST /authorize OAuth flow endpoint after NextAuthGuard has authenticated the session. The handler calls oauthClientRepository.getOAuthClient(clientId) using the :clientId route param; a null/undefined return triggers BadRequestException (HTTP 400). Note the status is 400 BadRequest, not 404 NotFound, even though the semantic is 'client does not exist' — this is intentional so callers cannot distinguish 'missing client' from 'bad client id'.

Source

Thrown at apps/api/v2/src/modules/oauth-clients/controllers/oauth-flow/oauth-flow.controller.ts:60

  constructor(
    private readonly oauthClientRepository: OAuthClientRepository,
    private readonly tokensRepository: TokensRepository,
    private readonly oAuthFlowService: OAuthFlowService
  ) {}

  @Post("/authorize")
  @HttpCode(HttpStatus.OK)
  @UseGuards(NextAuthGuard)
  @DocsExcludeEndpoint()
  async authorize(
    @Param("clientId") clientId: string,
    @Body() body: OAuthAuthorizeInput,
    @GetUser("id") userId: number,
    @Response() res: ExpressResponse
  ): Promise<void> {
    const oauthClient = await this.oauthClientRepository.getOAuthClient(clientId);
    if (!oauthClient) {
      throw new BadRequestException(`OAuth client with ID '${clientId}' not found`);
    }

    if (!isOriginAllowed(body.redirectUri, oauthClient.redirectUris)) {
      throw new BadRequestException("Invalid 'redirect_uri' value.");
    }

    const alreadyAuthorized = await this.tokensRepository.getAuthorizationTokenByClientUserIds(
      clientId,
      userId
    );

    if (alreadyAuthorized) {
      throw new BadRequestException(
        `User with id=${userId} has already authorized client with id=${clientId}.`
      );
    }

    const { id } = await this.tokensRepository.createAuthorizationToken(clientId, userId);

View on GitHub (pinned to 176037d0af)

Solutions

  1. Verify the clientId in the request path matches an id returned by GET /v2/oauth-clients for the same environment.
  2. Check the OAuth client still exists and is enabled in the org's client list before showing the authorize screen.
  3. If integrating, store clientIds in environment-scoped config rather than hardcoding.
  4. Treat HTTP 400 on /authorize as 'invalid client id' and re-fetch the client list in the UI.

Example fix

// before
const res = await fetch(`/v2/oauth-clients/${clientId}/authorize`, { method: 'POST', body: JSON.stringify({ redirectUri }) });

// after — verify the client exists in this environment first
const clients = await (await fetch('/v2/oauth-clients')).json();
if (!clients.data.some((c) => c.clientId === clientId)) {
  throw new Error('OAuth client not available in this environment');
}
const res = await fetch(`/v2/oauth-clients/${clientId}/authorize`, { method: 'POST', body: JSON.stringify({ redirectUri }) });
Defensive patterns

Strategy: validation

Validate before calling

// Before calling /authorize, confirm the client exists in this environment
async function assertClientExists(clientId: string, headers: HeadersInit) {
  const res = await fetch('/v2/oauth-clients', { headers });
  const { data } = await res.json();
  if (!data.some((c) => c.clientId === clientId)) {
    throw new Error(`OAuth client ${clientId} not available in this environment`);
  }
}

Type guard

function isOAuthClientRef(value: unknown): value is { clientId: string } {
  return typeof value === 'object' && value !== null && typeof (value as any).clientId === 'string';
}

Try / catch

try {
  await fetch(`/v2/oauth-clients/${clientId}/authorize`, { method: 'POST', body, headers });
} catch (e) {
  if (e instanceof BadRequestException && /not found/.test(e.message)) {
    // refresh client list and prompt user to re-select
  }
  throw e;
}

Prevention

When it happens

Trigger: POST /v2/oauth-clients/:clientId/authorize where :clientId is a non-existent, deleted, or wrong-environment client id (e.g. staging id sent to production). The authenticated NextAuth user is valid; only the clientId lookup fails.

Common situations: Copy/paste of a clientId that has a typo or trailing space; using a clientId from a different environment; the OAuth client was deleted by an admin between when the frontend fetched the list and when the user clicked authorize; integration tests that hardcode ids against a freshly-reset database.

Related errors


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