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.
| Role | Description |
|---|---|
| Owner | Full control. Created automatically for the org creator. |
| Admin | Manage workflows, teams, invites, delete resources. |
| Member | Create and update projects, tickets, sprints, time entries. |
| Reporter | Create tickets and comments. Limited to assigned projects. |
| Viewer | Read-only access to all resources. |
Full Permission Matrix
| Operation | Minimum Role |
|---|---|
| Orgs | |
| Create / update org | Owner |
| Invites | |
| Create / revoke invite | Admin |
| List invites | Any authenticated |
| Workflows | |
| Create / update / delete workflow | Admin |
| Teams | |
| Delete team | Admin |
| Projects | |
| Create / update project | Member |
| Delete project | Admin |
| Project Members | |
| Add / remove project member | Member |
| List project members | Any authenticated |
| Tickets | |
| Create ticket | Reporter (must be assigned to the project) |
| Update / transition ticket | Member |
| Delete ticket | Admin |
| Comments | |
| Create comment | Reporter |
| Update / delete comment | Author or Admin |
| Sprints | |
| Create / update / start / complete sprint | Member |
| Delete sprint | Admin |
| Time Entries | |
| Create / update / delete / submit time entry | Member |
| Approve time entry | Admin |
| Labor Rates | |
| All labor rate operations | Admin |
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.
| Role | Project Visibility |
|---|---|
| Member and above | All projects in the organization |
| Reporter | Only projects where they are a project member |
| Viewer | All 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 Status | Meaning |
|---|---|
| 401 | Missing or invalid authentication token |
| 403 | Role is insufficient for the requested operation |
| 404 | Resource 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}/membersto 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
- Projects & Tickets — manage the work your team members collaborate on
- Time Tracking & Finance — track hours and labor costs per team member
- Setting Up a New Team (Playbook) — end-to-end guide for onboarding a new team
- For Engineering Managers — role-based guide for managing engineering teams