Skip to content

Organizations

Organizations group team members under a shared billing account with centralized administration. Each member inherits the organization’s tier, storage pool, and feature set.

An organization is a billing and access boundary. When you create one, your personal subscription transfers to the org level. Members you invite share the org’s tier — they do not need individual subscriptions.

Organizations are available on the Team ($14.99/user/month) and Enterprise (contact sales) tiers.

You need a Team or Enterprise subscription before creating an org.

POST /api/v1/org
{
"name": "Acme Engineering",
"slug": "acme-eng"
}

Slugs must be lowercase alphanumeric with hyphens, at least 3 characters, and unique across all orgs. The creator automatically becomes the owner.

When the org is created, billing moves to the organization so seats, storage, and tier access are managed centrally.

Seat count is derived from the organization’s subscription. Defaults: 5 seats for Team, 25 for Enterprise.

A person can belong to several organizations, for example their employer’s org and a client’s. Being in one org never stops you from creating, joining or activating a license for another. Each org has its own seats: a person counts once in every org they belong to.

When you belong to more than one org, one of them is your default organization. Anything that names an org (an /api/v1/orgs/{org_id}/... route, a project, a --org flag) uses that org and its plan. Anything that doesn’t name one uses your default org:

  • the Organization page in the dashboard and the sfs org info, sfs org members, sfs org invite and sfs org remove commands
  • billing, and plan features that aren’t tied to a project or org
  • sfs project init without --org or --personal, and other commands whose --org defaults to your default org

Until you choose one, your default org is the org you joined first. Joining another org never changes it. Choose a default with the switcher at the top of the Organization page, with sfs config default-org <org_id>, or with PUT /api/v1/auth/me/default-org. If you leave or are removed from the org you chose, your choice is cleared and the org you joined first among the ones you still belong to is used. Being removed from one org never changes your other memberships.

sfs project init without --org creates an org project only when you have chosen a default org; otherwise the project is personal.

Your plan for features that aren’t tied to an org or project is your default org’s plan, not the best plan among all your orgs. Switching your default org can therefore change which of those features you see.

sfs org list shows every org you belong to and marks your default. GET /api/v1/auth/me returns them as orgs, each with org_id, name, slug, role, joined_at and is_default; its org_id, org_name and org_role fields describe your default org.

Organizations have these roles:

Role Capabilities
owner Everything an admin can do, plus transfer ownership of the organization. Every org has exactly one owner.
admin Invite/remove members, change roles, manage billing, activate a license, view all org settings
member Use all features included in the org tier, view org info

Admins cannot change their own role. This prevents accidental last-admin lockout. An organization can never be left without an owner — the last owner cannot be removed or deactivated without first transferring ownership (see Ownership transfer).

Only admins can send invites.

POST /api/v1/org/invite
{
"email": "dev@company.com",
"role": "member"
}

Invites expire after 7 days. The server checks seat capacity at invite time and again at acceptance time. If all seats are in use, the invite or acceptance is rejected with a seat_limit error.

You cannot invite someone who is already a member, and duplicate pending invites to the same email are blocked.

Starting in v0.10.22, both invite endpoints (/api/v1/org/invite and the multi-org /api/v1/orgs/{org_id}/members/invite) automatically send a notification email to the invitee through the configured email provider. Email dispatch is best-effort — the invite is created even if email delivery fails, and admins can use the resend endpoint below to retry delivery.

Every invite response carries email_sent and accept_url. On a self-hosted server with no email provider (SFS_EMAIL_PROVIDER=none), invites still work: email_sent is false, and you share the accept_url with the invitee yourself. The link holds no secret — the invitee still has to sign in with the invited email address. They can also run sfs org invites to see the invite and sfs org accept <invite_id> to join, or open Invites in the dashboard.

POST /api/v1/orgs/{org_id}/invites/bulk
{
"invites": [
{"email": "dev1@company.com"},
{"email": "dev2@company.com", "role": "admin"}
]
}

Up to 100 invites per request, and each email may appear only once. Each invite follows the same rules as a single invite (admin only, no existing members, no active invite for that email). The request is all or nothing: if any invite is refused, none are created and you get 422 bulk_item_failed with a results list showing which items failed and why. The seat check counts current members plus every invite in the batch. A successful answer lists each invite with its invite_id, email_sent and accept_url; emails go out after the invites are saved.

From the CLI, put one email per line (optionally email,role) in a file:

