---
title: Permissions and ACLs
path: core-concepts/permissions-and-acls
status: published
---

ScaiDrive's permission model is Discretionary Access Control with inheritance — the same shape as Windows NTFS. Every file and folder can have its own ACL with allow/deny entries granting specific permissions to users and groups. ACLs inherit from the parent unless explicitly broken.

## The six permissions

| Permission | Flag value | Meaning |
|------------|------------|---------|
| `READ` | 1 | View contents, list folder entries, read metadata |
| `WRITE` | 2 | Modify contents, rename, update metadata |
| `DELETE` | 4 | Move to trash |
| `CREATE` | 8 | Create new items inside (folders/files) |
| `SHARE` | 16 | Create external share links |
| `MANAGE_PERMISSIONS` | 32 | Modify the ACL, transfer ownership |

Permissions are modeled as a bitfield. A principal with `READ | WRITE = 3` can read and write but not delete. The API represents permissions as arrays of strings in request bodies (`["READ", "WRITE"]`) and as integer bitfields in effective-permission responses.

## The resolution algorithm

When a user attempts an action, ScaiDrive computes their **effective permissions** on the target resource:

```mermaid
flowchart TD
  Q["User wants permission P<br/>on resource R"] --> A{"Global admin?<br/>super_admin or<br/>tenant_admin"}
  A -- yes --> ALLOW(["Allow"])
  A -- no --> SR{"Share role grants P?<br/>(owner/admin/contributor/reader)"}
  SR -- yes --> ACL{"Resource has ACL?"}
  SR -- no --> ACL2{"Resource has ACL?"}
  ACL2 -- no --> DENY(["Deny"])
  ACL2 -- yes --> ALLOWS{"Allow ACE grants P?<br/>(direct, group, or Everyone;<br/>inherited from parents)"}
  ALLOWS -- yes --> DCHK
  ALLOWS -- no --> DENY
  ACL -- no --> ALLOW
  ACL -- yes --> DCHK{"Any deny ACE matches<br/>this user + P?"}
  DCHK -- yes --> DENY
  DCHK -- no --> ALLOW
```

1. **Check global admin.** If the user has `super_admin` or `tenant_admin` role (database-stored, not JWT-only), grant all permissions.
2. **Check share membership.** Determine the user's role in the share (via direct membership or any group they're in). `owner` and `admin` get all permissions; `contributor` gets `READ | WRITE | DELETE | CREATE`; `reader` gets only `READ`.
3. **Apply ACL.** If the resource has an ACL, evaluate ACEs:
   - Deny entries are collected first — any explicit deny overrides allow.
   - Allow entries are OR'd together.
   - An ACE matches if its principal is the user, a group the user is in, or `Everyone`.
   - If the ACL is set to inherit from the parent, walk up to the parent folder and include its ACEs.
4. **Combine.** Effective = (share role permissions ∪ allow ACEs) – deny ACEs.

This mirrors NTFS: **deny beats allow**, **explicit beats inherited**, and **inheritance walks up the tree**.

## Checking permissions

To ask "can this user do X on resource Y?":

```bash
curl -G $SCAIDRIVE_URL/api/v1/permissions/check \
  -H "Authorization: Bearer $SCAIDRIVE_TOKEN" \
  --data-urlencode "resource_type=file" \
  --data-urlencode "resource_id=fil_01J3K" \
  --data-urlencode "permission=WRITE"
```

```python
resp = httpx.get(
    f"{url}/api/v1/permissions/check",
    headers={"Authorization": f"Bearer {token}"},
    params={"resource_type": "file", "resource_id": "fil_01J3K", "permission": "WRITE"},
)
print(resp.json()["allowed"])
```

```typescript
const params = new URLSearchParams({
  resource_type: "file",
  resource_id: "fil_01J3K",
  permission: "WRITE",
});
const resp = await fetch(`${url}/api/v1/permissions/check?${params}`, {
  headers: { Authorization: `Bearer ${token}` },
});
const { allowed } = await resp.json();
```

Or get the full set of effective permissions in one call:

```bash
curl -G $SCAIDRIVE_URL/api/v1/permissions/effective \
  -H "Authorization: Bearer $SCAIDRIVE_TOKEN" \
  --data-urlencode "resource_type=folder" \
  --data-urlencode "resource_id=fld_01J3M"
```

```json
{
  "can_read": true,
  "can_write": true,
  "can_delete": false,
  "can_create": true,
  "can_share": false,
  "can_manage_permissions": false
}
```

For UI rendering (showing/hiding buttons), batch checks reduce round trips:

