Users, roles & organizations.
Last updated
Organizations & quotas
What it is. Multi-tenancy. A single AccessFlow deployment hosts one or more organizations, each a fully-isolated tenant — every user, datasource, query, and audit row belongs to exactly one org, and a tenant can never reach another's data. Reach for it when you host several teams or customers on one deployment and want per-tenant resource caps; most operators run a single org and can ignore the cross-org controls.
Platform admin (super-admin). Managing organizations across the cluster
requires the platform-admin capability — a flag on a user, separate from the five
roles below. A platform admin keeps their normal org role and additionally sees a
Platform navigation group with the /admin/organizations
screen. The first admin you create (via the setup wizard or the
bootstrap env vars) is provisioned as a platform admin
automatically.
/admin/organizations — platform admins manage every tenant and its quotas from one screen.Configure it. Platform admins manage tenants and their quotas from
/admin/organizations:
- Create an organization. From
/admin/organizations, click New organization, give it a name (an optional URL-safe slug is derived from the name when blank), and optionally set quotas. The org starts enabled and empty — add its first admin and datasources afterwards. - Set per-org quotas. Cap how much a tenant may consume:
max datasources, max users, and max queries per day (a rolling
trailing-24-hour count). Leave a field blank or set it to
0for unlimited. Quotas are enforced when the resource is created — a breach is rejected with HTTP409 QUOTA_EXCEEDEDand a message naming the limit, so the datasource / user / query simply isn't created. - Edit name & quotas. Open
/admin/organizations/<id>to change the name or raise / lower a quota, and to see live usage bars (current count vs. limit) for each cap. - Change what the org governs. The same screen carries the two switches
from the setup wizard — Govern outbound API
calls and Gate CI/CD deployments. An org admin does not need a platform
admin for this: the same two switches live at
/admin/governance-domains, under System in the sidebar. - Disable a tenant. Use Disable as a kill-switch — its users are blocked at login (local and SSO) and every in-flight session stops on its next request. The block is immediate (no cache, no waiting for token expiry). Enable restores access.
What the two switches actually do. They decide what the app shows, not what anyone is allowed to do. Turning a domain off removes its sidebar sections, its tab on the review queue, and its dashboard widgets, so a team that only governs databases is not looking at menus it will never use. Nobody loses a permission, no page stops working, and a link someone already has — from a notification, a bookmark, or a colleague — still opens. Turn the switch back on and everything reappears. API and deployment governance stay fully usable whichever way the switches are set.
Every organization action (create, update, disable, enable) is written to the audit log against the target org. Per-org login pages / SSO routing across multiple orgs are not yet available — see the roadmap.
Scheduled jobs
What it is. AccessFlow runs about thirty background jobs on a timer — approval timeouts, review escalations, scheduled and recurring query runs, access-grant expiry, retention and erasure, discovery scans, audit-sink delivery, schema drift. The Scheduled jobs page shows every one of them, whether its last run succeeded, and the history of its recent runs, so you can tell at a glance whether last night's retention sweep ran, and whether a job has been crashing or has stopped running. It is a platform-admin screen, like Organizations, because the jobs belong to the deployment rather than to any one tenant.
Use it. Open /admin/jobs from the Platform
group in the sidebar:
- Scan the table. Each row is one job, with the module it belongs to, how often it runs, the status and time of its last run, how long that took, and how many runs succeeded or failed over the last day. A red failed badge counts the failures in a row since the job last succeeded; hover it for the latest error.
- Open a job's history. Click a row to see its recent runs, newest first — which server ran it, when, for how long, and, for a failed run, the error. Filter by status to see only the failures.
- Read the states. Running means a run is in progress. Abandoned means a run started but never finished within its time limit — the server running it most likely stopped mid-run. Not run yet means there is no recorded run: the job has not run since recording started, or its older runs have been cleared out.
What Failed means. A run is marked failed when the job itself stops with an error. Most jobs work through a list of items — queries, grants, audit destinations — and deal with a problem on one item without stopping, so a run can show Success even though some items failed. Those per-item problems are shown where they belong: for example, a failing audit destination is flagged on the audit sinks settings, not here.
Good to know. When several AccessFlow servers run side by side, only one
of them runs each job per cycle; the others skip it, and those skips are not recorded.
The page is read-only — you cannot start, pause or reschedule a job from it. If the page
says Scheduler disabled, background jobs are switched off on that server
(ACCESSFLOW_SCHEDULING_ENABLED=false).
Tune it. ACCESSFLOW_SCHEDULING_EXECUTIONS_RETENTION (how long
history is kept, default P14D),
ACCESSFLOW_SCHEDULING_EXECUTIONS_MAX_PER_JOB (newest runs kept per job,
default 500), ACCESSFLOW_SCHEDULING_EXECUTIONS_SUMMARY_WINDOW
(the period the success and failure counts cover, default PT24H) and
ACCESSFLOW_SCHEDULING_EXECUTIONS_ENABLED (set false to stop
recording). All of them are listed in the
deployment configuration reference.
Languages & localization
What it is. AccessFlow ships seven bundled interface languages — English, Español, Deutsch, Français, 简体中文, Русский, and Հայերեն. An org admin decides which of them users may choose, which one users start on, and which language the AI reviewer writes in.
Configure it. /admin/languages (admin-only) has three
controls: an available languages checklist (at least one required) that drives
the language switcher in the top bar; a default language select, narrowed to that
allow-list, applied to users who have not picked one; and an AI review language
select spanning all seven languages independently of the user allow-list. Saving
refreshes the top-bar switcher immediately.
Tune it. Users override the default themselves from the language switcher next to the theme toggle, and the choice persists to their profile. Any string missing from a translation falls back to English.
Users
What it is. The people who can sign in to AccessFlow and the role each one carries. Create accounts directly, or let SAML / OAuth users be auto-provisioned on first sign-in when SSO is enabled (see SAML and OAuth).
Configure it. Manage everyone from /admin/users:
/admin/users → Invite via email. Enter the recipient's email, pick a role, and AccessFlow emails a one-time signup link.- Invite via email (default). From
/admin/users, click Invite via email, fill in the recipient's email, optional display name, and role, then submit. AccessFlow generates a signup token and emails it; the link expires afterACCESSFLOW_SECURITY_INVITATION_TTL(default 7 days). - Create with a password. Use the dropdown next to the invite button → Create with password to provision a user directly. Useful when SMTP isn't configured yet, or when you want to seed an account synchronously.
- Edit or deactivate. Click any row to change the role or flip the active toggle. Deactivated users can't sign in but their audit trail is preserved.
- Data usage. A user's row menu has Data usage: how much of each data budget that applies to them they have read, per datasource. Visible to admins who can manage users.
- Pending invitations are listed below the user table; resend or revoke them from there.
Tune it. ACCESSFLOW_SECURITY_INVITATION_TTL (invite-link
lifetime, default P7D), ACCESSFLOW_SECURITY_PASSWORD_RESET_TTL
(reset-link lifetime, default PT1H), and
ACCESSFLOW_SECURITY_PASSWORD_RESET_RESET_BASE_URL (link base, default
http://localhost:5173).
User roles & RBAC
What it is. Role-based access control. Every user carries one org-wide role that caps what they can do; on top of it, per-datasource permissions decide which databases they may touch. Pick the lowest-privilege role that still lets someone do their job — and a user can never approve their own query, whatever their role.
What are the user roles in AccessFlow?
AccessFlow has five org-wide roles. READONLY submits SELECT queries only. ANALYST adds DML. REVIEWER adds approving other people’s queries. ADMIN adds DDL and every configuration screen. AUDITOR is read-only across the audit log and compliance reports, and cannot submit queries at all.
Custom roles. Beyond the five built-in system roles, an admin can compose
custom roles on /admin/roles from a fixed catalog of functional
permissions (submit SELECT/DML/DDL, review queries, review access requests, manage
datasources, view the audit log, and so on) — e.g. a reviewer who may approve queries but
not manage users. The five system roles are immutable and behave exactly as the matrix
below; a custom role grants exactly the permissions you tick. Roles that are still
assigned to users cannot be deleted.
Configure it. Assign a system or custom role when you create or edit a
user on /admin/users; the matrix below is what each built-in role may do.
Platform admin is separate from the five roles. The
platform-admin capability is an orthogonal flag, not a
fifth role — a platform admin keeps whatever role their home org assigns and is
additionally allowed to manage organizations across the cluster
(/admin/organizations). It grants no extra capability inside any single org;
the matrix below still governs every tenant-scoped action.
| Capability | READONLY |
ANALYST |
REVIEWER |
ADMIN |
AUDITOR |
|---|---|---|---|---|---|
Submit SELECT queries | ✓ | ✓ | ✓ | ✓ | — |
Submit DML (INSERT / UPDATE / DELETE) | — | ✓ | ✓ | ✓ | — |
Submit DDL (CREATE / ALTER / DROP) | — | — | — | ✓ | — |
| View own query history | ✓ | ✓ | ✓ | ✓ | — |
| View all queries in the org | — | — | ✓ | ✓ | — |
| Approve / reject queries | — | — | ✓ | ✓ | — |
| Request time-bound datasource / API-connection access (JIT) | ✓ | ✓ | ✓ | ✓ | — |
| Review / approve access requests | — | — | ✓ | ✓ | — |
| Review / approve deployment requests | — | — | ✓ | ✓ | — |
| Manage datasources | — | — | — | ✓ | — |
| Manage users | — | — | — | ✓ | — |
| Manage user groups | — | — | — | ✓ | — |
| Manage data budgets | — | — | — | ✓ | — |
| Manage review plans | — | — | — | ✓ | — |
| Manage deployment pipelines | — | — | — | ✓ | — |
| View audit log | — | — | — | ✓ | — |
| Manage notification channels | — | — | — | ✓ | — |
| Manage external audit sinks | — | — | — | ✓ | — |
| Configure AI | — | — | — | ✓ | — |
| Configure SAML / OAuth | — | — | — | ✓ | — |
| View / export compliance reports | — | — | — | ✓ | ✓ |
| View the over-provisioned access report | — | — | — | ✓ | ✓ |
| View the privileged-access report | — | — | — | ✓ | ✓ |
| Who can write to a table (effective-access lookup) | — | — | — | ✓ | ✓ |
| Run a decision trace (query, API call or deployment) | — | — | — | ✓ | — |
| View behavioural anomalies (UBA) | — | — | — | ✓ | ✓ |
| Acknowledge / dismiss anomalies | — | — | — | ✓ | — |
| Break-glass / emergency execution† | ✓ | ✓ | ✓ | ✓ | — |
| View break-glass log | — | — | — | ✓ | ✓ |
| Acknowledge break-glass events | — | — | — | ✓ | — |
† Break-glass / emergency execution is not granted by role — it is gated by a
separate per-user, per-datasource can_break_glass permission that an admin
grants explicitly (required for everyone, including admins; time-boxed). A user can
break glass only on a datasource they hold that grant for, and only for query types they
already have the capability for.
Which role for what. Use READONLY for people who only
need to look at production data (analysts, on-call engineers reading dashboards).
Use ANALYST for people who write data through reviewed queries.
Use REVIEWER for people who approve other users' queries — typically
senior engineers or DBAs. Use ADMIN for the platform-team operators who
configure the system itself. Use AUDITOR for a dedicated, read-only
compliance reviewer — it sees only the compliance dashboard (/admin/auditor):
pre-built PII/PCI/GDPR access and DDL/DELETE reports with signed PDF/CSV export, and
nothing else.
Datasource-level permissions. Role is the org-wide ceiling. On top of
it, every user needs an explicit per-datasource permission grant to
access a given database — it controls read / write / DDL per
datasource, row caps, allowed schemas / tables, denied schemas / tables (everything
except these — a denial always beats the allowed list), restricted columns (which are masked
as *** in SELECT results), denied columns (a query that
references one is refused before it runs), and denied query shapes (for example no joins
or aggregate functions). See
docs/07-security.md
for the full authorization matrix.
Group-based access grants. Rather than a row per person, an admin can grant a user group access to a datasource or an API connector (same read / write / DDL / break-glass controls); every member inherits the grant, and adding someone to the group gives them access without a new grant. When a user has both a direct grant and one or more group grants, their effective access is the most-permissive union — capabilities are OR-ed, allow-lists merge, restricted-column masks apply only where every grant restricts them, and each grant's expiry is honoured independently. Two things work the other way. The row limit override and the bytes-scanned cap: for each, the smallest value wins, and a grant can only lower the datasource's own limit, never raise it. And denied schemas, tables, columns and query shapes add up: anything denied by any one grant stays denied, so a wider group grant can never undo a denial, and a group's denial applies to every member. The flip side: revoking or expiring the grant that carries a denial removes it, and the user's other grants may then reach the table.
Checking the result. To see the merged answer for one person, open the datasource's Effective access tab, or Effective access in their menu on Users. It shows their capabilities, allowed and denied objects, masked columns, row filters and effective row cap. Anything that comes from a grant names the direct grant or group behind it and when that grant expires. Masked columns and row filters name the policy behind them and whether it applies through the person's role, one of their groups, or everyone. The row cap says whether it was set by a grant, the datasource's own limit or the system-wide ceiling. The view needs the manage datasource permissions permission, and each time someone opens it, the audit log records who looked at whom.
Just-in-time (JIT) access requests
Instead of an admin pre-granting a
permission, any user can request temporary, scoped access from
/access-requests — to a datasource (pick the capabilities they
need — read / write / DDL — and an optional schema/table scope) or to an API
connection (read / write plus an optional allow-list of specific operations from
the connector's schema catalog), with a duration. The request runs
through the same reviewer-eligibility and multi-stage approval engine as query review
(a requester can never approve their own); API-connection requests route through the
connector's assigned review plan. Admins are the backstop approver: an admin
sees and can approve every pending access request from
/admin/access-requests — even on resources with no review plan — so a
request is never stuck waiting for an approver who was never configured. On final
approval AccessFlow writes a time-boxed permission grant (expiring at
now + duration) — a datasource permission, or an API-connection permission
visible on the connector's Permissions tab alongside admin-granted rows; it's revoked
automatically on expiry, and an admin can revoke an
active grant early from /admin/access-requests. Tune the revocation cadence
with ACCESSFLOW_ACCESS_GRANT_EXPIRY_POLL_INTERVAL (default PT5M)
and the allowed duration window with ACCESSFLOW_ACCESS_MIN_DURATION /
ACCESSFLOW_ACCESS_MAX_DURATION (defaults PT15M / P30D).
A requester can additionally tick “Pre-approve queries under this grant” on the
request form (off by default): while such a grant is active, queries it covers —
matching capability and schema/table scope — skip human review entirely and are
auto-approved with the grant and its approver recorded on the query detail and in the
audit log. The flag is shown as a highlighted tag in the approval queue so the reviewer
sees exactly what they authorize; auto-reject and escalation routing policies, high-risk
AI verdicts, and open behavioural anomalies still override the fast-path.
/admin/access-requests — pending JIT access requests; admins approve, reject, or revoke an active grant.Break-glass / emergency access
For genuine emergencies — production is
down and approvers are unreachable — an admin can grant a user the
can_break_glass permission on a datasource (a checkbox on the permission
grant, alongside read / write / DDL, time-boxed via the same expires_at).
With that grant, an Emergency access button appears on the editor for
that datasource: the user supplies a mandatory justification and the query
executes immediately, bypassing review — but still through every proxy
guard (schema/table allow-list, dynamic masking, row-level security, row caps). The grant
is required for everyone, including admins. Each break-glass execution fires
instant notifications to all admins (including PagerDuty), writes a prominently-tagged
QUERY_BREAK_GLASS_EXECUTED audit row, and opens a mandatory
retro-review on the /admin/break-glass log that an admin —
never the submitter — must acknowledge after the fact. The executed query keeps
its normal terminal state; the retro-review is tracked alongside it.
/admin/break-glass — every emergency execution opens a mandatory retro-review here for an admin (never the submitter, nor the person an agent ran it for) to acknowledge.User groups
What it is. Named, organisation-scoped collections of users. Use them to (1) bundle reviewers so you can attach a single group — instead of ten individual users — to a datasource as eligible reviewers, (2) grant a whole team data or API access (a datasource or API-connector grant on a group is inherited by every member, so you don't add a row per person), and (3) act as the target of IdP group mappings so SAML / OAuth2 logins keep membership in sync automatically.
Configure it. Manage groups from /admin/groups:
- Create a group. Go to
/admin/groups→ Create group. Pick a name (e.g. Billing Reviewers) and an optional description. - Add members. Open the group, click Add member, and pick users from the dropdown. Manually-added members are tagged Manual and stay put regardless of the IdP sync.
- Use the group. On a datasource's Reviewers tab
(
/datasources/<id>/settings), add the group as a reviewer. From that point on, members of the group can see and decide queries against that datasource (in addition to plan-approver rules). On the same page's Permissions tab (and an API connector's Permissions tab) you can also grant the group access — switch the grant target from User to Group and every member inherits the read / write / DDL / break-glass grant. - Optional: IdP-managed memberships. Configure
group_mappingson the SAML or OAuth2 admin pages so an IdP group claim auto-maps to the AccessFlow group. On every login, AccessFlow replaces the user's IdP-sourced memberships with the mapped set; Manual memberships are never touched.
Per-datasource reviewer scoping. Once a datasource has at least one assigned reviewer (a user or a group), only those reviewers see its queries. Datasources with none fall back to the review-plan approvers — so adopting groups is purely additive, no migration required.
/admin/groups — organisation-scoped user groups; open one to manage members.Service accounts
What it is. Non-human identities — a CI pipeline, an AI agent, an integration — that authenticate with an API key and never sign in. A service account carries a role like any user, so the same permissions and the same per-datasource grants apply, but it has no password, cannot use SSO, and is badged as a Service account wherever people are listed so a reviewer never mistakes a robot for a colleague.
Configure it. Manage them from /admin/service-accounts (requires the Manage service accounts permission, held by admins):
- Create an account. Click Create service account, give it an email (an identifier only — it never receives mail), a display name and a role. The role defaults to Read-only; the page warns when you pick a role that can approve requests, because an agent on such a role becomes an eligible approver. Optionally name an owner — the person accountable for the account, shown to attestation reviewers.
- Issue a key. On the API keys tab, Issue key shows the
plaintext exactly once — copy it into the pipeline or agent's secret store. Keys are the
account's only credential. Give each key an optional Application name (for
example
reporting-service) so the audit log shows which application used it; see Which application made a request? - Rotate rather than revoke. Rotate issues a replacement and keeps the old key working for a grace period (24 hours by default, or the number of hours you enter), so a consumer can be updated without an outage. The replacement keeps the old key's application name unless you enter a new one. Revoke cuts access immediately.
- Restrict its MCP tools. The MCP tools tab limits which tools the account's keys may call on the MCP server. The server still advertises every tool to the account; a call outside the allow-list is denied when it is invoked — the list is an enforcement boundary, not a discovery filter.
- Cap its request rate. The Limits tab sets per-minute and per-day request ceilings for this account; leave a field blank to inherit the deployment default.
- Give it row-security attributes. The Attributes tab holds the
key/value pairs a row security policy reads
as
:user.<key>— for exampleregion = EU, so the account only sees European rows. This is the only place to set them for a service account; the Users page does not edit service accounts. - Let it act on someone's behalf. The On-behalf-of principals
tab lists the people who consent to be named by this account through the
X-AccessFlow-On-Behalf-Ofheader. A delegation confers no permission — the account's own key decides what it may do — but the named person appears as an on behalf of chip on the request and in the audit log, and counts as a submitter for the self-approval ban. - Review its activity. The Activity tab is the audit log
pre-filtered to the account;
/admin/audit-logcan also filter by the person an account acted for.
Bootstrap-managed accounts. An account declared through
ACCESSFLOW_BOOTSTRAP_SERVICE_ACCOUNTS_<n>_* (see
Infrastructure as code) shows a
Bootstrap badge. Its email, display name, role and declared key are read-only on
the page and change only in that configuration — the declared key can be neither revoked
nor rotated here, because the next change to that configuration would re-import it. Rotate it by
changing the secret and restarting AccessFlow, or deactivate the account here in an emergency. Everything else
(owner, description, tools, limits, attributes, delegations, extra keys) stays editable. When the
configuration leaves out the role it defaults to Admin, so always set one. A later
change to the role or display name in the configuration is applied to the account on the
next restart. A custom role the account already holds is kept, because the configuration can
only name a built-in role.
Where robots show up. /admin/users lists service accounts
alongside people with a badge and a filter (People and service accounts / People only / Service accounts only); group member
and permission pickers badge them too; and an attestation reviewer sees the account's owner
next to its grant. Service accounts get no in-app notification inbox, although your
configured channels still reach them. They are also invisible to SCIM, so an identity
provider sync can never deactivate them.