Terminal window
sfs org invite --file emails.txt
GET /api/v1/org/invites # List pending invites (admin only)
GET /api/v1/org/invites/me # List pending invites addressed to me (v0.10.22)
DELETE /api/v1/org/invites/{id} # Revoke a pending invite (admin only)
POST /api/v1/orgs/{org_id}/invites/{id}/resend # Resend the invite email (admin only, v0.10.22)

The new /invites/me endpoint powers the dashboard /invites page where invitees see every org currently waiting on their decision.

POST /api/v1/org/invite/{invite_id}/accept
POST /api/v1/org/invite/{invite_id}/decline # v0.10.22 — optional body: {"decline_reason": "..."}

The authenticated user’s email must match the invite email. You can accept an invite while you belong to other organizations; only an invite to an org you are already a member of is refused (409). Accepting an invite never changes your chosen default org.

Seat capacity is rechecked at acceptance time. If the org filled up between invite and acceptance, the request is rejected.

Both accept and decline are protected by atomic rowcount-1 FSM transitions, so two browser tabs can’t both succeed on the same invite. Declining records declined_at and the optional decline_reason so admins can see why an invite was turned down.

Admins can change roles and remove members:

PUT /api/v1/org/members/{user_id}/role # Change role (admin only)
DELETE /api/v1/org/members/{user_id} # Remove member (admin only)

Role must be admin or member. Admins cannot change their own role or remove themselves, and ownership transfer is a separate flow (POST /api/v1/orgs/{org_id}/owner/transfer) — owner is never assignable via the role endpoint.

The CLI accepts a user ID or email for both operations, and supports bulk removal:

Terminal window
sfs org set-role alice@example.com admin
sfs org remove bob@example.com
sfs org remove --bulk members.txt --yes # one user ID/email per line

Organizations can automatically add users as members when their email is verified against a domain the org has already verified. This is managed via the org settings surface:

GET /api/v1/orgs/{org_id}/settings # includes auto_join_domains
PUT /api/v1/orgs/{org_id}/settings # {"auto_join_domains": ["example.com"]}
  • Each domain must already be verified by this org (via SSO domain verification). Submitting an unverified domain is rejected with a 422.
  • Auto-join requires a paid plan (the oidc_sso feature). If the org loses that entitlement, auto-join stops at join time.
  • When a user verifies their email address (by clicking the verification email, or when an administrator verifies them) and exactly one org they don’t already belong to lists their email domain for auto-join, they are added as a member (never admin or owner). Membership in other orgs doesn’t prevent this.
  • Auto-join needs email verification switched on, because it relies on users proving they own their address. On deployments that skip email verification (SFS_REQUIRE_EMAIL_VERIFICATION=false), auto-join never runs; use SSO sign-in, which provisions members automatically, instead.
  • Self-hosted: auto-join also requires SFS_VERIFICATION_SECRET to be set to a random value of at least 32 characters (the Helm chart generates one for you). With the built-in default secret, verification links could be forged, so auto-join stays off.
  • Auto-join applies when a user verifies their email. Accounts that were already verified before you enabled it are not added retroactively; invite them instead.
  • Seat limits still apply — a full org skips the join and records an auto_join_skipped_seat_cap audit event rather than failing the user’s sign-in or verification.
  • Users who were previously removed, or who already have an invite to the organization (accepted, declined or pending), are never added by auto-join.

The dashboard exposes a checkbox per verified domain under Organization → Policies, and the CLI mirrors it:

Terminal window
sfs org auto-join list
sfs org auto-join add example.com
sfs org auto-join remove example.com
GET /api/v1/org

Returns the org details, full member list, and the current user’s role. Non-org users get an empty response. The response includes:

  • Org name, slug, tier
  • Seat usage (used vs. limit)
  • Storage usage (used bytes vs. limit)
  • Each member’s email, display name, role, and join date

The organization owns the subscription for all members. Admins manage seats, payment, invoices, upgrades, downgrades, and cancellation from the dashboard billing page.

  • Admins access the subscription page to manage the org subscription.
  • Members cannot purchase individual plans while in an org. Their tier is determined entirely by the org.
  • If a member had a personal subscription before joining, it remains separate from the organization subscription and can be managed from the subscription page.
Tier Storage
Team 1 GB per seat (e.g., 5 seats = 5 GB pool)
Enterprise Unlimited

Storage is pooled across all org members. The GET /api/v1/org response includes storage_used_bytes and storage_limit_bytes so admins can monitor usage.

