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
- Upgrade the license or deactivate inactive users to free a seat.
- Have the global owner perform the reset (owner is exempt from the check).
- 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
- Surface remaining seat count in the admin panel.
- For quota-blocked resets, instruct the user to contact the global owner (who is exempt).
- Monitor seat usage and prune inactive users before the quota is hit.
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
- Maximum number of users reached
- Maximum number of users reached
- forgotPassword.ldapUserPasswordResetUnavailable
- Attempted to create a new project but quota is already exhau
- Rate limit exceeded. Please wait a moment and try again.
AI-assisted analysis of n8n-io/n8n@5ac6606e81 (2026-08-12).
Data as JSON: /api/errors/3aa023438561074a.
Report an issue: GitHub.