# Roles and permissions

Authorization answers one question on every request: **may this caller do this
thing, in this workspace?** Three independent things have to agree before the
answer is yes.

## The intersection

```
    what the user's roles grant
  ∩ what the workspace's plan entitles
  ∩ what the key was scoped to        =  what this request may do
```

Each narrows. None can widen another. A role that grants `workflow:deploy` has no effect
if the plan does not include the workflow module, and a plan that entitles everything has
no effect for a user whose roles grant nothing.

The set is computed when the session is built rather than consulted per query, so all
three are resolved before your request reaches a handler.

### 1. Roles grant

A user holds one or more roles in a workspace, and a role is a named list of permissions.
Holding two roles grants the union of their permissions: roles add to each other and
never subtract.

A workspace can also grant a permission to one person directly, without creating a role
for it. That grant is unioned in the same way.

### 2. The plan entitles

Many permissions belong to a module, and a workspace only holds the modules its
plan includes. This is a second, separate gate. See [plans and
modules](/plans-and-modules) for how the two differ and which failure you are
looking at, and the [permission reference](/permissions-reference) for which
permissions need one at all.

The practical effect: **a permission your plan does not entitle is dropped from your
session even if a role grants it.** No error is raised at login. You simply do not hold
it, and the endpoint answers 403.

### 3. The key narrows

An API key acts as the user who created it, so it starts from that user's resolved
permissions. `key_permissions` then filters that set down:

```json
{ "key_permissions": ["media:view", "media:download", "album:view"] }
```

An empty array means no narrowing: the key carries everything its owner has, which is
almost never appropriate for an integration. See [authentication](/authentication).

## Read a 403

A refusal reports which of the three gates stopped you in its `code`:

| Code | Meaning | What fixes it |
|---|---|---|
| `forbidden` | The permission is missing from the session. | A role change, or widening `key_permissions`. |
| `license_required` | The plan does not include the module. | A plan change. Adding permissions will not help. |
| `insufficient_credits` | The workspace is out of credits. | A top-up. Retrying will not help. |

Branching on `code` rather than on the status is what lets you tell someone "ask your
administrator for access" instead of "your plan does not include this". They are two
different conversations, and only one is about permissions. See [errors](/errors).

## Every operation names its permission

The permission an endpoint requires is not documentation maintained alongside the code. It
is read out of the middleware that enforces it when the spec is generated, so what the
[reference](/reference) shows on an operation is the gate that runs.

Most operations name exactly one permission. A few name several, and the spec records
which reading applies in `x-permission-mode`: `all` means every permission is required,
and `any` means one is enough. File operations use `any`, because which permission applies
depends on where the file lives, and the operation lists every surface that could satisfy
it.

The service also refuses to start if any route declares neither a permission nor an
explicit decision to be public. There is no allowlist and no warning-only mode. This
matters most for keys, because key scoping works by *narrowing* a permission set: a route
that reads no permission would ignore the narrowing entirely, and a read-only key would
call it exactly as an owner's key does.

## Permission naming

```
knowledge-base:record:view
└────────────┘ └────┘ └──┘
 |              |      └─ verb
 |              └─ sub-resource, where one exists
 └─ resource, named as the API path names it
```

The resource is the noun from the endpoint: `/knowledge-bases` is `knowledge-base:*`,
`/share-links` is `share-link:*`, and `/workflow-instances` is `workflow:instance:*`.
Reading a permission is usually enough to know what it does.

The verbs are a closed set:

| Verb | Covers |
|---|---|
| `view` | Reading: one record and the list of them. There is no separate list permission. |
| `create` | Making a new one. |
| `edit` | Changing an existing one. |
| `delete` | Removing one. Whether that is recoverable is a property of the resource, noted in the [permission reference](/permissions-reference). |

Anything else is a named action, granted separately because it is separately worth
deciding: `upload`, `download`, `analyze`, `search`, `curate`, `write`, `import`,
`export`, `crawl`, `fill`, `contribute`, `generate`, `deploy`, `execute`, `publish`,
`send`, `run`, `review`, `revoke`, `migration` and `deduplicate`. The [permission
reference](/permissions-reference) lists every one of them against the resource it
applies to.

Two consequences are worth knowing up front:

- **`view` includes content.** A knowledge base's documents are the knowledge, so
  `knowledge-base:view` opens them. The media library is the exception: `media:view`
  browses and previews, and `media:download` retrieves the original. It is the only
  download permission in the catalog.
- **Files have no permissions of their own.** A file is governed by whatever it belongs
  to: a media asset by `media:*`, a knowledge document by `knowledge-base:*`, a
  conversation's attachment by the conversation. A key scoped to `media:view` and
  `media:download` therefore reads the library and provably nothing else.

One prefix is worth noting: **`admin:`** covers workspace administration (users, roles,
settings, audit, billing) and the workspace-wide configuration behind a feature, such as
the media tag structure (`admin:media-tag:edit`) or the social accounts the whole
workspace posts under (`admin:social:edit`). The rule is who the change affects:
using a feature carries the feature's own prefix, while configuring it for everyone
carries `admin:`. It is not cross-workspace; that surface is not part of this API.

## The built-in roles

A new workspace is seeded with thirteen roles. They are ordinary records that a workspace
can edit, delete or add to, but most customers keep them, so an integration can usually
assume they exist.

| Role | For |
|---|---|
| Administrator | Full workspace access, including users, roles, and every setting. |
| Billing Administrator | The subscription, plan changes, and credit top-ups, and nothing else. |
| Album Admin | Creating and organizing albums and their contents. |
| Media Editor | The media library end to end: files, versions, albums. |
| Media Viewer | Browsing media, albums, and saved searches. Read-only. |
| Media Uploader | Adding files to the library and to albums. |
| Knowledge Admin | Knowledge bases, their schemas, wildcards, and the content behind them. |
| Knowledge Contributor | Writing knowledge content and records, without changing configuration. |
| Knowledge Viewer | Reading knowledge bases and their records. |
| Designer | Building designs, publishing templates, and defining their editable fields. |
| Design Contributor | Filling approved templates without changing their layout. |
| Chat User | Chat, threads, and AI features. |
| Power User | Chat User, plus building agents, prompts, and the inline AI helpers. |

The tiers are the useful part: **Viewer / Contributor / Admin** means the same thing in
media, knowledge and design, so "what can this person do" is a question you answer once.

## Manage roles through the API

Roles and memberships are ordinary resources under `/api/v1/admin`, each gated on the one
`admin:role:*` or `admin:user:*` permission it needs:

```bash
curl https://api.genuineai.app/api/v1/admin/roles \
  -H "X-Api-Key: gai_…" \
  -H "X-Tenant-Id: <workspace-id>"
```

Use this to provision people from your own directory rather than by hand. Every change
lands in the audit history (`GET /api/v1/admin/history`) with the before and after of the
record.

## What this does not cover

Permissions decide whether you may call an operation. They do not decide **which rows**
you get back: that is row-level security for the workspace boundary, and row ACLs for
sharing inside it. See [security and tenancy](/security-and-tenancy) and [sharing and
access](/sharing-and-access).

<Callout type="note">
	The product guide covers the same subject from the app's side: [what a role opens in the app, and how to tell which of the three gates stopped someone](/guide/admin/permissions).
</Callout>
