Organizations, teams & billing
UniAuth models teams and enterprises as a single organization entity. This guide covers how an organization is created, how its tier decides which features are unlocked, the seat cap, per-organization billing, and what happens when a subscription lapses.
One entity, two tiers
There is exactly one organizations entity. Its tier classifies it as a Team or an Enterprise:
- Team members, roles and invitations, native groups, OAuth clients, and an audit log.
- Enterprise everything in Team, plus inbound SSO (SAML SP + OIDC federation), SCIM 2.0 provisioning, and custom-domain verification.
The tier is derived from the organization's own per-org Stripe subscription — there is no separate plan flag to keep in sync.
Scope of tier gating: Tier gating applies only to the org-admin UI and the groups claim are tier-independent and unchanged. The OAuth/OIDC/SAML protocol endpoints and the /api/org/* routes — integrating an app never depends on an org's tier.
Creating a team (self-serve)
Any signed-in user can create a team with POST /api/user/organizations — no admin involvement required. New organizations start on the Team tier.
POST /api/user/organizations
Content-Type: application/json
Authorization: Bearer <user-session>
{
"name": "Acme Inc"
}
# Response — you become the org owner
{
"success": true,
"data": { "id": "org_...", "name": "Acme Inc", "tier": "team" }
}- You become the owner of the organization you create.
- Each user may own at most 10 organizations; creation is atomic.
- Joining organizations created by others is unlimited — the cap is on owned organizations only.
From the UI, use the “Create team” / “Manage” controls in your organizations in your account.
The org admin area
Owners and admins manage their organization from a delegated admin area at /org/[id]/admin/{users,groups,clients,domains,sso,billing,settings,policies,audit}. Each tab maps to org-scoped APIs.
users· members, roles, and invitationsgroups· native groups and membershipclients· OAuth clients owned by the organizationdomains· custom-domain verification (Enterprise)sso· inbound SAML and OIDC federation (Enterprise)billing· the per-org Stripe subscription and seatssettings· organization profile and preferencespolicies· access and security policiesaudit· the organization's audit events
Roles and isolation
Every /api/org/[id]/* route gates on verifyOrgRole(role) before doing any work. Three roles apply:
- owner · full control, including billing and deletion.
- admin · manage members, groups, clients, and enterprise settings.
- member · belongs to the organization with no admin rights.
Org routes always FORCE the org_id from the authenticated principal — never from the request body or path. A cross-org request returns 404, so one organization can never read or write another's data.
Team vs Enterprise — what's gated
Team covers everyday collaboration. Enterprise adds the identity-federation surface. The matrix below shows where the line falls:
| Capability | Team | Enterprise |
|---|---|---|
| Members, roles & invitations | ✓ | ✓ |
| Native groups | ✓ | ✓ |
| OAuth clients | ✓ | ✓ |
| Audit log | ✓ | ✓ |
| Inbound SSO (SAML SP + OIDC) | — | ✓ |
| SCIM 2.0 provisioning | — | ✓ |
| Custom-domain verification | — | ✓ |
Calling an Enterprise-only endpoint on a Team org returns HTTP 402 with { "code": "upgrade_required" } — upgrade the organization to unlock it.
# Calling an Enterprise-only endpoint on a Team org
POST /api/org/<orgId>/saml-idps
→ 402 Payment Required
{
"success": false,
"code": "upgrade_required"
}The 25-member cap
A Team-tier organization is capped at 25 members. The limit is enforced both when an invitation is sent and when it is accepted, so a pending invite can never push an org over the cap.
Exceeding the cap returns HTTP 402 with { "code": "member_limit_reached" }. Raise the limit by upgrading to Enterprise.
# 26th member, either at invite OR at accept
POST /api/org/<orgId>/invitations
→ 402 Payment Required
{
"success": false,
"code": "member_limit_reached"
}Per-org billing
Billing is scoped to the organization, not the user who created it.
- Each organization is its own Stripe customer.
- Subscriptions are billed per seat.
- One owner can hold separate subscriptions for multiple organizations, each invoiced independently.
Owners manage the subscription, seats, and invoices at /org/[id]/admin/billing.
What happens on downgrade
Enterprise features follow the subscription state, but a lapse never locks members out.
- Cancellation: a genuine end of subscription (
canceled/unpaid) disables the organization's SSO identity providers and revokes its SCIM tokens. - Past due: a Stripe
past_duestatus keeps the Enterprise tier during the dunning grace period — nothing is disabled yet. - Re-subscribing: restores the tier and automatically re-enables the identity providers that were auto-disabled on cancellation.
Members keep access. Losing the Enterprise tier only disables federation — members can always sign in by email or via Forgot Password.
Enterprise accounts
An Enterprise-tier organization can own other organizations — an organization-of-organizations, much like a GitHub Enterprise account sits above many orgs. The link is a single parent reference and nesting is one level deep.
- Create child orgs from the enterprise — owner-only, and always a brand-new org (existing organizations are never adopted):
POST /api/org/[id]/children - An enterprise owner or admin automatically administers every child org, so the same /org/[id]/admin tools work across all of them — without being added to each one.
- Child orgs inherit the enterprise's features (SSO, SCIM, verified domains) and unlimited seats; there is no separate subscription per child.
Apps can also request the org OIDC scope to receive the user's role within the requesting organization (id, slug, name, role), resolved per-client just like the groups claim.