Teams & Scopes
xbrain's entire data isolation model is built on teams. Every piece of data — conversations, extracted facts, Drive files, agent outputs — belongs to exactly one team and is permanently tagged with that team's scope identifier. This is not an optional feature: it is enforced at every layer of the stack.
Team Isolation
In xbrain, every piece of data belongs to exactly one team. The X-Team-Scope
HTTP header is mandatory on every authenticated API call. Team A cannot read, write, or search
Team B's data — enforced at the API layer, not just the UI.
This isolation is absolute. Even if two users share the same Google account or the same LibreChat instance, their data remains separated if they belong to different teams. There is no cross-team query, no cross-team aggregation, and no way to bypass the scope check via the API.
Design Principle
Security by isolation, not by obscurity. The X-Team-Scope check is enforced
server-side on every write and every search. Removing it from the UI would not grant access
to another team's data.
The team_scope Field
Every memory item carries a team_scope field. If the item's
team_scope doesn't match the X-Team-Scope header sent with the
request, the write returns HTTP 400. This prevents data from being written to the wrong
team's namespace, even by accident.
bash# This works — team_scope matches header curl -X POST https://api.grooveos.app/v1/memory/upsert \ -H "X-Team-Scope: excalibur" \ -H "Authorization: Bearer $JWT" \ -H "Content-Type: application/json" \ -d '{ "item": { "team_scope": "excalibur", "content": "Q2 planning is confirmed for May 15th", "truth_level": "WORKING", "source": "librechat:conv_abc123" } }' # This fails — team_scope mismatch curl -X POST https://api.grooveos.app/v1/memory/upsert \ -H "X-Team-Scope: excalibur" \ -H "Authorization: Bearer $JWT" \ -H "Content-Type: application/json" \ -d '{ "item": { "team_scope": "other-team", "content": "..." } }' # → 400 Bad Request: item.team_scope must match X-Team-Scope header
The X-Team-Scope header is validated against the authenticated user's team
memberships. A user cannot set an arbitrary team scope — the value must correspond to a team
they are a member of.
Project Scopes
Within a team, data can be further scoped by project (project_scope). This
allows isolation of e.g. fundraising data from engineering data within the same team. Project
scopes are optional — if omitted, a memory item is visible to all team members.
The full tagging contract requires at minimum: team_scope,
truth_level, source, and visibility. The
project_scope and confidence fields are strongly recommended.
Visibility Options
| Visibility | Who Can See It | Typical Use Case |
|---|---|---|
team |
All authenticated members of the team | Shared facts, architecture decisions, team agreements |
project |
Only members with access to that project_scope |
Fundraising docs (separate from engineering data), client-specific data |
private |
Only the owner (source_user_id) |
Personal notes, draft thoughts not yet shared with the team |
json — memory item with full tagging contract{ "team_scope": "excalibur", "project_scope": "fundraising", "visibility": "project", "truth_level": "VALIDATED", "confidence": 0.95, "source": "librechat:conv_def456", "source_user_id": "google:108765432109876543", "validation_status": "peer_reviewed", "content": "Series A target: $3M at $15M pre-money valuation" }
User Management
Users authenticate via Google OAuth (SSO) — the same identity works across LibreChat and Open WebUI. xbrain uses the OIDC claims from Google to build a stable user identity.
- source_user_id — OIDC
subclaim from Google (e.g.google:108765432109876543) orgithub:{login}for GitHub-authenticated users - Admin users — configured via the
ADMIN_USER_SUBSenvironment variable (comma-separated list of Googlesubclaims) - Team membership — managed via the memory-api admin endpoints; a user can belong to multiple teams
Single Identity Across Frontends
Because both LibreChat and Open WebUI use Google OAuth with the same
GOOGLE_CLIENT_ID, a user's identity is consistent across all xbrain frontends.
The source_user_id is always derived from the same Google sub claim.
Creating Teams
Teams are created by an admin via the memory-api admin endpoints. Once created, a team has a
team_scope string identifier that is used in all API calls and memory items. The
identifier should be short, lowercase, and URL-safe (e.g. excalibur,
engineering, growth).
bash — create a new teamcurl -X POST https://api.grooveos.app/v1/admin/teams \ -H "Authorization: Bearer $ADMIN_JWT" \ -H "Content-Type: application/json" \ -d '{ "name": "Engineering Team", "scope": "engineering" }' # Response: # { # "team_id": "team_abc123", # "scope": "engineering", # "name": "Engineering Team", # "created_at": "2026-05-06T10:00:00Z" # }
After creating the team, add users to it via the team membership endpoint:
bash — add a user to a teamcurl -X POST https://api.grooveos.app/v1/admin/teams/engineering/members \ -H "Authorization: Bearer $ADMIN_JWT" \ -H "Content-Type: application/json" \ -d '{ "user_id": "google:108765432109876543", "role": "member" }'
Drive Folder Mapping per Team
Each team can have one or more Google Drive folders mapped to it. When a file is updated in a
mapped folder, it's automatically synced to xbrain and tagged with that team's scope.
Drive mappings tie a specific Google Drive folder to a team_scope and optionally
a project_scope.
bash — map a Drive folder to a teamcurl -X POST https://api.grooveos.app/v1/admin/drive/mappings \ -H "Authorization: Bearer $ADMIN_JWT" \ -H "Content-Type: application/json" \ -d '{ "team_scope": "excalibur", "project_scope": "fundraising", "folder_id": "1ABC...xyz", "folder_name": "Fundraising Docs" }' # After this mapping, any file added or updated in the Drive folder # is automatically picked up by drive-sync and stored in memory-api # with team_scope="excalibur" and project_scope="fundraising".
Multiple Folders per Team
Multiple Drive folders can be mapped to the same team with different
project_scope values. For example, the excalibur team might have
a fundraising folder and an engineering folder, each synced with
their respective project scopes. See the
Drive Sync documentation for details on webhook setup and
sync frequency.
Drive Mapping Properties
| Field | Required | Description |
|---|---|---|
team_scope |
Yes | The team that owns the synced content |
project_scope |
No | Optional project subdivision within the team |
folder_id |
Yes | Google Drive folder ID (from the URL) |
folder_name |
No | Human-readable name for the mapping (display only) |
watch_enabled |
No | Whether to set up a Drive push notification webhook (default: true) |