> ## Documentation Index
> Fetch the complete documentation index at: https://cactal.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Access control

> Cactal's permission model: organization roles, website-scoped editor roles, capabilities as the underlying vocabulary, how API keys carry the same roles, 404 cross-tenant semantics, and the audit log.

Cactal authorizes every request the same way: a principal — a human member or an API key — holds role grants, roles bundle capabilities, and each operation requires one capability.

## Organization roles

An organization member holds exactly one role across the whole organization:

| Role             | Adds                                                                                                                                                                    |
| ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `content_editor` | Content-editor capabilities on every website in the organization: CMS content, assets, image generation, analytics                                                      |
| `full_editor`    | Full-editor capabilities on every website: everything above plus source code, publishing, domains, CMS schema                                                           |
| `admin`          | Everything above, plus organization administration: members, invitations, API keys, the audit log, website create/rename/delete/restore/transfer, website editor grants |
| `owner`          | Everything, plus billing administration, ownership transfer, and organization deletion                                                                                  |

Roles are strictly additive down this table. Assign a member's role with `PATCH /v1/organizations/{organizationId}/members/{memberPrincipalId}`; bring new members in with organization invitations. See [Invite teammates and clients](/docs/guides/invite-teammates-and-clients).

## Website-scoped editor roles

People outside the organization can be granted access to a single website — the agency-and-client pattern. Two roles exist at website scope:

| Role             | Can                                                                                                                                        | Cannot                                                  |
| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------- |
| `content_editor` | Read the website record and analytics; read and write CMS items; upload, generate, list, and delete assets; publish and unpublish items    | Touch source code, publishing, domains, or CMS schema   |
| `full_editor`    | Everything a content editor can, plus source code and leases, website publishing and rollback, domains, CMS collections and fields, builds | Rename, delete, or transfer the website; manage editors |

Grant directly with `PUT /v1/websites/{websiteId}/editors/{principalId}` and body `{ "role": "full" | "content" }`, or add by email with `POST /v1/websites/{websiteId}/editors` using the same role values. Invitations are `pending` until accepted or canceled and expire after 48 hours by default (override with `expiresAt`). If the email already belongs to an account, the grant applies immediately — no invitation email.

Directly shared websites appear in the editor's own Sites navigation — in a "Shared with you" sidebar group and in the unified Sites list — without making them an organization member or revealing anything about the owning organization beyond its name and URL slug. `GET /v1/websites?scope=shared` lists the same set; each website list item carries a `shared` flag and its `organizationSlug`.

<Note>
  Organization members already inherit website access from their organization role. Granting one directly with `PUT /v1/websites/{websiteId}/editors/{principalId}` returns `400`: `Organization team members already inherit website access from their org role.` Inviting one by email instead returns `already_inherited` and sends no invitation.
</Note>

## Capabilities

Capabilities are the underlying permission vocabulary — one per operation, named after it: `websites.create`, `cms.items.setPublished`, `websiteSourceCode.head.publish`, `auditLog.list`. Roles are fixed bundles of capabilities; you assign roles, and Cactal checks capabilities.

The same vocabulary drives MCP: `tools/list` on `POST /v1/mcp` returns only the tools whose required capability the authenticated principal holds, and every tool declares its capability in its metadata. OAuth uses the signed-in user's grants; API keys use their configured grants. A content editor never sees the publish tool.

## API keys carry the same roles

An API key is a principal with grants, exactly like a member — one of two access shapes, set at creation and changeable by sending `access` to `PATCH /v1/apiKeys/{apiKeyId}`:

```json Organization-scoped access theme={null}
{ "kind": "organization", "role": "full_editor" }
```

```json Website-scoped access theme={null}
{ "kind": "websites", "websiteIds": ["V1StGXR8_Z5jdHi6B-myT"], "role": "content_editor" }
```

Organization-scoped keys take `admin`, `full_editor`, or `content_editor`. All organization roles can read billing state, including credit usage and balances. The `owner` role is not grantable to keys, so no key can administer billing, transfer ownership, or delete the organization. Website-scoped keys take `full_editor` or `content_editor` across one or more websites, all in the key's organization, and cannot read organization billing state.

Because keys and members resolve through the same grants, enforcement is identical across the dashboard, REST, and MCP. Scope each key to the least access its job needs — a content bot gets `{ "kind": "websites", ..., "role": "content_editor" }`, not an organization role. See [Create an API key](/docs/create-an-api-key) and [Manage API keys](/docs/guides/manage-api-keys).

## Cross-tenant requests return 404

Requesting a website — or anything under one: source code, CMS, assets, domains — where you hold no grant returns `404` with `Website not found`, not `403`. Resource ids never confirm their own existence to principals outside the tenant.

`403` means something different: you can see the resource's tenant but your role lacks the capability. For example, a `content_editor` key calling `POST /v1/websiteSourceCode/head/publish` gets `403`.

<Warning>
  For an agent, a `404` on a known-good website id usually means the wrong API key, not a missing website — check which organization and websites the key is scoped to before retrying. A `403` means the key needs a higher role, which a human must grant.
</Warning>

## Audit log

Administrative and security-relevant mutations are recorded in the organization audit log: website create, delete, and transfer; editor grants and invitations; API key create, update, rotate, and revoke; custom domain changes; organization membership and invitations; billing changes. Each event records the acting principal — human or API key — the affected resource, a summary, and a timestamp.

```bash theme={null}
curl "https://api.cactal.ai/v1/organizations/kX9dQ2mNpL4wRt7vYc3Ub/audit-log" \
  -H "Authorization: Bearer $CACTAL_API_KEY"
```

Reading the log requires the `auditLog.list` capability — organization `admin` or `owner`, or a key with organization-scoped `admin` access. Use it to review what an agent did before approving its next run. See [Agent-safe publishing](/docs/agents/agent-safe-publishing).

## Constraints

| Constraint                 | Value                                                                                |
| -------------------------- | ------------------------------------------------------------------------------------ |
| Organization roles         | `owner`, `admin`, `full_editor`, `content_editor` — one per member                   |
| Website-scoped roles       | `full_editor`, `content_editor` (wire values `full` / `content` on editor endpoints) |
| API key organization roles | `admin`, `full_editor`, `content_editor`; never `owner`                              |
| API key website access     | 1 or more website ids, all in the key's organization                                 |
| Editor invitation expiry   | 48 hours by default                                                                  |
| No-grant website access    | `404`, not `403`; org-scoped endpoints return `403`                                  |

## Next steps

<Columns cols={2}>
  <Card title="Manage API keys" icon="key" href="/docs/guides/manage-api-keys">
    Scoping strategy, rotation, and revocation.
  </Card>

  <Card title="Invite teammates and clients" icon="users" href="/docs/guides/invite-teammates-and-clients">
    Organization membership versus website-scoped editors.
  </Card>
</Columns>
