Skip to content

Policies and Permissions Guide

Overview

Centrali uses Resource-Based Access Control (ReBAC) to manage authorization. This guide explains how policies and permissions work, and how to create custom access rules for your workspace.

Key Concepts

Resources

Resources represent entities that can be protected. Each resource has: - Name: Unique identifier (e.g., records, collections, files) - Category: Grouping for resources (workspace, billing, high_risk) - Actions: Operations that can be performed (e.g., create, retrieve, update, delete)

Policies

Policies define the rules for granting access. They contain: - Conditions: Logic that determines when access is granted - Effect: Either Allow or Deny - Variables: Dynamic values evaluated at runtime

Permissions

Permissions connect resources to policies: - Link a Resource with a Policy - Specify which Actions are allowed - Define the Priority when multiple permissions apply

Authorization Flow

Request → Resource Lookup → Policy Evaluation → Allow/Deny
                  Conditions Checked:
                  - User groups/roles
                  - Path parameters
                  - Request metadata

System Resources

Centrali provides pre-configured system resources for each workspace:

Core Resources

Resource Actions Description
workspace create, delete, update, retrieve, list, validate, manage Workspace management
workspace::users create, delete, update, retrieve, list, validate, manage, security User management
workspace::groups create, delete, update, retrieve, list, validate, manage Group management
workspace::roles create, delete, update, retrieve, list, validate, manage Role management
billing create, delete, update, retrieve, list, validate, manage Billing access

Data Service Resources

Resource Actions Description
structures create, retrieve, update, list, delete, manage, validate Schema management
records create, retrieve, update, list, delete, manage, validate, backup Record operations
smart-queries create, retrieve, update, list, delete, manage, validate, execute Saved queries
compute-functions create, retrieve, update, list, delete, manage, validate, execute Function management
function-triggers create, retrieve, update, list, delete, manage, validate, execute Trigger configuration
drafts retrieve, list, delete, update, validate, manage, create Draft resources

Storage Resources

Resource Actions Description
files retrieve, list, create, delete, update, manage File operations
folders retrieve, list, create, delete, update, manage Folder management

Notification Resources

Resource Actions Description
notifications create, retrieve, list, delete User notifications
notification-preferences create, retrieve, update, list, delete Notification settings

Other Resources

Resource Actions Description
search-entries create, delete, update, retrieve, list Search index
analytics create, retrieve, list Analytics data
scheduled-jobs create, delete, update, retrieve, list, pause, resume, retry Job scheduling
realtime subscribe, list, manage Real-time events

Policy Specification

Policies are defined using a specification format that supports complex conditions:

Basic Structure

{
  "rules": [
    {
      "rule_id": "Allow-Rule",
      "effect": "Allow",
      "conditions": [
        // Condition definitions
      ]
    }
  ],
  "default": {
    "rule_id": "Deny-Rule",
    "effect": "Deny"
  }
}

Condition Functions

Group/Role Membership

Check if user belongs to a group or has a role:

{
  "function": "in_list",
  "attribute": "groups",
  "value": "workspace_administrators"
}

Boolean Check

Verify a boolean attribute:

{
  "function": "boolean_equal",
  "attribute": "is_authenticated",
  "value": true
}

String Comparison

Compare string values:

{
  "function": "string_equal",
  "attribute": "user_id",
  "value": "{{@pathParamsId}}",
  "metadata_key": "id",
  "fromRequest": true,
  "source": "path_params",
  "valueKey": "id"
}

Path Matching

Check if a path starts with a prefix:

{
  "function": "string_starts_with",
  "attribute": "request_metadata",
  "value": "/root/users/{{$user_id}}",
  "metadata_key": "path"
}

Compound Conditions

Use operation to combine conditions:

{
  "operation": "or",
  "conditions": [
    { "function": "in_list", "attribute": "groups", "value": "workspace_administrators" },
    { "function": "in_list", "attribute": "groups", "value": "workspace_owners" }
  ]
}

Variables

Define dynamic values using variables:

