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

# Users, roles and permissions

> What a role is, the 12 built-in roles, roles of your own, the default role, giving access with a role per place, what API tokens can never hold, and how to read the permission each operation needs.

Every request to Flow Payroll is made by a **person** or an **API token**. What they can do depends on two things: **where** they have access (which organisations) and the **role** they hold there. A role is a named list of **permissions**, and each operation in this reference needs one permission.

> [!WARNING]
>
> **Roles other than Administrator are being switched on account by account.** Until they are on for your account, only Administrator can be given: `GET /roles` lists the other roles with `available: false`, and giving one, or choosing it as the default role, answers `400` with `role_not_yet_available`. They become available without any change on your side, so build your integration to handle the permission refusals on this page now.

## Permissions

A permission is an area of Flow Payroll and something you can do there, written `area:verb`. For example, `employee:create` adds employees, `employee:write` changes the details of employees who already exist, and `pay-run:lock` locks a pay run.

| Area | What it covers | Verbs |
| - | - | - |
| `employee` | Name, contact, address, date of birth, gender, job, department, start and leave dates, tags | `read`, `create`, `write`, `delete`, `export` |
| `employee-payroll` | Tax code, NI number and category, student loans, starter declaration, opening balances, payroll id and pay schedule, previous employment, director, bank details, salary, partner details | `read`, `write` |
| `working-pattern` | Employee working patterns, and organisation and pay schedule defaults | `read`, `write` |
| `organisation-details` | Legal and trading name, addresses, contacts, registration number | `read`, `write` |
| `organisation-settings` | HMRC settings, pay schedules, pay elements, balances, email wording | `read`, `write` |
| `integration` | Webhooks, connected apps, imports and syncs with other systems | `read`, `write` |
| `pay-run` | Pay runs, lines, payslips, P45, P60, recurring items, statutory pay, court orders, holiday | `read`, `write`, `lock`, `unlock` |
| `rti` | FPS, EPS, DPS notices, P32, year end | `read`, `submit` |
| `pension` | Schemes, enrolment, provider files | `read`, `write`, `export` |
| `payment` | Payment settings, payment (BACS) files, approving pension contributions for collection | `read`, `write`, `export`, `release` |
| `report` | Reports and exports | `read`, `write`, `export` |
| `journal` | Journal templates and postings | `read`, `write` |
| `user` | Who can sign in, what they reach, removing them | `read`, `write`, `delete` |
| `api-token` | API tokens and what they reach | `read`, `write` |
| `role` | Roles of your own and the default role | `read`, `write` |
| `organisation` | The organisations in the account, and organisation groups | `read`, `write` |
| `customer` | The account: its name, branding and settings | `read`, `write` |
| `billing` | Invoices, subscription and billing settings | `read`, `write` |

In every area, `write` changes what already exists and `create` adds something new:

| Verb | What it allows | Employee routes |
| - | - | - |
| `create` | Add a new employee | `POST /employees` (and so adding an employee in the app, onboarding and importing new starters) |
| `write` | Change an employee who already exists | `PUT` and `PATCH /employees/{employeeId}`, the starter details, the bulk update from a CSV |
| `delete` | Remove an employee record | `DELETE /employees/{employeeId}` |

A new employee's details are judged like any change: payroll fields need `employee-payroll:write`, working patterns `working-pattern:write`, and the rest `employee:write`. Every built-in role that could add employees before still can: Administrator, Payroll manager, HR manager and HR integration through `*` or `employee:*`, and Developer, Payroll processor and HR sync by name.

`payment:export` sees, downloads and regenerates a pay run's payment (BACS) file, which holds every employee's bank details. `pension:export` generates and downloads the pension provider's files and the contributions CSV. `payment:release` approves a pay run's pension contributions for the provider to collect.

`employee:export` exports employees as a CSV file (`POST /employees/exports`). Which details the file holds is still decided by `read` on each area: a role without `employee-payroll:read` exports no tax code, NI number or bank details. Developer, Payroll processor, Pay run approver and Client contact hold it, and so does every role with `employee:*`.

