Skip to content

Commit 2af6bb2

Browse files
committed
update docs
1 parent 230dc0a commit 2af6bb2

12 files changed

Lines changed: 216 additions & 40 deletions

File tree

apps/docs/content/docs/en/execution/costs.mdx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,17 @@ By default, your usage is capped at the credits included in your plan. To allow
308308

309309
## Plan Limits
310310

311+
### Workspaces
312+
313+
| Plan | Personal Workspaces | Shared (Organization) Workspaces |
314+
|------|---------------------|----------------------------------|
315+
| **Free** | 1 ||
316+
| **Pro** | Up to 3 ||
317+
| **Max** | Up to 10 ||
318+
| **Team / Enterprise** | Unlimited | Unlimited |
319+
320+
Team and Enterprise plans unlock shared workspaces that belong to your organization. Members invited to a shared workspace automatically join the organization and count toward your seat total. When a Team or Enterprise subscription is cancelled or downgraded, existing shared workspaces remain accessible to current members but new invites are disabled until the organization is upgraded again.
321+
311322
### Rate Limits
312323

313324
| Plan | Sync (req/min) | Async (req/min) |

apps/docs/content/docs/en/permissions/roles-and-permissions.mdx

Lines changed: 45 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,31 @@
22
title: "Roles and Permissions"
33
---
44

5+
import { Callout } from 'fumadocs-ui/components/callout'
56
import { Video } from '@/components/ui/video'
67

78
When you invite team members to your organization or workspace, you'll need to choose what level of access to give them. This guide explains what each permission level allows users to do, helping you understand team roles and what access each permission level provides.
89

10+
## Workspaces and Organizations
11+
12+
Sim has two kinds of workspaces:
13+
14+
- **Personal workspaces** live under your individual account. The number you can create depends on your plan.
15+
- **Shared (organization) workspaces** live under an organization and are available on Team and Enterprise plans. Any organization Owner or Admin can create them. Members invited to a shared workspace automatically join the organization and count toward your seat total.
16+
17+
### Workspace Limits by Plan
18+
19+
| Plan | Personal Workspaces | Shared Workspaces |
20+
|------|---------------------|-------------------|
21+
| **Free** | 1 ||
22+
| **Pro** | Up to 3 ||
23+
| **Max** | Up to 10 ||
24+
| **Team / Enterprise** | Unlimited | Unlimited (seat-gated invites) |
25+
26+
<Callout type="info">
27+
When a Team or Enterprise subscription is cancelled or downgraded, existing shared workspaces stay accessible to current members. New invitations are blocked until the organization is upgraded again.
28+
</Callout>
29+
930
## How to Invite Someone to a Workspace
1031

1132
<div className="mx-auto w-full overflow-hidden rounded-lg">
@@ -88,6 +109,10 @@ Every workspace has one **Owner** (the person who created it) plus any number of
88109
- Can do everything except delete the workspace or remove the owner
89110
- Can be removed from the workspace by the owner or other admins
90111

112+
<Callout type="info">
113+
For shared (organization) workspaces, the organization's Owner and Admins are treated as Admins of every workspace in the organization, even without an explicit per-workspace invite.
114+
</Callout>
115+
91116
---
92117

93118
## Common Scenarios
@@ -145,25 +170,38 @@ Periodically review who has access to what, especially when team members change
145170

146171
## Organization Roles
147172

148-
When inviting someone to your organization, you can assign one of two roles:
173+
An organization has three roles: **Owner**, **Admin**, and **Member**.
174+
175+
### Organization Owner
176+
**What they can do:**
177+
- Everything an Admin can do
178+
- Transfer organization ownership to another user
179+
- Only one Owner exists per organization
149180

150181
### Organization Admin
151182
**What they can do:**
152183
- Invite and remove team members from the organization
153-
- Create new workspaces
154-
- Manage billing and subscription settings
155-
- Access all workspaces within the organization
184+
- Create new shared workspaces under the organization
185+
- Manage billing, seat count, and subscription settings
186+
- Access all shared workspaces within the organization as a workspace Admin
187+
- Promote members to Admin or demote Admins to Member
188+
189+
<Callout type="info">
190+
Owners and Admins have the same day-to-day permissions. The only action reserved for the Owner is transferring ownership.
191+
</Callout>
156192