{
  "rule_id": "Allow-Rule",
  "effect": "Allow",
  "variables": {
    "userPath": {
      "operation": "concat",
      "parameters": {
        "strings": ["/root/users/", "{{$user_id}}"]
      }
    }
  },
  "conditions": [
    {
      "function": "string_starts_with",
      "attribute": "request_metadata",
      "value": "{{@userPath}}",
      "metadata_key": "path"
    }
  ]
}

Common Policy Patterns

1. Administrators Only

Allow only workspace administrators:

{
  "rules": [
    {
      "rule_id": "Allow-Rule",
      "effect": "Allow",
      "conditions": [
        { "function": "in_list", "attribute": "groups", "value": "workspace_administrators" }
      ]
    }
  ],
  "default": { "rule_id": "Deny-Rule", "effect": "Deny" }
}

2. Any Authenticated User

Allow any authenticated user:

{
  "rules": [
    {
      "rule_id": "Allow-Rule",
      "effect": "Allow",
      "conditions": [
        { "function": "boolean_equal", "attribute": "is_authenticated", "value": true }
      ]
    }
  ],
  "default": { "rule_id": "Deny-Rule", "effect": "Deny" }
}

3. User's Own Resources

Allow users to access only their own resources (e.g., their profile, their files):

{
  "rules": [
    {
      "rule_id": "Allow-Rule",
      "effect": "Allow",
      "conditions": [
        {
          "function": "string_equal",
          "attribute": "user_id",
          "value": "{{@pathParamsId}}",
          "metadata_key": "id",
          "fromRequest": true,
          "source": "path_params",
          "valueKey": "id"
        }
      ]
    }
  ],
  "default": { "rule_id": "Deny-Rule", "effect": "Deny" }
}

4. Path-Based Access

Allow access to files in a specific path:

{
  "rules": [
    {
      "rule_id": "Allow-Rule",
      "effect": "Allow",
      "variables": {
        "userPath": {
          "operation": "concat",
          "parameters": { "strings": ["/root/users/", "{{$user_id}}"] }
        }
      },
      "conditions": [
        {
          "function": "string_starts_with",
          "attribute": "request_metadata",
          "value": "{{@userPath}}",
          "metadata_key": "path"
        }
      ]
    }
  ],
  "default": { "rule_id": "Deny-Rule", "effect": "Deny" }
}

5. Admin OR Owner

Allow if user is either an admin or owner:

{
  "rules": [
    {
      "rule_id": "Allow-Rule",
      "effect": "Allow",
      "conditions": [
        {
          "operation": "or",
          "conditions": [
            { "function": "in_list", "attribute": "groups", "value": "workspace_administrators" },
            { "function": "in_list", "attribute": "roles", "value": "workspace_owners" }
          ]
        }
      ]
    }
  ],
  "default": { "rule_id": "Deny-Rule", "effect": "Deny" }
}

Creating Custom Resources

To add a custom resource via the API:

curl -X POST "https://api.centrali.io/workspace/{workspaceSlug}/api/v1/resources" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "custom-reports",
    "category": "workspace",
    "description": "Custom reporting resources",
    "actions": ["create", "retrieve", "update", "delete", "list", "export"],
    "labels": ["type=custom"]
  }'

Creating Custom Policies

Create a policy to define access rules:

curl -X POST "https://api.centrali.io/workspace/{workspaceSlug}/api/v1/policies" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "reports::managers",
    "description": "Allow managers to access reports",
    "specification": {
      "rules": [
        {
          "rule_id": "Allow-Rule",
          "effect": "Allow",
          "conditions": [
            { "function": "in_list", "attribute": "groups", "value": "managers" }
          ]
        }
      ],
      "default": { "rule_id": "Deny-Rule", "effect": "Deny" }
    },
    "labels": ["type=custom"]
  }'

Creating Permissions

Link a resource to a policy with specific actions:

curl -X POST "https://api.centrali.io/workspace/{workspaceSlug}/api/v1/permissions" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "custom-reports::managers",
    "description": "Allow managers to manage custom reports",
    "resourceName": "custom-reports",
    "policyName": "reports::managers",
    "actions": ["create", "retrieve", "update", "delete", "list"],
    "priority": 100,
    "labels": ["type=custom"]
  }'

