> ## Documentation Index
> Fetch the complete documentation index at: https://help.agatabo.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Understanding Permissions

> Role-based access control in Agatabo

## Overview

Agatabo uses **role-based access control (RBAC)** to manage who can perform what actions. Permissions are granted through roles, which are assigned to organization users.

<Note>
  **Key features**:

  * Roles are organization-specific (each organization defines its own roles)
  * Users can have multiple roles simultaneously
  * Permissions are cumulative (union of all assigned roles)
  * ANY scope takes precedence over SELF scope when same permission granted multiple times
</Note>

***

## Key Concepts

### Permissions

A **permission** is the ability to perform a specific action on a resource.

**Format**: `resource:action`

**Examples**:

* `savings:write` - Record deposits and withdrawals
* `loans:read` - View loan details
* `expenses:write` - Record expenses
* `audit_logs:read` - View audit trail

***

### Scopes

Each permission has a **scope** that determines what data you can access:

| Scope    | Access Level          | Example                            |
| -------- | --------------------- | ---------------------------------- |
| **SELF** | Only your own data    | View your own savings balance      |
| **ANY**  | All organization data | View all members' savings balances |

**How scopes work**:

* `savings:read` with **SELF** scope: Can only view own savings balance
* `savings:read` with **ANY** scope: Can view all members' savings balances
* `loans:write` with **SELF** scope: Can only apply for loans for yourself
* `loans:write` with **ANY** scope: Can create loans for any member

**Scope precedence**:

* If user has same permission with different scopes (from multiple roles), **ANY scope takes precedence**
* Example: User has `savings:read` with SELF from "Member" role + `savings:read` with ANY from "Treasurer" role = user gets **ANY scope** access

***

### Roles

A **role** is a collection of permissions assigned to a user.

**Roles are organization-specific**:

* Each organization defines its own roles
* Role names and permissions configured by administrators
* No global/predefined roles in the system

**Special roles**:

* **admin**: Protected role, cannot be directly assigned/unassigned (must use set-admin endpoint)
* **member**: When assigned, automatically creates a savings ledger account for the user

**Role properties**:

* `key`: Unique identifier (e.g., "treasurer", "loan-officer")
* `name`: Display name (e.g., "Treasurer", "Loan Officer")
* `description`: Optional description of role purpose
* `isProtected`: If true, role cannot be deleted
* `isEditable`: If true, role metadata and grants can be modified
* `tagColor`: UI color tag (SLATE, RED, BLUE, GREEN, etc.)

***

## Complete Permission List

**Organization Users** (`organization_users`):

* `organization_users:read` - View member list and details
* `organization_users:write` - Add, edit, remove members
* `organization_user_roles:read` - View role definitions
* `organization_user_roles:write` - Create, edit, delete role definitions
* `organization_user_roles:assign` - Assign/unassign roles to users

**Savings** (`savings`):

* `savings:read` - View deposit/withdrawal history
* `savings:write` - Record deposits and withdrawals

**Loans** (`loans`):

* `loans:read` - View loan details and installment schedules
* `loans:write` - Create new loans, record payments
* `loans:modify` - Modify existing loan terms
* `loans:approve` - Approve loan applications

**Bank Accounts** (`bank_accounts`):

* `bank_accounts:read` - View bank account list and balances
* `bank_accounts:write` - Create, update, delete bank accounts

**Expenses** (`expenses`):

* `expenses:read` - View expense history
* `expenses:write` - Record and categorize expenses

**Assets** (`assets`):

* `assets:read` - View fixed asset register
* `assets:write` - Add, modify, dispose assets

**Reserves** (`reserves`):

* `reserves:read` - View reserve balances
* `reserves:write` - Allocate to and release from reserves

**Dividends** (`dividends`):

* `dividends:read` - View dividend pools and distributions
* `dividends:write` - Create pools and distribute dividends

**General Ledger** (`ledger`):

* `ledger:read` - View ledger accounts, journal entries, trial balance
* `ledger:write` - Post manual journal entries

**Reports** (`reports`):

