Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Teams, Roles & Permissions

Every Alloy organization has a role-based access control system with five roles: Owner, Admin, Member, Reporter, and Viewer. Teams let you group users for project assignment and time-tracking reports. This guide walks through inviting users, managing roles, assigning project members, and handling permission boundaries.

1. Prerequisites

You need a running Alloy server and a registered user. Set these shell variables for the examples below:

BASE_URL="http://localhost:3000"

Register a user and capture the token:

curl -s -X POST "$BASE_URL/api/v1/auth/register" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "guide-tp@alloy.dev",
    "password": "guide-tp-pass1",
    "display_name": "TP Guide User"
  }' | jq .
{
  "user_id": "...",
  "email": "guide-tp@alloy.dev",
  "display_name": "TP Guide User",
  "access_token": "..."
}

Save the token and user ID:

TOKEN="<access_token from above>"
USER_ID="<user_id from above>"

Create an organization:

curl -s -X POST "$BASE_URL/api/v1/orgs" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  -d '{
    "name": "Permissions Guide Org",
    "slug": "guide-tp-org"
  }' | jq .
{
  "id": "...",
  "name": "Permissions Guide Org",
  "slug": "guide-tp-org"
}
ORG_ID="<id from above>"

2. Roles Overview

Every organization member has exactly one role. Roles are hierarchical — each role inherits all permissions of the roles below it.

RoleDescription
OwnerFull control. Created automatically for the org creator.
AdminManage workflows, teams, invites, delete resources.
MemberCreate and update projects, tickets, sprints, time entries.
ReporterCreate tickets and comments. Limited to assigned projects.
ViewerRead-only access to all resources.

Full Permission Matrix

OperationMinimum Role
Orgs
Create / update orgOwner
Invites
Create / revoke inviteAdmin
List invitesAny authenticated
Workflows
Create / update / delete workflowAdmin
Teams
Delete teamAdmin
Projects
Create / update projectMember
Delete projectAdmin
Project Members
Add / remove project memberMember
List project membersAny authenticated
Tickets
Create ticketReporter (must be assigned to the project)
Update / transition ticketMember
Delete ticketAdmin
Comments
Create commentReporter
Update / delete commentAuthor or Admin
Sprints
Create / update / start / complete sprintMember
Delete sprintAdmin
Time Entries
Create / update / delete / submit time entryMember
Approve time entryAdmin
Labor Rates
All labor rate operationsAdmin

3. Invite a User

Invites let you bring new users into your organization with a specific role. Only Admins and above can create invites.

Create an invite for a new team member:

curl -s -X POST "$BASE_URL/api/v1/orgs/$ORG_ID/invites" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  -d "{\"email\": \"alice-tp@alloy.dev\", \"role\": \"member\", \"created_by\": \"$USER_ID\"}" | jq .
{
  "id": "...",
  "invite_code": "...",
  "invite_link": "...",
  "email": "alice-tp@alloy.dev",
  "role": "Member",
  "expires_at": "..."
}

The response includes an invite_code that the recipient uses when registering. The invite_link is a ready-to-share URL.

INVITE_CODE="<invite_code from above>"

CLI shortcut:

alloy org invite create --org "$ORG_ID" --email alice-tp@alloy.dev --role member

4. List Invites

View all pending invites for the organization. Any authenticated member can list invites:

curl -s "$BASE_URL/api/v1/orgs/$ORG_ID/invites" \
  -H "Authorization: Bearer $TOKEN" | jq .
[
  {
    "id": "...",
    "org_id": "...",
    "email": "alice-tp@alloy.dev",
    "invite_code": "...",
    "role": "Member",
    "created_by": "...",
    "expires_at": "...",
    "accepted_at": null,
    "revoked_at": null,
    "created_at": "..."
  }
]

5. Register with an Invite Code

A user who receives an invite registers with the invite code to automatically join the organization with the assigned role:

curl -s -X POST "$BASE_URL/api/v1/auth/register" \
  -H "Content-Type: application/json" \
  -d "{
    \"email\": \"alice-tp@alloy.dev\",
    \"password\": \"alice-tp-pass1\",
    \"display_name\": \"Alice Member\",
    \"invite_code\": \"$INVITE_CODE\"
  }" | jq .
{
  "user_id": "...",
  "email": "alice-tp@alloy.dev",
  "display_name": "Alice Member",
  "access_token": "..."
}

