docs
// 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 villein).
  • 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: villein
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
Villein 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 Erebine 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. If the last owner's account is deleted, the operator who joined the project first becomes its owner, or, with no operator, the member who joined first.

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. Adding the address as a member again issues a new invitation.

Invitations

When you invite a user who does not yet have a Erebine 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 marks the invitation accepted (accepted_at). An invitation past its expires_at, or revoked, is not applied.
  4. The owner can view outstanding invitations via GET /:project_id/v1/management/project/invitations (owner-only). include_expired=true adds invitations past their expires_at.
  5. The owner can resend the invitation email via POST /:project_id/v1/management/project/invitations/:id/resend, which also moves expires_at seven days ahead. Adding an address whose invitation expired refreshes that invitation the same way; adding one with a live invitation answers 409 invitation_exists.
  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.

Inviting Someone to the Platform

Any signed-in account, whatever its project role, can invite someone to create an account. A platform invitation adds nobody to a project. To bring someone into a project, add them as a member (see Team Management).

Where

  • Dashboard: Invite someone in the account menu (your avatar, top right).
  • Desktop app: Invite someone on the Connection tab of the Project page, beside Sign out.
  • Mobile app: Invite someone in Settings, under the session card.

Each offers an email field and Send, your invite link with Copy link, and, in the apps, the system share sheet.

What the Invitee Gets

An email from the platform that names you by your account name and address and carries a Create Account button. The button opens the sign-up page through your invite link. The email carries no message of yours, and nothing is created until the invitee signs up.

Your Invite Link

  • While referrals are on, the link is the sign-up page with your referral code. An account created through it counts as your referral: both accounts get the referral bonus once the new account signs in (see Referrals). When the operator requires a referral to skip review, a sign-up through your link skips it.
  • While referrals are off, the link is the plain sign-up page.
  • Copy link and the share sheet hand the link over without email.

Limits

  • Each account can send 10 invitations in any 24 hours; the platform operator sets the number. Every accepted request counts.
  • An address gets at most one invitation every 7 days, whoever sends it, and an address that already has an account gets none. The answer is the same in every case: it never says whether an address has an account.
  • While sign-up is closed, invitations are off and the link is not issued.
  • When the platform has no email service, Send is refused and the link still works.

API

The dashboard surface takes a session cookie with CSRF, or a member bearer token as the apps use.

Endpoint Answer
GET /account/api/invite 200 with invite_url, referral, referral_bonus_credits (omitted without a referral), email_enabled, daily_limit and remaining.
POST /account/api/invite 202 with recipient, remaining and message. Body: {"email": "friend@example.com"}.
Status error.code When
400invalid_emailThe address is not an email address.
403registration_closedSign-up is closed. Both endpoints.
429invite_limit_reachedThe account has used its invitations for the last 24 hours.
503email_unavailableThe platform has no email service. Copy the link instead.

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 ere_<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.erebine.ai/PROJECT_ID/v1/management/project/members \ -H "Authorization: Bearer ere_PROJECT_SLUG_TOKEN"
Python
import requests headers = {"Authorization": "Bearer ere_PROJECT_SLUG_TOKEN"} response = requests.get( "https://api.erebine.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.erebine.ai/PROJECT_ID/v1/management/project/members", { headers: { "Authorization": "Bearer ere_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.erebine.ai/PROJECT_ID/v1/management/project/members \ -H "Authorization: Bearer ere_PROJECT_SLUG_TOKEN" \ -H "Content-Type: application/json" \ -d '{"email": "newuser@example.com", "role": "member"}'
Python
import requests headers = { "Authorization": "Bearer ere_PROJECT_SLUG_TOKEN", "Content-Type": "application/json" } response = requests.post( "https://api.erebine.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.erebine.ai/PROJECT_ID/v1/management/project/members", { method: "POST", headers: { "Authorization": "Bearer ere_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.erebine.ai/PROJECT_ID/v1/management/project/members/USER_ID \ -H "Authorization: Bearer ere_PROJECT_SLUG_TOKEN" \ -H "Content-Type: application/json" \ -d '{"role": "owner"}'

Remove a Team Member

curl
curl -X DELETE https://api.erebine.ai/PROJECT_ID/v1/management/project/members/USER_ID \ -H "Authorization: Bearer ere_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.erebine.ai/PROJECT_ID/v1/management/project/invitations \ -H "Authorization: Bearer ere_PROJECT_SLUG_TOKEN"
Python
import requests headers = {"Authorization": "Bearer ere_PROJECT_SLUG_TOKEN"} response = requests.get( "https://api.erebine.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.erebine.ai/PROJECT_ID/v1/management/project/invitations/INVITATION_ID/resend \ -H "Authorization: Bearer ere_PROJECT_SLUG_TOKEN"

Revoke a Pending Invitation

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

API Key Security

API keys authenticate inference requests to your project endpoints. Erebine 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

Erebine 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 override the platform defaults.

Quota Description Default
maxRequestsPerMinute Maximum inference requests allowed per minute. Unset inherits the platform default; 0 is unlimited. 1,024 (platform default)
maxTokensPerMinute Maximum tokens per minute, counted as prompt + completion - cached. Unset inherits the platform default; 0 is unmetered. 2,097,152 (platform default)
maxConcurrentRequests Fairness threshold, not a hard cap. Once a project has this many requests in flight, its further requests are ranked behind other projects' requests in scheduling. They are not rejected. None (no threshold)
storageQuotaGB Flat per-project storage backstop (storage is credit-governed; this caps unbounded growth between billing ticks) 50 GB

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 (rolling bucket); the non-rolling bonus bucket is spent first
  • subscriptionTier: Subscription plan slug; sets the credit grant and discount. It does not gate capabilities -- isPremiumCapable is derived from a positive available credit balance (creditsBalance + bonusBalance) or comped status
  • tierId: Compute tier identifier for per-token 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 Erebine 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.