Open Organization (under Manage in the sidebar) in the web dashboard. Its tabs are Members (view members, change roles, send invites), Personas (org-wide agent personas), Policies (organization defaults, domain auto-join, notifications and, on Enterprise, the session helper AI key), Integrations (Jira Cloud and Confluence Cloud, Enterprise) and, for admins, Security (single sign-on). Subscription management is under Billing. Members see the org info and their role but cannot modify settings.

Starting in v0.10.0, a user can belong to more than one organization. The new multi-org API lives at /api/v1/orgs/... and is the canonical surface for member and project management:

  • GET /api/v1/orgs — list the caller’s memberships with role.
  • GET/POST/PUT/DELETE /api/v1/orgs/{org_id}/members[/...] — list, invite, promote/demote, and remove members in a specific org.
  • GET/PUT /api/v1/orgs/{org_id}/settings — read/write the org’s knowledge base creation defaults (kb_retention_days, kb_max_context_words, kb_section_page_limit), verified-domain auto-join (auto_join_domains) session helper retention (helper_retention_days) and meeting retention (meeting_retention_days, default 180 days). New org-scoped projects inherit these defaults at create time. Since v0.23.0 a save merges: general settings you leave out are kept, and an explicit null returns a setting to its default.
  • GET /api/v1/auth/me includes default_org_id and every membership in orgs; PUT /api/v1/auth/me/default-org sets the default (membership validated). See Your default organization.

Removal preserves all member-authored data — sessions stay user-owned, org-scoped projects auto-transfer to the removing admin (with a ProjectTransfer audit row), KB entries keep their authorship, and pending transfers tied to the removed user’s standing are cancelled. The legacy /api/v1/org/members/... routes still work and delegate to the same services as the multi-org routes, so both URL surfaces enforce the same invariants.

Projects carry an explicit org scope (projects.org_id, nullable). Transfers move a project between scopes (personal ↔ org, or org ↔ org) through a durable state machine:

  • POST /api/v1/projects/{id}/transfer initiates a transfer.
  • POST /api/v1/transfers/{xfer_id}/accept | reject | cancel mutates state. Atomic UPDATE ... WHERE state='pending' prevents double-accept.
  • GET /api/v1/transfers?direction=&state= lists incoming and outgoing transfers.

Auto-accept fires when the initiator and target are the same user/org (e.g. moving a personal project into your own org). Standing is re-validated at accept/reject — a member demoted between initiate and accept loses the right to act on the transfer.

Admin-proposed transfers (v0.23.0). An owner or admin of an organization can ask the owner of a personal project to move it into that org, with a reason (POST /api/v1/orgs/{org_id}/projects/{project_id}/propose-transfer). The project owner must already be an active member of the org. The owner sees the proposal in their transfer inbox, labelled with the proposer, org, reason and expiry, and by email; nothing moves until they accept. The proposer can cancel it before then. Proposals expire after 7 days, a declined proposal can’t be repeated for 7 days (24 hours after a cancel), and an org can have at most 10 pending.

The CLI surface mirrors the API: sfs project transfer --to | --accept | --reject | --cancel, sfs project transfers --direction --state, and sfs config default-org [<id>|--clear]. See the CLI reference for full options.

Sessions carry an explicit project_id (sessions.project_id, nullable). On every upload (POST /sessions and PUT /sessions/{id}/sync) the server resolves the linkage from the workspace’s git remote inside the write transaction. Re-sync re-evaluates: pre-v0.10.0 sessions retroactively pick up a project_id when the matching project is created later; stale linkages clear when access is revoked.

As of v0.11.0, an organization’s plan tier, seat count, and storage allowance are resolved from a single authoritative entitlement record per organization. This replaced the older arrangement where plan signals were spread across user rows and license tables, which could leave enterprise admins unable to fully see or administer their own orgs.

  • The entitlement has its own lifecycle (active / canceled / expired / revoked), tracked separately from billing health (current / past_due).
  • Effective tier is resolved entitlement-first across the server, with a safe fall-back to the legacy columns for any record not yet migrated.
  • Existing organizations were backfilled deterministically from their strongest existing signal (license, then subscription, then manual grant), so no plan changed for anyone during the upgrade.

This is an internal model change — there is nothing to configure. It is the foundation that makes self-service license activation and ownership transfer possible.

