Skip to main content

Team seat quota and invite enforcement

Every organization in Orbit carries a numeric seat quota — the maximum number of people who can belong to the workspace at once, counting both live members and pending invites. The invite flow enforces that quota on every invitation, so when your invite is refused with a 422 you know exactly which counter tripped, and when you plan onboarding you know exactly which seats you still hold. This page governs how many members may join. For what each member may do, see Roles, teams, and permissions. For how the organization itself sits in the tenant hierarchy, see The subaccount organization model.

Section 1 - The organization’s seat quota

The quota is a single integer on the organization record. A brand-new workspace starts with 2 seats — the value mirrored in the plan catalog it is onboarded under, so what the plan page advertises as limits.team_members is what the invite flow enforces. From then on the quota is the org’s own setting, not a plan-managed constant. Two counters run against it:
  • Live members — every active member record. Soft-deleted members no longer occupy a seat.
  • Pending invites — every outstanding invitation. A pending invite reserves the not-yet-seated spot, so the same seat cannot be promised to two people while an invite is waiting on acceptance.
The quota is a seat count, orthogonal to role-based access control. It decides only whether one more member may join; it says nothing about which role the new member holds or which routes that role reaches. RBAC gates capabilities; the seat quota gates headcount.

Section 2 - Where the value comes from

Three surfaces can set the quota, in increasing order of privilege: A null value never survives into a gate decision: any read of the quota defaults back to 2, so an organization row missing the value behaves exactly like a fresh workspace.

Section 3 - Enforcement at invite time

The seat check runs on every invite-create request, before the invitation record is written, the dedup token is minted, or the email is sent. The formula is one comparison:
When it trips, the whole batch item is declined with a 422 and code TEAM_MEMBER_CAP_REACHED, with a message telling you how many seats your workspace includes and how to free one. The check degrades open on an unreadable database read — the quota gates invites, not billing, so a missed gate is a tolerable over-serve rather than a blocked team flow. To free a seat, either:
  • Remove a live member — the seat is available again immediately.
  • Cancel a pending invite — the reserved spot returns to the pool before the invitee ever accepts.
Raising the quota is a parent-scope or platform-staff action per Section 2; a member of the organization cannot raise their own ceiling.

Section 4 - How this differs from role-based access

The seat quota and the role hierarchy answer different questions and are enforced at different layers:
  • The seat quota counts heads. It is checked once, at invite time, against a per-org integer. The roles and permissions model never consults it — a route gate admits or denies on role alone, with no headcount arithmetic.
  • Roles decide what a member can do. They are checked on every request, per capability. The seat check does not constrain which role an invite assigns — it only decides whether the invite may exist.
In the subaccount model, each organization in the tree carries its own quota. Inviting into a child organization consumes the child’s seats; the parent’s quota is untouched.

Edge cases

Pending invites issued before a quota change

The cap is checked once, at invite-create time. Acceptance does not re-evaluate it — the acceptance flow validates only the token, the expiry, and that the accepting user is not already seated in another workspace; it never re-reads max_team_members. So a quota change after an invite is issued does not claw back outstanding invites:
  • Raising the cap (5 → 10): invites already sent stay valid; the new cap is what the next invite-create call measures against.
  • Lowering the cap (10 → 5): invites issued while the cap was 10 are not re-checked on acceptance — an invite legal when sent is honoured when accepted, even if it pushes the live count above the new, lower cap. The lowered cap applies to the next invite you create.
To stop an outstanding invite from counting after you lower the cap, cancel it from the pending-invites list — the reserved seat returns to the pool immediately.

Whether pending invites expire

An invite expires only if the inviter passed an explicit expires_at when creating it. That field is optional; an invite created without it never expires — there is no default time-to-live applied server-side. (The invitation email’s “expires in 7 days” line is display copy shown when no expiry was set, not a server-enforced deadline.) An invite that does carry an expires_at is rejected on acceptance once it is in the past, with a 410 INVITE_EXPIRED. An expired-but-uncancelled invite still counts against the cap until it is removed from the pending list — cancel it to free the seat.

What “soft-deleted” means for the seat count

A member occupies a seat only while their account still belongs to your workspace and has not been removed — the exact predicate the live-member count evaluates. A member becomes soft-deleted (and stops counting) the moment their account is removed from the workspace, whether through the team settings page or the account-deletion flow; the seat is then free. A member who is suspended from signing in but still listed in your workspace is not soft-deleted and still occupies a seat. Suspension is an auth state, not a seat state; the quota counts membership, not active sessions. Remove the member to free the seat.

Subaccounts provisioned without an explicit cap

When a parent creates a subaccount and omits max_team_members, the new subaccount does not inherit the parent’s cap — it gets the plan catalog’s default of 2 seats, the same value a brand-new self-serve workspace starts with. The parent’s quota is its own; nothing flows down to a child that did not ask for an explicit number. Pass max_team_members (1–500) in the create-subaccount body to set a specific cap. A parent that needs more than 500 seats on a child, or more than 10,000, uses the platform-staff admin operation from Section 2 — the subaccount provisioning range tops out at 500.

Cross-references