/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 /customertells 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 byclientId. - 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
organisationIdslist. Reported ascoversWholeCustomeron 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 /customernames them inowner. - 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
accessTypeofCustomerorGuest. 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:
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/draftreturns the draft and short-lived links to preview its files, with its version as theETagheader (and asversion).PATCH /customer/branding/draftchanges it with a JSON Merge Patch: leave a field out to keep it, sendnullto clear it. Send theETagyou read asIf-Match: if somebody else saved since, the answer is412withconcurrent_changeand nothing is written; withoutIf-Matchit is428withprecondition_required.DELETE /customer/branding/drafthonoursIf-Matchtoo.POST /customer/branding/revisionspublishes the draft as a new revision. It needs a product name and a main colour (400brand_incomplete), and an animation whenloadingStyleisAnimation(400loading_animation_missing). SendfromRevisionto publish an older revision again; the draft is left as it is.GET /customer/branding/revisionslists what was published, newest first, andGET /customer/brandingreturns what is live (404until the first publish).DELETE /customer/brandinggoes 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/draftempties the draft so you can start again; what is live does not change.canChangeon the draft says whether you can change the brand: one-organisation administrators can view only.emailButtonShape(Rounded,SquareorPill) andemailHeaderStyle(PlainorBrandBar) 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) andemailReplyToare the default for every email.emailSenderOverrideschanges any of the three for one kind of email, keyed by email type (PayslipGeneratedEvent,P60GeneratedEvent, …): an object changes the fields it names,nullputs 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 fromnoreply@your first verified domain, or fromstandardFromAddress(onGET /customer/branding/sending-domains) in your name. An address on a domain that is not one of yours is refused (400email_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/assetsanswers a short-lived upload form; then callGET /customer/branding/assets/{uploadId}until the upload isReady, with anassetIdto set on the draft, orRejected, 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/domainsandGET /customer/branding/sending-domainslist 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”:
GETandPATCH /organisations/{organisationId}/branding, with uploads through/organisations/{organisationId}/branding/assets.GET /organisations/{organisationId}/resolved-brandingsays 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.
Typical workflow
- Mint an access token — see Authentication — and call
GET /customerto confirm which account it belongs to. - Add an organisation with
POST /organisations, sending anIdempotency-Key. Note theorganisationIdin the response. - Create an API token for your integration with
POST /api-tokens, naming the organisations it should reach inorganisationIds. Store theclientSecretfrom the response immediately — it is not shown again. - Invite the people who will run payroll with
POST /users, giving each the organisations they need — orallOrganisations: truefor an administrator who should reach everything, present and future. - Use the new
organisationIdasX-Org-Idon the payroll APIs. - When staff change, adjust access with the per-organisation
PUTandDELETEroutes, and checkGET /activityto 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 withDELETE /users/{principalId}and revoke a token withDELETE /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 (
403withnot_customer_ownerotherwise), and the owner cannot remove themselves (409withcannot_delete_owner). - Creating organisations and API tokens is gated on billing. While
billingStatusisAwaitingSetup, both answer403withbilling_not_set_up; email hello@flowpayroll.ai to set billing up. - You cannot remove your own last access (
409withcannot_remove_own_last_access); ask another administrator. - Resending an invitation issues a fresh temporary password, so it is limited to once a minute (
429withinvitation_sent_recently) and refused once the person has signed in (409withalready_signed_in) or where they sign in through the account’s identity provider (409withsigns_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 takespageSize 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
PUTandDELETEroutes over replacing a whole set. They are idempotent and safe when two administrators edit at once, whereas a declarativePUTcan overwrite a concurrent change. - Treat
clientSecretlike 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
201when every entry went through,207when some did, and400when 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 theX-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.