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

# Tenant Management (organizations & members)

> AgentEye Tenant Management (organizations & members) documentation.

A single AgentEye deployment serves multiple fully isolated **organizations** (tenants), so one instance can host distinct teams, business units, or customers without exposing any one tenant's data to another. Every row of data (events, evaluations, sessions, dashboards, saved queries, alerts, API keys, and members) belongs to exactly one org. Primary isolation is enforced in application code: every request is scoped to its org with explicit `org_id` predicates. On ClickHouse — where the high-volume events and evaluations live — this is backed by strong engine-level enforcement: each org gets a dedicated read-only ClickHouse user with a per-org row policy, so even untrusted analytics SQL can never read another tenant's rows. On PostgreSQL, row-level security adds defence-in-depth on the read-only query path (`/queries/run`), narrowing what that path can see even if an application-level filter were ever missing; the server's own write connection runs as the table owner and so operates through the same app-level `org_id` scoping.

Tenant lifecycle is operator-controlled, while everything members do day-to-day stays self-service in the dashboard. Organizations and their memberships are created and managed with the **`agenteye-orgctl`** CLI, which ships inside the server image and runs **inside the existing server pod**. Tenant creation and deletion are deliberately kept off the dashboard and HTTP API: there is **no HTTP API and no dashboard button** for tenant lifecycle, so it is gated behind cluster/pod shell access rather than the application surface.

Within an org, members work entirely in the dashboard and API: they sign in, switch between the orgs they belong to, manage their own API keys, build dashboards and saved queries, and configure alerts for their org. The division is clean: operators provision and decommission tenants and their members via the CLI; members run everything inside a tenant through the UI.

> **Single-tenant deployments need none of this.** A single-tenant install runs without any operator action. All data, users, and keys live in a built-in `default` organization that is provisioned automatically. You only need this guide when you decide to add a second org.

***

## Prerequisites

Before you create your **second** organization (the built-in `default` org needs nothing):

* **PostgreSQL 15+.** The org-membership schema uses a column-list `ON DELETE SET NULL` foreign key that requires PostgreSQL 15+. Upgrade PostgreSQL before provisioning a second org.
* **A strong, stable `ORG_CH_SECRET`.** Each org's ClickHouse password is derived as `HMAC(ORG_CH_SECRET, org_id)`, so the publicly-known built-in dev default would yield publicly-derivable per-org credentials. `agenteye-orgctl org create` **refuses to run while `ORG_CH_SECRET` is unset or left at the built-in dev default**. Set your own value first (see [Deployment → environment variables](/agenteye/deployment) and, on Kubernetes, [§2.6 of the Kubernetes guide](/agenteye/kubernetes-deployment#26-multi-tenant-org-isolation-key-optional)). Keep it identical across all server replicas and don't rotate it casually; rotating it orphans every org's ClickHouse user until the next startup re-provisions them.

***

## Running the CLI

`agenteye-orgctl` is shipped in the **same image as the server** (alongside `agenteye-server`). You do **not** deploy a separate pod, Job, or Deployment for it; you exec it inside the server pod that is already running, so it reads the same `DATABASE_URL`, `CLICKHOUSE_URL`, and `ORG_CH_SECRET` the server uses.

**Kubernetes:**

```bash theme={null}
kubectl -n agenteye exec deploy/server -- agenteye-orgctl <args>
```

**Docker Compose:**

```bash theme={null}
docker compose exec server agenteye-orgctl <args>
```

The examples below show the bare `agenteye-orgctl <args>` for brevity; prefix each with whichever of the two lines above matches your deployment.

***

## Command reference

### Organizations

| Command                                      | What it does                                                                                                                                                                                                               |
| -------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `org create --slug <slug> --name <name>`     | Create a new organization. Refuses to run while `ORG_CH_SECRET` is unset or left at the built-in dev default (set your own first, see Prerequisites). Provisions the org's read-only ClickHouse user + row policy.         |
| `org list`                                   | List all organizations (slug, name, and lifecycle state).                                                                                                                                                                  |
| `org rename --slug <slug> --name <new name>` | Change an organization's display name. The slug (used in URLs and keys) is unchanged.                                                                                                                                      |
| `org delete --slug <slug>`                   | **Soft-delete** the org and drop its ClickHouse user. Data is **retained**. This revokes access and frees the per-org ClickHouse credential, but does not erase events. Reversible by ops; safe first step before a purge. |
| `org purge --slug <slug>`                    | **Irreversible data wipe.** The org must already be `delete`d. Never permitted on the built-in `default` org. Use only when you are certain the tenant's data should be destroyed.                                         |

### Members

| Command                                                                                                               | What it does                                                                                                                                                                                                                                                                                                                 |
| --------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `member add --org <slug> --email <email> [--set <permission-set>] [--add perm1,perm2] [--remove perm3] [--protected]` | Add a member to an org. Optionally start from a builtin permission set, then add/remove individual permissions. `--protected` pins the member so the dashboard can't remove or demote them (see below). The new member gets an OTP on their first dashboard login.                                                           |
| `member list --org <slug>`                                                                                            | List the org's members. The output columns are `EMAIL`, `SET` (the builtin set the member started from, or `-`), `PROT` (whether the member is protected), and `PERMISSIONS` (their effective permissions). An email shown with a trailing `*` is an instance admin; they have access to every org.                          |
| `member update --org <slug> --email <email> [--set ...] [--add ...] [--remove ...] [--protected \| --unprotect]`      | Change a member's permissions and/or protected flag. `--set` replaces from a builtin set; `--add` / `--remove` adjust individual permissions; `--protected` / `--unprotect` toggle protection. Passing only `--protected`/`--unprotect` (no grant flags) changes protection alone and leaves existing permissions untouched. |
| `member remove --org <slug> --email <email>`                                                                          | Remove a member from the org. Refuses if the member is protected; `--unprotect` them first. (A person can be a member of several orgs; this only affects the named org.)                                                                                                                                                     |