A role lists permissions in one of three forms: `employee:read` for one permission, `employee:*` for every verb in one area, or `*` for everything. A wildcard also covers permissions added to that area later. Every verb includes `read`, so a role with `employee:write` can also read employees.

## Built-in roles

Every account has the same 13 built-in roles. They cannot be changed or deleted. `GET /roles` returns each one with its exact `permissions` and `assignableTo`, which says whether a person, an API token or both can hold it.

| Role | `roleId` | What it is for | Who can hold it |
| - | - | - | - |
| Administrator | `administrator` | Everything in the organisations they reach, including people, API tokens and roles. | People and API tokens |
| Access administrator | `access-administrator` | Manages who can sign in and what they can do, and nothing else. | People |
| Developer | `developer` | Builds integrations: API tokens, webhooks and connected apps. Keeps employee details, working patterns and organisation names in step, and reads payroll data and settings to test them. Cannot change payroll or HMRC settings, or manage people. | People |
| Payroll manager | `payroll-manager` | Runs and locks payroll, gets payment files, submits to HMRC, manages employees and organisation settings. | People and API tokens |
| Payroll processor | `payroll-processor` | Prepares pay runs and keeps employee records up to date. Cannot lock pay runs, get payment files or submit to HMRC. | People and API tokens |
| Pay run approver | `pay-run-approver` | Reviews and locks pay runs, gets payment files and approves pension contributions. Cannot change them. | People and API tokens |
| Client contact | `client-contact` | A bureau's client: sees their employees, pay runs, payslips and reports for their own organisation, and keeps working patterns up to date. Employee details are changed by the bureau, or by a role of your own that allows it. | People |
| HR manager | `hr-manager` | Keeps employee details and working patterns up to date. No payroll. | People |
| Finance | `finance` | Gets payment files, approves pension contributions, runs reports and journals, sees invoices. Cannot change employees or pay runs. | People and API tokens |
| Accountant | `accountant` | Journals and reports for the books, read-only everywhere else. | People and API tokens |
| Viewer | `viewer` | Sees everything payroll-related but changes nothing. | People and API tokens |
| HR integration | `hr-integration` | For an HR system's API token: keeps employee details, working patterns, organisation details and people in step. | API tokens |
| HR sync | `hr-sync` | For an HR system's API token that keeps employee details, working patterns and organisation names in step, without managing people. A Developer can give it. | API tokens |

Giving a role to somebody who cannot hold it answers `400` with `role_not_assignable`.

## Roles of your own

When no built-in role fits, build your own with `POST /roles`, picking permissions from the table above. Its `roleId` is `custom.` followed by a short name, such as `custom.hr-team`, and never changes. An account can hold 50 roles of its own.

* You can only build or change a role with permissions you hold yourself (`403` with `role_change_denied`).
* `PATCH /roles/{roleId}` changes a role, and everyone who holds it has the change at once. `GET /roles/{roleId}/revisions` shows every version.
* `DELETE /roles/{roleId}` works only once nobody holds the role (`409` with `role_in_use`, listing up to 20 holders).

## Giving a role

