Documentation

Users, roles & organizations.

Last updated

Organizations & quotas

There is a guide for this. Invite your team and assign roles is the step-by-step version: create the accounts, pick roles, grant datasource access, and offboard cleanly. This chapter is the reference behind it.

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.

The AccessFlow platform-admin Organizations page listing each tenant organization with its per-org datasource, user, and daily-query quotas.
/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:

  1. 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.
  2. 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 0 for unlimited. Quotas are enforced when the resource is created — a breach is rejected with HTTP 409 QUOTA_EXCEEDED and a message naming the limit, so the datasource / user / query simply isn't created.
  3. 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.
  4. 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.
  5. 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:

  1. 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.
  2. 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.
  3. 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:

Invite a teammate modal in the AccessFlow admin Users page, with fields for email, display name, and a role dropdown defaulting to ANALYST.
/admin/users → Invite via email. Enter the recipient's email, pick a role, and AccessFlow emails a one-time signup link.
  1. 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 after ACCESSFLOW_SECURITY_INVITATION_TTL (default 7 days).
  2. 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.
  3. 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.
  4. 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.
  5. 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.

The /admin/access-requests queue listing a pending just-in-time access request — requester, datasource, requested capabilities, duration, and approve / reject / revoke actions.
/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.

The AccessFlow break-glass log listing emergency-access executions awaiting an admin's mandatory retro-review acknowledgement.
/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:

  1. Create a group. Go to /admin/groups → Create group. Pick a name (e.g. Billing Reviewers) and an optional description.
  2. 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.
  3. 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.
  4. Optional: IdP-managed memberships. Configure group_mappings on 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.

User groups list at /admin/groups showing a table of groups with name, description, member count, created date, and edit/delete actions, plus a Create group button.
/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):

  1. 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.
  2. 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?
  3. 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.
  4. 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.
  5. 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.
  6. Give it row-security attributes. The Attributes tab holds the key/value pairs a row security policy reads as :user.<key> — for example region = 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.
  7. 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-Of header. 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.
  8. Review its activity. The Activity tab is the audit log pre-filtered to the account; /admin/audit-log can 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.