{"record":{"id":"a6bc660940e6299e","repo":"toeverything/AFFiNE","slug":"member-quota-exceeded","errorCode":"member_quota_exceeded","errorMessage":"You have exceeded your workspace member quota.","messagePattern":"You have exceeded your workspace member quota\\.","errorType":"exception","errorClass":"MemberQuotaExceeded","httpStatus":402,"severity":"error","filePath":"packages/backend/server/src/core/quota/service.ts","lineNumber":118,"sourceCode":"      await this.quotaState.reconcileWorkspaceQuotaState(workspaceId);\n\n    return {\n      memberCount: state.memberCount,\n      memberLimit: state.seatLimit,\n    };\n  }\n\n  async tryCheckSeat(workspaceId: string, excludeSelf = false) {\n    const quota = await this.getWorkspaceSeatQuota(workspaceId);\n\n    return quota.memberCount - (excludeSelf ? 1 : 0) < quota.memberLimit;\n  }\n\n  async checkSeat(workspaceId: string, excludeSelf = false) {\n    const available = await this.tryCheckSeat(workspaceId, excludeSelf);\n\n    if (!available) {\n      throw new MemberQuotaExceeded();\n    }\n  }\n\n  formatWorkspaceQuota(\n    quota: Omit<WorkspaceQuotaType, 'humanReadable'>\n  ): WorkspaceQuotaHumanReadableType {\n    return {\n      name: quota.name,\n      blobLimit: formatSize(quota.blobLimit),\n      storageQuota: formatSize(quota.storageQuota),\n      storageQuotaUsed: formatSize(quota.usedStorageQuota),\n      historyPeriod: formatDate(quota.historyPeriod),\n      memberLimit: quota.memberLimit.toString(),\n      memberCount: quota.memberCount.toString(),\n      overcapacityMemberCount: quota.overcapacityMemberCount.toString(),\n    };\n  }\n","sourceCodeStart":100,"sourceCodeEnd":136,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/b4c8548c09da21b2898443559a5b846f0ccf5dd8/packages/backend/server/src/core/quota/service.ts#L100-L136","documentation":"Thrown by QuotaService.checkSeat (packages/backend/server/src/core/quota/service.ts:118). tryCheckSeat compares the workspace memberCount (minus 1 when excludeSelf) against the plan's memberLimit; when there is no free seat, checkSeat throws MemberQuotaExceeded, guarding invite and invitation-accept flows.","triggerScenarios":"Posting an invite, or accepting a pending invitation, when the workspace already has memberLimit members and excludeSelf does not apply.","commonSituations":"Free/small plan ceilings, a plan downgraded without pruning members, or bulk CSV invites exceeding remaining seats.","solutions":["Free a seat by removing or deactivating a workspace member.","Upgrade the workspace plan so memberLimit rises.","Call tryCheckSeat(workspaceId) before the invite and surface an upgrade prompt when it returns false."],"exampleFix":"// before\nawait inviteService.invite(workspaceId, email);\n\n// after\nif (!(await quota.tryCheckSeat(workspaceId))) {\n  return showUpgradeRequired('member_quota_exceeded');\n}\nawait inviteService.invite(workspaceId, email);","handlingStrategy":"validation","validationCode":"if (!(await quota.tryCheckSeat(workspaceId))) {\n  return { ok: false, reason: 'member_quota_exceeded' };\n}","typeGuard":"const isMemberQuotaExceeded = (e: unknown): e is MemberQuotaExceeded =>\n  e instanceof MemberQuotaExceeded;","tryCatchPattern":"try {\n  await invites.accept(inviteId);\n} catch (e) {\n  if (e instanceof MemberQuotaExceeded) return res.status(402).json({ code: e.code });\n  throw e;\n}","preventionTips":["Run tryCheckSeat before every invite/accept mutation.","Show remaining seats in the member management UI so over-invites are visible early."],"tags":["quota","billing","members","invite"],"backgroundTag":"quota-limit-exceeded","analyzedSha":"b4c8548c09da21b2898443559a5b846f0ccf5dd8","analyzedAt":"2026-08-18T21:16:52.546Z","contentChangedAt":"2026-08-18T21:16:52.546Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}