```bash
curl -X POST $SCAIDRIVE_URL/api/v1/permissions/check/batch \
  -H "Authorization: Bearer $SCAIDRIVE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "checks": [
      {"resource_type": "file", "resource_id": "fil_A", "permission": "WRITE"},
      {"resource_type": "file", "resource_id": "fil_B", "permission": "DELETE"},
      {"resource_type": "folder", "resource_id": "fld_C", "permission": "CREATE"}
    ]
  }'
```

## ACL entries

An ACL is a list of ACEs (Access Control Entries). Each ACE has:

| Field | Values |
|-------|--------|
| `principal_type` | `user`, `group`, `everyone` |
| `principal_id` | `usr_...`, `grp_...`, or `"everyone"` |
| `permissions` | Array of permission names, e.g., `["READ", "WRITE"]` |
| `ace_type` | `allow` or `deny` |
| `inherit_to_children` | `true` — ACE applies to this resource and descendants. `false` — applies only here |
| `inherited` | Server-set. `true` if this ACE came from an ancestor, `false` if set directly |

## Reading the ACL

```bash
curl -H "Authorization: Bearer $SCAIDRIVE_TOKEN" \
     $SCAIDRIVE_URL/api/v1/permissions/acl/folder/fld_01J3M
```

```json
{
  "resource_type": "folder",
  "resource_id": "fld_01J3M",
  "inherit_from_parent": true,
  "entries": [
    {
      "id": "ace_01J3N",
      "principal_type": "group",
      "principal_id": "grp_01J3L",
      "principal_name": "Engineering",
      "permissions": ["READ", "WRITE", "CREATE", "DELETE"],
      "ace_type": "allow",
      "inherited": false,
      "inherit_to_children": true
    },
    {
      "id": "ace_01J3P",
      "principal_type": "user",
      "principal_id": "usr_01J4A",
      "principal_name": "External Contractor",
      "permissions": ["WRITE", "DELETE"],
      "ace_type": "deny",
      "inherited": true,
      "inherit_to_children": true
    }
  ]
}
```

## Adding an ACE

```bash
curl -X POST $SCAIDRIVE_URL/api/v1/permissions/acl/folder/fld_01J3M \
  -H "Authorization: Bearer $SCAIDRIVE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "principal_type": "user",
    "principal_id": "usr_01J5B",
    "permissions": ["READ"],
    "ace_type": "allow",
    "inherit_to_children": true
  }'
```

You need `MANAGE_PERMISSIONS` on the resource to modify its ACL.

## Breaking inheritance

By default, a resource's ACL is "the parent's ACL plus anything set directly here." To stop inheriting:

```bash
curl -X PUT $SCAIDRIVE_URL/api/v1/permissions/acl/folder/fld_01J3M/inheritance \
  -H "Authorization: Bearer $SCAIDRIVE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "inherit_from_parent": false,
    "copy_inherited": true
  }'
```

`copy_inherited: true` copies the inherited ACEs into this resource as direct entries before breaking — the effective permissions don't change immediately, but further ancestor modifications stop propagating. `copy_inherited: false` drops inherited rights entirely.

To restore inheritance:

```bash
curl -X PUT $SCAIDRIVE_URL/api/v1/permissions/acl/folder/fld_01J3M/inheritance \
  -H "Authorization: Bearer $SCAIDRIVE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"inherit_from_parent": true}'
```

## Deny overrides allow

Classic NTFS rule: if any ACE denies a permission, the user doesn't have it, regardless of other allows.

Example: user `alice` is in group `engineering` which has `WRITE` allowed on a folder. A direct `deny WRITE` ACE on `alice` overrides — she can't write, even though her group can.

This lets you carve exceptions without restructuring group membership.

## Ownership is separate from permissions

A resource has an `owner_id` (user or group). The owner has `MANAGE_PERMISSIONS` implicitly, whether or not it's in the ACL — owners can always change who has access.

Ownership can be transferred via `POST /api/v1/permissions/ownership/{resource_type}/{resource_id}/transfer`.

## Common pitfall: inheritance is tree, not DAG

Each resource has exactly one parent. ACL inheritance walks up the tree and stops at the share. It doesn't follow move operations — if you move a file to a new folder, the file's inherited ACEs change.

If you need a resource that inherits from multiple sources (rare), break inheritance and set the ACEs explicitly.

## What's next

- [Shares and Ownership](/docs/scaidrive/core-concepts/shares-and-ownership) — the share-level baseline.
- [Permissions Reference](/docs/scaidrive/reference/permissions) — all permission endpoints.
- [Files Guide](/docs/scaidrive/api-guides/files) — permission checks in action.