Skip to main content

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.
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

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:
ScopeAccess LevelExample
SELFOnly your own dataView your own savings balance
ANYAll organization dataView 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:
GET /me/permissions
Headers:
  Authorization: Bearer {accessToken}
  x-organization-id: {organizationId}
Response:
{
  "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:
GET /role-definitions
Headers:
  Authorization: Bearer {accessToken}
  x-organization-id: {organizationId}
Permission required: organization_user_roles:write Response:
[
  {
    "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:
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:
FieldTypeRequiredDescription
keystringYesUnique role identifier (lowercase, no spaces)
namestringYesDisplay name
descriptionstringNoRole description
isEditablebooleanNoCan role be modified? (default: true)
tagColorstringNoUI color tag (default: “SLATE”)
Response:
{
  "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.):
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:
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:
FieldTypeRequiredDescription
grantsarrayYesArray of permission grants
grants[].permissionKeystringYesPermission key (e.g., “savings:read”)
grants[].scopeenumYes”SELF” or “ANY”
Empty grants array removes all permissions from role (allowed). Response: Role object with updated grants

Delete Role Definition

Delete a role:
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:
{
  "message": "Role definition deleted successfully"
}
Error if protected role:
{
  "message": "Protected role definitions cannot be deleted"
}

Assign Role to User

Grant a role to organization user:
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:
FieldTypeRequiredDescription
roleDefinitionIdstringYesID of role to assign
assignedAtstring (ISO date)NoAssignment 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:
{
  "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:
StatusMessageCause
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:
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:
{
  "message": "Direct admin removal is not allowed. Use set-admin"
}

Update Role Assignment Date

Change assignment date for a role:
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:
// 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

ErrorStatusMeaningSolution
”Authorization header is required”401Missing or invalid access tokenLogin and include valid Bearer token
”X-Organization-ID header is required”401Missing organization headerInclude x-organization-id header
”OrganizationUser not found for this organization”401User not member of organizationVerify organization ID, check membership
”Insufficient permissions”403Lack required permission entirelyAsk administrator to grant permission
”Insufficient permission scope”403Have permission with SELF, need ANYAsk administrator to grant ANY scope
”Permission scope denied”403Trying to access others’ data with SELF scopeOnly access your own data, or request ANY scope
”Protected role definitions cannot be deleted”403Trying to delete protected roleCannot delete, use different role
”Role definition is not editable”403Trying to modify non-editable roleCannot modify, create new role instead
”Direct admin assignment is not allowed. Use set-admin”403Trying to assign/unassign admin roleUse 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

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

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:
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//grants (add permissions)
  3. POST /organization-users//role-assignments (assign to users)
See API endpoint documentation above for details.

Member Roles

Role management guide

Permissions Matrix

Complete permission reference

Managing Members

Add and manage organization users

Audit Trail

Track role and permission changes