Access management

When you're running FeatureQL queries from a notebook without an access token, you're running them as guest. You cannot read or write persisted features.

To persist features, you need to create a project. Features are project specific and cannot be shared between projects.

To share a project with others, add them to the project with the appropriate role.

Admin panel

https://console.featuremesh.com/login is where everything starts.

You can manage access to FeatureMesh through the admin panel.

If you prefer code, generate a user_token and use FeatureQL in a notebook to manage users, orgs and projects.

Data model

Entities and relations

  • A user automatically gets a personal org on signup (org_name = user_id)
  • An org can have multiple users with OWNER or ADMIN roles
  • An org can have multiple projects
  • A project belongs to exactly one org
  • A project can have multiple users with OWNER, ADMIN, READ_WRITE or READ_ONLY roles
  • A user can have roles in multiple orgs
  • A user can have roles in multiple projects across orgs
  • An org must have at least one OWNER
  • A project must have at least one OWNER

Personal organizations

Personal orgs are special:

  • Created automatically on user signup
  • One user can own only one personal org
  • Cannot have additional members - the owner is the only member
  • Cannot share projects - projects in personal orgs cannot have additional members
  • No collaboration features - no inviting, no role changes, no sharing

For collaboration:

  • Create a regular organization (non-personal)
  • Create projects in the regular organization
  • Full permission model applies to regular organizations

Project references

Projects are identified by org_name/project_name. This is the unique identifier across all orgs.

For personal projects, the reference is user_id/project_name.

Note: Project name uniqueness is per-org, not global. Different orgs can have projects with the same name (e.g., acme/data and corp/data can both exist).

Roles and permissions

Org roles:

  • OWNER: create/delete projects, manage all org members, transfer org ownership, delete org
  • ADMIN: create projects, invite users to org, manage project access

Project roles:

  • OWNER: all project operations, manage all project members, transfer project ownership, delete project, transfer project to another org
  • ADMIN: read/write features, invite READ_WRITE and READ_ONLY users, manage READ_WRITE and READ_ONLY roles
  • READ_WRITE: read/write features
  • READ_ONLY: read features

Permission matrix

Org-level permissions:

ActionOWNERADMINNon-member
Create projects
Invite users to org
Modify any member's role
List org members
Delete org
Cancel own invitations
Cancel any invitation
View audit logs

Project-level permissions:

ActionOWNERADMINREAD_WRITEREAD_ONLY
Read/write featuresRead only
Invite usersREAD_WRITE/READ_ONLY only
Modify OWNER/ADMIN roles
Modify READ_WRITE/READ_ONLY roles
List project members
Transfer project
Delete project
View audit logs

Multiple owners behavior: When multiple OWNERs exist in an org or project, any OWNER can modify or remove other OWNERs' roles. The only protection is that at least one OWNER must always remain.

Access tokens

JWT access tokens authenticate requests to the FeatureMesh API. Only the user_id in the token is used for access control.

Token validity:

  • Tokens expire after 1 week from creation timestamp
  • Tokens can be manually invalidated via client.invalidate_token()
  • Manual invalidation sets an invalidation timestamp; tokens older than this timestamp are immediately invalid
  • New tokens issued after invalidation are valid - this allows you to generate a new token after invalidating old ones

Create a user

Go to https://console.featuremesh.com/login and login with your github or google account, or register with your email.

A personal org is automatically created with org_name = user_id.

Important: Your personal org cannot be deleted. It's a permanent workspace for your individual projects.

You can now manage users, orgs and projects in the UI or with FeatureQL.

Manage in the UI

[screenshot to come]

Manage with featuremesh Python client

Get your access_token and start using the featuremesh Python client in your notebook.

import featuremesh
access_token = "your_access_token"
client = featuremesh.Client(access_token=access_token)
python

Individual user workflow

Goal: Persist features on your own project.

Your personal org is already created. Just create a project in it.

Create a project

client.create_project(project_name=<project_name>)
python

The project_name must be 2 to 16 alphanumeric characters plus "_", unique within the org. Different orgs can have projects with the same name.

This creates the project in your personal org with project_reference = user_id/project_name.

Persist features on the project

client.select_project(project=<project_reference>)
client.query("CREATE FEATURE MY_FEATURE IN FM.MY_PROJECT AS 1;")
python

Team/Enterprise workflow

Goal: Share features with your team.

Create an org

client.create_org(org_name=<org_name>)
python

The org_name must be 2 to 16 alphanumeric characters plus "_", unique across all orgs.

Create a project

client.create_project(org=<org_name>, project_name=<project_name>)
python

You must be at least ADMIN in the org to create projects.

Invite users to the org

client.invite_to_org(user_emails=[<email1>, <email2>, ...], org=<org_name>, role=<role>)
python

Role must be OWNER or ADMIN.

This creates a pending invitation. The user receives an email notification.

Invite users to the project

client.invite_to_project(user_emails=[<email1>, <email2>, ...], project=<project_reference>, role=<role>)
python

