{"record":{"id":"02d523dda7ade8d2","repo":"calcom/cal.diy","slug":"responsebody-error","errorCode":null,"errorMessage":"{responseBody.error}","messagePattern":"\\{responseBody\\.error\\}","errorType":"http","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"apps/api/v2/src/modules/conferencing/services/office365-video.service.ts","lineNumber":92,"sourceCode":"      grant_type: \"authorization_code\",\n      code,\n      scope: this.scopes.join(\" \"),\n      redirect_uri: this.redirectUri,\n      client_secret,\n    });\n\n    const response = await fetch(\"https://login.microsoftonline.com/common/oauth2/v2.0/token\", {\n      method: \"POST\",\n      headers: {\n        \"Content-Type\": \"application/x-www-form-urlencoded;charset=UTF-8\",\n      },\n      body,\n    });\n\n    const responseBody = await response.json();\n\n    if (!response.ok) {\n      throw new BadRequestException(responseBody.error);\n    }\n\n    const whoami = await fetch(\"https://graph.microsoft.com/v1.0/me\", {\n      headers: { Authorization: `Bearer ${responseBody.access_token}` },\n    });\n\n    const graphUser = await whoami.json();\n\n    // In some cases, graphUser.mail is null. Then graphUser.userPrincipalName most likely contains the email address.\n    responseBody.email = graphUser.mail ?? graphUser.userPrincipalName;\n    responseBody.expiry_date = Math.round(+new Date() / 1000 + responseBody.expires_in); // set expiry date in seconds\n    delete responseBody.expires_in;\n\n    const existingCredentialOffice365Video = teamId\n      ? await this.credentialsRepository.findAllCredentialsByTypeAndTeamId(OFFICE_365_VIDEO_TYPE, teamId)\n      : await this.credentialsRepository.findAllCredentialsByTypeAndUserId(OFFICE_365_VIDEO_TYPE, userId);\n\n    const credentialIdsToDelete = existingCredentialOffice365Video.map((item) => item.id);","sourceCodeStart":74,"sourceCodeEnd":110,"githubUrl":"https://github.com/calcom/cal.diy/blob/176037d0afbe572f870a3c702985e7cd83fe6c0c/apps/api/v2/src/modules/conferencing/services/office365-video.service.ts#L74-L110","documentation":"Thrown after POSTing the authorization code to https://login.microsoftonline.com/common/oauth2/v2.0/token when response.ok is false. The thrown value is responseBody.error — the raw error object/string Azure returns (e.g. invalid_grant, invalid_client). It is passed verbatim to BadRequestException (HTTP 400), so the surfaced message is whatever Microsoft sent, not a fixed string.","triggerScenarios":"connectOffice365App with an expired/reused authorization code, wrong redirect_uri, mismatched client_id/secret, or a user who denied consent. The token endpoint returns non-2xx and responseBody.error is forwarded.","commonSituations":"User pastes the OAuth URL or retries with an already-consumed code; redirect_uri in the request differs from the one registered in Azure; client_secret rotated in Azure but not in the app keys; clock skew or expired code window (codes are short-lived).","solutions":["Inspect the forwarded error string: 'invalid_grant' means reuse/expiry of code (re-auth), 'invalid_client' means bad secret/id.","Regenerate the auth URL and complete the flow in one shot without reusing the code.","Confirm the redirect_uri in the token request matches the Azure app registration exactly (scheme, host, path, trailing slash)."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  await office365Service.connectOffice365App(state, code, userId, teamId);\n} catch (e) {\n  if (e instanceof BadRequestException && e.message) {\n    const azureError = String(e.message);\n    if (azureError.includes('invalid_grant')) {\n      // re-initiate OAuth flow with a fresh code\n    } else if (azureError.includes('invalid_client')) {\n      // alert ops: client_id/secret mismatch\n    }\n  }\n  throw e;\n}","preventionTips":["Use each authorization code exactly once and immediately after issuance.","Register the exact redirect_uri in Azure and reuse it verbatim.","Rotate client secrets on both Azure and the app in lockstep."],"tags":["conferencing","office365","oauth","azure","external-api","token-exchange"],"backgroundTag":null,"analyzedSha":"176037d0afbe572f870a3c702985e7cd83fe6c0c","analyzedAt":"2026-08-12T19:12:41.464Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}