The new user is now a Member of the organization. In a real workflow you would save Alice’s credentials separately from the Owner’s token.

6. List Organization Members

Verify that both the Owner and the invited user appear in the member list:

curl -s "$BASE_URL/api/v1/orgs/$ORG_ID/members" \
  -H "Authorization: Bearer $TOKEN" | jq .
{
  "items": [
    {
      "user_id": "...",
      "display_name": "...",
      "email": "...",
      "role": "...",
      "joined_at": "..."
    }
  ]
}

The org creator has role Owner. Alice (who registered with the invite code) has role member.

CLI shortcut:

alloy org members list --org "$ORG_ID"

7. Create a Project and Add Members

Create a project, then add users as project members. Project membership controls which projects Reporters can access (see Section 8).

curl -s -X POST "$BASE_URL/api/v1/projects" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  -d "{
    \"org_id\": \"$ORG_ID\",
    \"key\": \"PERM\",
    \"name\": \"Permissions Demo\",
    \"description\": \"Project for the permissions guide\"
  }" | jq .
{
  "id": "...",
  "org_id": "...",
  "key": "PERM",
  "name": "Permissions Demo",
  "description": "Project for the permissions guide",
  "ticket_counter": 0
}

Add a user as a project member:

curl -s -X POST "$BASE_URL/api/v1/projects/$PROJECT_ID/members" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  -d "{\"user_id\": \"$USER_ID\"}" | jq .
{
  "project_id": "...",
  "user_id": "...",
  "created_at": "..."
}

List project members:

curl -s "$BASE_URL/api/v1/projects/$PROJECT_ID/members" \
  -H "Authorization: Bearer $TOKEN" | jq .
[
  {
    "project_id": "...",
    "user_id": "...",
    "created_at": "..."
  }
]

CLI shortcut:

alloy project members add --project "$PROJECT_ID" --user "$USER_ID"
alloy project members list --project "$PROJECT_ID"

8. Reporter Scoping

Reporters only see projects they are explicitly assigned to as project members. When a Reporter calls GET /api/v1/projects, only assigned projects are returned. Attempting to access an unassigned project returns 403.

This scoping is silent — the Reporter receives fewer results rather than an error when listing. This makes it safe to give Reporters broad read access while limiting their view to relevant projects.

RoleProject Visibility
Member and aboveAll projects in the organization
ReporterOnly projects where they are a project member
ViewerAll projects (read-only)

To grant a Reporter access, add them as a project member (see Section 7).

9. Handling Forbidden Actions

When a user tries to perform an action above their role, the API returns a 403 Forbidden error with a JSON body explaining which role is required.

For example, a Viewer attempting to create a project:

curl -s -X POST "$BASE_URL/api/v1/projects" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $VIEWER_TOKEN" \
  -d "{
    \"org_id\": \"$ORG_ID\",
    \"key\": \"DENY\",
    \"name\": \"Should Fail\"
  }" | jq .

The API responds with a 403 and a structured error body:

{
  "error": {
    "code": "FORBIDDEN",
    "message": "requires Member role or above",
    "details": []
  }
}

The response body always includes a machine-readable code and a human-readable message indicating the minimum role needed.

Common permission errors and their causes:

HTTP StatusMeaning
401Missing or invalid authentication token
403Role is insufficient for the requested operation
404Resource not found (or hidden by tenant isolation)

Tip: Use curl -w "\nHTTP_STATUS: %{http_code}\n" to see the status code alongside the response body when debugging permission issues.

10. Best Practices

  • Start with the least privilege. Invite new users as Reporters or Viewers, then promote as needed.
  • Use project membership for scoping. Rather than creating separate organizations, use project membership to control which Reporters see which work.
  • Audit with the members list. Regularly review GET /orgs/{id}/members to ensure role assignments are up to date.
  • Prefer invites over direct adds. Invites create an audit trail and let the recipient choose their own password.
  • Revoke unused invites. Delete pending invites with DELETE /orgs/{id}/invites/{invite_id} when they are no longer needed.
  • Use SCIM for large teams. If your identity provider supports SCIM 2.0, use the /scim/v2/ endpoints to automate user and group provisioning instead of managing invites manually.

Learn More