{"record":{"id":"6e55a1ad813e591d","repo":"n8n-io/n8n","slug":"maximum-number-of-users-reached-6e55a1","errorCode":null,"errorMessage":"Maximum number of users reached","messagePattern":"Maximum number of users reached","errorType":"exception","errorClass":"ForbiddenError","httpStatus":403,"severity":"error","filePath":"packages/cli/src/controllers/auth.controller.ts","lineNumber":235,"sourceCode":"\t\t}\n\n\t\tif (!payload.token) {\n\t\t\tthis.logger.debug('Request to resolve signup token failed because token is missing');\n\t\t\tthrow new BadRequestError('Token is required');\n\t\t}\n\n\t\tconst { inviterId, inviteeId } = await this.userService.getInvitationIdsFromPayload(\n\t\t\tpayload.token,\n\t\t);\n\n\t\tconst isWithinUsersLimit = this.license.isWithinUsersLimit();\n\n\t\tif (!isWithinUsersLimit) {\n\t\t\tthis.logger.debug('Request to resolve signup token failed because of users quota reached', {\n\t\t\t\tinviterId,\n\t\t\t\tinviteeId,\n\t\t\t});\n\t\t\tthrow new ForbiddenError(RESPONSE_ERROR_MESSAGES.USERS_QUOTA_REACHED);\n\t\t}\n\n\t\tconst users = await this.userRepository.findManyByIds([inviterId, inviteeId], {\n\t\t\tincludeRole: true,\n\t\t});\n\n\t\tif (users.length !== 2) {\n\t\t\tthis.logger.debug(\n\t\t\t\t'Request to resolve signup token failed because the ID of the inviter and/or the ID of the invitee were not found in database',\n\t\t\t\t{ inviterId, inviteeId },\n\t\t\t);\n\t\t\tthrow new BadRequestError('Invalid invite URL');\n\t\t}\n\n\t\tconst invitee = users.find((user) => user.id === inviteeId);\n\t\tif (!invitee || invitee.password) {\n\t\t\tthis.logger.error('Invalid invite URL - invitee already setup', {\n\t\t\t\tinviterId,","sourceCodeStart":217,"sourceCodeEnd":253,"githubUrl":"https://github.com/n8n-io/n8n/blob/5ac6606e81f67bb9534255570cd4e86fd8101eee/packages/cli/src/controllers/auth.controller.ts#L217-L253","documentation":"ForbiddenError (HTTP 403) thrown from auth.controller.ts:235 when resolving a signup invitation token while the instance license reports the active user count is already at the entitlement ceiling. `RESPONSE_ERROR_MESSAGES.USERS_QUOTA_REACHED` is the literal 'Maximum number of users reached'. The license check (`this.license.isWithinUsersLimit()`) runs on every invitation-link resolve, so even an already-issued invite becomes unresolvable once a later signup filled the last seat.","triggerScenarios":"A GET/POST to the signup-invite resolve endpoint with a valid token whose `inviterId`/`inviteeId` decode successfully, but the license's current seat usage equals the entitlement. Reproducible by inviting user N+1 after the Nth user already claimed a seat, or by an admin deleting and re-issuing an invite after headcount grew.","commonSituations":"Self-hosted instances on a fixed-seat Enterprise/Pro plan that onboarded up to the limit; trial licenses expiring mid-onboarding; stale invite links mailed before a hiring freeze hit. Cloud-managed instances rarely hit this because seat enforcement is upstream.","solutions":["Raise the seat entitlement: upgrade the license / add seats in the billing console, then reactivate via `n8n license:info` and re-resolve the same invite URL.","Free a seat: deactivate or delete an unused user in Settings > Users, confirm the count drops, then re-open the invite link.","Re-issue the invitation after the seat change so the resolve runs against an in-limit state.","If unexpected, audit actual user count (`SELECT COUNT(*) FROM user`) against the license entitlement — orphaned/inactive users still consume seats."],"exampleFix":"// before — invite resolves against a saturated instance\nGET /resolve-signup-token?token=...\n// 403 Maximum number of users reached\n\n// after — free a seat, then re-resolve\nDELETE /users/<inactive-user-id>   // admin frees the seat\nGET /resolve-signup-token?token=... // succeeds","handlingStrategy":"validation","validationCode":"// Before mailing/reusing an invite, check license headroom\nimport { Service } from '@n8n/di';\n\n// in an admin client\nasync function canInvite(license: LicenseService, userService: UserService) {\n  const activeUsers = await userService.count(); // your repo method\n  return license.isWithinUsersLimit() && activeUsers < license.userLimit;\n}\n\n// only re-share the invite URL when canInvite(...) returns true","typeGuard":null,"tryCatchPattern":"// fetch invite resolve\ntry {\n  await fetch('/resolve-signup-token?token=' + tok);\n} catch (e) {\n  if (e.status === 403 && /quota|maximum/i.test(e.message)) {\n    // surface 'request more seats' to the admin instead of retrying\n  }\n}","preventionTips":["Monitor active-user count vs license entitlement with an alert before the limit is hit.","Revoke pending invites when headcount drops, so stale links don't resolve against a saturated license.","Provision license seat bumps before mailing new invites."],"tags":["auth","license","invitation","users-quota","forbidden"],"backgroundTag":null,"analyzedSha":"5ac6606e81f67bb9534255570cd4e86fd8101eee","analyzedAt":"2026-08-12T05:26:35.080Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}