---
title: 'REST API: Flow ACLs'
path: reference/rest-api/flow-acls
status: published
---

# REST API: Flow ACLs

Per-flow access control. List, grant, revoke.

All endpoints under `/api/v1/flows/{flow_id}/acls`. Require `admin` ACL on the target flow (or super_admin / tenant_admin).

## Access levels (strict hierarchy)

| Level | Implies | What it permits |
|---|---|---|
| `view` | — | GET the flow + content. |
| `edit` | `view` | PUT updates to the flow. |
| `deploy` | `edit` | POST deploy + run-tests + publish to catalog. |
| `admin` | `deploy` | DELETE the flow + manage ACLs. |

Higher levels imply lower ones — granting `deploy` automatically allows everything below it.

Implicit `admin`:

- The flow's **owner** (always; can't be removed).
- **Super admin** users (every flow).
- **Tenant admin** users (every flow in their tenant).

## `GET /v1/flows/{flow_id}/acls`

List explicit ACLs on this flow (the implicit owner/admin/tenant-admin grants aren't listed).

**Response:**

```jsonc
[
  { "id": "acl_xxx",
    "flow_id": "flow_abc123",
    "principal_type": "user",     // or "group"
    "principal_id": "usr_alice",
    "level": "edit",
    "granted_by": "usr_owner",
    "granted_at": "2026-04-29..." }
]
```

`admin` ACL on the flow.

## `POST /v1/flows/{flow_id}/acls`

Grant an ACL.

**Body:**

```jsonc
{
  "principal_type": "user",      // or "group"
  "principal_id": "usr_alice",
  "level": "edit"
}
```

**Response:** `201 Created` with the new `FlowACLOut`.

`admin` ACL.

Errors:

- **400** — invalid level, unknown principal_type.
- **404** — `principal_id` doesn't exist in the local user/group mirror. (Run a sync first or use a valid id.)
- **409** — an ACL with the same principal already exists; PATCH instead, or delete + re-create.

## `DELETE /v1/flows/{flow_id}/acls/{acl_id}`

Revoke an ACL. `204 No Content`.

`admin` ACL.

Refuses to revoke the implicit owner ACL — change ownership via super_admin if you need to remove the owner.

## Patterns

### "Everyone in my tenant can view"

Don't add a group ACL for the tenant's all-users group — that's what `scaicore:view` + the flow's tenant scope already does at the permission level. Instead, leave the flow with no group ACLs and let tenant membership do the work.

For tighter scoping (only some users in the tenant), use a smaller group:

```jsonc
{ "principal_type": "group", "principal_id": "grp_acme_eng", "level": "view" }
```

### "Bob can edit, Alice can deploy"

```jsonc
{ "principal_type": "user", "principal_id": "usr_bob", "level": "edit" }
{ "principal_type": "user", "principal_id": "usr_alice", "level": "deploy" }
```

### "All engineers can deploy, all PMs can view"

```jsonc
{ "principal_type": "group", "principal_id": "grp_eng", "level": "deploy" }
{ "principal_type": "group", "principal_id": "grp_pm", "level": "view" }
```

A user in both groups gets the highest applicable level (`deploy` in this case).

### "Pin a flow to one person"

Just don't add any ACLs. The owner has implicit `admin` and nobody else has access (unless they're a tenant admin or super admin).