* `reports:read` - Access financial reports (balance sheet, profit & loss, etc.)

**Settings** (`settings`):

* `settings:read` - View organization settings
* `settings:write` - Modify organization settings

**Period Closing** (`periods`):

* `periods:close` - Close accounting periods

**Audit Trail** (`audit_logs`):

* `audit_logs:read` - View audit trail (requires ANY scope)

***

## API Endpoints

### Get My Permissions

**Retrieve your own permissions:**

```http theme={null}
GET /me/permissions
Headers:
  Authorization: Bearer {accessToken}
  x-organization-id: {organizationId}
```

**Response:**

```json theme={null}
{
  "organizationUserId": "orguser-123",
  "roleKeys": ["treasurer", "member"],
  "grants": [
    {
      "permissionKey": "savings:read",
      "scope": "ANY"
    },
    {
      "permissionKey": "savings:write",
      "scope": "ANY"
    },
    {
      "permissionKey": "expenses:write",
      "scope": "ANY"
    }
  ]
}
```

**System admins** get all permissions with ANY scope automatically.

***

### List Role Definitions

**Get all roles in organization:**

```http theme={null}
GET /role-definitions
Headers:
  Authorization: Bearer {accessToken}
  x-organization-id: {organizationId}
```

**Permission required**: `organization_user_roles:write`

**Response:**

```json theme={null}
[
  {
    "id": "role-123",
    "organizationId": "org-abc",
    "key": "treasurer",
    "name": "Treasurer",
    "description": "Handles deposits and expenses",
    "tagColor": "BLUE",
    "isProtected": false,
    "isEditable": true,
    "createdAt": "2025-01-15T10:00:00.000Z",
    "updatedAt": "2025-01-15T10:00:00.000Z",
    "grants": [
      {
        "id": "grant-1",
        "roleDefinitionId": "role-123",
        "permissionKey": "savings:write",
        "scope": "ANY",
        "createdAt": "2025-01-15T10:00:00.000Z",
        "updatedAt": "2025-01-15T10:00:00.000Z"
      },
      {
        "id": "grant-2",
        "roleDefinitionId": "role-123",
        "permissionKey": "expenses:write",
        "scope": "ANY",
        "createdAt": "2025-01-15T10:00:00.000Z",
        "updatedAt": "2025-01-15T10:00:00.000Z"
      }
    ],
    "_count": {
      "assignments": 3
    }
  }
]
```

**Response includes**:

* Role metadata (key, name, description, colors, protection status)
* All permission grants for the role
* Count of users assigned this role

**Sorted by**: Protected status (desc), then key (asc)

***

### Create Role Definition

**Create a new role:**

```http theme={null}
POST /role-definitions
Headers:
  Authorization: Bearer {accessToken}
  x-organization-id: {organizationId}
Body:
{
  "key": "accountant",
  "name": "Accountant",
  "description": "Financial reporting and ledger access",
  "isEditable": true,
  "tagColor": "GREEN"
}
```

**Permission required**: `organization_user_roles:assign`

**Request body fields**:

| Field         | Type    | Required | Description                                   |
| ------------- | ------- | -------- | --------------------------------------------- |
| `key`         | string  | Yes      | Unique role identifier (lowercase, no spaces) |
| `name`        | string  | Yes      | Display name                                  |
| `description` | string  | No       | Role description                              |
| `isEditable`  | boolean | No       | Can role be modified? (default: true)         |
| `tagColor`    | string  | No       | UI color tag (default: "SLATE")               |

**Response:**

```json theme={null}
{
  "id": "role-456",
  "organizationId": "org-abc",
  "key": "accountant",
  "name": "Accountant",
  "description": "Financial reporting and ledger access",
  "tagColor": "GREEN",
  "isProtected": false,
  "isEditable": true,
  "createdAt": "2026-06-13T14:00:00.000Z",
  "updatedAt": "2026-06-13T14:00:00.000Z"
}
```

**Note**: New role has NO permissions by default. Use "Update Role Grants" endpoint to add permissions.

***

### Update Role Definition

