Skip to main content
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.
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.

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

Typical workflow

  1. Mint an access token — see 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 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 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. 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.