Using Authorization in Your Code

Backend Authorization Check

import { authorize } from '../utils/route_helpers';

router.get('/reports/:reportId', async (req, res) => {
  const { workspaceSlug, reportId } = req.params;

  // Check authorization
  const auth = await authorize(
    req,
    res,
    'retrieve',           // action
    workspaceSlug,        // workspace
    'custom-reports',     // resource name
    'workspace'           // resource category
  );

  if (!auth) return; // 401 or 403 already sent

  // Proceed with business logic
  const report = await ReportService.get(workspaceSlug, reportId);
  res.json({ success: true, data: report });
});

Passing Request Data

For policies that need request metadata (like path-based access):

const auth = await authorize(
  req,
  res,
  'create',
  workspaceSlug,
  'files',
  'workspace',
  { path: req.body.path }  // Pass path for policy evaluation
);

Default Permissions

When a workspace is created, these permissions are automatically configured:

Administrator Permissions

  • Full access to all workspace resources
  • Manage users, groups, and roles
  • Configure billing and high-risk operations

Authenticated User Permissions

  • Read access to users list
  • Manage own profile
  • Access their own files
  • Subscribe to real-time events
  • Manage their own notification preferences

Public Permissions

  • Retrieve avatar images
  • Access support files

Troubleshooting

Common Issues

403 Forbidden - Permission Denied - Verify the user has the correct group/role membership - Check if the policy conditions match the request - Ensure the permission links the correct resource and policy

401 Unauthorized - Not Authenticated - Verify the JWT token is valid and not expired - Check the Authorization header format: Bearer {token}

Policy Not Evaluated - Ensure the resource exists in the workspace - Verify the policy is properly linked via a permission - Check for typos in resource/policy names

Debugging Authorization

  1. Check user groups: Verify the user's group membership
  2. Review policy conditions: Ensure conditions match the request
  3. Test with admin: If admin works but regular user doesn't, it's a policy issue
  4. Check logs: Look for authorization logs in the IAM service

Best Practices

  1. Use groups for role-based access: Assign users to groups rather than creating user-specific policies
  2. Follow least privilege: Grant only the minimum permissions needed
  3. Use descriptive names: Make policy names self-documenting (e.g., reports::readonly_users)
  4. Document custom policies: Keep track of what custom policies are for
  5. Test thoroughly: Test both allow and deny cases
  6. Use priority wisely: Higher priority permissions are evaluated first
  7. Audit regularly: Review and clean up unused policies/permissions

Managing Resources

Resources represent entities that can be protected. The access routes live under /workspace/{workspace}/api/v1/access/.

List Resources

curl "https://auth.centrali.io/workspace/{workspace}/api/v1/access/resources?page=1&pageSize=50" \
  -H "Authorization: Bearer YOUR_TOKEN"

Response:

{
  "data": [
    {
      "id": "uuid",
      "name": "custom-reports",
      "category": "workspace",
      "description": "Custom reporting resources",
      "actions": ["create", "retrieve", "update", "delete", "list", "export"],
      "labels": ["type=custom"],
      "workspaceSlug": "acme",
      "createdBy": "user-uuid",
      "createdAt": "2026-01-15T10:00:00.000Z",
      "lastUpdated": "2026-01-15T10:00:00.000Z"
    }
  ],
  "meta": { "total": 12, "page": "1", "pageSize": "50" }
}

Get a Resource

curl "https://auth.centrali.io/workspace/{workspace}/api/v1/access/resources/{id}" \
  -H "Authorization: Bearer YOUR_TOKEN"

Returns the resource object directly.

Update a Resource

Use PUT for a full replacement or PATCH for a partial update:

curl -X PUT "https://auth.centrali.io/workspace/{workspace}/api/v1/access/resources/{id}" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "custom-reports",
    "category": "workspace",
    "description": "Updated description",
    "actions": ["create", "retrieve", "update", "delete", "list", "export", "archive"]
  }'
curl -X PATCH "https://auth.centrali.io/workspace/{workspace}/api/v1/access/resources/{id}" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "description": "Only update the description"
  }'