**Update role metadata (name, description, etc.):**

```http theme={null}
PUT /role-definitions/{id}
Headers:
  Authorization: Bearer {accessToken}
  x-organization-id: {organizationId}
Body:
{
  "name": "Senior Accountant",
  "description": "Updated description",
  "tagColor": "BLUE"
}
```

**Permission required**: `organization_user_roles:assign`

**Validation**:

* Role must exist in organization
* Role must have `isEditable: true` (protected roles cannot be edited)

**Response**: Updated role object

***

### Update Role Grants

**Replace all permissions for a role:**

```http theme={null}
PUT /role-definitions/{id}/grants
Headers:
  Authorization: Bearer {accessToken}
  x-organization-id: {organizationId}
Body:
{
  "grants": [
    {
      "permissionKey": "ledger:read",
      "scope": "ANY"
    },
    {
      "permissionKey": "reports:read",
      "scope": "ANY"
    },
    {
      "permissionKey": "savings:read",
      "scope": "ANY"
    }
  ]
}
```

**Permission required**: `organization_user_roles:assign`

**How it works**:

1. Deletes ALL existing grants for this role
2. Creates new grants from request body
3. Returns updated role with new grants

**Request body**:

| Field                    | Type   | Required | Description                           |
| ------------------------ | ------ | -------- | ------------------------------------- |
| `grants`                 | array  | Yes      | Array of permission grants            |
| `grants[].permissionKey` | string | Yes      | Permission key (e.g., "savings:read") |
| `grants[].scope`         | enum   | Yes      | "SELF" or "ANY"                       |

**Empty grants array** removes all permissions from role (allowed).

**Response**: Role object with updated grants

***

### Delete Role Definition

**Delete a role:**

```http theme={null}
DELETE /role-definitions/{id}
Headers:
  Authorization: Bearer {accessToken}
  x-organization-id: {organizationId}
```

**Permission required**: `organization_user_roles:assign`

**Validation**:

* Role must exist in organization
* Role must NOT be protected (`isProtected: false`)

**What happens**:

1. All role assignments deleted (users lose this role)
2. All permission grants deleted
3. Role definition deleted

**Response:**

```json theme={null}
{
  "message": "Role definition deleted successfully"
}
```

**Error if protected role**:

```json theme={null}
{
  "message": "Protected role definitions cannot be deleted"
}
```

***

### Assign Role to User

**Grant a role to organization user:**

```http theme={null}
POST /organization-users/{organizationUserId}/role-assignments
Headers:
  Authorization: Bearer {accessToken}
  x-organization-id: {organizationId}
Body:
{
  "roleDefinitionId": "role-123",
  "assignedAt": "2026-06-13T00:00:00.000Z"
}
```

**Permission required**: `organization_user_roles:assign`

**Request body**:

| Field              | Type              | Required | Description                       |
| ------------------ | ----------------- | -------- | --------------------------------- |
| `roleDefinitionId` | string            | Yes      | ID of role to assign              |
| `assignedAt`       | string (ISO date) | No       | Assignment date (defaults to now) |

**Special behavior**:

* **admin role**: Cannot assign directly. Use set-admin endpoint instead.
* **member role**: Automatically creates savings ledger account for user

**Response:**

```json theme={null}
{
  "id": "assignment-789",
  "organizationUserId": "orguser-123",
  "roleDefinitionId": "role-123",
  "assignedAt": "2026-06-13T00:00:00.000Z",
  "createdAt": "2026-06-13T14:30:00.000Z",
  "updatedAt": "2026-06-13T14:30:00.000Z"
}
```

**Errors**:

| Status | Message                                                 | Cause                       |
| ------ | ------------------------------------------------------- | --------------------------- |
| 400    | "Organization user not found"                           | Invalid organizationUserId  |
| 400    | "Role definition not found"                             | Invalid roleDefinitionId    |
| 403    | "Direct admin assignment is not allowed. Use set-admin" | Trying to assign admin role |

***

### Unassign Role from User

**Remove a role from organization user:**

