> ## 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.

# Administration API overview

> Manage your Flow Payroll account over the API: the organisations it owns, the people who can sign in, the API tokens it holds, your white-label brand, and what changed recently.

The Administration API is where you manage the account itself rather than the payroll inside it. Add organisations one at a time or in bulk, invite people and decide which organisations they reach, create and rotate the API tokens your integrations authenticate with, and read back a feed of who changed what. Everything is scoped to the account your credentials belong to — the same account the Flow Payroll administration screens show.

All endpoints sit under the `/v1/administration` base path, relative to the environment base URLs documented on the [API reference introduction](/api-reference/introduction).

<Note>
  Unlike the payroll APIs, this API is not scoped to one organisation. The account comes from your credentials, and where an organisation is involved it comes from the path. The `X-Org-Id` header is not needed here — if you send it out of habit it is ignored, never rejected.
</Note>

## Key concepts

* **Account (Customer)** — the tenant that owns your organisations, people and API tokens. The API calls it a customer in resource and field names (`/customer`, `coversWholeCustomer`); You never pass its id — `GET /customer` tells you which account your credentials belong to.
* **Organisation** — an employer you run payroll for. Every other Flow Payroll API scopes requests to one organisation with `X-Org-Id`; this API is where organisations are created and where access to them is granted.
* **Person (user)** — somebody who can sign in to the account. Addressed by `principalId`.
* **API token** — a client credential (`client_id` / `client_secret`) your integrations exchange for access tokens. Addressed by `clientId`.
* **Direct grant** — access to one named organisation. A person or token can hold any number of them.
* **Account-wide access** — access to every organisation the account owns, including ones added later. It names no organisation, so somebody who holds it typically has an empty `organisationIds` list. Reported as `coversWholeCustomer` on people and tokens alike.
* **Owner** — the one person in the account whose access cannot be revoked from inside it. Only the owner can remove people or transfer ownership. `GET /customer` names them in `owner`.
* **Guest** — somebody from another business invited into specific organisations of yours, or an organisation of another business that you were invited into. Rows carry an `accessType` of `Customer` or `Guest`. A guest sees only what was granted, and neither account manages the other's people.

## Two kinds of access

Every person and every API token holds access in one or both of two forms, and every read of their access reports both:

| | Direct grants | Account-wide access |
| - | - | - |
| Reaches | The organisations named | Every organisation the account owns, now and in future |
| Field | `organisationIds` | `coversWholeCustomer` |
| Managed at | `/users/{principalId}/organisations`, `/api-tokens/{clientId}/organisations` | `/users/{principalId}/customer-access`, `/api-tokens/{clientId}/customer-access` |
| Who may grant it | Anybody who reaches the organisations being granted | Only somebody who already holds account-wide access |

<Warning>
  Read `coversWholeCustomer` before treating an empty `organisationIds` as no access. An administrator who runs the whole account usually holds no grant on any single organisation.
</Warning>

Nobody can grant wider than they hold. Granting an organisation you cannot reach answers `404`, because that organisation is not visible to you; granting account-wide access without holding it answers `403` with `whole_customer_access_denied`.

## API structure

### Account

`GET /customer` returns the account your credentials belong to — its id, display name and `owner`. `PATCH /customer` renames it. There is exactly one, so neither takes a parameter. `PUT /customer/owner` transfers ownership to another of the account's people, and only the current owner may call it.

### Organisations

`GET /organisations` lists every organisation the account owns, followed by any you reach as a guest of another business. `accessType` on each row says which, and the `accessType` query parameter filters to one kind. It is paginated with `pageSize` (default 50, at most 100) and `paginationToken`; one page holds one kind, so follow the token to see both. `search` matches the start of a name. Each row carries headcounts and `callerAccess.granted`, which says whether you can act on that organisation — the list deliberately includes organisations you cannot reach, so you know they exist. `GET /organisations/{organisationId}` returns one in full, including its company registration number and PAYE reference.

`POST /organisations` adds one; `POST /organisations/bulk` adds several with shared `defaults` and per-entry overrides. Send an `Idempotency-Key` header on both so a retry after a lost response cannot create anything twice.

### People

