Groups, Roles & Permissions

Roles, groups, and permissions in Arthur work through a hierarchical role-based access control (RBAC) model — and if your organization uses an identity provider (IDP) like Okta or Azure AD, you can configure IDP group mapping so that adding a user to an IDP group automatically grants them the correct Arthur access, with no manual steps required inside Arthur. This page explains how the permission model works, what roles are available, and how to configure IDP-managed group membership for your organization.

Overview

Managing access for dozens or hundreds of users one-by-one doesn't scale. Arthur's permission system is designed to solve this with three complementary mechanisms:

  1. Roles — named sets of permissions (e.g., admin, member, viewer) that define what a principal can do.
  2. Role bindings — assignments that attach a role to a user or group at a specific scope (org, workspace, or project).
  3. Groups — collections of users that can receive a single role binding, so you manage membership in one place rather than updating individual bindings.

When you combine groups with IDP group mapping, your identity provider becomes the single source of truth for Arthur access. A user added to the arthur-data-scientists Okta group is automatically placed in the corresponding Arthur group and inherits its role bindings — no Arthur admin action required.

flowchart TD
    IDP["Identity Provider (Okta / Azure AD)"]
    AG["Arthur Group"]
    RB["Role Binding (e.g., Workspace Member)"]
    U["User"]

    IDP -- "IDP group mapping" --> AG
    AG -- "has role binding" --> RB
    U -- "member of IDP group" --> IDP
    U -- "inherits permissions" --> RB

The Arthur Permission Model

Arthur uses a three-level hierarchy. Permissions granted at a higher level flow down unless overridden at a lower level.

graph TD
    ORG["Organization (top level)"]
    WS["Workspace (groups related projects)"]
    PROJ["Project (individual AI model or agent)"]

    ORG --> WS
    WS --> PROJ
LevelScopeTypical use
OrganizationAll workspaces and projects in the tenantOrg-wide admins, billing contacts
WorkspaceAll projects within a workspaceTeam leads, ML engineers working on a product area
ProjectA single model or agentExternal reviewers, limited-access contractors

A role binding at the organization level grants access across every workspace and project. A role binding at the workspace level grants access only within that workspace and its projects. A role binding at the project level is the most narrowly scoped.

Inheritance note: A user with an org-level role automatically has at least that level of access in every workspace and project. You can grant a more permissive role at a lower level (e.g., a workspace admin who is only an org member), but you cannot restrict an org-level role at a lower scope.


Built-In Roles

Arthur ships with a set of built-in roles. You can list all available roles via the API:

roles = arthur_client.get("/api/v1/organization/roles")
print(roles.json())
const response = await fetch("https://<your-arthur-host>/api/v1/organization/roles", {
  method: "GET",
  headers: {
    "Authorization": "Bearer <your-token>",
    "Content-Type": "application/json"
  }
});
const roles = await response.json();
console.log(roles);
curl -X GET https://<your-arthur-host>/api/v1/organization/roles \
  -H "Authorization: Bearer <your-token>" \
  -H "Content-Type: application/json"

The roles returned by this endpoint are the authoritative list for your deployment. Built-in roles are organized by scope:

Organization-scoped roles (bindable at the org level):

RoleDescription
Organization Super AdminFull read and write access to all resources and configurations in the account — org, all workspaces, and all projects
Organization AdminRead and write access to the organization and its configuration. Does not grant access to workspace or project resources
Organization Read AllFull read access to all resources in the account — org, all workspaces, and all projects
Organization ReaderRead access to the organization and its configuration. Does not grant access to workspace or project resources
Organization MemberMinimal read access that allows a user to view the basic Arthur UI

Workspace-scoped roles (bindable at the workspace level):

RoleDescription
Workspace Super AdminFull read and write access to all resources and configurations in a workspace and its projects
Workspace AdminRead and write access to a workspace and its configuration. Does not grant access to project resources or engine management
Workspace Read AllFull read access to a workspace and all projects within it
Workspace ReaderRead access to a workspace and its configuration. Does not grant access to project resources
Governance AdminAccess to view and manage workspace governance features
Custom Aggregation ManagerRead and write access to all custom aggregations in a workspace
Engine ManagerRead access to a workspace plus write access to engines in the workspace