```http theme={null}
DELETE /organization-users/{organizationUserId}/role-assignments/{roleDefinitionId}
Headers:
  Authorization: Bearer {accessToken}
  x-organization-id: {organizationId}
```

**Permission required**: `organization_user_roles:assign`

**Validation**:

* Cannot unassign **admin role** directly (use set-admin endpoint)

**Response**: Deletion count

**Error if admin role**:

```json theme={null}
{
  "message": "Direct admin removal is not allowed. Use set-admin"
}
```

***

### Update Role Assignment Date

**Change assignment date for a role:**

```http theme={null}
PATCH /organization-users/{organizationUserId}/role-assignments/{roleDefinitionId}
Headers:
  Authorization: Bearer {accessToken}
  x-organization-id: {organizationId}
Body:
{
  "assignedAt": "2026-01-01T00:00:00.000Z"
}
```

**Permission required**: `organization_user_roles:assign`

**Use case**: Backdate role assignment to reflect historical effective date

**Response**: Updated assignment object

***

## How Permissions Are Checked

When you perform an action in Agatabo:

1. **System verifies authentication**:
   * Valid access token required
   * x-organization-id header required

2. **System checks if you have required permission**:
   * Example: Recording deposit requires `savings:write`
   * Permission checked against your effective grants (from all assigned roles)

3. **System checks scope**:
   * If endpoint specifies `requiredScope: "ANY"`, you must have ANY scope
   * If endpoint has `scopeParam`, system validates you're accessing your own data (SELF scope) or allows any data (ANY scope)

4. **Action allowed or denied**:
   * ✓ Allowed: Action proceeds
   * ✗ Denied: Error "Insufficient permissions" or "Permission scope denied"

**System admins bypass all checks**: `userType: "system_admin"` grants all permissions with ANY scope automatically.

***

## Permission Guard Logic

**From permission.guard.ts:**

```typescript theme={null}
// 1. Verify JWT token
const payload = JwtHelper.verifyToken(token, process.env.JWT_SECRET!);

// 2. System admins get ALL permissions
if (payload.userType === 'system_admin') {
  return true; // Bypass permission checks
}

// 3. Resolve organization user
const actor = await organizationUserRbacService.resolveActorOrganizationUser(
  payload.id,
  organizationId
);

// 4. Build effective grants from all assigned roles
const grants = await organizationUserRbacService.buildEffectiveGrants(
  organizationId,
  actor.roles
);

// 5. Check if user has required permission
const grant = organizationUserRbacService.getGrantForPermission(
  grants,
  requirement.key
);

if (!grant) {
  throw new ForbiddenException('Insufficient permissions');
}

// 6. Check required scope (if specified)
if (requirement.options?.requiredScope && grant.scope !== requirement.options.requiredScope) {
  throw new ForbiddenException('Insufficient permission scope');
}

// 7. Check SELF scope restriction (if applicable)
if (grant.scope === 'SELF' && requirement.options?.scopeParam) {
  const targetId = request.params[requirement.options.scopeParam];
  if (targetId !== actor.organizationUserId) {
    throw new ForbiddenException('Permission scope denied');
  }
}
```

***

## Common Error Messages

| Error                                                   | Status | Meaning                                       | Solution                                        |
| ------------------------------------------------------- | ------ | --------------------------------------------- | ----------------------------------------------- |
| "Authorization header is required"                      | 401    | Missing or invalid access token               | Login and include valid Bearer token            |
| "X-Organization-ID header is required"                  | 401    | Missing organization header                   | Include x-organization-id header                |
| "OrganizationUser not found for this organization"      | 401    | User not member of organization               | Verify organization ID, check membership        |
| "Insufficient permissions"                              | 403    | Lack required permission entirely             | Ask administrator to grant permission           |
| "Insufficient permission scope"                         | 403    | Have permission with SELF, need ANY           | Ask administrator to grant ANY scope            |
| "Permission scope denied"                               | 403    | Trying to access others' data with SELF scope | Only access your own data, or request ANY scope |
| "Protected role definitions cannot be deleted"          | 403    | Trying to delete protected role               | Cannot delete, use different role               |
| "Role definition is not editable"                       | 403    | Trying to modify non-editable role            | Cannot modify, create new role instead          |
| "Direct admin assignment is not allowed. Use set-admin" | 403    | Trying to assign/unassign admin role          | Use set-admin endpoint instead                  |

