Skip to main content
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 and, on Kubernetes, §2.6 of the Kubernetes guide). 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:
kubectl -n agenteye exec deploy/server -- agenteye-orgctl <args>
Docker Compose:
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

CommandWhat 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 listList 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 deleted. Never permitted on the built-in default org. Use only when you are certain the tenant’s data should be destroyed.

Members

CommandWhat 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. The Users page: a card per dashboard user with their email, granted permissions, and edit/disable controls To prevent that, mark one member protected:
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:
SetIntended for
adminFull access within the org, including managing the org’s API keys and users.
standardDay-to-day use: read + run queries, build dashboards, acknowledge incidents.
read-onlyView-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. 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):
kubectl -n agenteye exec deploy/server -- \
  agenteye-orgctl org create --slug acme --name "Acme Corp"
2. Add the first member as an org admin:
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. 4. Adjust a member later:
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):
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):
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.
  • 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: ORG_CH_SECRET and the rest of the server environment.
  • Kubernetes Deployment: §2.6 creates the agenteye-org-ch-secret Secret before your first multi-tenant org.
  • API Keys: the per-org key model and the permission tokens used by --add / --remove.
  • Troubleshooting: multi-tenant provisioning and ClickHouse-isolation issues.