Project-scoped roles (bindable at the project level):

RoleDescription
Project AdminRead and write access to a project and all its resources
Project ReaderRead access to a project and its resources. Does not grant access to raw dataset data

Multi-scope role (bindable at org, workspace, or project level):

RoleDescription
Raw Data ReaderRead access to view raw dataset data within the bound scope

Use GET /api/v1/permissions to retrieve the full list of granular permissions, and POST /api/v1/permissions/check to programmatically verify whether a principal has a specific permission.


Groups and Bulk Access Management

A group is a named collection of users. Instead of creating one role binding per user, you create a single role binding for the group and manage membership separately. This is especially powerful when combined with IDP group mapping (see below).

Why use groups?

  • Onboarding: Add a new hire to the ml-engineers group in your IDP — they immediately have the right Arthur access.
  • Offboarding: Remove a user from the IDP group — their Arthur access is revoked automatically.
  • Auditing: Review a group's role bindings in one API call rather than scanning individual user bindings.

View a group's role bindings

group_id = "grp_abc123"
bindings = arthur_client.get(f"/api/v1/groups/{group_id}/role_bindings")
print(bindings.json())
const groupId = "grp_abc123";
const response = await fetch(
  `https://<your-arthur-host>/api/v1/groups/${groupId}/role_bindings`,
  {
    method: "GET",
    headers: {
      "Authorization": "Bearer <your-token>",
      "Content-Type": "application/json"
    }
  }
);
const bindings = await response.json();
console.log(bindings);
curl -X GET https://<your-arthur-host>/api/v1/groups/grp_abc123/role_bindings \
  -H "Authorization: Bearer <your-token>" \
  -H "Content-Type: application/json"

Assign Roles at Org, Workspace, and Project Level

Role bindings connect a principal (user or group) to a role at a specific scope. The following examples show how to create role bindings at each level.

Organization-level role binding

import json

payload = {
    "principal_id": "grp_abc123",      # group or user ID
    "principal_type": "group",          # "user" or "group"
    "role": "Organization Member"
}
response = arthur_client.post(
    "/api/v1/organization/role_bindings",
    data=json.dumps(payload)
)
print(response.json())
const response = await fetch(
  "https://<your-arthur-host>/api/v1/organization/role_bindings",
  {
    method: "POST",
    headers: {
      "Authorization": "Bearer <your-token>",
      "Content-Type": "application/json"
    },
    body: JSON.stringify({
      principal_id: "grp_abc123",
      principal_type: "group",
      role: "Organization Member"
    })
  }
);
const binding = await response.json();
console.log(binding);
curl -X POST https://<your-arthur-host>/api/v1/organization/role_bindings \
  -H "Authorization: Bearer <your-token>" \
  -H "Content-Type: application/json" \
  -d '{
    "principal_id": "grp_abc123",
    "principal_type": "group",
    "role": "Organization Member"
  }'

Workspace-level role binding

import json

workspace_id = "ws_xyz789"
payload = {
    "principal_id": "grp_abc123",
    "principal_type": "group",
    "role": "Workspace Admin"
}
response = arthur_client.post(
    f"/api/v1/workspaces/{workspace_id}/role_bindings",
    data=json.dumps(payload)
)
print(response.json())
const workspaceId = "ws_xyz789";
const response = await fetch(
  `https://<your-arthur-host>/api/v1/workspaces/${workspaceId}/role_bindings`,
  {
    method: "POST",
    headers: {
      "Authorization": "Bearer <your-token>",
      "Content-Type": "application/json"
    },
    body: JSON.stringify({
      principal_id: "grp_abc123",
      principal_type: "group",
      role: "Workspace Admin"
    })
  }
);
const binding = await response.json();
console.log(binding);
curl -X POST https://<your-arthur-host>/api/v1/workspaces/ws_xyz789/role_bindings \
  -H "Authorization: Bearer <your-token>" \
  -H "Content-Type: application/json" \
  -d '{
    "principal_id": "grp_abc123",
    "principal_type": "group",
    "role": "Workspace Admin"
  }'