***

## Example Role Configurations

These are examples. Actual roles are organization-defined.

### Administrator Role

**Suggested permissions** (all with ANY scope):

* `organization_users:write` - Manage members
* `organization_user_roles:assign` - Assign roles
* `savings:write` - Record deposits
* `loans:write`, `loans:modify`, `loans:approve` - Full loan management
* `expenses:write` - Record expenses
* `assets:write` - Manage assets
* `reserves:write` - Manage reserves
* `dividends:write` - Distribute dividends
* `bank_accounts:write` - Manage bank accounts
* `ledger:write` - Post journal entries
* `settings:write` - Change settings
* `periods:close` - Close periods
* `audit_logs:read` - View activity logs
* `reports:read` - Access reports

**Can do**: Everything

***

### Treasurer Role

**Suggested permissions** (all with ANY scope):

* `organization_users:read` - View members
* `savings:write` - Record deposits/withdrawals
* `expenses:write` - Record expenses
* `bank_accounts:read` - View bank accounts
* `ledger:read` - View ledger
* `reports:read` - View reports

**Can do**: Handle deposits, record expenses, view financial reports

**Cannot do**: Create loans, modify settings, assign roles, close periods

***

### Loan Officer Role

**Suggested permissions** (all with ANY scope):

* `organization_users:read` - View members
* `loans:write` - Create and manage loans
* `loans:modify` - Modify loan terms
* `loans:approve` - Approve loans
* `savings:read` - View savings (verify eligibility)
* `reports:read` - View reports

**Can do**: Manage all loan operations

**Cannot do**: Record deposits/expenses, change settings, assign roles

***

### Accountant Role

**Suggested permissions** (all with ANY scope):

* `savings:read` - View deposit history
* `loans:read` - View loan portfolio
* `expenses:read` - View expenses
* `assets:read` - View asset register
* `reserves:read` - View reserve balances
* `dividends:read` - View dividend history
* `bank_accounts:read` - View bank accounts
* `ledger:read` - Full ledger access
* `ledger:write` - Post adjustments
* `reports:read` - Access reports

**Can do**: View all financial data, run reports, post journal entries

**Cannot do**: Record operational transactions (deposits, loans), assign roles

***

### Member Role

**Suggested permissions** (all with SELF scope):

* `organization_users:read` - View own profile
* `savings:read` - View own savings balance
* `loans:read` - View own loans
* `ledger:read` - View own ledger account statement

**Can do**: View own financial information

**Cannot do**: View other members' data, record transactions, access settings

**Special**: Assigning "member" role automatically creates savings ledger account

***

## Best Practices

<Note>
  **Permission management guidelines:**

  **Principle of least privilege:**

  * ✅ Grant only permissions needed for job function
  * ✅ Start with minimal permissions, add as needed
  * ✅ Use SELF scope for member-facing roles
  * ✅ Reserve ANY scope for staff with organization-wide responsibilities

  **Role design:**

  * ✅ Create roles matching actual job functions
  * ✅ Use descriptive role names ("Treasurer", not "Role1")
  * ✅ Document role purpose in description field
  * ✅ Use color tags for visual organization
  * ✅ Limit number of roles (5-10 typical)

  **Role assignment:**

  * ✅ Assign roles based on user's responsibilities
  * ✅ Users can have multiple roles (permissions cumulative)
  * ✅ Backdate assignments if needed (assignedAt field)
  * ✅ Review assignments quarterly
  * ✅ Revoke roles promptly when responsibilities change

  **Security:**

  * ✅ Protect critical roles (isProtected: true)
  * ✅ Limit who has `organization_user_roles:assign`
  * ✅ Audit role changes regularly (check audit logs)
  * ✅ Don't create overly permissive "super user" roles
  * ✅ Separate duties (accountant ≠ treasurer)

  **Scope usage:**

  * ✅ Default to SELF scope for member roles
  * ✅ Use ANY scope for operational staff (treasurers, loan officers)
  * ✅ Remember ANY scope takes precedence when combined
  * ✅ Test permissions after assignment (verify access)

  **Maintenance:**

  * ✅ Document role structure in organization policies
  * ✅ Keep written record of who has what roles and why
  * ✅ Review role definitions annually (remove unused roles)
  * ✅ Update role grants when new features added
  * ✅ Train users on their permissions and limitations
