Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
11 changes: 8 additions & 3 deletions apps/docs/content/docs/de/api-reference/authentication.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,23 @@ To access the Sim API, you need an API key. Sim supports two types of API keys

| | **Personal Keys** | **Workspace Keys** |
| --- | --- | --- |
| **Billed to** | Your individual account | Workspace owner |
| **Billing** | Workspace payer for workspace-hosted usage | Workspace payer |
| **Scope** | Across workspaces you have access to | Shared across the workspace |
| **Managed by** | Each user individually | Workspace admins |
| **Permissions** | Must be enabled at workspace level | Require admin permissions |

<Callout type="info">
Workspace admins can disable personal API key usage for their workspace. If disabled, only workspace keys can be used.
Personal keys identify the user making a request; they do not select who pays.
Hosted usage is billed to the workspace's organization or personal billing
account and, for organizations, is attributed to the actor's member cap.
Workspace admins can disable personal API key usage for their workspace. If
disabled, only workspace keys can be used.
</Callout>

## Generating API Keys

To generate a key, open the Sim dashboard and navigate to **Settings**, then go to **Sim Keys** and click **Create**.
To generate a personal key, open **Account settings** → **Sim API keys**. Workspace
administrators can create shared keys from **Workspace settings** → **Sim API keys**.

<Callout type="warn">
API keys are only shown once when generated. Store your key securely — you will not be able to view it again.
Expand Down
11 changes: 8 additions & 3 deletions apps/docs/content/docs/en/api-reference/authentication.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,23 @@ To access the Sim API, you need an API key. Sim supports two types of API keys

| | **Personal Keys** | **Workspace Keys** |
| --- | --- | --- |
| **Billed to** | Your individual account | Workspace owner |
| **Billing** | Workspace payer for workspace-hosted usage | Workspace payer |
| **Scope** | Across workspaces you have access to | Shared across the workspace |
| **Managed by** | Each user individually | Workspace admins |
| **Permissions** | Must be enabled at workspace level | Require admin permissions |

<Callout type="info">
Workspace admins can disable personal API key usage for their workspace. If disabled, only workspace keys can be used.
Personal keys identify the user making a request; they do not select who pays.
Hosted usage is billed to the workspace's organization or personal billing
account and, for organizations, is attributed to the actor's member cap.
Workspace admins can disable personal API key usage for their workspace. If
disabled, only workspace keys can be used.
</Callout>

## Generating API Keys

To generate a key, open the Sim dashboard and navigate to **Settings**, then go to **Sim Keys** and click **Create**.
To generate a personal key, open **Account settings** → **Sim API keys**. Workspace
administrators can create shared keys from **Workspace settings** → **Sim API keys**.

<Callout type="warn">
API keys are only shown once when generated. Store your key securely — you will not be able to view it again.
Expand Down
7 changes: 5 additions & 2 deletions apps/docs/content/docs/en/platform/costs.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,9 @@ By default, your usage is capped at the credits included in your plan. To allow
- **Disable On-Demand**: Resets your usage limit back to the plan's included amount (only available if your current usage hasn't already exceeded it).

<Callout type="info">
On-demand billing is managed by workspace admins for team plans. Non-admin team members cannot toggle on-demand billing.
On-demand billing for team plans is managed by organization owners and
administrators. Workspace admin permission alone does not grant billing
authority.
</Callout>

## Plan Limits
Expand Down Expand Up @@ -371,7 +373,8 @@ Sim uses a **base subscription + overage** billing model:

**Team Plans:**
- Usage is pooled across internal team members in the organization
- External workspace members keep their own organization or personal billing context for runs where they are the billing actor
- Usage in an organization-owned workspace is billed to that workspace's organization, including work performed by external collaborators
- Each authenticated actor's usage is also counted toward their member cap for that organization
- Overage is calculated from total team usage against the pooled limit
- Organization owner receives one bill

Expand Down
11 changes: 8 additions & 3 deletions apps/docs/content/docs/es/api-reference/authentication.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,23 @@ To access the Sim API, you need an API key. Sim supports two types of API keys