Returns the updated resource object.

Delete a Resource

curl -X DELETE "https://auth.centrali.io/workspace/{workspace}/api/v1/access/resources/{id}" \
  -H "Authorization: Bearer YOUR_TOKEN"

Returns 204 No Content on success. Resources are soft-deleted.


Managing Policies

Policies define the rules for granting or denying access. The access routes live under /workspace/{workspace}/api/v1/access/.

List Policies

curl "https://auth.centrali.io/workspace/{workspace}/api/v1/access/policies?page=1&pageSize=50" \
  -H "Authorization: Bearer YOUR_TOKEN"

Response:

{
  "data": [
    {
      "id": "uuid",
      "name": "reports::managers",
      "description": "Allow managers to access reports",
      "specification": { "rules": [...], "default": {...} },
      "labels": ["type=custom"],
      "workspaceSlug": "acme",
      "createdBy": "user-uuid",
      "createdAt": "2026-01-15T10:00:00.000Z",
      "lastUpdated": "2026-01-15T10:00:00.000Z"
    }
  ],
  "meta": { "total": 8, "page": "1", "pageSize": "50" }
}

Get a Policy

curl "https://auth.centrali.io/workspace/{workspace}/api/v1/access/policies/{id}" \
  -H "Authorization: Bearer YOUR_TOKEN"

Returns the policy object directly, including the full specification.

Update a Policy

Use PUT for a full replacement or PATCH for a partial update. System policies (those without the type=custom label) cannot be modified.

curl -X PUT "https://auth.centrali.io/workspace/{workspace}/api/v1/access/policies/{id}" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "reports::managers",
    "description": "Updated policy for managers",
    "specification": {
      "rules": [
        {
          "rule_id": "Allow-Rule",
          "effect": "Allow",
          "conditions": [
            {
              "operation": "or",
              "conditions": [
                { "function": "in_list", "attribute": "groups", "value": "managers" },
                { "function": "in_list", "attribute": "groups", "value": "workspace_administrators" }
              ]
            }
          ]
        }
      ],
      "default": { "rule_id": "Deny-Rule", "effect": "Deny" }
    }
  }'

The specification is validated on update. If validation fails, you receive a 400 with the validation errors.

Returns the updated policy object.

Validate a Policy Specification

You can validate a specification without creating or updating a policy:

curl -X POST "https://auth.centrali.io/workspace/{workspace}/api/v1/access/policies/validate" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "specification": {
      "rules": [
        {
          "rule_id": "Allow-Rule",
          "effect": "Allow",
          "conditions": [
            { "function": "in_list", "attribute": "groups", "value": "managers" }
          ]
        }
      ],
      "default": { "rule_id": "Deny-Rule", "effect": "Deny" }
    }
  }'

Response (valid):

{ "success": true, "data": { ... } }

Response (invalid):

{ "success": false, "errors": ["Invalid function name: not_a_function"] }

Delete a Policy

curl -X DELETE "https://auth.centrali.io/workspace/{workspace}/api/v1/access/policies/{id}" \
  -H "Authorization: Bearer YOUR_TOKEN"

Returns 204 No Content on success. Policies are soft-deleted.


Managing Permissions

Permissions connect resources to policies and define which actions are allowed. The access routes live under /workspace/{workspace}/api/v1/access/.

List Permissions

curl "https://auth.centrali.io/workspace/{workspace}/api/v1/access/permissions?page=1&pageSize=50" \
  -H "Authorization: Bearer YOUR_TOKEN"

Response:

{
  "data": [
    {
      "id": "uuid",
      "name": "custom-reports::managers",
      "description": "Allow managers to manage custom reports",
      "resourceName": "custom-reports",
      "policyName": "reports::managers",
      "actions": ["create", "retrieve", "update", "delete", "list"],
      "priority": 100,
      "labels": ["type=custom"],
      "workspaceSlug": "acme",
      "createdBy": "user-uuid",
      "createdAt": "2026-01-15T10:00:00.000Z",
      "lastUpdated": "2026-01-15T10:00:00.000Z"
    }
  ],
  "meta": { "total": 15, "page": "1", "pageSize": "50" }
}

