RocketChat/Rocket.Chat · warning · Error

Invalid Api parameter provided, it must be a valid IApi…

Error message

Invalid Api parameter provided, it must be a valid IApi object.

What it means

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.

Solutions

  1. If the user intentionally denied, treat access_denied as terminal: show a message or redirect back with error=access_denied instead of retrying
  2. 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
  3. For programmatic calls, include allow=yes in the form-urlencoded body
  4. Verify no middleware/proxy strips form fields from the POST body

Example fix

// before
await fetch('/oauth/authorize', { method: 'POST', body: new URLSearchParams({ client_id, redirect_uri, response_type: 'code' }) });
// after
await fetch('/oauth/authorize', { method: 'POST', body: new URLSearchParams({ client_id, redirect_uri, response_type: 'code', allow: 'yes', access_token }) });
Defensive patterns

Strategy: validation

Validate before calling

const params = new URLSearchParams(formBody);
if (params.get('allow') !== 'yes') { /* show the consent UI; do not POST /oauth/authorize yet */ }

Try / catch

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.

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of RocketChat/Rocket.Chat@b2c16d5842 (2026-08-18). Data as JSON: /api/errors/e4d5e51741137146. Report an issue: GitHub.

Appendix: source

Thrown at apps/meteor/app/apps/server/bridges/api.ts:92

			router[method](
				routePath,
				authenticationMiddleware({ rejectUnauthorized: !!endpoint.authRequired }),
				Meteor.bindEnvironment(this._appApiExecutor(endpoint, appId)),
			);
		}
	}

	public async unregisterApis(appId: string): Promise<void> {
		this.orch.debugLog(`The App ${appId} is unregistering all apis`);

		if (this.appRouters.get(appId)) {
			this.appRouters.delete(appId);
		}
	}

	private _verifyApi(api: IApi, endpoint: IApiEndpoint): void {
		if (typeof api !== 'object') {
			throw new Error('Invalid Api parameter provided, it must be a valid IApi object.');
		}

		if (typeof endpoint.path !== 'string') {
			throw new Error('Invalid Api parameter provided, it must be a valid IApi object.');
		}
	}

	private _appApiExecutor(endpoint: IApiEndpoint, appId: string): RequestHandler {
		return (req: IRequestWithPrivateHash, res: Response): void => {
			const request: IApiRequest = {
				method: req.method.toLowerCase() as RequestMethod,
				headers: req.headers as { [key: string]: string },
				query: (req.query as { [key: string]: string }) || {},
				params: req.params || {},
				content: req.body,
				privateHash: req._privateHash,
				user: req.user && this.orch.getConverters()?.get('users')?.convertToApp(req.user),
			};

View on GitHub (pinned to b2c16d5842)