| | **Personal Keys** | **Workspace Keys** |
| --- | --- | --- |
| **Billed to** | Your individual account | Workspace owner |
| **Billing** | Workspace payer for workspace-hosted usage | Workspace payer |
| **Scope** | Across workspaces you have access to | Shared across the workspace |
| **Managed by** | Each user individually | Workspace admins |
| **Permissions** | Must be enabled at workspace level | Require admin permissions |

<Callout type="info">
Workspace admins can disable personal API key usage for their workspace. If disabled, only workspace keys can be used.
Personal keys identify the user making a request; they do not select who pays.
Hosted usage is billed to the workspace's organization or personal billing
account and, for organizations, is attributed to the actor's member cap.
Workspace admins can disable personal API key usage for their workspace. If
disabled, only workspace keys can be used.
</Callout>

## Generating API Keys

To generate a key, open the Sim dashboard and navigate to **Settings**, then go to **Sim Keys** and click **Create**.
To generate a personal key, open **Account settings** → **Sim API keys**. Workspace
administrators can create shared keys from **Workspace settings** → **Sim API keys**.

<Callout type="warn">
API keys are only shown once when generated. Store your key securely — you will not be able to view it again.
Expand Down
11 changes: 8 additions & 3 deletions apps/docs/content/docs/fr/api-reference/authentication.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,23 @@ To access the Sim API, you need an API key. Sim supports two types of API keys

| | **Personal Keys** | **Workspace Keys** |
| --- | --- | --- |
| **Billed to** | Your individual account | Workspace owner |
| **Billing** | Workspace payer for workspace-hosted usage | Workspace payer |
| **Scope** | Across workspaces you have access to | Shared across the workspace |
| **Managed by** | Each user individually | Workspace admins |
| **Permissions** | Must be enabled at workspace level | Require admin permissions |

<Callout type="info">
Workspace admins can disable personal API key usage for their workspace. If disabled, only workspace keys can be used.
Personal keys identify the user making a request; they do not select who pays.
Hosted usage is billed to the workspace's organization or personal billing
account and, for organizations, is attributed to the actor's member cap.
Workspace admins can disable personal API key usage for their workspace. If
disabled, only workspace keys can be used.
</Callout>

## Generating API Keys

To generate a key, open the Sim dashboard and navigate to **Settings**, then go to **Sim Keys** and click **Create**.
To generate a personal key, open **Account settings** → **Sim API keys**. Workspace
administrators can create shared keys from **Workspace settings** → **Sim API keys**.

<Callout type="warn">
API keys are only shown once when generated. Store your key securely — you will not be able to view it again.
Expand Down
11 changes: 8 additions & 3 deletions apps/docs/content/docs/ja/api-reference/authentication.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,23 @@ To access the Sim API, you need an API key. Sim supports two types of API keys

| | **Personal Keys** | **Workspace Keys** |
| --- | --- | --- |
| **Billed to** | Your individual account | Workspace owner |
| **Billing** | Workspace payer for workspace-hosted usage | Workspace payer |
| **Scope** | Across workspaces you have access to | Shared across the workspace |
| **Managed by** | Each user individually | Workspace admins |
| **Permissions** | Must be enabled at workspace level | Require admin permissions |

<Callout type="info">
Workspace admins can disable personal API key usage for their workspace. If disabled, only workspace keys can be used.
Personal keys identify the user making a request; they do not select who pays.
Hosted usage is billed to the workspace's organization or personal billing
account and, for organizations, is attributed to the actor's member cap.
Workspace admins can disable personal API key usage for their workspace. If
disabled, only workspace keys can be used.
</Callout>

## Generating API Keys

To generate a key, open the Sim dashboard and navigate to **Settings**, then go to **Sim Keys** and click **Create**.
To generate a personal key, open **Account settings** → **Sim API keys**. Workspace
administrators can create shared keys from **Workspace settings** → **Sim API keys**.

<Callout type="warn">
API keys are only shown once when generated. Store your key securely — you will not be able to view it again.
Expand Down
11 changes: 8 additions & 3 deletions apps/docs/content/docs/zh/api-reference/authentication.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,23 @@ To access the Sim API, you need an API key. Sim supports two types of API keys