Role must be OWNER, ADMIN, READ_WRITE or READ_ONLY.

This creates a pending invitation. The user receives an email notification.

Note: You cannot invite a user who already has access to the project. To change their role, use client.set_project_roles() instead. Or revoke their access first, then send a new invitation.

Users accept invitations

Users go to cloud.featuremesh.com and login/register with the email used to invite them.

If they don't have an account, they register first. Pending invitations are automatically linked to their account.

They see pending invitations in the UI and can accept or decline them.

# Accept an invitation
client.accept_invitation(invitation_id=<invitation_id>)

# Decline an invitation (cannot be accepted later - need new invitation)
client.decline_invitation(invitation_id=<invitation_id>)
python

Once accepted, they can select the project and use their access token to read/write features.

Note: Each invitation can only be accepted once. Declined invitations cannot be accepted later - a new invitation must be sent. Ignored invitations expire after 30 days.

Cancel invitations

client.cancel_invitations(user_emails=[<email1>, <email2>, ...], project=<project_reference>)
client.cancel_invitations(user_emails=[<email1>, <email2>, ...], org=<org_name>)
python

Who can cancel invitations:

  • The person who sent the invitation (OWNER or ADMIN)
  • Any OWNER of the org/project (even if they didn't send it)
  • ADMINs can only cancel invitations they sent themselves

Invitations expire after 30 days if not accepted or declined.

Managing access

List operations

client.list_orgs()  # orgs where you have a role
client.list_org_members(org=<org_name>) # for ADMIN and OWNER roles only

client.list_projects(org=<org_name>)  # all projects in org you have a role in
client.list_project_members(project=<project_reference>) # for ADMIN and OWNER roles only

client.list_invitations(
    scope="sent",  # or "received" or "all"
    org=<org_name>,  # optional filter
    project=<project_reference>  # optional filter
)

client.list_my_roles() # returns your roles in all orgs and projects
client.list_my_roles(org=<org_name>) # returns your roles in the org
client.list_my_roles(project=<project_reference>) # returns your roles in the project
python

Privacy and access control:

  • list_org_members: Only OWNER and ADMIN can list members. Non-members get PERMISSION_DENIED.
  • list_project_members: Only OWNER and ADMIN can list members. READ_WRITE and READ_ONLY users cannot.
  • This ensures users can't enumerate members of orgs/projects they don't have admin access to.

Update roles

On org:

client.set_org_roles(user_emails=[<email1>, <email2>, ...], org=<org_name>, role=<role>)
client.revoke_org_roles(user_emails=[<email1>, <email2>, ...], org=<org_name>)
python

You must be OWNER to change another user's role. When multiple OWNERs exist, any OWNER can modify or remove other OWNERs. The only protection is that at least one OWNER must always remain.

On project:

client.set_project_roles(user_emails=[<email1>, <email2>, ...], project=<project_reference>, role=<role>)
client.revoke_project_roles(user_emails=[<email1>, <email2>, ...], project=<project_reference>)
python

Permission rules:

  • ADMIN can modify READ_WRITE and READ_ONLY roles only
  • OWNER can modify all roles (including other OWNERs and ADMINs)
  • You cannot revoke the last OWNER from a project

Transfer ownership

Transfer project ownership

Add a second OWNER first:

client.set_project_roles(user_emails=[<email_of_new_owner>], project=<project_reference>, role="OWNER")
python

Then revoke your own role:

client.revoke_project_roles(user_emails=[<email_of_former_owner>], project=<project_reference>)
python

Transfer project to another org

client.transfer_project_to_org(
    project=<project_reference>,
    new_org=<org_name>
)
python

Requirements:

  • You must be OWNER of the project
  • You must be at least ADMIN in the new org

The project_reference changes to new_org/project_name. Features are preserved.

Transfer org ownership

Same pattern as project, add a second OWNER first:

client.set_org_roles(user_emails=[<email_of_new_owner>], org=<org_name>, role="OWNER")
python

Then revoke your own role:

client.revoke_org_roles(user_emails=[<email_of_former_owner>], org=<org_name>)
python

Delete project

client.delete_project(project=<project_reference>)  # OWNER only
# Returns a validation_code that you must manually type in the next call

client.delete_project(project=<project_reference>, validation_code=<validation_code>)  # OWNER only
# Deletes the project
python

Conditions that must be met:

  • You must be OWNER of the project
  • You must type the validation_code that is returned by the API
  • Validation codes expire after 5 minutes - if expired, request a new code
  • Deleting a project is irreversible. To prevent accidental deletion of active collaborative projects, you must first remove all users except yourself. This ensures you've consciously wound down the project before destroying it.

Delete org

client.delete_org(org=<org_name>)  # OWNER only
# Returns a validation_code that you must manually type in the next call

client.delete_org(org=<org_name>, validation_code=<validation_code>)  # OWNER only
# Deletes the org
python

Conditions that must be met:

  • You must be OWNER of the org
  • You must type the validation_code that is returned by the API
  • Validation codes expire after 5 minutes - if expired, request a new code
  • Deleting an org is irreversible. To prevent accidental deletion of active collaborative orgs, you must first remove all projects and all users except yourself. This ensures you've consciously wound down the org before destroying it.
  • Personal orgs cannot be deleted (org_name = user_id)

Service accounts

Service accounts are API-only identities scoped to a project that allow programmatic access with limited permissions.

Core rules

  • Service accounts belong to a project (cannot be created in personal org projects)
  • Each service account has an owner (the user who created it)
  • Service accounts have a role: READ_ONLY or READ_WRITE (cannot be OWNER or ADMIN)
  • Service account role cannot exceed the owner's permissions
  • Service accounts authenticate via JWT tokens only (no password, no UI login)
  • Service accounts cannot create other service accounts or use access management endpoints
  • If owner loses access to the project, service account is automatically deleted
  • Only project OWNER or ADMIN can create service accounts

Creating and managing service accounts

# Create a service account
result = client.create_service_account(
    project="my_org/my_project",
    name="ml-training-bot",
    role="READ_WRITE"  # or "READ_ONLY"
)
service_account_id = result["service_account"]["id"]

# List service accounts in a project
service_accounts = client.list_service_accounts(project="my_org/my_project")
for sa in service_accounts:
    print(f"{sa['name']}: {sa['role']} (owner: {sa['owner_email']})")

# Delete a service account (owner or project admin only)
client.delete_service_account(service_account_id="<service_account_id>")
python

Generating and using tokens

# Generate initial token (owner only, 28-day expiration)
token_result = client.generate_service_account_token(
    service_account_id="<service_account_id>"
)
sa_token = token_result["token"]
expires_at = token_result["expires_at"]

# Use the service account token
sa_client = featuremesh.OfflineClient(
    access_token=sa_token,
    backend=featuremesh.Backend.DUCKDB,
    sql_executor=query_duckdb
)

# Query features with service account
result = sa_client.query("SELECT feature1 := 1, feature2 := 2;")
python

Self-service token refresh

Service accounts can refresh their own tokens - this is the ONLY access management operation they can perform:

# Using a service account token
sa_access_client = featuremesh.create_access_client(sa_token)

# Refresh token (returns new 28-day token)
refresh_result = sa_access_client.refresh_service_account_token()
new_sa_token = refresh_result["token"]
new_expires_at = refresh_result["expires_at"]
python

Service account restrictions

Service accounts CANNOT:

  • Create other service accounts
  • Delete service accounts
  • Generate tokens for other service accounts
  • Create or manage orgs, projects, roles, or invitations
  • Access any access management endpoints (except token refresh)

Service accounts CAN:

  • Query features according to their role (READ_ONLY or READ_WRITE)
  • Refresh their own token before it expires

Automatic cleanup

Service accounts are automatically deleted when:

  • The owner loses access to the project (role revocation)
  • The owner is removed from the project
  • The project admin explicitly deletes them

When a service account is deleted, all its tokens become invalid immediately.

Best practices

  1. Use descriptive names: ml-training-service, data-pipeline-bot, analytics-worker
  2. Minimum permissions: Use READ_ONLY unless write access is required
  3. Token rotation: Refresh tokens before the 28-day expiration
  4. One service account per service: Don't share service accounts across different applications
  5. Monitor audit logs: Service account operations are logged with the owner's user ID

Security operations

client.invalidate_token()  # invalidates your current token immediately
client.invalidate_all_user_tokens(user_email=<user_email>, org=<org_name>)  # org OWNER only
client.get_audit_log(org=<org_name>, limit=100)  # returns recent access changes
client.get_audit_log(project=<project_reference>, limit=100)
python

Token invalidation:

  • invalidate_token() - Invalidates your current token immediately
  • invalidate_all_user_tokens() - Org OWNERs can invalidate all tokens for a specific user in their org
  • After invalidation, only tokens issued after the invalidation timestamp are valid

Audit log access:

  • get_audit_log(org=...) - Available to OWNER and ADMIN of the org
  • get_audit_log(project=...) - Available to OWNER and ADMIN of the project
  • READ_WRITE and READ_ONLY users cannot access audit logs

Audit logs include: who did what, when, from which IP.

Error handling

All operations return success/failure status. Common errors:

  • PERMISSION_DENIED: you lack the required role
  • NOT_FOUND: org/project/user doesn't exist
  • ALREADY_EXISTS: duplicate name
  • LAST_OWNER: cannot remove the last OWNER
  • INVALID_EMAIL: email format is wrong
  • INVITATION_EXPIRED: invitation is older than 30 days
  • INVALID_TOKEN: token expired or invalidated
  • USERS_STILL_PRESENT: cannot delete project/org while other users have access
  • PROJECTS_STILL_PRESENT: cannot delete org while it contains projects
  • VALIDATION_REQUIRED: deletion requires validation_code (returned in error message)
Last update at: 2025/11/06 07:00:15
Last updated: 2025-11-06 07:00:51