{"record":{"id":"5b41f81bad3ec00f","repo":"calcom/cal.diy","slug":"error-description","errorCode":null,"errorMessage":"{error_description}","messagePattern":"\\{error_description\\}","errorType":"http","errorClass":"BadRequestException","httpStatus":400,"severity":"warning","filePath":"apps/api/v2/src/modules/conferencing/controllers/conferencing.controller.ts","lineNumber":154,"sourceCode":"    description: \"Conferencing application type\",\n    enum: [ZOOM, OFFICE_365_VIDEO],\n    required: true,\n  })\n  async save(\n    @Query(\"state\") state: string,\n    @Param(\"app\") app: string,\n    @Query(\"code\") code: string,\n    @Query(\"error\") error: string | undefined,\n    @Query(\"error_description\") error_description: string | undefined\n  ): Promise<{ url: string }> {\n    if (!state) {\n      throw new BadRequestException(\"Missing `state` query param\");\n    }\n\n    const decodedCallbackState: OAuthCallbackState = JSON.parse(state);\n    try {\n      if (error) {\n        throw new BadRequestException(error_description);\n      }\n\n      if (decodedCallbackState.teamId && decodedCallbackState.orgId) {\n        const apiUrl = this.config.get(\"api.url\");\n        const url = `${apiUrl}/organizations/${decodedCallbackState.orgId}/teams/${decodedCallbackState.teamId}/conferencing/${app}/oauth/callback`;\n        const params: Record<string, string | undefined> = { state, code, error, error_description };\n        const headers = {\n          Authorization: `Bearer ${decodedCallbackState.accessToken}`,\n        };\n        try {\n          const response = await this.httpService.axiosRef.get(url, { params, headers });\n          const redirectUrl = response.data?.url || decodedCallbackState.onErrorReturnTo || \"\";\n          return { url: redirectUrl };\n        } catch (err) {\n          const fallbackUrl = decodedCallbackState.onErrorReturnTo || \"\";\n          return { url: fallbackUrl };\n        }\n      }","sourceCodeStart":136,"sourceCodeEnd":172,"githubUrl":"https://github.com/calcom/cal.diy/blob/176037d0afbe572f870a3c702985e7cd83fe6c0c/apps/api/v2/src/modules/conferencing/controllers/conferencing.controller.ts#L136-L172","documentation":"Thrown by ConferencingController.save when the OAuth provider redirected back with an `error` query param, indicating the user denied consent or the provider encountered an error during authorization. The thrown message is the raw error_description query value from the provider (e.g. 'The user denied access' from Zoom). Returns HTTP 400. Note: the outer try/catch in save swallows this and redirects to onErrorReturnTo, so end users see a redirect rather than the 400.","triggerScenarios":"User clicked 'Cancel'/'Deny' on the Zoom or Microsoft consent screen; the provider rate-limited the authorization; redirect_uri mismatch caused the provider to return an error redirect; app not approved in a locked-down Workspace.","commonSituations":"User cancelled consent; Zoom app is still in 'development' mode and the user is not on the allowlist; Microsoft admin consent required but not granted; clock skew or expired authorize request.","solutions":["Handle the redirect gracefully in the UI — the user lands on onErrorReturnTo with no credential; show a 'connection cancelled' message.","If the error is access_denied, prompt the user to retry and approve consent.","For redirect_uri mismatches, fix the URI in the provider app console to exactly match what Cal.com sends.","For Microsoft admin-consent errors, have a global admin pre-approve the app or grant admin consent."],"exampleFix":"// before: client ignores error_description\nif (response.status === 400) { alert('Connection failed'); }\n\n// after\nif (response.status === 400) {\n  const reason = response.data?.message || 'connection_failed';\n  showToast(reason.includes('denied') ? 'You cancelled Google Meet connection.' : `Provider error: ${reason}`);\n}","handlingStrategy":"fallback","validationCode":"// Pre-flight: validate the OAuth request is well-formed before redirecting.\nfunction buildAuthorizeUrl(app: string, scopes: string[], redirectUri: string, state: string): string {\n  if (!state) throw new Error('Cannot start OAuth flow without a valid session state.');\n  return `${authorizeBaseUrl}?response_type=code&client_id=${clientId}&scope=${scopes.join(' ')}&redirect_uri=${encodeURIComponent(redirectUri)}&state=${encodeURIComponent(state)}`;\n}","typeGuard":"function isProviderErrorQuery(q: URLSearchParams): q is URLSearchParams {\n  return q.has('error');\n}","tryCatchPattern":"// The controller already swallows this and redirects to onErrorReturnTo.\n// On the client, detect the redirect-without-credential outcome:\nif (!connectedApps.includes(app)) {\n  showToast('Connection was cancelled or failed. Please try again.');\n}","preventionTips":["Make onErrorReturnTo a route that shows a clear 'connection failed/cancelled' UI.","For access_denied specifically, show a retry CTA; for other errors, log the provider reason.","Verify the OAuth client is out of development mode and the user is allowlisted.","Confirm admin consent is granted for Microsoft enterprise apps."],"tags":["oauth","conferencing","zoom","office365","nestjs","callback","consent-denied"],"backgroundTag":null,"analyzedSha":"176037d0afbe572f870a3c702985e7cd83fe6c0c","analyzedAt":"2026-08-12T19:12:41.464Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}