Get a Permission

curl "https://auth.centrali.io/workspace/{workspace}/api/v1/access/permissions/{id}" \
  -H "Authorization: Bearer YOUR_TOKEN"

Returns the permission object directly. Returns 404 if not found.

Update a Permission

Use PUT for a full replacement or PATCH for a partial update. System permissions cannot be modified.

curl -X PUT "https://auth.centrali.io/workspace/{workspace}/api/v1/access/permissions/{id}" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "custom-reports::managers",
    "description": "Updated permission description",
    "resourceName": "custom-reports",
    "policyName": "reports::managers",
    "actions": ["create", "retrieve", "update", "delete", "list", "export"],
    "priority": 100
  }'

Returns the updated permission object. Returns 403 if you try to update a system permission.

Delete a Permission

curl -X DELETE "https://auth.centrali.io/workspace/{workspace}/api/v1/access/permissions/{id}" \
  -H "Authorization: Bearer YOUR_TOKEN"

Returns 204 No Content on success. Permissions are soft-deleted.


Managing Roles

Roles group permissions together and can be assigned to users and service accounts. The routes live under /workspace/{workspace}/api/v1/.

Create a Role

curl -X POST "https://auth.centrali.io/workspace/{workspace}/api/v1/roles" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "report_viewer",
    "description": "Can view reports but not modify them"
  }'

Response (201 Created):

{
  "id": "uuid",
  "name": "report_viewer",
  "description": "Can view reports but not modify them",
  "workspaceSlug": "acme",
  "createdBy": "user-uuid",
  "createdAt": "2026-01-15T10:00:00.000Z",
  "updatedAt": "2026-01-15T10:00:00.000Z"
}

List Roles

curl "https://auth.centrali.io/workspace/{workspace}/api/v1/roles?page=1&pageSize=100" \
  -H "Authorization: Bearer YOUR_TOKEN"

Response:

{
  "data": [
    {
      "id": "uuid",
      "name": "report_viewer",
      "description": "Can view reports but not modify them",
      "workspaceSlug": "acme",
      "createdBy": "user-uuid",
      "createdAt": "2026-01-15T10:00:00.000Z",
      "updatedAt": "2026-01-15T10:00:00.000Z"
    }
  ],
  "meta": { "total": 5, "page": 1, "pageSize": 100 }
}

Get a Role

curl "https://auth.centrali.io/workspace/{workspace}/api/v1/roles/{id}" \
  -H "Authorization: Bearer YOUR_TOKEN"

Returns the role object directly.

Update a Role

Role names are immutable after creation. You can update the description:

curl -X PUT "https://auth.centrali.io/workspace/{workspace}/api/v1/roles/{id}" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "description": "Updated role description"
  }'

Returns the updated role object. Returns 400 if you attempt to change the name.

Delete a Role

curl -X DELETE "https://auth.centrali.io/workspace/{workspace}/api/v1/roles/{id}" \
  -H "Authorization: Bearer YOUR_TOKEN"

Returns 204 No Content on success.

Assign a Role to a User

curl -X POST "https://auth.centrali.io/workspace/{workspace}/api/v1/users/{userId}/roles/{roleId}" \
  -H "Authorization: Bearer YOUR_TOKEN"

Returns 201 Created with the assignment record.

Remove a Role from a User

curl -X DELETE "https://auth.centrali.io/workspace/{workspace}/api/v1/users/{userId}/roles/{roleId}" \
  -H "Authorization: Bearer YOUR_TOKEN"

Returns 204 No Content.

List Users with a Role

curl "https://auth.centrali.io/workspace/{workspace}/api/v1/roles/{roleId}/users?page=1&pageSize=100" \
  -H "Authorization: Bearer YOUR_TOKEN"

Response:

{
  "data": [
    { "userId": "uuid", "roleId": "uuid", "workspaceSlug": "acme", "createdAt": "..." }
  ],
  "meta": { "total": 3, "page": 1, "pageSize": 100 }
}

Get a User's Roles

