// Platform

Teams & User Management

RBAC scoped per project, not globally. Owners hold billing, keys, and member management; members get inference and read-only views. Owners can delegate risk-tiered approval without handing over the whole project.

CmdK CtrlK copies the active code panel

Access control operates at two layers:

  • System-level roles, platform-wide capabilities (admin vs operator).
  • Project-level roles, what a user can do inside a specific project (owner vs member).
flowchart LR
    User["User account"]
    SysAdmin["System role: admin
platform-wide"] SysOp["System role: operator
scoped to memberships"] ProjA["Project A
role: owner"] ProjB["Project B
role: member"] ProjC["Project C
role: owner + delegate"] User --> SysOp User -.->|"if elevated"| SysAdmin SysOp --> ProjA SysOp --> ProjB SysOp --> ProjC

Roles are scoped per project, not globally. A user can hold different roles in different projects, owner in one, member in another. The system-level role only gates platform administration; project capabilities live on the project pivot row.

Roles & Permissions

System-Level Roles

System-level roles control access to platform administration features.

Role Description Capabilities
Admin Platform administrator Full access to admin panel, user management, system settings, audit logs, and all projects
Operator Standard user Access only to projects where they are a team member

Project-Level Permissions

Within each project, team members are either an owner or a member. The table below shows what each role can do.

Capability Owner Member
View team members yes yes
Use inference endpoints yes yes
View models and agents yes yes
Manage team members yes no
Manage invitations yes no
Project settings yes no
Billing management yes no
API key management yes no
Manage agents yes no
Manage endpoints yes no
Approve risk-tiered operations yes delegated

Owners can scope approval authority to specific members for specific risk tiers through project role delegations, see Delegations below.

Team Management

Inviting Members

Project owners can invite users to join their team. The invitation flow depends on whether the user already has a Xerotier.ai account:

  • Existing user: The user is added to the project team immediately and can access the project on their next login.
  • New user: An invitation record is created (wire object: invitation). When the invited email address registers an account, they are automatically added to the project team.

Delegations

Owners can scope approval authority for risk-tiered operations to specific members via project role delegations. Delegations are managed through the management API:

  • GET /:project_id/v1/management/project/delegations, list current delegations
  • POST /:project_id/v1/management/project/delegations, create a delegation

This lets owners grant approval rights for specific operation risk tiers without promoting the member to owner.

Search Team Members

For typeahead and access-control UIs, member search is exposed at GET /:project_id/v1/project-members/search with API-key auth. The handler returns a ProjectMemberSearchResponse envelope.

Tip: You can invite users before they have created an account. The invitation will be fulfilled automatically when they register with the invited email address.

Changing Roles

Owners can change the role of other team members between owner and member. Two safety rules apply:

Self-demotion blocked. You cannot change your own role. Another owner must do it. The server rejects self-PATCH with 403 Forbidden.

Last-owner protection. The last remaining owner cannot be demoted. Promote another member to owner first, then the previous owner can step down.

Removing Members

Owners can remove other members from the project team. Two safety rules apply:

Self-removal blocked. You cannot remove yourself from the team. Ask another owner to remove you, or transfer ownership first.

Last-owner protection. The last remaining owner cannot be removed. Transfer ownership first by promoting another member, then the previous owner can be removed.

Revoking Invitations

Owners can revoke pending invitations that have not yet been accepted. Once revoked, the invitation link is invalidated and the email address will not be auto-added on registration.

Invitations

When you invite a user who does not yet have a Xerotier.ai account, an invitation record is created (wire object: invitation). Invitations are stored per-project and carry the invited email address, role, the inviting user id, creation timestamp, and a hard expiry.

Invitation Lifecycle

  1. Owner calls POST /:project_id/v1/management/project/members (router API, Bearer key with management scope), or the dashboard equivalent POST /projects/members (session cookie + CSRF), with an email that does not exist in the system.
  2. An invitation row is created. If email is configured, an invitation email is sent.
  3. When the invited email registers an account, the system automatically adds them to the project team with the specified role and clears the invitation.
  4. The owner can view outstanding invitations via GET /:project_id/v1/management/project/invitations (owner-only).
  5. The owner can resend the invitation email via POST /:project_id/v1/management/project/invitations/:id/resend.
  6. The owner can cancel a pending invitation at any time via DELETE /:project_id/v1/management/project/invitations/:id.

Pending Invitation Fields

Field Type Description
object string Always invitation.
invitation_id string (UUID) Unique invitation identifier. Used to resend or revoke the invitation.
email string Email address of the invited user.
role string Role the user will receive on registration: owner or member.
invited_by string (UUID) User id of the owner who sent the invitation.
created_at string (ISO 8601) Timestamp when the invitation was created.
expires_at string (ISO 8601) Hard expiry of the invitation. After this time the invitation is no longer redeemable.
accepted_at string (ISO 8601) or null Set when the invited user registers and joins the project.
revoked_at string (ISO 8601) or null Set when an owner cancels the invitation before acceptance.

Rate limit: The Frontend dashboard proxy (/projects/*) limits invitation operations to 20 per hour per project. Direct calls to the router-side management API (/:project_id/v1/management/project/*) are governed by the standard sliding-window limiter on that surface and do not share the dashboard bucket.

Access Control

The following table lists the canonical router-side team management endpoints and the role required to access each one. The dashboard Frontend (/projects/*) exposes a parallel cookie+CSRF surface that proxies to these routes; see the "Two surfaces" callout in API Examples.

Endpoint Method Required Role
/:project_id/v1/management/project/members GET Any member
/:project_id/v1/project-members/search GET Any member
/:project_id/v1/management/project/invitations GET Owner
/:project_id/v1/management/project/members POST Owner
/:project_id/v1/management/project/members/:user_id PATCH Owner
/:project_id/v1/management/project/members/:user_id DELETE Owner
/:project_id/v1/management/project/invitations/:id/resend POST Owner
/:project_id/v1/management/project/invitations/:id DELETE Owner
/:project_id/v1/management/project/delegations GET, POST Owner

API Examples

Two surfaces. Team management is exposed on two distinct surfaces with different authentication. The examples below target the canonical router-side management API.

  • Router management API, /:project_id/v1/management/project/*. Authenticate with an API key (Authorization: Bearer xero_<project_slug>_<token>) that includes the management scope. No CSRF token required.
  • Frontend dashboard proxy, /projects/* and /projects/api/*. Authenticate with the browser session cookie and an X-CSRF-Token header. API-key Bearer auth is not accepted on this surface. Used by the dashboard UI; the proxy forwards to the router management API.

The :project_id path segment accepts either the project UUID or the operator-facing project external id.

List Team Members

Returns an OpenAI-style list envelope ({"object":"list","data":[...]}) of member objects. Each member object includes the following fields:

Field Type Description
user_id string (UUID) Unique identifier of the user.
email string User's email address.
display_name string User's display name.
role string Role raw value: owner or member.
joined_at string (ISO 8601) Timestamp when the user joined the project.
curl
curl https://api.xerotier.ai/PROJECT_ID/v1/management/project/members \ -H "Authorization: Bearer xero_PROJECT_SLUG_TOKEN"
Python
import requests headers = {"Authorization": "Bearer xero_PROJECT_SLUG_TOKEN"} response = requests.get( "https://api.xerotier.ai/PROJECT_ID/v1/management/project/members", headers=headers ) for member in response.json()["data"]: print(f"{member['email']} - {member['role']}")
Node.js
const response = await fetch( "https://api.xerotier.ai/PROJECT_ID/v1/management/project/members", { headers: { "Authorization": "Bearer xero_PROJECT_SLUG_TOKEN" } } ); const payload = await response.json(); payload.data.forEach(m => console.log(`${m.email} - ${m.role}`));

Invite a Team Member

curl
curl -X POST https://api.xerotier.ai/PROJECT_ID/v1/management/project/members \ -H "Authorization: Bearer xero_PROJECT_SLUG_TOKEN" \ -H "Content-Type: application/json" \ -d '{"email": "newuser@example.com", "role": "member"}'
Python
import requests headers = { "Authorization": "Bearer xero_PROJECT_SLUG_TOKEN", "Content-Type": "application/json" } response = requests.post( "https://api.xerotier.ai/PROJECT_ID/v1/management/project/members", headers=headers, json={"email": "newuser@example.com", "role": "member"} ) print(response.json())
Node.js
const response = await fetch( "https://api.xerotier.ai/PROJECT_ID/v1/management/project/members", { method: "POST", headers: { "Authorization": "Bearer xero_PROJECT_SLUG_TOKEN", "Content-Type": "application/json" }, body: JSON.stringify({ email: "newuser@example.com", role: "member" }) } ); const data = await response.json(); console.log(data);

Change a Member's Role

curl
curl -X PATCH https://api.xerotier.ai/PROJECT_ID/v1/management/project/members/USER_ID \ -H "Authorization: Bearer xero_PROJECT_SLUG_TOKEN" \ -H "Content-Type: application/json" \ -d '{"role": "owner"}'

Remove a Team Member

curl
curl -X DELETE https://api.xerotier.ai/PROJECT_ID/v1/management/project/members/USER_ID \ -H "Authorization: Bearer xero_PROJECT_SLUG_TOKEN"

List Pending Invitations

Only accessible to project owners. Returns an OpenAI-style list envelope ({"object":"list","data":[...]}) of invitation objects (each with object: invitation).

curl
curl https://api.xerotier.ai/PROJECT_ID/v1/management/project/invitations \ -H "Authorization: Bearer xero_PROJECT_SLUG_TOKEN"
Python
import requests headers = {"Authorization": "Bearer xero_PROJECT_SLUG_TOKEN"} response = requests.get( "https://api.xerotier.ai/PROJECT_ID/v1/management/project/invitations", headers=headers ) for inv in response.json()["data"]: print(f"{inv['email']} ({inv['role']}) - invited by {inv['invited_by']}")

Resend a Pending Invitation

curl
curl -X POST https://api.xerotier.ai/PROJECT_ID/v1/management/project/invitations/INVITATION_ID/resend \ -H "Authorization: Bearer xero_PROJECT_SLUG_TOKEN"

Revoke a Pending Invitation

curl
curl -X DELETE https://api.xerotier.ai/PROJECT_ID/v1/management/project/invitations/INVITATION_ID \ -H "Authorization: Bearer xero_PROJECT_SLUG_TOKEN"

API Key Security

API keys authenticate inference requests to your project endpoints. Xerotier.ai uses several layers of protection to keep your keys secure.

  • 256-bit random generation: Keys are generated using 32 bytes of cryptographically secure random data, providing 256 bits of entropy.
  • SHA-256 hash storage: The raw key is never stored. Only a SHA-256 hash is persisted in the database. The full key is shown once at creation time.
  • Scoped permissions: Each key can be restricted to one or more permission scopes drawn from the canonical set inference, management, execution, research.
  • Endpoint restriction: Keys can be locked to specific endpoints, preventing use on other endpoints within the same project.
  • IP allowlist/blocklist: Keys support optional IP address filtering to restrict which networks can use them.

Important: Copy your API key immediately after creation. It cannot be retrieved later because only the hash is stored.

See Authentication for the full key lifecycle (creation, rotation, revocation) and the canonical scope set.

Audit Trail

All team management actions are recorded in the project audit log. The following events are tracked:

Audit Action Description
team_member_added A user was added to the project team
team_member_role_changed A team member's role was changed (owner/member)
team_member_removed A user was removed from the project team
pending_invitation_revoked A pending invitation was cancelled before acceptance
api_key_created A new API key was generated
api_key_rotated An existing API key was rotated (old key invalidated)
api_key_revoked An API key was permanently revoked

Encryption: The changes payload on each audit row is encrypted at rest using AES-256-GCM. Metadata fields (action, resource, ip_address, user_agent, success, error_message, created_at) are stored in plaintext.

Security Practices

Xerotier.ai enforces several security measures to protect your team and project:

  • Session destruction on suspension: When a user account is suspended by an admin, all active sessions are immediately invalidated.
  • CSRF protection: All state-changing requests require a valid CSRF token, preventing cross-site request forgery attacks.
  • Password change invalidates sessions: Changing your password automatically destroys all existing sessions, forcing re-authentication on all devices.
  • Optional TOTP two-factor authentication: Users can enable time-based one-time password (TOTP) 2FA for an additional layer of login security.
  • Principle of least privilege: Members receive only the permissions they need. Use the member role for users who only need inference access.
  • Multiple owners for redundancy: Promote at least two team members to the owner role to avoid single points of failure for administrative tasks.

Project Quotas

Each project has configurable resource quotas that control rate limiting and storage allocation. Quotas are managed by project owners and can override the defaults set by your subscription tier.

Quota Description Default
maxRequestsPerMinute Maximum inference requests allowed per minute Tier default
maxConcurrentRequests Maximum simultaneous inference requests Tier default
burstLimit Short burst allowance above the per-minute rate Tier default
storageQuotaGB Maximum model storage in gigabytes 4 GB (free) / 500 GB (premium)

When a project exceeds its rate limits, the isRateLimited flag is set and subsequent requests receive a 429 (Too Many Requests) response until the rate drops below the limit.

Team Billing Context

Billing is managed at the project level. Key billing fields on each project include:

  • creditsBalance: Available credits for per-token inference billing
  • accountTier: Free or Premium, determines default quotas and storage limits
  • tierId: Service tier identifier for pricing and feature access
  • isDelinquent: Set when credit balance is exhausted and usage continues
  • status: Active or Suspended, suspended projects cannot process inference requests

See Billing & Subscriptions for details on managing your plan, and Usage Tracking & Billing for cost monitoring.

Frequently Asked Questions

Can a user belong to multiple projects?

Yes. A single Xerotier.ai account can be a member of multiple projects, each with an independent role. You might be an owner in your primary project and a member in a collaborator's project.

What happens when I invite someone who does not have an account?

A pending invitation is created for that email address. When the person registers with that email, they are automatically added to your project team with the role you specified. You can revoke the invitation at any time before they register.

Can a project have multiple owners?

Yes, and it is recommended. Multiple owners keep administrative tasks (billing, settings, member management) unblocked if one owner is unavailable. There is no limit on the number of owners.

What happens to a suspended user's sessions?

When an admin suspends a user account, all of that user's active sessions are immediately destroyed. The user is logged out on all devices and cannot create new sessions until the suspension is lifted.

How are API keys protected?

API keys are generated with 256 bits of cryptographic randomness; only the SHA-256 hash is stored. The full key is shown once at creation and cannot be recovered. Keys can be scoped to specific permissions, locked to specific endpoints, and filtered by IP address. See Authentication for the full key lifecycle.