Project-level role binding

import json

project_id = "proj_def456"
payload = {
    "principal_id": "usr_reviewer01",
    "principal_type": "user",
    "role": "Project Reader"
}
response = arthur_client.post(
    f"/api/v1/projects/{project_id}/role_bindings",
    data=json.dumps(payload)
)
print(response.json())
const projectId = "proj_def456";
const response = await fetch(
  `https://<your-arthur-host>/api/v1/projects/${projectId}/role_bindings`,
  {
    method: "POST",
    headers: {
      "Authorization": "Bearer <your-token>",
      "Content-Type": "application/json"
    },
    body: JSON.stringify({
      principal_id: "usr_reviewer01",
      principal_type: "user",
      role: "Project Reader"
    })
  }
);
const binding = await response.json();
console.log(binding);
curl -X POST https://<your-arthur-host>/api/v1/projects/proj_def456/role_bindings \
  -H "Authorization: Bearer <your-token>" \
  -H "Content-Type: application/json" \
  -d '{
    "principal_id": "usr_reviewer01",
    "principal_type": "user",
    "role": "Project Reader"
  }'

Remove a role binding

To revoke access, delete the role binding by its ID:

role_binding_id = "rb_001"
arthur_client.delete(f"/api/v1/role_bindings/{role_binding_id}")
const roleBindingId = "rb_001";
await fetch(
  `https://<your-arthur-host>/api/v1/role_bindings/${roleBindingId}`,
  {
    method: "DELETE",
    headers: { "Authorization": "Bearer <your-token>" }
  }
);
curl -X DELETE https://<your-arthur-host>/api/v1/role_bindings/rb_001 \
  -H "Authorization: Bearer <your-token>"

IDP Group Mapping

IDP group mapping is the feature that makes large-scale access management practical. Once configured, Arthur listens to group membership claims in your IDP's tokens and automatically syncs users into the corresponding Arthur groups — no manual Arthur admin action required.

How it works

sequenceDiagram
    participant Admin as IT Admin
    participant IDP as Identity Provider (Okta)
    participant Arthur as Arthur Platform

    Admin->>IDP: Add user to "arthur-ml-engineers" group
    Note over IDP: User's token now includes group claim
    IDP->>Arthur: User authenticates (SSO)
    Arthur->>Arthur: Read group claims from token
    Arthur->>Arthur: Sync user into mapped Arthur group
    Arthur->>Arthur: Apply group's role bindings
    Note over Arthur: User has workspace Member access

Configuration steps

Prerequisites: You must be an org_admin and have SSO already configured for your organization. Contact your Arthur account team if SSO is not yet enabled.

Step 1 — Identify your IDP groups

Decide which IDP groups should map to Arthur access. A common pattern:

IDP GroupArthur GroupRole Binding
arthur-adminsArthur AdminsOrg Organization Admin
arthur-ml-engineersML EngineersWorkspace Workspace Reader on Production workspace
arthur-reviewersModel ReviewersProject Project Reader on specific projects

Step 2 — Create Arthur groups

Create a group in Arthur for each IDP group you want to map. Note the returned group_id — you will need it for the mapping configuration.

Step 3 — Configure the IDP group claim

In your IDP (e.g., Okta), ensure that group membership is included as a claim in the SAML assertion or OIDC token sent to Arthur. The claim name (e.g., groups) must match what Arthur is configured to read.

Work with your Arthur account team or platform administrator to set the expected claim name in Arthur's SSO configuration.

Step 4 — Map IDP group names to Arthur groups

In Arthur's admin settings, create a mapping entry that links the IDP group name (as it appears in the token claim) to the Arthur group ID.

Step 5 — Assign role bindings to the Arthur group

Once the mapping is in place, assign role bindings to the Arthur group using the API calls shown in the Assign Roles section above. These bindings are inherited by every user who is a member of the mapped IDP group.

Step 6 — Test the mapping

  1. Add a test user to the IDP group.
  2. Have the test user log in to Arthur via SSO.
  3. Verify the user appears in the Arthur group and has the expected access.
