{"record":{"id":"907e929ce543442f","repo":"toeverything/AFFiNE","slug":"no-more-seat","errorCode":"no_more_seat","errorMessage":"No more seat available in the Space ${spaceId}.","messagePattern":"No more seat available in the Space (.+?)\\.","errorType":"http","errorClass":"NoMoreSeat","httpStatus":400,"severity":"warning","filePath":"packages/backend/server/src/core/workspaces/resolvers/member.ts","lineNumber":334,"sourceCode":"              email: candidate.normalizedEmail,\n              registered: false,\n            });\n          }\n\n          const existingMember = await this.models.workspaceUser.get(\n            workspaceId,\n            target.id\n          );\n          if (existingMember) {\n            throw new AlreadyInSpace({ spaceId: workspaceId });\n          }\n\n          if (!isTeam) {\n            const needMoreSeat =\n              quota.memberCount + successfulCandidates.length + 1 >\n              quota.memberLimit;\n            if (needMoreSeat) {\n              throw new NoMoreSeat({ spaceId: workspaceId });\n            }\n          }\n\n          // no need to check quota, directly go allocating seat path\n          if (isTeam) {\n            const role = await this.models.workspaceUser.set(\n              workspaceId,\n              target.id,\n              WorkspaceRole.Collaborator,\n              {\n                status: WorkspaceMemberStatus.AllocatingSeat,\n                source: WorkspaceMemberSource.Email,\n                inviterId: me.id,\n              }\n            );\n            await this.allocateAvailableTeamSeats(\n              workspaceId,\n              quota.memberLimit","sourceCodeStart":316,"sourceCodeEnd":352,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/26c515e050211269e911f7d9cfe162a26c83ed98/packages/backend/server/src/core/workspaces/resolvers/member.ts#L316-L352","documentation":"Thrown inside inviteMembers (non-team workspace) when admitting one more seat would exceed the workspace seat quota: quota.memberCount + already-successful-this-batch + 1 > quota.memberLimit. Team workspaces skip this (they go through allocating-seat path); non-team workspaces enforce the cap per candidate. Reported as no_more_seat (bad_request, HTTP 400).","triggerScenarios":"Inviting to a non-team workspace when the number of members plus pending invites in this batch would exceed memberLimit.","commonSituations":"Plan limit reached and the user tries to invite more; a batch where earlier candidates consumed the last seats; stale quota shown in the UI that no longer reflects reality.","solutions":["Upgrade the workspace plan / increase memberLimit to fit the new seats.","Reduce the batch size to the remaining available seats (memberLimit - memberCount).","Remove unused/pending members to free seats before inviting.","Refresh quota.getWorkspaceSeatQuota in the UI before enabling invite."],"exampleFix":"// before\ninviteMembers(workspaceId, manyEmails); // exceeds seats\n// after\nconst free = quota.memberLimit - quota.memberCount;\ninviteMembers(workspaceId, manyEmails.slice(0, Math.max(0, free)));","handlingStrategy":"validation","validationCode":"const quota = await getWorkspaceSeatQuota(workspaceId);\nconst free = Math.max(0, quota.memberLimit - quota.memberCount);\nconst toInvite = emails.slice(0, free);\nif (toInvite.length === 0) throw new Error('No seats available; upgrade plan to invite more');\nawait inviteMembers(workspaceId, toInvite);","typeGuard":"function hasFreeSeats(quota: { memberCount: number; memberLimit: number }): boolean {\n  return quota.memberCount < quota.memberLimit;\n}","tryCatchPattern":null,"preventionTips":["Refresh seat quota in the UI before enabling invite.","Cap the batch to (memberLimit - memberCount).","Prompt plan upgrade when free seats run out."],"tags":["graphql","quota","invite","workspace","billing"],"backgroundTag":null,"analyzedSha":"26c515e050211269e911f7d9cfe162a26c83ed98","analyzedAt":"2026-08-12T13:15:16.447Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}