curl "https://auth.centrali.io/workspace/{workspace}/api/v1/users/{userId}/roles" \
  -H "Authorization: Bearer YOUR_TOKEN"

Returns an array of role objects assigned to the user.

Assign a Role to a Service Account

curl -X POST "https://auth.centrali.io/workspace/{workspace}/api/v1/service-accounts/{serviceAccountId}/roles/{roleId}" \
  -H "Authorization: Bearer YOUR_TOKEN"

Returns 201 Created with the assignment record.

Remove a Role from a Service Account

curl -X DELETE "https://auth.centrali.io/workspace/{workspace}/api/v1/service-accounts/{serviceAccountId}/roles/{roleId}" \
  -H "Authorization: Bearer YOUR_TOKEN"

Returns 204 No Content.

List Service Accounts with a Role

curl "https://auth.centrali.io/workspace/{workspace}/api/v1/roles/{roleId}/service-accounts" \
  -H "Authorization: Bearer YOUR_TOKEN"

Returns a paginated list of service accounts assigned to the role.


Managing Groups

Groups collect users and service accounts for policy evaluation. The routes live under /workspace/{workspace}/api/v1/.

Create a Group

curl -X POST "https://auth.centrali.io/workspace/{workspace}/api/v1/groups" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "managers",
    "description": "Department managers with elevated access"
  }'

Response (201 Created):

{
  "id": "uuid",
  "name": "managers",
  "description": "Department managers with elevated access",
  "workspaceSlug": "acme",
  "createdBy": "user-uuid",
  "createdAt": "2026-01-15T10:00:00.000Z",
  "updatedAt": "2026-01-15T10:00:00.000Z"
}

List Groups

curl "https://auth.centrali.io/workspace/{workspace}/api/v1/groups?page=1&limit=50" \
  -H "Authorization: Bearer YOUR_TOKEN"

Response:

{
  "data": [
    {
      "id": "uuid",
      "name": "managers",
      "description": "Department managers with elevated access",
      "workspaceSlug": "acme",
      "createdBy": "user-uuid",
      "createdAt": "2026-01-15T10:00:00.000Z",
      "updatedAt": "2026-01-15T10:00:00.000Z"
    }
  ],
  "meta": { "total": 4, "page": "1", "pageSize": "50" }
}

Get a Group

curl "https://auth.centrali.io/workspace/{workspace}/api/v1/groups/{id}" \
  -H "Authorization: Bearer YOUR_TOKEN"

Returns the group object directly.

Update a Group

Group names are immutable after creation. You can update the description:

curl -X PUT "https://auth.centrali.io/workspace/{workspace}/api/v1/groups/{id}" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "description": "Updated group description"
  }'

Returns the updated group object. Returns 400 if you attempt to change the name.

Delete a Group

curl -X DELETE "https://auth.centrali.io/workspace/{workspace}/api/v1/groups/{id}" \
  -H "Authorization: Bearer YOUR_TOKEN"

Returns 204 No Content on success. Returns 404 if the group does not exist.

Add a User to a Group

curl -X POST "https://auth.centrali.io/workspace/{workspace}/api/v1/users/{userId}/groups/{groupId}" \
  -H "Authorization: Bearer YOUR_TOKEN"

Returns 201 Created with the membership record.

Remove a User from a Group

curl -X DELETE "https://auth.centrali.io/workspace/{workspace}/api/v1/users/{userId}/groups/{groupId}" \
  -H "Authorization: Bearer YOUR_TOKEN"

Returns 204 No Content.

List Users in a Group

curl "https://auth.centrali.io/workspace/{workspace}/api/v1/groups/{groupId}/users" \
  -H "Authorization: Bearer YOUR_TOKEN"

Returns an array of user membership records for the group.

Get a User's Groups

curl "https://auth.centrali.io/workspace/{workspace}/api/v1/users/{userId}/groups" \
  -H "Authorization: Bearer YOUR_TOKEN"

Returns an array of group objects the user belongs to.

Add a Service Account to a Group

curl -X POST "https://auth.centrali.io/workspace/{workspace}/api/v1/service-accounts/{serviceAccountId}/groups/{groupId}" \
  -H "Authorization: Bearer YOUR_TOKEN"