| | **Personal Keys** | **Workspace Keys** |
| --- | --- | --- |
| **Billed to** | Your individual account | Workspace owner |
| **Billing** | Workspace payer for workspace-hosted usage | Workspace payer |
| **Scope** | Across workspaces you have access to | Shared across the workspace |
| **Managed by** | Each user individually | Workspace admins |
| **Permissions** | Must be enabled at workspace level | Require admin permissions |

<Callout type="info">
Workspace admins can disable personal API key usage for their workspace. If disabled, only workspace keys can be used.
Personal keys identify the user making a request; they do not select who pays.
Hosted usage is billed to the workspace's organization or personal billing
account and, for organizations, is attributed to the actor's member cap.
Workspace admins can disable personal API key usage for their workspace. If
disabled, only workspace keys can be used.
</Callout>

## Generating API Keys

To generate a key, open the Sim platform and navigate to **Settings**, then go to **Sim Keys** and click **Create**.
To generate a personal key, open **Account settings** → **Sim API keys**. Workspace
administrators can create shared keys from **Workspace settings** → **Sim API keys**.

<Callout type="warn">
API keys are only shown once when generated. Store your key securely — you will not be able to view it again.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -819,6 +819,18 @@ export default function ResumeExecutionPage({
</div>
</div>

{selectedDetail.pausePoint.automaticResumeWaitingReason && (
<div className='rounded-[8px] border border-[var(--border)] bg-[var(--surface-1)] px-4 py-3'>
<Label>Waiting to resume automatically</Label>
<p className='mt-1 text-[13px] text-[var(--text-secondary)]'>
{selectedDetail.pausePoint.automaticResumeWaitingReason}
</p>
<p className='mt-1 text-[12px] text-[var(--text-muted)]'>
Sim will retry automatically.
</p>
</div>
)}

{/* Already resolved - show form fields with submitted values */}
{selectedStatus === 'resumed' || selectedStatus === 'failed' ? (
<div className='overflow-hidden rounded-[8px] border border-[var(--border)] bg-[var(--surface-1)]'>
Expand Down
104 changes: 97 additions & 7 deletions apps/sim/app/_shell/providers/session-provider.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,22 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { createRoot, type Root } from 'react-dom/client'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'

const { mockGetSession, mockSetActive, mockRequestJson } = vi.hoisted(() => ({
const { mockGetSession, mockListOrganizations, mockSetActive } = vi.hoisted(() => ({
mockGetSession: vi.fn(),
mockListOrganizations: vi.fn(),
mockSetActive: vi.fn(),
mockRequestJson: vi.fn(),
}))

vi.mock('@/lib/auth/auth-client', () => ({
client: {
getSession: mockGetSession,
organization: { setActive: mockSetActive },
organization: {
list: mockListOrganizations,
setActive: mockSetActive,
},
},
}))

vi.mock('@/lib/api/client/request', () => ({
requestJson: mockRequestJson,
}))

vi.mock('posthog-js', () => ({
default: {
identify: vi.fn(),
Expand Down Expand Up @@ -66,6 +65,16 @@ const FRESH_SESSION: AppSession = {
session: { id: 's1', userId: 'user-1', activeOrganizationId: 'org-1' },
}

const NO_ACTIVE_ORGANIZATION_SESSION: AppSession = {
user: { id: 'user-1', email: 'u@x.com', name: 'No active organization' },
session: { id: 's1', userId: 'user-1' },
}

const RECOVERED_ORGANIZATION_SESSION: AppSession = {
user: { id: 'user-1', email: 'u@x.com', name: 'Recovered organization' },
session: { id: 's1', userId: 'user-1', activeOrganizationId: 'org-member' },
}

interface Harness {
ctx: () => SessionHookResult | null
queryClient: QueryClient
Expand Down Expand Up @@ -152,6 +161,8 @@ describe('useSessionQuery', () => {
describe('SessionProvider', () => {
beforeEach(() => {
vi.clearAllMocks()
mockListOrganizations.mockResolvedValue({ data: [], error: null })
mockSetActive.mockResolvedValue({ data: null, error: null })
setSearch('')
})

Expand Down Expand Up @@ -179,6 +190,85 @@ describe('SessionProvider', () => {
h.unmount()
})

it('preserves an intentional no-active-organization state on a normal load', async () => {
mockGetSession.mockResolvedValue({ data: NO_ACTIVE_ORGANIZATION_SESSION })
mockListOrganizations.mockResolvedValue({
data: [{ id: 'org-member', name: 'Member organization' }],
error: null,
})

const h = renderProvider()
await flushUntil(() => h.ctx()?.data != null)

expect(h.ctx()?.data).toEqual(NO_ACTIVE_ORGANIZATION_SESSION)
expect(mockListOrganizations).not.toHaveBeenCalled()
expect(mockSetActive).not.toHaveBeenCalled()

h.unmount()
})

it('does not auto-select an organization for an external-only user during recovery', async () => {
setSearch('?upgraded=true')
mockGetSession.mockResolvedValue({ data: NO_ACTIVE_ORGANIZATION_SESSION })
mockListOrganizations.mockResolvedValue({ data: [], error: null })

const h = renderProvider()
await flushUntil(() => mockListOrganizations.mock.calls.length > 0)

expect(mockListOrganizations).toHaveBeenCalledTimes(1)
expect(mockSetActive).not.toHaveBeenCalled()

h.unmount()
})

it('recovers the sole valid viewer organization membership when upgrade recovery is explicit', async () => {
window.history.replaceState({}, '', '/workspace/workspace-b?upgraded=true')
mockListOrganizations.mockResolvedValue({
data: [{ id: 'org-member', name: 'Member organization' }],
error: null,
})
mockGetSession.mockImplementation(() =>
Promise.resolve({
data:
mockSetActive.mock.calls.length > 0
? RECOVERED_ORGANIZATION_SESSION
: NO_ACTIVE_ORGANIZATION_SESSION,
})
)

const h = renderProvider()
await flushUntil(
() =>
h.queryClient.getQueryData<AppSession>(sessionKeys.detail())?.session
?.activeOrganizationId === 'org-member'
)

expect(mockSetActive).toHaveBeenCalledWith({ organizationId: 'org-member' })
expect(h.queryClient.getQueryData(sessionKeys.detail())).toEqual(RECOVERED_ORGANIZATION_SESSION)

h.unmount()
})

it('does not choose arbitrarily when multiple valid memberships need recovery', async () => {
setSearch('?upgraded=true')
mockGetSession.mockResolvedValue({ data: NO_ACTIVE_ORGANIZATION_SESSION })
mockListOrganizations.mockResolvedValue({
data: [
{ id: 'org-a', name: 'Organization A' },
{ id: 'org-b', name: 'Organization B' },
],
error: null,
})

const h = renderProvider()
await flushUntil(() => mockListOrganizations.mock.calls.length > 0)

expect(mockListOrganizations).toHaveBeenCalledTimes(1)
expect(mockSetActive).not.toHaveBeenCalled()

h.unmount()
})

it('upgrade path: fresh disableCookieCache read wins even when the stale mount query resolves LAST', async () => {
setSearch('?upgraded=true')

Expand Down
9 changes: 3 additions & 6 deletions apps/sim/app/_shell/providers/session-provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ import type React from 'react'
import { createContext, useEffect, useMemo } from 'react'
import { createLogger } from '@sim/logger'
import { useQueryClient } from '@tanstack/react-query'
import { requestJson } from '@/lib/api/client/request'
import { listCreatorOrganizationsContract } from '@/lib/api/contracts/organizations'
import { client } from '@/lib/auth/auth-client'
import {
type AppSession,
Expand Down Expand Up @@ -79,10 +77,9 @@ export function SessionProvider({ children }: { children: React.ReactNode }) {
}

try {
const orgData = await requestJson(listCreatorOrganizationsContract, {}).catch(() => null)
if (!orgData) return

const organizationId = orgData.organizations?.[0]?.id
const organizationsResponse = await client.organization.list()
const organizations = organizationsResponse.data ?? []
const organizationId = organizations.length === 1 ? organizations[0]?.id : null

if (!organizationId || isCancelled) {
return
Expand Down
Loading
Loading