`GET /users` lists everyone who can sign in to the account, followed by your guests; add `organisationId` to see who can reach one organisation, or `accessType` to see one kind. `POST /users` invites somebody by email address, or adds access to somebody already in the account (the response's `alreadyExisted` says which); send `asGuest: true` to invite somebody from another business into specific organisations only. `POST /users/bulk` does the same for many of your own people. `DELETE /users/{principalId}` removes somebody from the account and disables their sign-in; only the owner may call it. `GET /users/{principalId}` returns one person with both kinds of access, and `POST /users/{principalId}/resend-invitation` re-sends an invitation to somebody who has not signed in yet.

How somebody signs in is fixed when their account is created, and follows the account's own sign-in setup, which `GET /customer` reports in `signIn`: the identity `providers` it links people to, and whether `passwordSignIn` is `Allowed`, `OnRequest` or `Denied`. Where the account links people to a provider and passwords are not `Allowed`, a new person is linked rather than emailed: `invitationEmailed` is `false`, they open Flow Payroll from the provider, and there is no password to reset or invitation to resend (`409` with `signs_in_through_identity_provider`). Send `signIn: "Password"` to give somebody outside the provider, such as a bureau, a password of their own; it is refused where passwords are `Denied` (`409` with `password_sign_in_denied`), and `signIn: "IdentityProvider"` is refused where the account has no provider (`409` with `identity_provider_not_configured`). Each person's `signInProviders` and `passwordSignIn` say how they sign in.

Access is managed on two sub-resources: `/users/{principalId}/organisations` for direct grants (`GET`, `PUT` to replace the set, and `PUT` / `DELETE` on `/{organisationId}` for one at a time) and `/users/{principalId}/customer-access` for account-wide access (`GET`, `PUT`, `DELETE`).

Each person can set a profile photo for themselves under `/users/me/photo`. Upload it in two steps, as for brand files: `POST /users/me/photo/uploads` answers a short-lived upload form, then call `GET /users/me/photo/uploads/{uploadId}` until it is `Ready` or `Rejected`. PNG, JPEG, GIF, WebP and SVG are accepted, up to 2 MB. Location and camera data are removed, and SVGs are cleaned. `GET /users/me/photo` returns a link that works for 15 minutes, and `DELETE /users/me/photo` removes the photo. API tokens cannot have a photo (`403` with `profile_photo_needs_a_person`).

### API tokens

`GET /api-tokens` lists the account's client credentials. `POST /api-tokens` creates one and returns its `clientSecret` exactly once. `PATCH /api-tokens/{clientId}` renames it, `POST /api-tokens/{clientId}/rotate` issues a new secret for the same client id, and `DELETE /api-tokens/{clientId}` revokes it. Access is managed exactly as for people: `/api-tokens/{clientId}/organisations` for direct grants and `/api-tokens/{clientId}/customer-access` for account-wide access.

### Activity

`GET /activity` returns the account's recent administration actions, newest first — organisations added, people invited, access changed, tokens issued, rotated, renamed, rescoped or revoked — paginated with `pageSize` (default 10, at most 50). Events are kept for 90 days.

### Branding

White label lets your customers and their employees see your product instead of Flow Payroll: your name, logo, colours, font, loading animation and help links, in the app, in emails and on payslips. Your brand applies once Flow has switched white label on for your account; `entitlement` on the draft says whether it has.

You edit a draft and publish it when it is ready. Nobody else sees the draft.

* `GET /customer/branding/draft` returns the draft and short-lived links to preview its files, with its version as the `ETag` header (and as `version`). `PATCH /customer/branding/draft` changes it with a JSON Merge Patch: leave a field out to keep it, send `null` to clear it. Send the `ETag` you read as `If-Match`: if somebody else saved since, the answer is `412` with `concurrent_change` and nothing is written; without `If-Match` it is `428` with `precondition_required`. `DELETE /customer/branding/draft` honours `If-Match` too.
* `POST /customer/branding/revisions` publishes the draft as a new revision. It needs a product name and a main colour (`400` `brand_incomplete`), and an animation when `loadingStyle` is `Animation` (`400` `loading_animation_missing`). Send `fromRevision` to publish an older revision again; the draft is left as it is. `GET /customer/branding/revisions` lists what was published, newest first, and `GET /customer/branding` returns what is live (`404` until the first publish).
* `DELETE /customer/branding` goes back to the Flow brand: your organisations, their employees, emails, payslips and web addresses show it again within 15 minutes. Every revision stays in your history, so publishing one again brings your brand back. `DELETE /customer/branding/draft` empties the draft so you can start again; what is live does not change. `canChange` on the draft says whether you can change the brand: one-organisation administrators can view only.
* `emailButtonShape` (`Rounded`, `Square` or `Pill`) and `emailHeaderStyle` (`Plain` or `BrandBar`) set how the buttons and the top of each email look.
* Who emails come from: `emailFromName`, `emailFromAddress` (a full address on one of your email domains) and `emailReplyTo` are the default for every email. `emailSenderOverrides` changes any of the three for one kind of email, keyed by email type (`PayslipGeneratedEvent`, `P60GeneratedEvent`, ...): an object changes the fields it names, `null` puts that email back on the default, and one request can change any number of emails. An address is used once its domain is verified; until then those emails come from `noreply@` your first verified domain, or from `standardFromAddress` (on `GET /customer/branding/sending-domains`) in your name. An address on a domain that is not one of yours is refused (`400` `email_sender_domain_not_the_accounts`).
* Files (the logo, icon, fonts and loading animation, and optional dark-mode versions of the logo, icon and animation) are uploaded in two steps. `POST /customer/branding/assets` answers a short-lived upload form; then call `GET /customer/branding/assets/{uploadId}` until the upload is `Ready`, with an `assetId` to set on the draft, or `Rejected`, with a message to show. An SVG keeps a logo or icon sharp, and a PNG works too. A loading animation is an SVG and may move with SMIL or CSS keyframes. Every file is checked by its content, and SVGs are cleaned of scripts and links to other files.
* `GET /customer/branding/domains` and `GET /customer/branding/sending-domains` list the web addresses and email sending domains Flow has set up for you. They are read-only.
* An organisation can have its own company logo, shown first with your brand as "Powered by": `GET` and `PATCH /organisations/{organisationId}/branding`, with uploads through `/organisations/{organisationId}/branding/assets`. `GET /organisations/{organisationId}/resolved-branding` says whose brand the app shows for an organisation. `GET /organisations/branding?organisationIds=` returns the company logos of up to 100 organisations in one call, and leaves out any you cannot reach.

The wording of each email is set through the [Email Wording API](/api-reference/email-wording/overview).

## Typical workflow

1. Mint an access token — see [Authentication](/api-reference/authentication) — and call `GET /customer` to confirm which account it belongs to.
2. Add an organisation with `POST /organisations`, sending an `Idempotency-Key`. Note the `organisationId` in the response.
3. Create an API token for your integration with `POST /api-tokens`, naming the organisations it should reach in `organisationIds`. Store the `clientSecret` from the response immediately — it is not shown again.
4. Invite the people who will run payroll with `POST /users`, giving each the organisations they need — or `allOrganisations: true` for an administrator who should reach everything, present and future.
5. Use the new `organisationId` as `X-Org-Id` on the payroll APIs.
6. When staff change, adjust access with the per-organisation `PUT` and `DELETE` routes, and check `GET /activity` to see what changed.

## Limits and safeguards

* An account may hold **500 people** and **5 API tokens**. Reaching either limit answers `409` (`user_limit_reached`, `token_limit_reached`); accounts already past a limit keep everything they have. Contact [hello@flowpayroll.ai](mailto:hello@flowpayroll.ai) to raise an allowance.
* A person or token must always reach something. Removing the last direct grant from somebody without account-wide access answers `400` (`user_access_would_be_empty`, `token_access_would_be_empty`). Remove a person with `DELETE /users/{principalId}` and revoke a token with `DELETE /api-tokens/{clientId}` rather than emptying their access. A guest is the exception: taking back the last organisation you granted is how guest access ends.
* Only the account owner can remove a person or transfer ownership (`403` with `not_customer_owner` otherwise), and the owner cannot remove themselves (`409` with `cannot_delete_owner`).
* Creating organisations and API tokens is gated on billing. While `billingStatus` is `AwaitingSetup`, both answer `403` with `billing_not_set_up`; email [hello@flowpayroll.ai](mailto:hello@flowpayroll.ai) to set billing up.
* You cannot remove your own last access (`409` with `cannot_remove_own_last_access`); ask another administrator.
* Resending an invitation issues a fresh temporary password, so it is limited to once a minute (`429` with `invitation_sent_recently`) and refused once the person has signed in (`409` with `already_signed_in`) or where they sign in through the account's identity provider (`409` with `signs_in_through_identity_provider`).
* Revocation takes effect immediately. An access token already minted from a revoked credential decodes for up to an hour but is refused on every request, because access is checked against live data.

## Idempotency

`POST /organisations`, `POST /organisations/bulk` and `POST /api-tokens` accept an `Idempotency-Key` header — any unique string up to 255 characters, a UUID by convention. A retry with the same key and body answers `200` with what the first attempt made (without a token's secret, which is shown once); the same key with a different body answers `409` with `idempotency_key_reused`. Keys are remembered for 24 hours. Inviting a person needs no key: the email address is the identity, so a repeated invitation is recognised as the same person and answered as an access change.

## Pagination

Every list takes `pageSize` and `paginationToken`, and publishes the next cursor in `content.metadata.paginationToken` (prefer this) and on the list body. Follow the token until it is absent — a page is never the whole list while a token is present. `GET /users?search=` filters within the page it returns, so a searched page can hold fewer rows than `pageSize` while a token is still present; keep following it.

## Notes and best practices

* Prefer the per-organisation `PUT` and `DELETE` routes over replacing a whole set. They are idempotent and safe when two administrators edit at once, whereas a declarative `PUT` can overwrite a concurrent change.
* Treat `clientSecret` like a password: it appears once, in the create or rotate response. If it is lost, rotate.
* Headcounts in organisation responses trail the payroll by seconds and, for leavers, by up to a day. Use them for a headline figure, never a figure to file or pay on.
* Bulk endpoints answer `201` when every entry went through, `207` when some did, and `400` when none did. The body lists every entry in all three cases, and nothing is rolled back.
* Reason tokens are snake\_case (`user_limit_reached`); activity actions are PascalCase (`UserInvited`). They are different vocabularies.

## Authentication

Every request must carry a valid token in the `X-Auth-Token` header — see [Authentication](/api-reference/authentication). `X-Org-Id` is not used: the account is derived from the token, and organisations are named in the path. Responses use the shared envelope described in [Response format](/api-reference/response-format).


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