157193
### Organization Member
158194
**What they can do:**
159-
- Access workspaces they've been specifically invited to
195+
- Access shared workspaces they've been specifically invited to
160196
- View the list of organization members
161-
- Cannot invite new people or manage organization settings
197+
- Cannot invite new people, create shared workspaces, or manage organization settings
162198

163199
import { FAQ } from '@/components/ui/faq'
164200

165201
<FAQ items={[
166-
{ question: "What is the difference between organization roles and workspace permissions?", answer: "Organization roles (Admin or Member) control who can manage the organization itself, including inviting people, creating workspaces, and handling billing. Workspace permissions (Read, Write, Admin) control what a user can do within a specific workspace, such as viewing, editing, or managing workflows. A user needs both an organization role and a workspace permission to work within a workspace." },
202+
{ question: "What is the difference between organization roles and workspace permissions?", answer: "Organization roles (Owner, Admin, or Member) control who can manage the organization itself, including inviting people, creating shared workspaces, and handling billing. Workspace permissions (Read, Write, Admin) control what a user can do within a specific workspace, such as viewing, editing, or managing workflows. A user needs both an organization role and a workspace permission to work within a shared workspace." },
203+
{ question: "How many workspaces can I create?", answer: "Free users get 1 personal workspace. Pro users get up to 3 personal workspaces. Max users get up to 10 personal workspaces. Team and Enterprise plans support unlimited shared workspaces under the organization — new invites are gated by your seat count." },
204+
{ question: "What happens to my shared workspaces if I cancel or downgrade my Team plan?", answer: "Existing shared workspaces remain accessible to current members, but new invitations are disabled until you upgrade back to a Team or Enterprise plan. No workspaces or members are deleted — the organization is simply dormant until billing is re-enabled." },
167205
{ question: "Can I restrict which integrations or model providers a team member can use?", answer: "Yes. Organization admins can create permission groups with fine-grained controls, including restricting allowed integrations and allowed model providers to specific lists. You can also disable access to MCP tools, custom tools, skills, and various platform features like the knowledge base, API keys, or Copilot on a per-group basis." },
168206
{ question: "What happens when a personal environment variable has the same name as a workspace variable?", answer: "The personal environment variable takes priority. When a workflow runs, if both a personal and workspace variable share the same name, the personal value is used. This allows individual users to override shared workspace configuration when needed." },
169207
{ question: "Can an Admin remove the workspace owner?", answer: "No. The workspace owner cannot be removed from the workspace by anyone. Only the workspace owner can delete the workspace or transfer ownership to another user. Admins can do everything else, including inviting and removing other users and managing workspace settings." },

apps/sim/app/(landing)/components/pricing/pricing.tsx

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ const PRICING_TIERS: PricingTier[] = [
3939
'1,000 credits (trial)',
4040
'5GB file storage',
4141
'3 tables · 1,000 rows each',
42+
'1 personal workspace',
4243
'5 min execution limit',
4344
'7-day log retention',
4445
'CLI/SDK/MCP Access',
@@ -56,6 +57,7 @@ const PRICING_TIERS: PricingTier[] = [
5657
'6,000 credits/mo · +50/day',
5758
'50GB file storage',
5859
'25 tables · 5,000 rows each',
60+
'Up to 3 personal workspaces',
5961
'50 min execution · 150 runs/min',
6062
'Unlimited log retention',
6163
'CLI/SDK/MCP Access',
@@ -73,6 +75,7 @@ const PRICING_TIERS: PricingTier[] = [
7375
'25,000 credits/mo · +200/day',
7476
'500GB file storage',
7577
'25 tables · 5,000 rows each',
78+
'Up to 10 personal workspaces',
7679
'50 min execution · 300 runs/min',
7780
'Unlimited log retention',
7881
'CLI/SDK/MCP Access',
@@ -89,6 +92,7 @@ const PRICING_TIERS: PricingTier[] = [
8992
'Custom credits & infra limits',
9093
'Custom file storage',
9194
'10,000 tables · 1M rows each',
95+
'Unlimited shared workspaces',
9296
'Custom execution limits',
9397
'Unlimited log retention',
9498
'SSO & SCIM · SOC2',
@@ -264,10 +268,12 @@ export default function Pricing() {
264268
Pricing
265269
</h2>
266270
<p className='sr-only'>
267-
Sim pricing: Community plan is free with 1,000 credits and 5GB storage. Pro plan is $25
268-
per month with 6,000 credits and 50GB storage. Max plan is $100 per month with 25,000
269-
credits and 500GB storage. Enterprise pricing is custom with SSO, SCIM, SOC2 compliance,
270-
self-hosting, and dedicated support. All plans include CLI, SDK, and MCP access.
271+
Sim pricing: Community plan is free with 1,000 credits, 5GB storage, and 1 personal
272+
workspace. Pro plan is $25 per month with 6,000 credits, 50GB storage, and up to 3
273+
personal workspaces. Max plan is $100 per month with 25,000 credits, 500GB storage, and
274+
up to 10 personal workspaces. Enterprise pricing is custom with unlimited shared
275+
workspaces, SSO, SCIM, SOC2 compliance, self-hosting, and dedicated support. All plans
276+
include CLI, SDK, and MCP access.
271277
</p>
272278
</div>
273279

apps/sim/app/api/workspaces/invitations/route.test.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ vi.mock('@/lib/core/telemetry', () => ({
135135
},
136136
}))
137137

138+
import { UPGRADE_TO_INVITE_REASON } from '@/lib/workspaces/policy-constants'
138139
import { POST } from '@/app/api/workspaces/invitations/route'
139140

140141
describe('POST /api/workspaces/invitations', () => {
@@ -187,7 +188,7 @@ describe('POST /api/workspaces/invitations', () => {
187188
})
188189
mockGetWorkspaceInvitePolicy.mockResolvedValueOnce({
189190
allowed: false,
190-
reason: 'Upgrade to invite more members',
191+
reason: UPGRADE_TO_INVITE_REASON,
191192
requiresSeat: false,
192193
organizationId: null,
193194
upgradeRequired: true,
@@ -204,7 +205,7 @@ describe('POST /api/workspaces/invitations', () => {
204205
const data = await response.json()
205206

206207
expect(response.status).toBe(403)
207-
expect(data.error).toBe('Upgrade to invite more members')
208+
expect(data.error).toBe(UPGRADE_TO_INVITE_REASON)
208209
expect(data.upgradeRequired).toBe(true)
209210
})
210211

@@ -219,7 +220,7 @@ describe('POST /api/workspaces/invitations', () => {
219220
})
220221
mockGetWorkspaceInvitePolicy.mockResolvedValueOnce({
221222
allowed: false,
222-
reason: 'Upgrade to invite more members',
223+
reason: UPGRADE_TO_INVITE_REASON,
223224
requiresSeat: false,
224225
organizationId: null,
225226
upgradeRequired: true,

apps/sim/app/workspace/[workspaceId]/settings/components/subscription/plan-configs.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import {
22
Clock,
33
HardDrive,
44
HeadphonesIcon,
5+
LayoutGrid,
56
Server,
67
ShieldCheck,
78
Table2,
@@ -18,6 +19,7 @@ export const PRO_PLAN_FEATURES: PlanFeature[] = [
1819
{ icon: Timer, text: '50 min sync run limit' },
1920
{ icon: HardDrive, text: '50GB file storage' },
2021
{ icon: Table2, text: '25 tables · 5,000 rows each' },
22+
{ icon: LayoutGrid, text: 'Up to 3 personal workspaces' },
2123
]
2224

2325
export const MAX_PLAN_FEATURES: PlanFeature[] = [
@@ -26,19 +28,22 @@ export const MAX_PLAN_FEATURES: PlanFeature[] = [
2628
{ icon: Timer, text: '50 min sync run limit' },
2729
{ icon: HardDrive, text: '500GB file storage' },
2830
{ icon: Table2, text: '25 tables · 5,000 rows each' },
31+
{ icon: LayoutGrid, text: 'Up to 10 personal workspaces' },
2932
]
3033

3134
export const TEAM_INLINE_FEATURES: PlanFeature[] = [
3235
{ icon: Users, text: 'Shared credit pool' },
3336
{ icon: Zap, text: 'Max plan rate limits' },
3437
{ icon: HardDrive, text: 'Max plan file storage' },
3538
{ icon: Table2, text: '100 tables · 10,000 rows each' },
39+
{ icon: LayoutGrid, text: 'Unlimited shared workspaces' },
3640
{ icon: ShieldCheck, text: 'Access controls' },
3741
{ icon: SlackMonoIcon, text: 'Dedicated Slack channel' },
3842
]
3943

4044
export const ENTERPRISE_PLAN_FEATURES: PlanFeature[] = [
4145
{ icon: Zap, text: 'Custom infra limits' },
46+
{ icon: LayoutGrid, text: 'Unlimited shared workspaces' },
4247
{ icon: Server, text: 'SSO' },
4348
{ icon: ShieldCheck, text: 'SOC2' },
4449
{ icon: HardDrive, text: 'Self hosting' },

apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/components/invite-modal/invite-modal.tsx

Lines changed: 62 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
44
import { createLogger } from '@sim/logger'
5-
import { useParams } from 'next/navigation'
5+
import { useParams, useRouter } from 'next/navigation'
66
import {
77
Button,
88
type FileInputOptions,
@@ -27,6 +27,7 @@ import {
2727
useResendWorkspaceInvitation,
2828
useUpdateWorkspacePermissions,
2929
} from '@/hooks/queries/invitations'
30+
import { useOrganizationBilling } from '@/hooks/queries/organization'
3031
import type { PermissionType, UserPermissions } from './components/types'
3132

3233
const logger = createLogger('InviteModal')
@@ -36,14 +37,17 @@ interface InviteModalProps {
3637
onOpenChange: (open: boolean) => void
3738
workspaceName?: string
3839
inviteDisabledReason?: string | null
40+
organizationId?: string | null
3941
}
4042

4143
export function InviteModal({
4244
open,
4345
onOpenChange,
4446
workspaceName,
4547
inviteDisabledReason = null,
48+
organizationId = null,
4649
}: InviteModalProps) {
50+
const router = useRouter()
4751
const formRef = useRef<HTMLFormElement>(null)
4852
const [emailItems, setEmailItems] = useState<TagItem[]>([])
4953
const [userPermissions, setUserPermissions] = useState<UserPermissions[]>([])
@@ -76,6 +80,8 @@ export function InviteModal({
7680
const { data: pendingInvitations = [], isLoading: isPendingInvitationsLoading } =
7781
usePendingInvitations(open ? workspaceId : undefined)
7882

83+
const { data: organizationBillingData } = useOrganizationBilling(organizationId ?? '')
84+
7985
const batchSendInvitations = useBatchSendWorkspaceInvitations()
8086
const cancelInvitation = useCancelWorkspaceInvitation()
8187
const resendInvitation = useResendWorkspaceInvitation()
@@ -87,6 +93,22 @@ export function InviteModal({
8793
const hasNewInvites = validEmails.length > 0
8894
const canInviteMembers = userPerms.canAdmin && !inviteDisabledReason
8995

96+
const totalSeats = organizationBillingData?.data?.totalSeats ?? 0
97+
const usedSeats = organizationBillingData?.data?.usedSeats ?? 0
98+
const availableSeats = Math.max(0, totalSeats - usedSeats)
99+
const hasSeatData = !!organizationId && totalSeats > 0
100+
const exceedsSeatCapacity =
101+
hasSeatData && userPerms.canAdmin && validEmails.length > availableSeats
102+
const isAtSeatCapacity = hasSeatData && userPerms.canAdmin && availableSeats === 0
103+
const isOutOfSeats = exceedsSeatCapacity || isAtSeatCapacity
104+
const seatLimitReason = hasSeatData
105+
? availableSeats === 0
106+
? `No available seats. Using ${usedSeats} of ${totalSeats}.`
107+
: exceedsSeatCapacity
108+
? `Only ${availableSeats} seat${availableSeats === 1 ? '' : 's'} available.`
109+
: null
110+
: null
111+
90112
const isSubmitting = batchSendInvitations.isPending
91113
const isSaving = updatePermissionsMutation.isPending
92114
const isRemovingMember = removeMember.isPending
@@ -399,12 +421,23 @@ export function InviteModal({
399421
[workspaceId, userPerms.canAdmin, resendCooldowns, resendingInvitationIds, resendInvitation]
400422
)
401423

424+
const handleUpgradeRedirect = useCallback(() => {
425+
if (!workspaceId) return
426+
onOpenChange(false)
427+
router.push(`/workspace/${workspaceId}/settings/subscription`)
428+
}, [onOpenChange, router, workspaceId])
429+
402430
const handleSubmit = useCallback(
403431
(e: React.FormEvent) => {
404432
e.preventDefault()
405433

406434
setErrorMessage(null)
407435

436+
if (isOutOfSeats) {
437+
handleUpgradeRedirect()
438+
return
439+
}
440+
408441
if (!canInviteMembers || validEmails.length === 0 || !workspaceId) {
409442
return
410443
}
@@ -439,7 +472,15 @@ export function InviteModal({
439472
}
440473
)
441474
},
442-
[canInviteMembers, validEmails, workspaceId, userPermissions, batchSendInvitations]
475+
[
476+
canInviteMembers,
477+
isOutOfSeats,
478+
handleUpgradeRedirect,
479+
validEmails,
480+
workspaceId,
481+
userPermissions,
482+
batchSendInvitations,
483+
]
443484
)
444485

445486
const resetState = useCallback(() => {
@@ -543,6 +584,9 @@ export function InviteModal({
543584
{inviteDisabledReason && (
544585
<p className='mt-1 text-[var(--text-muted)] text-caption'>{inviteDisabledReason}</p>
545586
)}
587+
{isOutOfSeats && seatLimitReason && (
588+
<p className='mt-1 text-[var(--text-muted)] text-caption'>{seatLimitReason}</p>
589+
)}
546590
{errorMessage && (
547591
<p className='mt-1 text-[var(--text-error)] text-caption'>{errorMessage}</p>
548592
)}
@@ -596,9 +640,20 @@ export function InviteModal({
596640
<Button
597641
type='button'
598642
variant='primary'
599-
onClick={() => formRef.current?.requestSubmit()}
643+
onClick={() => {
644+
if (isOutOfSeats) {
645+
handleUpgradeRedirect()
646+
return
647+
}
648+
formRef.current?.requestSubmit()
649+
}}
600650
disabled={
601-
!canInviteMembers || isSubmitting || isSaving || !workspaceId || !hasNewInvites
651+
!userPerms.canAdmin ||
652+
!!inviteDisabledReason ||
653+
isSubmitting ||
654+
isSaving ||
655+
!workspaceId ||
656+
(!isOutOfSeats && !hasNewInvites)
602657
}
603658
className='ml-auto'
604659
>
@@ -608,7 +663,9 @@ export function InviteModal({
608663
? 'Admin Access Required'
609664
: isSubmitting
610665
? 'Inviting...'
611-
: 'Invite'}
666+
: isOutOfSeats
667+
? 'Upgrade to invite'
668+
: 'Invite'}
612669
</Button>
613670
</ModalFooter>
614671
</form>

apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/workspace-header.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -814,6 +814,7 @@ function WorkspaceHeaderImpl({
814814
onOpenChange={setIsInviteModalOpen}
815815
workspaceName={activeWorkspace?.name || 'Workspace'}
816816
inviteDisabledReason={inviteDisabledReason}
817+
organizationId={activeWorkspaceFull?.organizationId ?? null}
817818
/>
818819
{/* Delete Confirmation Modal */}
819820
<DeleteModal

0 commit comments

Comments
 (0)