A user who has received a license key can activate it from the dashboard, with required email verification. The flow is available in the dashboard for a user who is not yet in an organization:

  1. Enter the license key and confirm the previewed organization and tier.
  2. Submit, then enter the single-use, time-limited code emailed for verification.
  3. The activated user lands as the owner of the newly created organization.

Activation completes only after the emailed code is confirmed — verification is required, not optional. The activation lookup is non-oracular (it never confirms whether an arbitrary key exists) and the flow is rate-limited. Note that activation creates a new organization from the license; it does not attach a license to an existing organization. Invalid-key and bad/expired-code states are handled inline, and the exact-match-email path completes without a separate code round-trip.

This gives Enterprise customers a direct, self-service path to stand up a licensed organization without manual back-office steps.

Every organization has exactly one owner. The owner can transfer ownership to an existing admin directly from the dashboard members surface, through a two-step flow:

  1. The current owner initiates a transfer to a target admin.
  2. The target sees an Accept / Decline banner; the initiator sees a pending banner with Cancel. Each shows the transfer’s expiry.
  3. On accept, the target becomes the new owner.

The owner role is shown in the members list, and the owner row is protected from demotion or removal. Both parties’ standing is re-validated at accept time, and a pending transfer expires if it is not accepted. Last-owner safety is enforced throughout: an org can never be left without an owner, so removing or deactivating the last owner is blocked until ownership is transferred, and admins have a force-transfer path with a full audit trail. (This is organization ownership transfer, distinct from project transfers.)

Org personas: activity and history (v0.23.0)

Section titled “Org personas: activity and history (v0.23.0)”

Clicking an org persona opens a panel with its recent sessions, open tickets, latest knowledge entry and latest agent run, limited to the projects you can see. Org admins also see who has represented the persona over time. Your account page lists the org personas you represent, each linking to its organization.

  • GET /api/v1/projects/{id}/personas/{name}/rollup
  • GET /api/v1/orgs/{org_id}/personas/{name}/rollup
  • GET /api/v1/orgs/{org_id}/personas/{name}/represent-history

Owners and admins of Team and Enterprise orgs get a weekly email covering the previous week (Monday to Sunday, UTC): sessions, tickets, agent runs, knowledge claims, handoffs and admin actions, each linking to the matching view in the dashboard. Each owner or admin can turn it off for themselves under Organization → Policies → Notifications, or with PUT /api/v1/orgs/{org_id}/members/me/digest ({"opt_out": true}). On self-hosted servers the digest is sent by a scheduled sweep; see Self-Hosted.

Session helper AI key (Enterprise, v0.23.0)

Section titled “Session helper AI key (Enterprise, v0.23.0)”

Owners and admins can set one provider, model, API key and optional base URL that the session helper uses for members without a key of their own: Organization → Policies → AI key for the session helper, or GET/PUT/DELETE /api/v1/orgs/{org_id}/llm-settings. The key is encrypted at rest and never shown again after saving (only its last four characters). It can be switched off without deleting it, and a custom base URL passes the same address checks as other user-supplied LLM endpoints. A member’s own key always takes precedence. Since v0.28.0, Fetch models lists the models the key can use, as on the personal form, and you can still type a model id when the provider can’t list them.

Owners and admins connect the org to Jira Cloud from the Integrations tab (or sfs org jira connect), map projects to Jira projects and check the integration’s health. Members then promote tickets to Jira issues from the ticket panel. Since v0.26.0, status changes and comments can sync both ways and admins can define custom story templates. Since v0.27.0, a ticket can also be linked to an issue that already exists in Jira. Since v0.28.0, a publish can place the issue in a sprint, set its parent from the ticket’s parent and carry the due date and estimate; admins review the detected sprint and epic-link fields under Connection → Agile fields. See Jira Cloud.

Owners and admins connect the org to Confluence Cloud from the Integrations tab (or sfs org confluence connect) and map projects to a space and parent page. Project wiki pages and the compiled context can then be published there. See Confluence Cloud.

Members can keep a directory of the people the org works with who are not SessionFS users, such as a vendor contact or a partner’s clinical lead, so meetings, chats and tickets can point at them. Owners and admins merge, delete and export contacts. A contact links to a member automatically when that person joins with the same verified email, and their open tickets move with them. See External contacts.

Owners and admins can group the org’s projects, grant members reader, editor or admin access to a whole group, and turn on restricted project visibility so members see only the projects they own, created, or reach through a group. See Project Groups.