Access is a list of **grants**. Each grant is a role at one place: the whole account, one organisation group or one organisation. Send the list as `access` when you invite a person (`POST /users`), invite several (`POST /users/bulk`, as `defaults.access` and on each entry, where an entry's list replaces the defaults) or create an API token (`POST /api-tokens`):

```json theme={null}
{
  "email": "jane.doe@example.com",
  "givenName": "Jane",
  "access": [
    { "scope": { "type": "Account" }, "roleId": "viewer" },
    { "scope": { "type": "OrganisationGroup", "id": "01JGROUP0000000000000000AA" }, "roleId": "finance" },
    { "scope": { "type": "Organisation", "id": "01JORG00000000000000000AAA" }, "roleId": "payroll-processor" }
  ]
}
```

* `scope.type` is `Account`, `OrganisationGroup` or `Organisation`. `id` names the group or organisation; for `Account` leave it out (it is your own account).
* One grant per place: naming the same place twice answers `400` with `duplicate_access`.
* Every grant is checked before anything is written, so a request with one grant you may not give writes nothing.
* Inviting somebody who is already in the account keeps the role they hold at each place, and only adds the places they lack.
* `GET /users/{principalId}` and `GET /api-tokens/{clientId}` return `access` in the same shape.

To change the role at a place afterwards, use that place's own route with `{ "roleId": "..." }`: `PUT /users/{principalId}/customer-access` for the whole account, `PUT /users/{principalId}/organisations/{organisationId}` for one organisation and `PUT /users/{userId}/organisation-groups/{groupId}` for a group, and the matching `/api-tokens/{clientId}/…` routes for a token. Leave `roleId` out there to give `administrator`, or to keep the role already held.

`organisationIds` and `allOrganisations` on those requests, and `organisationIds` and `coversWholeCustomer` on the two reads, are [deprecated](https://docs.flowpayroll.ai/api-reference/deprecations.md) in favour of `access`. They keep working, but a request that sends `access` together with either answers `400` with `access_with_deprecated_fields`. A request without `access`, or a grant route without `roleId`, gives your account's default role on access they did not have (see below), and `administrator` while roles are not on for your account.

* You can give a role only if you hold every permission in it (`403` with `role_assignment_denied`). An Access administrator may also give roles with payroll permissions they do not hold, but never more over people, roles, API tokens or the account than they hold.
* Locking pay runs (`pay-run:lock`), unlocking them (`pay-run:unlock`), submitting to HMRC (`rti:submit`), getting payment files (`payment:export`) and approving pension contributions (`payment:release`) can only ever be given by somebody who holds them.
* You need permission to manage people, or API tokens, where you give the role (`403` with `access_management_denied`).

## The default role

Your account's **default role** is the role new access gets whenever a request names no role: inviting a person, creating an API token, giving an organisation or the whole account, and people your identity provider adds through [SCIM](https://docs.flowpayroll.ai/api-reference/administration/scim-provisioning.md). Access somebody already holds keeps its role. `GET /customer/access-settings` returns it, and `PATCH /customer/access-settings` changes it. It is Viewer until you choose one (`defaultRoleChosen` is `false`). While roles are not on for your account, new access without a role is Administrator whatever the default role says.

When the default role is one for people only, creating an API token without a role answers `400` with `role_not_assignable`: name the token's role. `roleId` stays required where it already was: each entry of `access`, and organisation group grants.

The default role can never include managing people, roles, API tokens or the account (`403` with `default_role_denied`), because it reaches every new person without anybody reviewing it.

## Where a role applies

A role applies where it was given. Somebody can reach an organisation in three ways, and each carries its own role:

| Access | Reaches | Role field |
| - | - | - |
| **One organisation** (direct grant) | The organisation named | The role on that grant |
| **Organisation group** | Every organisation in the group, including ones added later | The role on the group grant |
| **Account-wide** | Every organisation the account owns, including ones added later | The role on the account-wide grant |

Where somebody reaches one organisation in more than one way, they can do everything any of those roles allows there. A payroll processor across the whole account who is also a pay run approver for one client can lock pay runs for that client only.

`GET /users/{principalId}` and `GET /api-tokens/{clientId}` list the role at each place in `access`. `GET /users/me` returns the signed-in person and their own `access` whatever their role, so a Viewer can always see their own roles (an API token reads itself with `GET /api-tokens/{clientId}`). `GET /organisations/{organisationId}/access` lists everybody who can reach one organisation, one row for each way in, with its role. `GET /access-changes` lists every change to who has access, with the role before and after.

## A payroll-only role

When an HR system keeps your employee details, people who run payroll should change payroll fields but not the HR fields the HR system sends. Give them a role of your own that holds `employee-payroll:write` and `employee:read` but not `employee:write`. Fields on the same employee are judged one by one: a save that changes a tax code, NI category, bank details or director details is allowed, and one that changes a name answers `403` with `insufficient_permissions`, naming `employee:write`. Unchanged fields are never judged, so a form that sends the whole employee back still saves. Working days work the same way with `working-pattern:write`: a role holding it but not `employee:write` changes an employee's working days and nothing else.

Reading works the same way. A role sees an employee's payroll details (tax code, NI number and category, bank details, director details, loans, salary and year to date) only with `employee-payroll:read`, and their working days only with `working-pattern:read`. Without it, `GET /employees/{employeeId}`, the list and every answer that returns an employee give those fields empty: `null`, or `{}`, `""` or `false` where the field is always present. A `PUT` that sends them back empty leaves them as they are stored; sending anything else is a change, refused without the write permission.

The account owner and Administrators can always change everything, including data an HR system sent.

## New starters only from your HR system

When your HR system adds new starters through its API token, people who run payroll can keep employee details correct without adding employees themselves. Give them a role of your own that holds `employee:write` but not `employee:create` (in the app: Employee details on **Manage**, and **Add employees** off). Adding an employee then answers `403` with `insufficient_permissions`, naming `employee:create`, and the app hides **Add employee** and importing new starters. Give the HR system's token a role that holds `employee:create`, such as HR integration.

A role holding `employee:*` includes `employee:create`, and so does one holding `*`. List the verbs one by one to leave adding out.

## API tokens

An API token holds roles exactly as a person does, and is limited by them in the same way. A few things stay out of its reach whatever its role says:

* **Roles.** An API token never holds a `role` permission, even as an Administrator. It cannot create, change or delete roles, or change the default role (`403` with `insufficient_permissions`). A token that could write roles would turn one leaked secret into a wider role for everyone.
* **Transferring the account.** Only the account owner, signed in as a person, can transfer ownership.
* **Roles for people only.** Access administrator, Developer, Client contact and HR manager cannot be given to an API token (`400` with `role_not_assignable`).
* **Access to the whole account.** An API token cannot give or take away account-wide access, create an API token with account-wide access, or give or take away access through an organisation group (`403` with `whole_customer_access_denied`). A person does these.

Within those limits, an API token can manage people, other API tokens and the account as far as its role allows. The HR integration role exists for an HR system's token: it covers employee details, working patterns, organisation details and people, and nothing else. HR sync is the same without people, so a Developer, who never manages people, can give it.

## Reading the permission an operation needs

Every operation in the Administration API and the Pay API names the one permission it needs, in two places:

* the first line of its description, such as **Needs permission employee:write.**
* the `x-flow-permission` extension on the operation in the OpenAPI document, which a generated client or a test can read:

```yaml theme={null}
/employees/{employeeId}:
  patch:
    operationId: PatchEmployee
    x-flow-permission: employee:write
```

A few operations also accept other permissions, listed in `x-flow-permission-also-accepts` and in the description (**Needs permission organisation-settings:read, or any of pay-run:read.**): anyone who sees pay runs can read the pay schedules they belong to, and anyone who gives roles to people or API tokens can read the roles. Saving an employee (`PUT` and `PATCH /employees/{employeeId}`, `PUT /employees/{employeeId}/starter`) and the employee update import also accept `employee-payroll:write` or `working-pattern:write`, and each field the request changes still needs its own permission (see [A payroll-only role](#a-payroll-only-role)).

To check an integration before you give its API token a narrower role, list the operations it calls, collect their `x-flow-permission` values, and make sure the role covers every one. Download the OpenAPI documents from [Use in Postman](https://docs.flowpayroll.ai/api-reference/use-in-postman.md).

## When a request is refused

A caller whose role does not include the permission gets `403` with `insufficient_permissions`, and `missingPermission` names what was missing. [Permission errors](https://docs.flowpayroll.ai/api-reference/administration/permission-errors.md) shows every refusal with an example body.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.