Returns 201 Created with the membership record.

Remove a Service Account from a Group

curl -X DELETE "https://auth.centrali.io/workspace/{workspace}/api/v1/service-accounts/{serviceAccountId}/groups/{groupId}" \
  -H "Authorization: Bearer YOUR_TOKEN"

Returns 204 No Content.

List Service Accounts in a Group

curl "https://auth.centrali.io/workspace/{workspace}/api/v1/groups/{groupId}/service-accounts" \
  -H "Authorization: Bearer YOUR_TOKEN"

Returns an array of service account records in the group.


Access Evaluation

You can programmatically evaluate whether a request would be allowed without actually performing the action.

Evaluate Access

curl -X POST "https://auth.centrali.io/workspace/{workspace}/api/v1/access/evaluate" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "action": "retrieve",
    "resource_name": "custom-reports",
    "resource_category": "workspace",
    "request_data": {}
  }'

Response:

{
  "decision": "Allow",
  "message": "Access granted"
}

Debug Evaluation

For troubleshooting, use the debug endpoint to see why access was granted or denied:

curl -X POST "https://auth.centrali.io/workspace/{workspace}/api/v1/access/evaluate/debug" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "resource": "custom-reports",
    "resourceCategory": "workspace",
    "action": "retrieve",
    "debug": true,
    "includeTrace": true
  }'

Response:

{
  "decision": "Allow",
  "evaluation_context": { ... },
  "why": {
    "classification": "allowed",
    "message": "Access granted via policy reports::managers"
  },
  "trace": [ ... ]
}

You can also evaluate as a specific service account by including a principal:

curl -X POST "https://auth.centrali.io/workspace/{workspace}/api/v1/access/evaluate/debug" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "resource": "records",
    "resourceCategory": "workspace",
    "action": "create",
    "principal": { "type": "service_account", "id": 42 },
    "debug": true
  }'

Requires security:debug permission on workspace::service-accounts.

Batch Evaluation

Evaluate multiple resource/action combinations in a single request:

curl -X POST "https://auth.centrali.io/workspace/{workspace}/api/v1/access/evaluate/batch" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "principal": { "type": "service_account", "id": 42 },
    "resourceNames": ["records", "files", "structures"],
    "actions": ["create", "retrieve", "delete"],
    "debug": true,
    "pagination": { "offset": 0, "limit": 50 }
  }'

Response:

{
  "results": [
    {
      "resource": "records",
      "resourceCategory": "workspace",
      "action": "create",
      "decision": "Allow",
      "why": { "classification": "allowed", "message": "..." }
    },
    {
      "resource": "records",
      "resourceCategory": "workspace",
      "action": "delete",
      "decision": "Deny",
      "why": { "classification": "policy_denied", "message": "..." }
    }
  ],
  "serviceAccount": { ... },
  "pagination": { "offset": 0, "limit": 50, "total": 9, "hasMore": false }
}

Remediation Workflows

When a service account lacks access, Centrali can generate and apply fix options automatically.

Generate Remediation Options

curl -X POST "https://auth.centrali.io/workspace/{workspace}/api/v1/access/permissions/remediation/generate" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "targetType": "service_account",
    "targetId": 42,
    "resource": "records",
    "resourceCategory": "workspace",
    "actions": ["create", "retrieve"]
  }'

Response:

{
  "targetInfo": { ... },
  "requestedAccess": { "resource": "records", "actions": ["create", "retrieve"] },
  "options": [
    {
      "optionId": "role_assignment_1",
      "type": "role_assignment",
      "description": "Assign existing role 'data_editor'",
      "effort": "low",
      "recommended": true,
      "steps": [ ... ],
      "sideEffects": [ "Also grants update and delete on records" ]
    },
    {
      "optionId": "group_assignment_1",
      "type": "group_assignment",
      "description": "Add to group 'data_users'",
      "effort": "low",
      "recommended": false,
      "steps": [ ... ],
      "sideEffects": [ ... ]
    },
    {
      "optionId": "create_new_1",
      "type": "create_new",
      "description": "Create minimal custom policy",
      "effort": "medium",
      "recommended": false,
      "steps": [ ... ],
      "sideEffects": []
    }
  ],
  "currentAccessStatus": { ... }
}