# Verify the group's role bindings are correct
group_id = "grp_ml_engineers"
bindings = arthur_client.get(f"/api/v1/groups/{group_id}/role_bindings")
print(bindings.json())

# Verify a specific user's effective role bindings
user_id = "usr_test_user"
user_bindings = arthur_client.get(f"/api/v1/users/{user_id}/role_bindings")
print(user_bindings.json())
// Verify the group's role bindings
const groupResponse = await fetch(
  "https://<your-arthur-host>/api/v1/groups/grp_ml_engineers/role_bindings",
  { headers: { "Authorization": "Bearer <your-token>" } }
);
console.log("Group bindings:", await groupResponse.json());

// Verify a specific user's effective role bindings
const userResponse = await fetch(
  "https://<your-arthur-host>/api/v1/users/usr_test_user/role_bindings",
  { headers: { "Authorization": "Bearer <your-token>" } }
);
console.log("User bindings:", await userResponse.json());
# Verify the group's role bindings
curl -X GET https://<your-arthur-host>/api/v1/groups/grp_ml_engineers/role_bindings \
  -H "Authorization: Bearer <your-token>"

# Verify a specific user's effective role bindings
curl -X GET https://<your-arthur-host>/api/v1/users/usr_test_user/role_bindings \
  -H "Authorization: Bearer <your-token>"

Checking a specific permission

You can programmatically verify whether a principal has a specific permission using the permissions check endpoint:

import json

payload = {
    "principal_id": "usr_test_user",
    "principal_type": "user",
    "permission": "project:read",
    "resource_id": "proj_def456",
    "resource_type": "project"
}
result = arthur_client.post(
    "/api/v1/permissions/check",
    data=json.dumps(payload)
)
print(result.json())  # {"allowed": true}
const response = await fetch(
  "https://<your-arthur-host>/api/v1/permissions/check",
  {
    method: "POST",
    headers: {
      "Authorization": "Bearer <your-token>",
      "Content-Type": "application/json"
    },
    body: JSON.stringify({
      principal_id: "usr_test_user",
      principal_type: "user",
      permission: "project:read",
      resource_id: "proj_def456",
      resource_type: "project"
    })
  }
);
const result = await response.json();
console.log(result); // { allowed: true }
curl -X POST https://<your-arthur-host>/api/v1/permissions/check \
  -H "Authorization: Bearer <your-token>" \
  -H "Content-Type: application/json" \
  -d '{
    "principal_id": "usr_test_user",
    "principal_type": "user",
    "permission": "project:read",
    "resource_id": "proj_def456",
    "resource_type": "project"
  }'

Manage Permissions in the UI

All of the operations described above are also available in the Arthur platform UI, which is the recommended starting point for most admins.

Organization-level access

  1. Navigate to Settings → Organization → Members.
  2. Use the Groups tab to create and manage groups.
  3. Use the Role Bindings tab to assign org-level roles to users or groups.
  4. Use the IDP Mapping tab to configure IDP group → Arthur group mappings.

Workspace-level access

  1. Open the workspace from the left navigation.
  2. Navigate to Workspace Settings → Access.
  3. Click Add Role Binding to assign a role to a user or group within this workspace.

Project-level access

  1. Open the project.
  2. Navigate to Project Settings → Access.
  3. Click Add Role Binding to assign a narrowly scoped role for this project only.

Tip: The UI shows the effective permissions for each user, including permissions inherited from group memberships and higher-level scopes. Use this view to audit access before making changes.


Next Steps

Now that you understand how roles, groups, and permissions work in Arthur, here are the recommended next steps for your organization:

TaskWhere to go
Set up SSO for your organizationSettings → Organization → SSO Configuration
Create your first workspace and assign a teamWorkspaces guide
Invite users and assign them to groupsSettings → Organization → Members
Configure alert notifications for your teamAlert Rules guide
Audit current access across your orgUse GET /api/v1/organization/role_bindings and GET /api/v1/workspaces/{workspace_id}/role_bindings

For questions about SSO configuration or IDP group mapping setup, contact your Arthur account team or open a support ticket from the platform.