</Note>

***

## Troubleshooting

**Q: Can't see expected menu or button in UI**

A: You lack required permission.

**Solution**:

1. Identify what you're trying to do
2. Check required permission (see permission list above)
3. Ask administrator to grant appropriate role
4. Log out and back in (refreshes permissions)
5. Verify menu/button now visible

***

**Q: Error: "Insufficient permissions"**

A: You don't have the required permission at all.

**Check**:

* GET /me/permissions to see your current grants
* Identify missing permission
* Ask administrator to assign role with that permission

***

**Q: Error: "Insufficient permission scope"**

A: You have permission with SELF scope, but endpoint requires ANY scope.

**Example**: Viewing audit logs requires `audit_logs:read` with ANY scope.

**Solution**: Ask administrator to grant permission with ANY scope.

***

**Q: Error: "Permission scope denied"**

A: You have permission with SELF scope, but trying to access someone else's data.

**Example**: You have `savings:read` with SELF scope, but tried to view another member's savings.

**Solutions**:

* Only access your own data, OR
* Ask administrator to grant ANY scope

***

**Q: Can view data but can't edit**

A: You have `read` permission but not `write` permission.

**Example**: Can view loans (`loans:read`) but can't create them (need `loans:write`).

**Solution**: Request `write` permission for that resource.

***

**Q: Administrator changed my role but I still can't access feature**

A: Permissions cached in access token.

**Try**:

1. **Log out completely**
2. **Log back in** (refreshes token with new permissions)
3. **Try action again**

If still doesn't work: Role may not include expected permission. Ask administrator to verify role grants.

***

**Q: How do I see my own permissions?**

A: Call GET /me/permissions endpoint:

```bash theme={null}
curl -X GET "/me/permissions" \
  -H "Authorization: Bearer {token}" \
  -H "x-organization-id: {organizationId}"
```

Response shows all your permissions and scopes.

***

**Q: Can I assign myself permissions?**

A: Only if you have `organization_user_roles:assign` permission. Cannot escalate beyond your own permissions.

**Safety**: System doesn't prevent self-assignment. Organizations should limit who has assign permission.

***

**Q: What happens when user has multiple roles?**

A: Permissions are **cumulative** (union of all roles).

**Example**:

* Role A grants: `savings:read` (SELF), `loans:read` (SELF)
* Role B grants: `savings:read` (ANY), `expenses:write` (ANY)
* **Effective grants**: `savings:read` (ANY), `loans:read` (SELF), `expenses:write` (ANY)

**Note**: ANY scope takes precedence when same permission granted with different scopes.

***

**Q: How do I create a custom role?**

A: Use role management API endpoints:

1. POST /role-definitions (create role)
2. PUT /role-definitions/{id}/grants (add permissions)
3. POST /organization-users/{userId}/role-assignments (assign to users)

See API endpoint documentation above for details.

***

## Related Topics

<CardGroup cols={2}>
  <Card title="Member Roles" icon="users" href="/tontines/organization-users/roles">
    Role management guide
  </Card>

  <Card title="Permissions Matrix" icon="table" href="/tontines/reference/permissions-matrix">
    Complete permission reference
  </Card>

  <Card title="Managing Members" icon="user-plus" href="/tontines/organization-users/managing-users">
    Add and manage organization users
  </Card>

  <Card title="Audit Trail" icon="file-magnifying-glass" href="/tontines/advanced/audit-trail">
    Track role and permission changes
  </Card>
</CardGroup>
