Skip to content

Permissions and roles

Three roles control access to Formation. Every authenticated request carries one or more of them; the role determines what the user (or service) is allowed to do.

RoleWho has itWhat it grants
UserEvery human end-userFull read and write access to all primary data: addresses, schemes, developments, companies, investment events, occupier events, portfolios.
AdminA subset of human usersEverything the User role grants, plus access to system settings, tag management, and market-boundary editing.
API.AccessService principals (load tests, machine-to-machine integrations)Full read and write access to primary data. Cannot access admin functions.

Roles are assigned via Azure AD group membership and surfaced into the request as claims.

  • View every list page and detail page.
  • Create, edit, and delete records they have access to (across all primary entities).
  • Add, edit, and delete notes, tags applications, and external links on records.
  • Apply existing tags to records.
  • Run searches across every list.
  • Export list pages to Excel.

Everything User can do, plus:

  • Open Settings → Admin panels.
  • Use the Tag Manager: create new tags, rename or delete existing tags, manage tag colours / categories.
  • Create, edit, or delete Market Boundaries through the in-app editor. Non-admins can see boundaries on maps but can’t change them.
  • Other admin-only settings panels that affect system behaviour (visibility may change as features are added).

The Tag Manager renders in read-only mode for non-Admin users: they can browse the tag list but the edit / add / delete buttons are hidden.

Service principals (load tests, automation, future machine-to-machine integrations) get API.Access. This grants the same primary-data permissions as User: read and write across addresses, schemes, companies, events, portfolios. It explicitly does not grant Admin — service principals are deliberately excluded from administrative actions.

Every API endpoint requires authentication and one of the three roles. Anonymous requests get rejected by the auth layer before they reach any controller. A request authenticated as a member of an unrecognised group gets the same outcome — no access.

Two server-side policies control role-restricted operations:

  • AdminOnlyRequireRole("Admin"). Used on endpoints that should reject service principals as well as ordinary users. Applied to tag management, market-boundary writes, and admin-only settings.
  • RequireRoleRequireRole("User", "Admin", "API.Access"). Used on endpoints that any authenticated principal can hit.

The split exists because service principals get API.Access but not User — so a naive RequireRole("User", "Admin") would lock them out unintentionally. AdminOnly deliberately doesn’t include API.Access so admin-only endpoints stay human-only.

Wherever the UI hides an action (e.g. the Tag Manager’s edit buttons), it’s a defensive measure on top of the server-side policy: the hidden action would also be rejected by the API if the user found a way to invoke it.

The UI never grants access — only the server does. UI gating is for tidiness, not security.

AreaVisible toEditable by
Tag Manager (Settings → Tags)EveryoneAdmin only
Market Boundary editor (/market-boundaries/[id])Everyone (view)Admin only (edit)
Settings → Admin panelsAdmin onlyAdmin only
All other list / detail / edit pagesUser, Admin, API.AccessUser, Admin, API.Access

If a feature added later restricts certain entities to certain roles (e.g. “only Admins can edit verified companies”), it will be documented on the relevant entity page’s Permissions section.

  • Policy registration: src/services/api/app/app.api/Program.cs (around line 552-562)
  • Role transformation from Azure AD groups: src/services/api/app/app.api/Services/GroupRoleClaimsTransformation.cs
  • UI role check: src/services/web/src/lib/stores/roles.svelte.ts (hasRole('Admin'))
  • Settings / Tag Manager admin-gating consumers: src/services/web/src/lib/components/system/Settings.svelte, TagManager.svelte