Permissions
A permission is an area of Flow Payroll and something you can do there, writtenarea: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.
In every area,
write changes what already exists and create adds something new:
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.
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 withPOST /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 (
403withrole_change_denied). PATCH /roles/{roleId}changes a role, and everyone who holds it has the change at once.GET /roles/{roleId}/revisionsshows every version.DELETE /roles/{roleId}works only once nobody holds the role (409withrole_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 asaccess 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):
scope.typeisAccount,OrganisationGrouporOrganisation.idnames the group or organisation; forAccountleave it out (it is your own account).- One grant per place: naming the same place twice answers
400withduplicate_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}andGET /api-tokens/{clientId}returnaccessin the same shape.
{ "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 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 (
403withrole_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 (
403withaccess_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. 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:
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 holdsemployee-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 holdsemployee: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
rolepermission, even as an Administrator. It cannot create, change or delete roles, or change the default role (403withinsufficient_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 (
400withrole_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 (
403withwhole_customer_access_denied). A person does these.
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-permissionextension on the operation in the OpenAPI document, which a generated client or a test can read:
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).
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.
When a request is refused
A caller whose role does not include the permission gets403 with insufficient_permissions, and missingPermission names what was missing. Permission errors shows every refusal with an example body.