{"record":{"id":"e4d5e51741137146","repo":"RocketChat/Rocket.Chat","slug":"invalid-api-parameter-provided-it-must-be-a-valid","errorCode":null,"errorMessage":"Invalid Api parameter provided, it must be a valid IApi object.","messagePattern":"Invalid Api parameter provided, it must be a valid IApi object\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"apps/meteor/app/apps/server/bridges/api.ts","lineNumber":92,"sourceCode":"\t\t\trouter[method](\n\t\t\t\troutePath,\n\t\t\t\tauthenticationMiddleware({ rejectUnauthorized: !!endpoint.authRequired }),\n\t\t\t\tMeteor.bindEnvironment(this._appApiExecutor(endpoint, appId)),\n\t\t\t);\n\t\t}\n\t}\n\n\tpublic async unregisterApis(appId: string): Promise<void> {\n\t\tthis.orch.debugLog(`The App ${appId} is unregistering all apis`);\n\n\t\tif (this.appRouters.get(appId)) {\n\t\t\tthis.appRouters.delete(appId);\n\t\t}\n\t}\n\n\tprivate _verifyApi(api: IApi, endpoint: IApiEndpoint): void {\n\t\tif (typeof api !== 'object') {\n\t\t\tthrow new Error('Invalid Api parameter provided, it must be a valid IApi object.');\n\t\t}\n\n\t\tif (typeof endpoint.path !== 'string') {\n\t\t\tthrow new Error('Invalid Api parameter provided, it must be a valid IApi object.');\n\t\t}\n\t}\n\n\tprivate _appApiExecutor(endpoint: IApiEndpoint, appId: string): RequestHandler {\n\t\treturn (req: IRequestWithPrivateHash, res: Response): void => {\n\t\t\tconst request: IApiRequest = {\n\t\t\t\tmethod: req.method.toLowerCase() as RequestMethod,\n\t\t\t\theaders: req.headers as { [key: string]: string },\n\t\t\t\tquery: (req.query as { [key: string]: string }) || {},\n\t\t\t\tparams: req.params || {},\n\t\t\t\tcontent: req.body,\n\t\t\t\tprivateHash: req._privateHash,\n\t\t\t\tuser: req.user && this.orch.getConverters()?.get('users')?.convertToApp(req.user),\n\t\t\t};","sourceCodeStart":74,"sourceCodeEnd":110,"githubUrl":"https://github.com/RocketChat/Rocket.Chat/blob/b2c16d5842cbe6b69b59bdf6fc5e5f1afcd1f0b0/apps/meteor/app/apps/server/bridges/api.ts#L74-L110","documentation":"Rocket.Chat's POST /oauth/authorize handler (the OAuth2 consent step) responds 401 with the standard RFC 6749 error 'access_denied' when the submitted form does not contain allow='yes'. It means the resource owner (the logged-in Rocket.Chat user) declined to authorize the client application, or the request simply lacks the allow field. This is the consent screen's 'Cancel/Deny' outcome, also hit by clients that post to the endpoint programmatically without replicating the form.","triggerScenarios":"POST /oauth/authorize with req.body.allow !== 'yes': the user clicked Deny/Cancel on the authorization dialog; the consent form was submitted without the hidden allow='yes' field; a curl or script replay of the flow omitted allow.","commonSituations":"Third-party apps integrating Rocket.Chat OAuth2; headless clients that skip or automate the consent screen; customized/AJAX-submitted consent forms that drop the allow field; manual curl testing of the authorization step.","solutions":["If the user intentionally denied, treat access_denied as terminal: show a message or redirect back with error=access_denied instead of retrying","When the user approves, make sure the consent form POSTs allow='yes' together with access_token (or legacy token), client_id, redirect_uri and the other OAuth params","For programmatic calls, include allow=yes in the form-urlencoded body","Verify no middleware/proxy strips form fields from the POST body"],"exampleFix":"// before\nawait fetch('/oauth/authorize', { method: 'POST', body: new URLSearchParams({ client_id, redirect_uri, response_type: 'code' }) });\n// after\nawait fetch('/oauth/authorize', { method: 'POST', body: new URLSearchParams({ client_id, redirect_uri, response_type: 'code', allow: 'yes', access_token }) });","handlingStrategy":"validation","validationCode":"const params = new URLSearchParams(formBody);\nif (params.get('allow') !== 'yes') { /* show the consent UI; do not POST /oauth/authorize yet */ }","typeGuard":null,"tryCatchPattern":"After the POST, branch on the parsed body: if (res.status === 401 && body.error === 'access_denied') treat it as a user decision — stop the flow and inform/re-prompt; do not auto-retry the same denied request.","preventionTips":["Always submit allow=yes together with the OAuth params when the user approves","Handle the access_denied error branch in the OAuth client redirect handler","Never cache or replay consent submissions across user sessions"],"tags":["oauth2","authorization","http-401","access-denied","consent"],"backgroundTag":"oauth2-access-denied","analyzedSha":"b2c16d5842cbe6b69b59bdf6fc5e5f1afcd1f0b0","analyzedAt":"2026-08-18T15:26:39.429Z","contentChangedAt":"2026-08-18T15:26:39.429Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}