n8n-io/n8n · error · ForbiddenError

Maximum number of users reached

Error message

Maximum number of users reached

What it means

POST /rest/forgot-password is rejected for a non-global-owner user when the license's user seat quota is exhausted (`license.isWithinUsersLimit()` returns false). The owner is exempt. Mirrors `RESPONSE_ERROR_MESSAGES.USERS_QUOTA_REACHED` returned as 403 Forbidden.

Source

Thrown at packages/cli/src/controllers/password-reset.controller.ts:98

				'Email sending must be set up in order to request a password reset email',
			);
		}

		try {
			const { email } = payload;

			// User should just be able to reset password if one is already present
			const user = await this.userRepository.findNonShellUser(email);
			if (!user) {
				this.logger.debug('No user found in the system');
				return;
			}

			if (user.role.slug !== GLOBAL_OWNER_ROLE.slug && !this.license.isWithinUsersLimit()) {
				this.logger.debug(
					'Request to send password reset email failed because the user limit was reached',
				);
				throw new ForbiddenError(RESPONSE_ERROR_MESSAGES.USERS_QUOTA_REACHED);
			}

			if (
				(isSamlCurrentAuthenticationMethod() || isOidcCurrentAuthenticationMethod()) &&
				!(hasGlobalScope(user, 'user:resetPassword') || user.settings?.allowSSOManualLogin === true)
			) {
				const currentAuthenticationMethod = isSamlCurrentAuthenticationMethod() ? 'SAML' : 'OIDC';
				this.logger.debug(
					`Request to send password reset email failed because login is handled by ${currentAuthenticationMethod}`,
				);
				throw new ForbiddenError(
					`Login is handled by ${currentAuthenticationMethod}. Please contact your Identity Provider to reset your password.`,
				);
			}

			const ldapIdentity = user.authIdentities?.find((i) => i.providerType === 'ldap');
			if (!user.password || (ldapIdentity && user.disabled)) {
				this.logger.debug(

View on GitHub (pinned to 5ac6606e81)

Solutions

  1. Upgrade the license or deactivate inactive users to free a seat.
  2. Have the global owner perform the reset (owner is exempt from the check).
  3. Verify the license is active and the seat count in Settings > Licensing.
Defensive patterns

Strategy: validation

Validate before calling

// For non-owner users, check license seat availability before submitting.
if (me.role !== 'owner' && !license.withinUsersLimit) {
  throw new Error('User seat quota reached — ask the owner to reset your password.');
}

Try / catch

try {
  await restApi.post('/forgot-password', { email });
} catch (e) {
  if (e.response?.status === 403 && /maximum number of users/i.test(e.response.data.message)) {
    routeToOwnerReset('Seat quota reached — contact the instance owner.');
  } else throw e;
}

Prevention

When it happens

Trigger: forgotPassword for a user whose role.slug !== GLOBAL_OWNER_ROLE.slug while `license.isWithinUsersLimit()` is false.

Common situations: License seats filled; trial or plan limit reached; license expired and fell back to a tiny seat count.

Related errors


AI-assisted analysis of n8n-io/n8n@5ac6606e81 (2026-08-12). Data as JSON: /api/errors/3aa023438561074a. Report an issue: GitHub.