A person can be a member of more than one org with **different** permissions in each, e.g. an admin in one org and read-only in another. Each membership is administered independently per org: granting or changing a person's permissions in one org has no effect on their membership in any other.

### Protected members (an un-removable org admin)

Protection guarantees an org can never accidentally lock itself out of self-management. By default an org's own admins can add and remove each other through the dashboard's self-service users page, so they could remove the last admin and leave the org with no one able to manage it.

<img src="https://mintcdn.com/exosphere/RgxYS1UZshqb4m7m/agenteye/images/users.png?fit=max&auto=format&n=RgxYS1UZshqb4m7m&q=85&s=00099f45dd3d40bd7e31b337a678bfed" alt="The Users page: a card per dashboard user with their email, granted permissions, and edit/disable controls" width="2880" height="1800" data-path="agenteye/images/users.png" />

To prevent that, mark one member **protected**:

```bash theme={null}
kubectl -n agenteye exec deploy/server -- \
  agenteye-orgctl member add --org acme --email owner@acme.example --set admin --protected
```

A protected member **cannot be removed or demoted through the dashboard**; those actions return an error. Only an operator can change them, and only via this CLI: run `member update --org acme --email owner@acme.example --unprotect` first, then remove or demote. This guarantees every org keeps at least one admin its own members can't lock out, while keeping tenant control operator-only. Protection is **per-org**; protecting someone in one org has no effect on their membership in another.

### Builtin permission sets

`--set` accepts one of three builtin sets, applied per org:

| Set         | Intended for                                                                 |
| ----------- | ---------------------------------------------------------------------------- |
| `admin`     | Full access within the org, including managing the org's API keys and users. |
| `standard`  | Day-to-day use: read + run queries, build dashboards, acknowledge incidents. |
| `read-only` | View-only access to the org's data and dashboards.                           |

Start from a set with `--set`, then fine-tune with `--add` / `--remove` using the individual permission tokens listed in [API Keys](/agenteye/api-keys). The permission tokens themselves are identical to those used for API keys.

***

## Worked example

Provision a new `acme` tenant, add its first admin, let them mint a key, then decommission the org.

**1. Create the org** (`ORG_CH_SECRET` must already be set to a strong, stable value, not unset or the built-in dev default):

```bash theme={null}
kubectl -n agenteye exec deploy/server -- \
  agenteye-orgctl org create --slug acme --name "Acme Corp"
```

**2. Add the first member as an org admin:**

```bash theme={null}
kubectl -n agenteye exec deploy/server -- \
  agenteye-orgctl member add --org acme --email alice@acme.example --set admin
```

Alice receives an OTP the first time she signs in to the dashboard. From then on she works entirely in the UI under her org's URL prefix (e.g. `/acme/sessions`).

**3. Mint a per-org API key (in the dashboard):**

The operator does **not** mint per-org data keys from the CLI. Alice (or any org member with `keys:create`) creates collector / dashboard keys for the `acme` org from the dashboard's **Keys** page. Every key she creates is automatically stamped with her org and can only ever read or write `acme`'s data. See [API Keys](/agenteye/api-keys).

**4. Adjust a member later:**

```bash theme={null}
kubectl -n agenteye exec deploy/server -- \
  agenteye-orgctl member update --org acme --email alice@acme.example --add alerts:write

kubectl -n agenteye exec deploy/server -- \
  agenteye-orgctl member list --org acme
```

**5. Soft-delete the org** (revokes access + drops its ClickHouse user; data retained):

```bash theme={null}
kubectl -n agenteye exec deploy/server -- \
  agenteye-orgctl org delete --slug acme
```

**6. Purge the org** (irreversible; only after a soft-delete; never the `default` org):

```bash theme={null}
kubectl -n agenteye exec deploy/server -- \
  agenteye-orgctl org purge --slug acme
```

On Docker Compose, replace each `kubectl -n agenteye exec deploy/server --` prefix with `docker compose exec server`.

***

## Division of responsibilities

Everything an org member needs day-to-day is self-service in the dashboard and API, scoped automatically to their current org:

* **Per-org API keys** are created and managed by org members in the dashboard (or via the keys API with a key that carries `keys:create`). The CLI does **not** mint data keys. See [API Keys](/agenteye/api-keys).
* **Org switching** is built into the dashboard; members switch between the orgs they belong to from the org switcher, and org-scoped pages live under `/<org-slug>/…`.
* **Dashboards, saved queries, alerts, and all data use** happen entirely in the UI and API, scoped to the member's current org.

The operator, using `agenteye-orgctl`, owns only the org + member **lifecycle**: create / rename / delete / purge an org, and add / list / update / remove a member.

***

## See also

* [Deployment](/agenteye/deployment): `ORG_CH_SECRET` and the rest of the server environment.
* [Kubernetes Deployment](/agenteye/kubernetes-deployment): §2.6 creates the `agenteye-org-ch-secret` Secret before your first multi-tenant org.
* [API Keys](/agenteye/api-keys): the per-org key model and the permission tokens used by `--add` / `--remove`.
* [Troubleshooting](/agenteye/troubleshooting): multi-tenant provisioning and ClickHouse-isolation issues.