Preview Remediation

Preview what a remediation option would change before applying it:

curl -X POST "https://auth.centrali.io/workspace/{workspace}/api/v1/access/permissions/remediation/preview" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "targetType": "service_account",
    "targetId": 42,
    "resource": "records",
    "resourceCategory": "workspace",
    "actions": ["create", "retrieve"],
    "optionId": "role_assignment_1",
    "option": { ... }
  }'

Response:

{
  "status": "preview",
  "wouldCreate": [],
  "wouldModify": [
    { "type": "role_assignment", "roleId": "uuid", "roleName": "data_editor" }
  ],
  "summary": ["Would assign role 'data_editor' to service account 42"]
}

Apply Remediation

curl -X POST "https://auth.centrali.io/workspace/{workspace}/api/v1/access/permissions/remediation/apply" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "targetType": "service_account",
    "targetId": 42,
    "resource": "records",
    "resourceCategory": "workspace",
    "actions": ["create", "retrieve"],
    "optionId": "role_assignment_1",
    "option": { ... }
  }'

Response:

{
  "status": "success",
  "created": [],
  "modified": [
    { "type": "role_assignment", "roleId": "uuid", "roleName": "data_editor" }
  ],
  "verification": {
    "testResult": "Allow",
    "message": "Access verified after remediation",
    "testedActions": ["create", "retrieve"]
  },
  "errors": []
}

Pass "dryRun": true to get a preview instead of applying changes.


Cleanup Patterns

When tearing down test resources or cleaning up custom access control objects, follow this order to avoid broken references:

1. Remove Membership Assignments

Remove users and service accounts from groups and roles first:

# Remove user from group
curl -X DELETE "https://auth.centrali.io/workspace/{workspace}/api/v1/users/{userId}/groups/{groupId}" \
  -H "Authorization: Bearer YOUR_TOKEN"

# Remove user from role
curl -X DELETE "https://auth.centrali.io/workspace/{workspace}/api/v1/users/{userId}/roles/{roleId}" \
  -H "Authorization: Bearer YOUR_TOKEN"

# Remove service account from group
curl -X DELETE "https://auth.centrali.io/workspace/{workspace}/api/v1/service-accounts/{saId}/groups/{groupId}" \
  -H "Authorization: Bearer YOUR_TOKEN"

# Remove service account from role
curl -X DELETE "https://auth.centrali.io/workspace/{workspace}/api/v1/service-accounts/{saId}/roles/{roleId}" \
  -H "Authorization: Bearer YOUR_TOKEN"

2. Delete Permissions

Remove permissions that link resources to policies:

curl -X DELETE "https://auth.centrali.io/workspace/{workspace}/api/v1/access/permissions/{permissionId}" \
  -H "Authorization: Bearer YOUR_TOKEN"

3. Delete Policies

Remove custom policies after their permissions are deleted:

curl -X DELETE "https://auth.centrali.io/workspace/{workspace}/api/v1/access/policies/{policyId}" \
  -H "Authorization: Bearer YOUR_TOKEN"

4. Delete Resources

Remove custom resources last:

curl -X DELETE "https://auth.centrali.io/workspace/{workspace}/api/v1/access/resources/{resourceId}" \
  -H "Authorization: Bearer YOUR_TOKEN"

5. Delete Groups and Roles

Remove groups and roles after all membership is cleared:

curl -X DELETE "https://auth.centrali.io/workspace/{workspace}/api/v1/groups/{groupId}" \
  -H "Authorization: Bearer YOUR_TOKEN"

curl -X DELETE "https://auth.centrali.io/workspace/{workspace}/api/v1/roles/{roleId}" \
  -H "Authorization: Bearer YOUR_TOKEN"

System objects cannot be deleted

Resources, policies, and permissions with system labels (without type=custom) are protected and cannot be updated or deleted. Only custom objects you have created can be removed.