Skip to main content
Every change to the Flow Payroll API lands here, newest first: new services and endpoints, new request headers, and any change in behaviour your integration should know about. Use the tags to filter by area, or subscribe to the RSS feed to hear about changes as they ship. For product-wide release notes, see What’s new in the Releases tab.
AdministrationBrandingNew endpoints

White label: going back to the Flow brand, email history and profile photos

More control over your brand and your emails, following the white-label release of 9 October.What’s new
  • Branding in the Administration API:
    • DELETE /customer/branding goes back to the Flow brand. Your revisions stay, 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 and emailHeaderStyle set how the buttons and the top of each email look.
    • GET /organisations/branding returns the company logos of up to 100 organisations in one call.
  • Profile photos: each person sets, reads and removes their own photo under /users/me/photo. PNG, JPEG, GIF, WebP or SVG, up to 2 MB. Location and camera data are removed.
  • The Email Wording API:
    • Each email shows its standard wording, who receives it (audience), what it always includes (locked) and its placeholders with sample values.
    • POST .../rendered-previews returns the email exactly as it would be sent, as HTML and plain text.
    • Every save is kept as a version. List them with GET .../revisions and put one back with POST .../revisions/{revisionId}/restorations.
    • enabled: false switches an email off. Only the emails to administrators can be switched off.
    • The sign-in emails (invitation, email verification, password reset and security alert) are worded for your whole account.
Get startedRead Branding in the Administration API overview and the Email Wording API overview.
New APIAdministrationBranding

White label: your brand, and the wording of your emails

Accounts with white label switched on can show their own product instead of Flow Payroll: in the app, in emails and on payslips. The brand and the wording of each email are now set over the API, the same way the administration screens set them.What’s new
  • Branding in the Administration API:
    • A brand draft (GET and PATCH /customer/branding/draft) that nobody else sees, changed with a JSON Merge Patch and guarded by version.
    • POST /customer/branding/revisions publishes it, or publishes an older revision again with fromRevision. GET /customer/branding returns what is live and GET /customer/branding/revisions lists the history.
    • What it holds: product name, colours, font, logo, icon, help links and legal footer, whether the product name shows beside the logo in each place, and what the app shows while it loads (loadingStyle: Spinner, Icon or Animation), with optional dark-mode versions of the logo, icon and animation.
    • File uploads in two steps: POST /customer/branding/assets, then GET /customer/branding/assets/{uploadId} until it is Ready.
    • A company logo per organisation, through /organisations/{organisationId}/branding, and GET /organisations/{organisationId}/resolved-branding to see whose brand the app shows for an organisation.
  • The Email Wording API: the subject, heading, paragraphs and button label of each email, for your account or for one organisation, with previews and test sends.
  • POST /payslip/samples in the Pay API: a made-up payslip as a PDF in your brand draft, with unsaved changes if you send them. Nothing is stored or sent.
Get startedAsk Flow to switch white label on for your account, then set your brand in the administration screens under Branding, or with PATCH /customer/branding/draft and POST /customer/branding/revisions.
Court ordersBehaviour changeDeprecations

Scottish and Northern Ireland court orders, and court or DWP variations

The Court orders API now takes four more order types, and PUT /orders/{orderId} can record a variation from the court or DWP through each type’s own fields.What’s new
  • Four new values of orderType on POST /orders, every response and the court_order.* webhook events:
    • Cma, a current maintenance arrestment (CMA), Scotland. Send dailyRate in pounds per day.
    • Cao, a conjoined arrestment order (CAO), Scotland. Send ordinaryDebtAmount, maintenanceDailyRate, or both; at least one must be more than 0.
    • NiFinesAeo, an attachment of earnings order (Northern Ireland) for fines. Send totalAmountOwed, and agreedHigherRatePercentage if the employee agreed to pay more than the table.
    • NiEjoAeo, an attachment of earnings order (Northern Ireland) from the Enforcement of Judgments Office. Send normalDeductionRate, protectedEarningsRate, ratePeriod (Weekly, Monthly or Daily) and totalAmountOwed.
  • Read-only pay days in the order’s details: lastDeductionPayDate on a Cma or Cao, lastPayDate on a NiEjoAeo, and firstPayDate on a Dea.
  • Two new remittanceDeadline values: AsSoonAsReasonablyPracticable (Cma) and AsTheOrderRequires (Cao and NiEjoAeo).
  • Variations on PUT /orders/{orderId}: rateType (DEA), dailyRate (CMA), ordinaryDebtAmount and maintenanceDailyRate (CAO), agreedHigherRatePercentage (Northern Ireland fines), normalDeductionRate, protectedEarningsRate and ratePeriod (Northern Ireland EJO), and deductionBasis (AEOs). Only the fields you send change. A field that belongs to another order type is refused with a 400.
  • Reference data: GET /orders/constants/{taxYear} adds cma.protectedEarningsPerDay, the Northern Ireland fines tables in niFinesAeo, and a daily tableC for AEOs and earnings arrestments.
Changed
  • An AEO for maintenance or a civil debt is created with the court’s rates: the normal deduction rate (NDR) and protected earnings rate (PER). On POST /orders with orderType AeoNonPriority, or AeoPriority with aeoCategory PriorityMaintenance, send deductionBasis FixedAmount with the normal deduction rate in fixedAmount, and protectedEarningsSetting CourtPerAmount with the court’s protected earnings rate. Table or Percentage is now refused with a 400 (Attachment of Earnings Act 1971 s6(5)). Fines orders keep Table. An order created before this change keeps its method until an update changes it.
  • Orders that depend on the employee are checked. Only one Cma can run for an employee at a time, and a Cao needs the employee’s Ea and Cma orders ended first. Each is refused with a 400.
  • instructionAmount is not accepted for a Cma, Cao, NiFinesAeo or NiEjoAeo, which always use their own rates.
  • Orders end on their end date. An order whose endDate has passed with no pay period left before it is now Discharged automatically, and the court_order.status_changed event gives the reason end_date_reached.
  • Payslip line names. A court order deduction on the payslip, and its admin fee line, is now named with the order’s abbreviation and reference, such as DEO - AB/123456, AEO priority - AB/123456 or CMA - AB/123456. Lines from earlier pay runs keep their text, so don’t parse the order type or reference from a description; read the line’s courtOrderId and courtOrderType instead.
Deprecated: percentageRate stops being accepted as input on 4 January 2027
  • percentageRate on a deduction from earnings order (CmsDeo) request, on POST /orders and PUT /orders/{orderId}, is an alias of percentage. It is read only when percentage is not sent. It stops being accepted as input on 4 January 2027. details.percentageRate on a CmsDeo response keeps being returned, equal to percentage.
What you need to doSend percentage instead of percentageRate before 4 January 2027. If you create AEOs for maintenance or a civil debt, send the court’s normal deduction rate and protected earnings rate as described above. A client that treats an unknown orderType or remittanceDeadline as an error should accept the new values. See Deprecations.
PensionsBehaviour change

Bring your own pension provider

A pension scheme can now be run on your own terms. Flow Payroll still assesses, enrols and calculates every contribution, and your integration decides how those contributions reach the provider: in a file you build, through the provider’s own API, or by whatever route it gives you. Nothing in the pay run waits on a file from us.What’s new
  • A Manual provider in GET /pension-provider in the Pensions API. Create a scheme against it for a provider you file with yourself. The scheme is assessed, enrolled and calculated like any other, every contribution is on the payslip, and no export file is produced for it.
  • exportSupported on every provider from GET /pension-provider, GET /pension-provider/{id} and GET /pension-provider/slug/{slug}, and on the provider embedded in each scheme from GET /pension-schema/organisation. It is false for Manual and true for every other provider, so your integration can tell the schemes it handles from the ones Flow Payroll files for you.
  • Contributions to build your own file from. Once a pay run completes, read each employee’s contributions with GET /pension-calculation/pension-provider/{pensionProviderId}/by-year/{year}/by-month/{month}, or every provider’s at once with GET /pension-calculation/by-year/{year}/by-month/{month}. The payroll_run.completed webhook tells you when a period’s figures are final.
What you need to doNothing, if your schemes stay on the providers Flow Payroll files for. For a scheme on Manual, do not wait on GET /export-file-retrieval/pension-calculation/... or GET /pension-calculation/export/...: they never list a file for it. Read the calculation endpoints instead. A client that treats a missing exportSupported as true keeps working against an environment that has not yet picked the field up.
Recurring line items

Retry creating a recurring deduction without creating it twice

If a request to set up a recurring deduction or payment times out, you can now send it again safely. Add an Idempotency-Key header and a retry gives you back the first answer instead of setting up a second schedule for the same employee.What’s new
  • Idempotency-Key header on POST / in the Recurring line items API: a unique value you choose, such as a UUID, up to 255 characters.
  • Sending the same key with the same body within 24 hours returns the first response, and nothing is created again.
  • The same key with a different body, or from a different user, is refused with a 409 and the token idempotency_key_reused. A retry that arrives while the first request is still running gets a 409 with idempotent_request_in_progress; wait a moment and send it again.
What you need to doNothing, unless you want safe retries. The header is optional, and a request without it behaves exactly as before. Use a new key for each new deduction.
HMRCPay runsDeprecations

Tell HMRC why an FPS is late

An FPS that reaches HMRC after the employees’ payment date must give a late reporting reason, or HMRC may send a penalty warning. You can now send that reason with the FPS through the Pay API, for a submission or a preview.What’s new
  • lateReason on the body of POST /rti/fps/{payrollConfigId}/{taxYear}/{period}. Send one of HMRC’s current reasons by name: NotionalPaymentToExpartByThirdPartyOrOverseasEmployer (A), NotionalPaymentEmploymentRelatedSecurity (B), NotionalPaymentOther (C), PaymentSubjectToClass1NicButP11dP9dForTax (D), NoRequirementToMaintainADeductionsWorkingSheetOrImpossibleToReportWorkDoneOnTheDay (F), ReasonableExcuse (G) or CorrectionToEarlierSubmission (H). HMRC’s single code letter works too. The reason is reported against every employee in the submission. Leave it out when you are reporting on time.
  • Code E, which HMRC withdrew on 6 April 2016, and any other value are refused with a 400 and the token hmrcRtiFpsInvalidLateReason.
  • lateReason on hmrcDetails.fullPaymentSubmissionReportDetails of a pay run shows the reason the FPS was sent with, or the one a ready preview was built with. It is null when none was given.
Changed
  • The FPS request body is now an object with two optional fields, finalSubmissionDetails and lateReason. The final submission details, needed for the last period of the tax year, go inside finalSubmissionDetails.
Deprecated: the old body shape stops being accepted on 15 January 2027
  • Sending the final submission details object as the whole body ({ "forYear": true, ... }) still works, and is read as finalSubmissionDetails with no late reason. It stops being accepted as input on 15 January 2027. Nothing in the response changes.
What you need to doBefore 15 January 2027, wrap any final submission details you send in finalSubmissionDetails. Add lateReason whenever an FPS goes after the payment date. See Deprecations.
Webhooks

Bulk P45 and P60 emails now tell you who was missed, and why

When you email P45s or P60s to a group of employees, the event that says the send has finished now names the employees who did not get their form and the reason. You can fix their records and send again without calling back to look the job up.What’s new on p_form_bulk_send.completed and p_form_bulk_send.failed:
  • failures: up to 100 employees who were not sent their form, ordered by employee id, each with a reason.
  • failuresTruncated: true when more than 100 employees were missed. Fetch the full list from jobUrl.
  • failureCountsByReason: every failure in the send, counted by reason, for example {"EmployeeEmailMissing": 12}. This is never cut short.
A reason is always one of a fixed set of codes, such as EmployeeEmailMissing, EmployeeBirthDateUnparseable, RecentlySent or ConnectionError, so you can match on it exactly. It never contains employee details or error text. GET /pforms/send-notification/jobs/{jobId} uses the same codes.What you need to doNothing. The fields are added to the existing events; read them when you want to follow up on missed employees.
Webhooks

Webhooks for court orders, journals, pensions, recurring deductions, HMRC notices, salary settings, connections, billing and payments

Your integration can now react to much more of the payroll without polling: a court order deduction taken, a journal posted to your accounting system, an employee enrolled into a pension, a new tax code notice from HMRC, a payment file ready for the bank, and more. Add the new event types to an endpoint to start receiving them. Nothing changes for the events you already receive.New families
  • Court orders (court_order.*): an order added to an employee, changed, removed or changing status, and each deduction taken or skipped in a pay run.
  • Journals (journal.*): a pay run’s journal locked, corrected, posted to your accounting system or refused by it, and the accounting entry for a replaced version reversed.
  • Pensions (pension.*): each step of automatic enrolment (assessed, enrolled, opted out, ceased, postponed, exempt), the enrolment letter sent or not, and each submission to the pension provider: contributions, new members and payment approval.
  • Recurring deductions and payments (recurring_line_item.*): an ongoing schedule or a loan or advance set up, changed, paused, resumed, stopped, finished or removed, and what each payslip took from it.
  • HMRC notices (hmrc_notice.* and hmrc_notice_job.*): a tax code or student loan notice received, matched to an employee, applied, dismissed or reopened, and each check for notices or bulk apply or dismiss finishing, failing or being cancelled.
  • Salary settings (salary_organisation_config.*, salary_employee_config.*, salary_organisation_statutory_leave_policy.* and salary_employee_statutory_leave_policy.*): how salary is worked out for part periods, and how it is handled during statutory leave, for the organisation or for one employee.
  • Connections (integration_connection.*): a connection to your accounting system or pension provider ready, disconnected, needing you to sign in again, or removed.
  • Billing your clients (invoice.*, credit_note.* and billing_client.*): for a bureau, the invoices and credit notes it raises to its clients, and the clients themselves.
  • Payments (payment_file.* and payment_settings.*): a pay run’s payment file ready or failed, and the bank details and settings it is made from added, changed or removed.
Know whether an email reached the inbox
  • payslip.delivered, payslip.bounced and payslip.complained tell you whether the recipient’s mail server accepted the email, rejected it, or the recipient marked it as spam. They usually arrive minutes or hours after the email was sent. The same three events exist for p45, p60, pension_enrolment_letter and report.
  • report.emailed and report.email_failed tell you whether a scheduled report’s email was sent. For pension enrolment letters, keep using pension.enrolment_letter_sent and pension.enrolment_letter_failed.
Fixed
  • The Webhooks API spec now lists null as an allowed value on fields that accept it, such as payloadStyle on POST /endpoints. If you generated a client from the previous spec and it refuses null, generate it again.
What you need to doAdd the event types or families you want with POST /endpoints/{endpointId}/event-types, as described in Event types. A family covers the event types that share its name up to the dot, so hmrc_notice.* does not include hmrc_notice_job.*.
BillingDeprecations

Billing responses move to the shared envelope; the old fields are deprecated

The Billing API now answers with the same envelope as every other Flow Payroll service, described in Response format. Nothing has been taken away: every field billing sent before is still sent, with the value it sent before, so an integration that reads the old fields keeps working unchanged.What’s new
  • message.token on every response, a stable snake_case key for the message (clients_listed, not_found, bad_request), with message.tokenArguments where a message has values to fill in. On an unexpected failure that includes the requestId to quote to support, the same id as the X-Request-Id response header.
  • validationIssues on every error, each with field, reason and reasonToken.
  • content.metadata with dateFormat and dateTimeFormat, and paginationToken on every paginated list, including the lists read from your accounting software. A list’s rows stay where they are in content.data.
Fixed
  • A pageSize of 0 or less is refused with a 400 naming pageSize. It used to fail with a 500.
  • Creating a credit note without a clientId is refused with a 400 naming clientId. It used to answer 404 for a client with an empty id.
  • The request id on a failure now matches the X-Request-Id response header, so quoting either one to support finds the request.
Deprecated, and removed on 31 December 2026These stop being returned on 31 December 2026:
  • errors repeats validationIssues: reasonText is reason, reasonId is reasonToken, reasonParameters is reasonTokenArguments.
  • The top-level metadata repeats content.metadata.
  • message.textId and message.textParameters keep their old values, which do not always match message.token and message.tokenArguments. Match on token.
  • On a paginated list, paginationToken, hasMorePages and pageSize inside content.data go. Read the cursor from content.metadata.paginationToken and keep reading until there is none. On the contacts, tax rates and accounts read from your accounting software, nextPageToken goes the same way; the pageToken query parameter is not changing.
The schema names change with the envelope (ApiResponse_ClientDto becomes ClientDtoResponseBody, and so on). The old names stay in the spec as deprecated aliases of the new ones, so a client generated from the previous spec still compiles when you regenerate it.See Deprecations.
PensionsDeprecations

Pension responses move to the shared envelope; the old fields are deprecated

The Pensions API now answers with the same envelope as every other Flow Payroll service, described in Response format. Nothing has been taken away: every field pension sent before is still sent, carrying the same values as its replacement, so an integration that reads the old fields keeps working unchanged.What’s new
  • message.token on every response, a stable snake_case key for the message (pension_scheme_created, not_found), with message.tokenArguments where a message has values to fill in. On a 500 that includes the requestId to quote to support.
  • validationIssues on every error, each with field, reason and reasonToken.
  • content.metadata with dateFormat and dateTimeFormat, and paginationToken on every paginated list (eight endpoints). It is the same cursor the list already carried inside data. A list’s rows stay in content.data.items.
Deprecated, and removed on 30 December 2026These stop being returned on 30 December 2026. Until then each one is an exact copy of its replacement:
  • errors repeats validationIssues: reasonText is reason, reasonId is reasonToken, reasonParameters is reasonTokenArguments. It is now on every error response, including the ones that used to answer without it.
  • The top-level metadata repeats content.metadata.
  • message.textId and message.textParameters repeat message.token and message.tokenArguments.
  • On a paginated list, content.data.paginationToken repeats content.metadata.paginationToken. Pass either back as the paginationToken query parameter until then.
The schema names change with the envelope (PensionSchemeV2ApiResponse becomes PensionSchemeV2ResponseBody, and so on). The old names stay in the spec as deprecated aliases of the new ones, so a client generated from the previous spec still compiles when you regenerate it.See Deprecations.
ReportsWebhooksDeprecations

Report exports announce themselves by webhook; the schedule’s own webhookUrl is deprecated

Every report export now sends a webhook event when it finishes, whether a schedule started it or you queued it with POST /exports. Subscribe an endpoint to the new report_export.* family and you no longer poll GET /exports/{id} or give each schedule its own URL.What’s new
  • Two event types in a new Reports family: report_export.completed and report_export.failed. Each carries the runId, mode (EXPORT or SCHEDULED), definitionId, scheduleId, format, status and rowCount, and a resourceUrl pointing at GET /exports/{runId}, which answers your authenticated request with a fresh download link. The event never carries the link itself.
  • Delivered like every other event: signed, retried for about three days, logged, replayable. See Delivery and retries.
Deprecations
  • delivery.webhookUrl on POST /report-definitions/{definitionId}/schedules, and the webhookSecret it returned once at creation, are deprecated. A schedule created with a webhookUrl keeps receiving the legacy X-Flow-Signature POST until 30 December 2026; on that date the field stops being accepted as input and stops being returned, and the legacy POST stops. Subscribe an endpoint to report_export.completed instead and route on scheduleId. See Deprecations.
Get startedCreate or reuse a webhook endpoint and add report_export.* to its event types. Email delivery on schedules is unchanged.
WebhooksNew endpoints

Webhooks: be told the moment something happens

The new Webhooks API sends an HTTPS POST to your server when something happens in Flow Payroll, so your integration reacts at once instead of polling. It follows the open Standard Webhooks specification, so official libraries verify every delivery in one line.What’s new
  • 62 event types across 11 families: pay runs, payslips, pay run line items, employees, leavers, HMRC RTI, P45s and P60s, statutory pay, holiday and leave, payslip and P-form emails, and operational events about your own endpoints. Each has a reference page with its payload. See Event types.
  • Endpoints at /v1/webhooks/endpoints: subscribe to exact types, whole families (payslip.*) or everything (*), and choose Full or Minimal payloads, strict ordering, a rate limit and custom headers. See Manage endpoints.
  • Signed deliveries with webhook-id, webhook-timestamp and webhook-signature. Rotating a secret keeps the old one signing for 24 hours, so you switch with no missed deliveries. See Verify signatures.
  • Automatic retries for just over three days (10 attempts over about 75.6 hours) on a fixed schedule, Retry-After honoured, and a delivery log of every attempt. See Delivery and retries.
  • Resend and replay: send one delivery again, or replay a window of up to 90 days after an outage. See Replay and recovery.
  • Account-wide endpoints for bureaus: one URL for every client organisation, optionally filtered. See Account-wide endpoints.
  • Test events on demand, answered while you wait. See Testing webhooks.
Get startedFollow the Webhooks quickstart: a verified receiver in about fifteen minutes.
Pay runsNew endpoint

Download a payslip as a PDF

The Pay API now serves the payslip document itself, the same PDF an employee receives by email. Until now the only way to get a payslip out of the API was the JSON figures.New endpoint
  • GET /payslip/data/{employeeId}/{year}/{period}/file returns application/pdf. Send Accept: application/pdf; the body is the file, not the usual JSON envelope.
  • Content-Disposition carries the file name, Payslip-{year}-{period}-{LastName}-{FirstName}.pdf.
  • Preview payslips can be downloaded before the run is locked, and a download always reflects the payslip’s current figures.
  • The file is not password-protected. Only the copy emailed to the employee is, with their date of birth as ddMMyyyy.
  • A period with no payslip answers 404 with the token payslipNotFound.
See the Pay API overview.
SalaryNew endpoints

Statutory leave on the Salary API

The Salary API now decides what happens to a salaried employee’s pay on the days they are paid statutory pay. It takes those days back with an adjustment line and, if the policy says so, adds a top-up.New endpoints
  • GET, PUT and DELETE /organisation/leave and the same three on /employees/{employeeId}/leave. The document has sickness, familyLeave, and byType for an exception for one family-leave payment. Each policy is:
    • DeductAbsentDays (the default), with optional topUp.tiers; or
    • FullPay, which tops every day of the leave up to 100%.
  • Every response carries effective: the policy each of the seven statutory payments resolves to, with its source (Employee, Organisation or Default).
  • A tier’s length is a duration, { "value": 6, "unit": "Week" }. Week is the only unit today; months can be added later as another unit value without changing the shape. The tiers must end within 52 weeks of the start of the leave.
On the pay run
  • Adjustment lines have ids starting internal_salary_adjustment_ and are posted against internal_salary_adjustment_sickness or internal_salary_adjustment_family_leave.
  • Top-up lines start internal_salary_topup_ and are posted against internal_salary_topup_sickness or internal_salary_topup_family_leave.
  • Both are posted with canEdit: false and follow the statutory line they respond to.
POST /employees/{employeeId}/calculate takes optional statutoryLines to preview the adjustment and top-up. Each returned line now carries:
  • kind: Salary, StatutoryAbsenceAdjustment or StatutoryTopUp;
  • payElementId;
  • statutoryType on an adjustment or top-up.
Compatibility. The Salary API spec now states its rule for adding things: enums may gain values and objects may gain properties without a new version. Ignore properties you do not know, and treat an enum value you do not recognise as unsupported rather than as an error.
PensionNew endpoints

Send pension letters to many members at once

POST /pension/employee-pension/letters/{letterType} queues one kind of letter for a list of members and answers 202 with a job to watch. letterType is Enrolment today; it is a path segment rather than a field because each letter is its own collection, so the notices added later will not change the shape of this call.Nothing has been sent when it responds. The letter still has a queue, the notification service and an email provider to cross, which is why the response is a job rather than a result:
GET /pension/employee-pension/letters/jobs/{jobId} is how far it has got: status (Queued, InProgress, Completed, Failed), totalCount, sentCount, failedCount, and failures[] naming each member who was missed with a stable reason (AlreadySending, NotEnrolled, SendFailed, UnsupportedLetterType). A member who is no longer enrolled is reported and skipped; the rest of the batch still goes.sentCount means on its way, never delivered. A member stays on the outstanding list until the provider confirms delivery, and only then does their opt-out period begin.GET /pension/auto-enrolment/outstanding-enrolment-information gains totalCount, the number of members owed a letter across every page, and lastAttemptStatus on each row (Sending, Sent, Failed) so a letter already on its way can be told from one nobody has started.Repeated employee ids in one request are collapsed, so asking twice still posts one letter.
ReportsBehaviour change

Synchronous runs are now in GET /runs, and schedules report their last delivery

Every run is recorded. Yesterday’s note said a SYNC run returns its rows and is not recorded in the run history. That is no longer true: POST /reports/run and POST /report-definitions/{id}/run now write a meta_report_run row, and the runId in the response is one you can quote. If you built around synchronous runs being absent, GET /runs will return considerably more than it used to.
  • GET /runs?mode=SYNC|EXPORT|SCHEDULED narrows the list to one kind. Worth adding if you poll for exports: synchronous rows are far more numerous and would otherwise push an export you are waiting on off the first page. limit defaults to 50 and is clamped to 200.
  • requestedBy is now populated on synchronous and export runs, carrying the kind of principal with the identity — USER#… for a person, M2M#… for an API client, scheduler for a scheduled run. It stays null on runs made before this shipped.
  • Retention: SYNC rows are kept 90 days. EXPORT and SCHEDULED rows are kept indefinitely.
  • A definition that fails validation (400) or is refused by the concurrency cap (429) is not recorded — neither reached your data. A run that times out (408) is.
Schedules report their last delivery. GET /report-definitions/{definitionId}/schedules returns four new fields on each schedule: lastDeliveryStatus (DELIVERED or FAILED), lastDeliveryAt, lastDeliveryError, and consecutiveDeliveryFailures, which resets to 0 on the first success. Poll these instead of waiting to hear that a report stopped arriving. A run that never produced a file records a FAILED delivery too, so a schedule cannot show a stale success while its reports have been failing.All new fields are optional and everything else is unchanged, so existing integrations keep working.
ReportsBehaviour changeDocumentation

A concurrency limit on synchronous report runs, and two corrections to these docs

429 on synchronous runs. One organisation may now have four synchronous report runs in flight at once. A fifth returns 429 from POST /reports/run and POST /report-definitions/{id}/run. The limit is per organisation rather than global, so a busy tenant cannot slow anyone else’s reads. Queue an export instead of retrying in a loop: exports are not limited this way.Two things these docs had wrong, both worth checking against your integration:
  • Export download URLs last 12 hours, not 15 minutes. The reference said 15 minutes. The service has issued 12-hour URLs throughout, so anything built to refetch every 15 minutes was refetching far more often than it needed to.
  • Synchronous runs are not recorded in GET /runs. The reference said every run lands in the history. EXPORT and SCHEDULED runs do; a SYNC run returns its rows in the response and is not recorded. If you relied on run history to audit interactive queries, it has not been capturing them.
No change to any request or response shape beyond the new 429.
PensionsNew endpointsBehaviour change

See who is still owed an enrolment letter, keep opt-out forms, and set your own re-enrolment policy

Three additions and two behaviour changes across automatic enrolment. If you read pension data through the API, the behaviour changes are the ones to look at.New endpoints
  • GET /auto-enrolment/enrolment-information/outstanding — every member who has not been given their enrolment information, worst first: Failed (the send did not arrive, so it was never given), Overdue (the six weeks have run out), Due (still in time). Each row carries the employee id, the date they joined the pension, the dueBy date and, where relevant, what went wrong — join to GET /employees for names, as you would for any other list. Paginated like every other list in this API: pageSize (1 to 100, default 50) and paginationToken, answering { items, paginationToken }. Follow the token until it is absent rather than treating a short page as the last one. Takes an optional asOfDate so you can ask the question for a date other than today. An organisation that has set employerIssuesEnrolmentInformation gets an empty list: the duty is discharged outside Flow, so Flow asserts nothing about it.
  • POST /employee-pension/{employeeId}/opt-out-notices — returns a short-lived URL to upload the signed opt-out form to, plus the date it must be kept until. The document goes straight to storage and does not pass through the API, so a scanned form is not limited by request size. Send the returned contentType as the Content-Type of your PUT, or the URL will refuse it.
  • GET /employee-pension/{employeeId}/opt-out-notices?membershipEndDate= — the forms kept for one membership ending, newest first, each with a short-lived URL to read it. These outlive the membership: deleting a member’s pension record does not remove their opt-out forms, which is the point of keeping them.
Behaviour changes
  • lastEmailedStatus on an enrolment letter can now be Sending. It is published as an enum (Sending, Sent, Failed) rather than a free string, so a generated client gives you the union. Sending means a send was asked for and has not come back yet: treat it as not yet sent. It never means the information was given — only enrolmentInformationSentDate means that, and the member’s opt-out period counts from that alone.
  • Asking to send a letter while one is in flight now answers 409. The member would otherwise receive a statutory letter twice, which cannot be undone. A send that never resolves is treated as lost after 15 minutes and can be retried.
  • Enrolling someone without giving a date now uses the next pay reference period. POST /employee-pension with no enrolmentDate previously enrolled them from the day the call was made, part way through a period. It now uses the first day of the next pay reference period. If you want a different date — because your payroll has already closed for that period, or you are back-dating — send enrolmentDate explicitly and it is used unchanged.
  • Contributions are no longer produced for a period that ends before the enrolment date. Previously a forward-dated enrolment could still attract a deduction for a period the member was not yet in. If a period ends before the enrolment date no contribution line is created, and one already created is removed.
New setting
  • PUT /auto-enrolment-settings takes reEnrolRecentOptOuts. At your three-yearly re-enrolment date you may choose whether to re-enrol people who opted out, or left the scheme at their own request, in the preceding 12 months. Flow used to always leave them out, and still does unless you set this to true — nothing changes for anyone who does not set it. Reading the organisation settings always returns the field, so you can see which way it is set.
PensionsBehaviour change

The enrolment information declaration sent to your provider now reflects what happened

NEST’s enrolment file carries a field confirming the worker has been given accurate information about their enrolment and their new pension rights, which their schema describes as required by law and set out in the NEST employer terms. Flow was filling it in as “yes” on every submission, for every employer, whether or not anything had been sent.What changed
  • The declaration is now made per member, from evidence: an enrolment letter Flow sent and the email provider accepted, or a standing declaration from you that you issue the information yourself. With neither, the field is left empty, which is what the provider’s own schema expects for “not confirmed” (the value N is not permitted and never was).
  • PUT /auto-enrolment-settings takes employerIssuesEnrolmentInformation: set it when you give workers the information by post or your own systems rather than relying on Flow’s email. Who set it and when are recorded and returned as enrolmentInformationDeclaredBy and enrolmentInformationDeclaredDate; they are stamped by the service and are not accepted from the request.
  • A letter that bounced is not evidence: the information was not given, so no declaration is made for that member.
What you should doIf you issue enrolment information yourself, set employerIssuesEnrolmentInformation to true once. If you rely on Flow’s enrolment letters, turn them on with sendEnrolmentLetterOnAutoEnrolment and no further action is needed. Until one of those is true, submissions will leave the declaration empty rather than assert something we cannot evidence.
PensionsBehaviour change

Opt-out refunds are now checked against the statutory opt-out period

A jobholder may opt out within one month, and that period runs from the later of becoming an active member and being given their enrolment information (The Pensions Regulator’s detailed guidance 7, paragraphs 17 to 18). Outside it a defined-contribution scheme owes nothing back: contributions stay invested and the member becomes deferred. Until now DELETE /employee-pension/{employeeId} refunded whenever processRefund=true was sent, with nothing checking the date.What changed
  • DELETE /employee-pension/{employeeId} now refuses an out-of-period refund with a 400 unless the new refundOutsideOptOutPeriodReason query parameter says why. The employer may still choose to refund; the parameter records that it was a deliberate decision. Supplying it when the date is inside the period is also refused, so a reason never ends up on a refund that did not need one.
  • GET /employee-pension/{employeeId}/refund-preview and the removal response now return enrolmentDate, optOutPeriodEndDate and withinOptOutPeriod, so you can show the decision and its dates before acting. withinOptOutPeriod is null when the period could not be worked out, which is not the same as false.
  • GET /pension-information/{employeeId} now carries enrolmentInformationSentDate, enrolmentInformationSentBy and an enrolmentLetter object (lastEmailedAt, lastEmailedTo, lastEmailedBy, lastEmailedStatus, lastEmailError) so you can tell whether a member has been sent their enrolment letter, and what happened to it. The field names match the payslip and P-form email metadata.
  • Membership endings keep the evidence. enrolmentInformationSentDate, optOutPeriodEndDate, withinOptOutPeriod and refundOutsideOptOutPeriodReason are recorded on the leaver record, because the membership rows they were assessed from are deleted by the removal.
  • PUT /auto-enrolment-settings takes sendEnrolmentLetterOnAutoEnrolment, which emails the enrolment letter when Flow enrols somebody automatically and records the date it went out. Off by default.
What you should doCall the refund preview before a removal that refunds, and read withinOptOutPeriod. If it is false and you still intend to refund, send refundOutsideOptOutPeriodReason. The date is indicative where we have no record of when the enrolment letter was given: it is counted from the enrolment date, which is the earliest the period can close, so it may run later in practice.
PensionsNew endpoint

Employee-level auto-enrolment exemptions

Until now the only way to keep one worker out of automatic enrolment was to switch auto-enrolment off for the whole organisation. A new per-employee exemption covers The Pensions Regulator’s exceptions to the employer duties — directors without a contract of employment, office holders, LLP members, workers with HMRC tax protection, and the other discretionary cases — without touching anyone else’s assessment.What’s new
  • PUT /auto-enrolment-exemption marks an employee exempt, with a reason from a closed set (Director, OfficeHolder, LlpMember, TaxProtection, CeasedMembershipWithinTwelveMonths, WindingUpLumpSum, NoticeOfTermination) and an optional free-text note for evidence such as which HMRC protection certificate was seen. One exemption per employee — submitting again replaces it. A reason outside the set is refused with a 400 listing the choices.
  • GET /auto-enrolment-exemption/{employeeId} reads it back, and DELETE on the same path removes it, resuming assessment on the next pay run with the determination history untouched.
  • GET /pension-information/{employeeId} now carries the exemption alongside the enrolment, determination, and postponement, so one read still paints the whole picture.
  • While exempt, an employee is never assessed or automatically enrolled — including at cyclical re-enrolment — and drops out of GET /auto-enrolment/not-complete, the listing the pay run’s enrolment alerts are built from.
  • The exemption removes the employer’s duty, not the worker’s right to join: POST /employee-pension keeps working for an exempt employee, exactly as it does for anyone else.
Get startedCall PUT /auto-enrolment-exemption with the employee, a reason and an optional note. The endpoint reference under Pensions → Auto enrolment documents all three operations and the reason vocabulary.
EmployeesDeprecationsNew endpointSalary

Salary becomes a history, and a Salary API pro-rates it onto the pay run

An employee’s salary used to be a single number on their record. It could say what they are paid, but not what they were paid, so a rise part-way through a month was either applied early, applied late, or worked out by hand. Salary is now an effective-dated history on the employee, and a new Salary API turns that history into pay-run lines, pro-rated to the days somebody was actually employed in the period.What’s new
  • Salary history on the employee, in the Pay API: GET /employees/{employeeId}/salary returns every version oldest first with the one in force today; PUT /employees/{employeeId}/salary records a version from a date; DELETE /employees/{employeeId}/salary/{effectiveFrom} removes one. Sending the same effectiveFrom again replaces that version rather than appending another, so retries and corrections are safe. The last remaining version cannot be deleted — set its amount to 0 instead, which states that no salary is paid.
  • salaryHistory on every employee response, read-only, carrying the same versions and the one in force. salary keeps its meaning: the annual amount in force today.
  • Employees whose salary predates effective dating need no migration. They read back as a single version effective from their start date, so nothing has to special-case them.
  • A corrected start date carries the starting salary with it. PUT /employees/{employeeId}/starter-details moves the earliest salary version when it sits on the old start date, alongside the New Starter NI category and initial tax code. A move that would land on or after the next version answers 400 with token salaryVersionBlocksStartDate; tidy the history first.
  • A Salary API at /v1/salary: GET, PUT and DELETE /organisation set how the organisation pro-rates salary and whether the service runs at all; the same three verbs on /employees/{employeeId} override the method for one person; and POST /employees/{employeeId}/calculate is a dry run that returns the lines a period would produce without posting anything.
Deprecationsalary on PUT /employees/{employeeId} and PATCH /employees/{employeeId} is deprecated as input and stops being accepted on those two operations on 20 December 2026. It keeps being returned on every employee response, as the annual salary in force today, so only what you send changes. POST /employees is unaffected: a salary sent at creation is still the starting salary, and becomes the first version effective from the start date. While it is still accepted on PUT and PATCH, a changed value updates the most recent salary version in place rather than adding a new one, so it cannot express a change from a date — that is what PUT /employees/{employeeId}/salary is for. See Deprecations.How the pay run is worked out
  • A full period pays a fraction of the year: the annual salary divided by the number of periods in the year (12 monthly, 52 weekly, and so on), as one line.
  • A part period is paid per day. The qualifying days in the period are multiplied by the annual salary divided by the qualifying days in the tax year containing them, so both sides of the division count the same kind of day. Which days qualify is the calculation method: CalendarDays counts every day and divides by 365 or 366; WorkingDays counts only the days the employee works, taken from their working pattern, and divides by that employee’s working days across the tax year — so a three-day week divides by roughly 156, not 260.
  • The method resolves employee, then organisation, then a default of calendar days, and every response that resolves it says which level answered.
  • A joiner, a leaver or a mid-period salary change is pro-rated automatically. A change part-way through the period produces two lines, the old salary up to the day before it takes effect and the new one from it, each naming its dates in the description.
  • Salary lines are posted with canEdit: false, like statutory payment lines, so a later salary change or a corrected record always reaches the open pay run. Adjustments are separate manual lines.
  • Payslip events now carry a features attribute with a salary token when the employee has a salary in force for the period, so a consumer can filter on it without reading the employee record.
Get startedRecord salary with PUT /employees/{employeeId}/salary, then check an awkward period with POST /employees/{employeeId}/calculate before you open the pay run. The product walkthrough is Salary. Salary lines are posted only for organisations where the module is enabled; it is being turned on organisation by organisation.
IntegrationsBillingPayroll journals

Accounting joins the Integrations API, and billing drops its own

The Integrations API now covers accounting providers as well as pension ones, and billing’s embedded integration endpoints are gone. If you were driving billing’s own OAuth or connection routes, they have moved.Removed from the Billing APIEvery /integrations route: GET /integrations, GET|DELETE /integrations/{provider}, /connect, /callback, /exchange, /credentials, /setup-info, /tenant, /tenants and /custom-connection/test-and-save. Connections are the Integrations API’s job for every service, so there is one place to hold credentials and one record of what was sent.New in the Integrations API
  • Connecting a provider that signs in on its own website: POST /connections/{connectionId}/authorise returns the URL to send someone to, POST /connections/{connectionId}/exchange finishes it, and GET /connections/{connectionId}/tenants with PUT /connections/{connectionId}/tenant choose which of the provider’s own organisations to write to.
  • Reads for mapping screens, under /connections/{connectionId}/accounting/: accounts, tax-rates, tracking-categories, contacts, and per document documents/{documentType}/{externalId}/pdf and /attachment-count. type and status on an account come back normalised — Revenue, Expense, Asset, Liability, Equity and Active, Archived, Deleted — so the same code reads the same way whichever accounting system answered.
  • The provider catalogue now declares what a provider will hold: supportsDraftJournals, maxAttachmentsPerDocument and maxContactPersons. Read them rather than hardcoding a limit, so a second accounting provider needs no change in your client.
New in the Payroll journals API
  • POST /journals/{journalId}/postings posts a finalised journal through the organisation’s accounting connection and answers 202 with the operation to follow. Track it on GET /operations in the Integrations API.
BehaviourSending is never automatic. Closing an invoice or finalising a journal creates nothing; a send is always an explicit call. Results arrive on the operation, not as the response to the send.
IntegrationsPensions

A new Integrations API, and pay runs you can send to NEST

Flow Payroll can now send a pay run’s pension data straight to the provider instead of producing files for you to upload. Two things arrived to make that work: an Integrations API that holds provider connections and the record of everything sent or read, and a Provider submissions group in the Pensions API that sends a pay run, approves the payment and reads back what the provider reported. NEST is the first and, for now, the only provider.What’s new
  • Integrations API at /v1/integrations: GET /providers for the catalogue; POST /connections, PUT /connections/{connectionId}/credentials and POST /connections/{connectionId}/test to connect an employer’s NEST account; PUT /bindings/pension/pension-scheme/{schemeId} to link a scheme, which is the opt-in; GET /operations to follow every submission and every read with retry and cancel; GET and POST /connections/{connectionId}/imports to see how fresh the provider’s data is and refresh it; and POST /connections/{connectionId}/sync-sessions to import what the provider already holds.
  • Pensions API, with the pay run addressed in the path as /provider-sync/{payrollConfigId}/{year}/{frequency}/{period}: /sendable says which schemes can be sent and why not; /send submits enrolments or the contribution schedule; the collection root follows them per scheme with status, member count, total and the provider’s schedule reference; /approve-payment approves collection; /issues returns every reason a provider refused a submission; /refresh-members re-reads named members from their employee records so a refused detail can be corrected without re-running the pay run. Outside a pay run, GET /provider-sync/returns lists what providers sent back, GET /provider-sync/reconciliation compares a scheme with its provider, and /provider-sync/member-changes is the queue of reported opt-outs and stopped contributions with apply, dismiss, acknowledge and delete.
  • contributionDueDateOffsetDays on a pension scheme, for the rare case where a provider has published no schedule to read the due date from.
  • /refresh-members now rebuilds the pay run’s export files from the corrected details and reports exportFilesRebuilt. The files are rendered from the stored calculations, so a correction that stopped at the record left the download showing the old values, which matters most to a scheme delivering by file rather than through a connection. False means the details are corrected but the file is still the old render.
  • Every provider submission record carries createdBy and createdDate (who sent it, and when) alongside updatedBy and updatedDate (who last changed it). Nothing is sent to a provider on its own, so createdBy is the person who submitted it; updatedBy reads System once the provider’s own answer has landed, which is how you tell a person’s action apart from the provider’s reply. Records written before this shipped report System for both, because the name was never captured.
  • A published vocabulary of message tokens (provider_validation_failed, connection_circuit_open, pension_missing_payment_source and the rest), each with default English you can show as-is. Branch on the token, never the text.
How it behaves
  • Nothing sends on its own, and nothing a provider reports is applied on its own. Completing a pay run produces the export files and stops. A submission happens only on /send, payment approval only on /approve-payment, and a reported opt-out changes nobody’s pension until /member-changes/{changeId}/apply.
  • Retries are automatic and bounded. A temporary failure is retried up to five times over roughly eight hours, then parked as NeedsAttention, visible and retryable, never dropped. Failures a retry cannot fix (rejected sign-in, locked account, rejected content) park at once. Five failures in a row pause a connection for ten minutes; a provider that has not answered in two days is flagged.
  • Sending twice is safe. Every submission carries an idempotency key, and the Pensions API derives its key from the pay run’s content, so an unchanged run is deduplicated and a corrected one goes as a new submission.
  • Provider data is kept fresh. Opt-outs and stopped contributions are read within six hours of a pay run opening, again at lock, and daily; setup, schedules and refunds weekly and after every submission. Reading never changes payroll: what comes back is queued on /provider-sync/member-changes, and applying one ends the membership through the same flow as DELETE /employee-pension/{employeeId}, with no payroll refund.
  • The contribution due date comes from the provider. It is read from the provider’s own published schedules and matched on the earnings period, so sending late does not change it. If it cannot be determined, the submission is refused with pension_unknown_contribution_due_date rather than sent with a guess the provider would reject.
  • A refusal names who it refused. Each issue on /issues carries the record it relates to, so /refresh-members can re-read exactly those people. Amounts on a locked pay run are never touched; only the details a provider validates are rewritten.
  • The full rules are on the Integrations API overview.
Get startedConnect and link once through the Integrations API, then send each completed pay run with POST /provider-sync/{payrollConfigId}/{year}/{frequency}/{period}/send. The product walkthrough is sending pension data to NEST.
PensionsPay runs

A payslip now says why a pension deduction was taken gross

A member with no National Insurance number has the full pension contribution deducted, not the 80% net share, because HMRC needs that number for a relief-at-source scheme to reclaim basic-rate relief. Their deduction is therefore higher than an identical colleague’s, while the same amount reaches their pot. That is correct, but until now nothing in the API said so, and the larger figure read like a mistake.What’s new
  • pensionMetadata.reliefAtSourceApplied on a payslip summary item: false when the full gross was deducted, true when relief was netted off.
  • pensionMetadata.reliefAtSourceWithheldReason, currently MissingNationalInsuranceNumber, saying why.
How to read it
  • Both fields are null where relief at source does not apply (net pay arrangement, salary sacrifice) and on payslips stored before the fields existed, so absence is not “relief was applied”.
  • Nothing about the calculation changed. These fields explain an amount that was already correct.
  • Adding the member’s NI number restores relief on future pay runs. It does not change contributions already sent to the pension provider, and it is not pushed to the provider automatically, so update the member there too if relief is to be reclaimed on earlier contributions.
DeprecationsPensions

Every deprecated field on one page, with removal dates

Deprecated fields used to be visible only on the endpoint page that happened to render them — so a field you send on employee migration, or read back on a pension group, could be marked deprecated without you ever passing that page. Deprecations now lists every deprecated field in the API, what to use instead, and the date it is removed.What’s new
  • A Deprecations page in the sidebar, covering every service in this reference. It is generated from the same OpenAPI specs the reference is built from, so a field cannot be deprecated in a spec without appearing there.
  • A stated policy: a deprecated field keeps working with its existing behaviour — in requests and responses alike — until its removal date, and every field gets at least 90 days between deprecation and removal. If we need to move a date, we move it later, never earlier.
  • Each field says what removal takes away. Most of ours are deprecated as input only and keep being returned afterwards, so only what you send has to change — the page marks those Input only — still returned, and marks the fields that stop coming back Responses.
  • Removal dates are now published for every deprecated field. The Pay API opening-balance fields deprecated on 9 August — the flat NI totals on openingBalanceDirector and the aggregate pension on openingBalance — stop being accepted as input on 7 November 2026. Send niBandResult and pensionContributionsByType instead; both fields keep being returned on responses, as the sum across letters and scheme types respectively.
  • The five pension fields deprecated yesterday are removed on 7 December 2026.
Get startedOpen Deprecations and work back from the earliest date. The fields marked Input only need a change to what you send and nothing else; the ones marked Responses need your parsing to tolerate their absence. If a date does not give you enough time, email hello@flowpayroll.ai before it lands — we would rather move it than break your integration.
DeprecationsPensions

Five deprecated pension fields

Some of the fields on a pension group and on an employee’s enrolment describe how we store the record rather than what the employee’s pension actually is. Five of them are now deprecated in favour of the fields that answer the question directly — pensionType for the arrangement, pensionGroup for the group in force, and isAutoEnrolled for how the employee got there.What’s new
  • isSalarySacrifice is deprecated on POST /pension-group and PUT /pension-group/{id} — send pensionType instead. The flag is read only when pensionType is omitted, and it cannot express NetPayArrangement, so it can only ever describe two of the three arrangements.
  • isSalarySacrifice is deprecated on the pension group you read back. It is derived — true only when pensionType is SalarySacrifice — so read pensionType, which also tells ReliefAtSource and NetPayArrangement apart.
  • organisationId is deprecated on a pension group. A group always belongs to the organisation in your access token, so the field never told you anything your own request did not already state.
  • autoEnrolmentPensionGroup and manualEnrolmentPensionGroup are deprecated on an employee’s enrolment, returned by GET /employee-pension/{employeeId}, GET /employee-pension/batch, and GET /pension-information/{employeeId}. Read pensionGroup for the group in force and isAutoEnrolled for how the employee came to be enrolled — one field for the answer, one for the route to it, instead of inferring both from which of two fields is populated.
  • All five keep working, with their existing behaviour, until 7 December 2026. Unlike the Pay API opening-balance deprecations, four of these are response fields that stop being returned on that date — only isSalarySacrifice on a create or update is an input change. Read Deprecations for which is which.
Get startedSend pensionType on every pension group create and update, and switch your reads to pensionType, pensionGroup, and isAutoEnrolled. The Pensions API overview describes the group and enrolment shapes, and Deprecations lists each field with the endpoints that carry it.
PensionsBehaviour changeNew endpoint

Itemised pension contributions on payslips

Pension used to reach a payslip as a single total. It now reaches it as one line per contribution, which is what makes voluntary contributions, single-run overrides and opt-out refunds through payroll possible at all. Nothing was removed: the payslip schema gained four fields, no endpoint changed shape, and forPeriod.pension still carries the same total it always did.There is one thing to check in your integration.
If you build your own payslip from the API, take pension from the summary items or from forPeriod.pension, never from both. Summing the deductions in summaryItems and adding forPeriod.pension on top double counts every contribution. Relief-at-source and net-pay-arrangement schemes are the ones to look at: their contributions previously appeared only in forPeriod.pension, and now appear as rows as well. Salary sacrifice is unaffected, because its line has always been there.
What’s new
  • pensionLineType on every payslip summary item classifies pension rows as Contribution, RecurringAdditionalVoluntaryContribution, AdditionalVoluntaryContribution or OptOutRefund, and is null on everything else. Branch on it rather than parsing line ids or reading an organisation setting: the presence of a contribution row is what tells you which source to total.
  • pensionEmployerContribution gives the employer contribution attributable to a row. It is already inside forPeriod.employerPension, so it is display metadata and never additive.
  • pensionMetadata records the context a row calculated under: the pension group, the contribution basis, the employer share, and a contributionSource saying whether the settings came from the group, a standing per-employee override, or an override for that pay run only.
  • pensionRefundsByType on the period, year-to-date and employment-to-date totals breaks refunds down per scheme type. A refund is money going back to the member, so it is a payable row with a positive amount, and forPeriod.pension has it netted off already.
  • Seven new endpoints under /v2/pension: GET, PUT and DELETE for additional voluntary contributions and for per-period contribution overrides, both keyed by employee, tax year and period; GET /employee-pension/{employeeId}/refund-preview; and GET /employee-pension/{employeeId}/membership-ends plus GET /organisation/pension-membership-ends for the refunds report.
  • DELETE /employee-pension/{employeeId} gained processRefund and reason, and optOutDate now means what it says: supplying it records an opt-out rather than a bare removal, and it is refused if it precedes the enrolment date. A removal with neither is recorded as an administrative correction. Preview the refund first, then pass processRefund=true to pay it on the next payslip.
Get startedRead reading pension on a payslip for the field to branch on and a worked example, then the Pensions API overview for the new endpoints. The product-side view is in pension contributions and refunds.
New APIAdministration

Administration API

You can now manage your Flow Payroll account over the API, not just the payroll inside it. The new Administration service covers what used to need a support request or a trip to the admin screens: adding organisations, inviting people and deciding which organisations they reach, and issuing, rotating and revoking the API tokens your integrations authenticate with — plus a feed of who changed what.What’s new
  • A new Administration API group in the sidebar, served under /v1/administration. It is scoped to your account rather than to one organisation, so it needs no X-Org-Id header.
  • GET /customer and PATCH /customer to read and rename your account, and PUT /customer/owner to transfer its ownership. Each account has one owner, whose access cannot be revoked from inside it.
  • Organisation endpoints: list with search and headcounts, get one in full, add one with POST /organisations, or add several with POST /organisations/bulk using shared defaults and per-entry overrides. Both creates honour an Idempotency-Key header.
  • People endpoints: list who can sign in, invite one person or many, resend an invitation, remove somebody (owner only), and manage each person’s access — direct grants on named organisations and account-wide access — through /users/{principalId}/organisations and /users/{principalId}/customer-access.
  • Guests: invite somebody from another business into specific organisations with asGuest: true. Guest people and guest organisations appear in the lists with an accessType of Guest, and the accessType query parameter filters either list to one kind.
  • API token endpoints: create a credential and receive its secret once, rename, rotate, revoke, and manage its access with the same two sub-resources as people. Self-service credentials replace asking support for a client_id / client_secret pair.
  • GET /activity for the last 90 days of administration actions, newest first.
  • Two kinds of access, both reported on every read: organisationIds for direct grants and coversWholeCustomer for account-wide access, on people and tokens alike. Only somebody who holds account-wide access can grant it.
  • Every refusal carries a stable snake_case reason token in message.token (user_limit_reached, token_limit_reached, duplicate_token_name, idempotency_key_reused, billing_not_set_up, and so on), and bulk endpoints report the same tokens per entry.
Get startedCall GET /v1/administration/customer with your existing token to confirm which account it belongs to, then browse the Administration API group in the sidebar. The Administration API overview explains the two kinds of access, the limits (500 people and 5 API tokens per account), idempotency and pagination.
PensionsNew endpoint

Auto-enrolment assessment preview

The Pensions API can now classify a worker for auto-enrolment from nothing but the details you send it. POST /auto-enrolment/assessments/preview (under /v2/pension) takes a date of birth, earnings with their frequency, and the pay reference period dates, and returns the worker category — EligibleJobholder, NonEligibleJobholder, EntitledWorker, or NotEligible — together with what follows from it. Nothing is read from your employee records, nothing is stored, and no enrolment is triggered.We built this with agents in mind. Auto-enrolment categorisation is deterministic statutory logic, and it belongs in the API rather than in a model’s head: an AI assistant answering “would this new starter be auto-enrolled?” or “why can this employee only opt in?” can call the endpoint and ground its answer in the same rules engine that assesses real pay runs, instead of approximating the thresholds itself.What’s new
  • A stateless preview endpoint in the Pensions group: send dateOfBirth, earnings, payReferencePeriodStartDate, and payReferencePeriodEndDate; get back the worker category and its consequences — autoEnrolled, canOptIn, and employerPaysIn.
  • The response also carries the worker’s age at assessment, whether they are under State Pension age, their State Pension age date, and the effective assessment date, so a caller can explain the outcome, not just state it.
  • Classification follows The Pensions Regulator’s categorisation exactly: the 16–74 age bounds, the State Pension age to 74 opt-in band, and strict earnings thresholds pro-rated per pay frequency.
  • Invalid input — an unknown frequency, negative earnings, a period end before its start, or a non-ISO date — returns a 400 that names the problem, so agents and integrations can self-correct.
Get startedThe endpoint appears in the Pensions group as Preview auto enrolment assessment. Start from the Pensions API overview, and see the auto-enrolment guide for the product-side view of assessment outcomes.
EmployeesNew endpoint

Update an employee with PATCH

Integrations that only need to change one thing about an employee no longer have to send the whole record. PATCH /employees/{employeeId} accepts a JSON Merge Patch: send the fields you want to change, and everything you leave out keeps its current value. PUT is unchanged, so existing integrations keep working exactly as they do today.What’s new
  • Partial updates on the employee record — send {"salary": 42000} and nothing else moves.
  • Merge semantics follow RFC 7386: omitted fields keep their current values, an explicit null clears an optional field, nested objects merge recursively, and arrays replace wholesale.
  • One call covers the whole updatable surface — personal details, tax codes, NI details, student loans, bank accounts, director details and more — instead of a sequence of dedicated endpoints.
  • Fields with their own guarded endpoints (starter details, leaver details, payroll ID, payroll configuration, opening balances) are rejected with a 400 rather than silently ignored, so a mistyped field can never touch HMRC-relevant state.
  • Required fields (name, email, birth gender, birth date, hours worked category) cannot be cleared.
Get startedThe endpoint appears as Partially update an employee under Employees in the Pay API section — see the Pay API overview. If your platform holds the HR record and overwrites on save, keep using PUT; if you read and write individual fields, PATCH is the call to build on.
EmployeesDirectors

Split opening balances for directors

Opening balances now describe a director’s year in the detail HMRC needs, so you can migrate a director to Flow Payroll part-way through a tax year — including the awkward cases, where someone is a director for only part of the year or changes NI category letter mid-year.What’s new
  • openingBalanceDirector.{year} gains niBandResult, a map keyed by NI category letter carrying the NI-able gross and contributions accrued while the director basis applied, with optional band splits.
  • Earnings are now split by where they were assessed: openingBalance covers the entire employment, openingBalanceDirector covers the director stretch from appointment onwards. Each keeps its own band splits.
  • The flat totals on openingBalanceDirector (grossForEmployeeNi, grossForEmployerNi, ni, employerNi) are deprecated as input — they cannot express more than one NI letter in a year. They still work, and we still return them as the sum across letters.
  • Opening balance updates return specific validation issues (unknown NI letter, negative values, band splits exceeding the letter’s gross) instead of one generic message.
Get startedSend niBandResult on PUT /employees/{employeeId}/opening-balance rather than the flat totals. For a director on the annual basis appointed before the tax year began, leave bandResults out of openingBalance — the annualised calculation derives the band split itself. For the alternative basis, include bandResults inside each openingBalanceDirector letter. See the Pay API overview for the full request shape.
New APIReportsBeta

Reports API (Beta)

You can now query your payroll data programmatically. The new Reports service takes a single JSON report definition — dataset, columns, filters, grouping, aggregation, sorting — and runs it synchronously, exports it at scale, or delivers it on a schedule. It ships in beta: build against it, and expect refinements before general availability.What’s new
  • A new Reports group in the sidebar (badged Beta), served under /v1/report.
  • Dataset discovery endpoints: list the queryable datasets and inspect each field catalog — types, filterability, groupability, aggregations, and sensitivity.
  • POST /reports/run for synchronous runs of inline definitions (10,000-row cap), plus saved report definitions you can re-run by ID.
  • Asynchronous exports to CSV or JSONL for up to 5,000,000 rows, downloaded via presigned URLs.
  • Cron schedules on saved definitions, delivering exports by HMAC-signed webhook or email.
  • A run history endpoint that doubles as an audit trail, recording access to sensitive fields.
Get startedCall GET /v1/report/datasets to see what you can query, then iterate on a definition with POST /v1/report/reports/run. The Reports API overview walks through the definition document, relative-period tokens, and the export and scheduling flows.
New APIPayroll journals

Payroll journals API

You can now drive payroll journals entirely over the API. A new Payroll journals service turns every finalised pay run into a balanced double-entry journal — mapped to your chart of accounts, split by department or cost centre, and exportable to Xero or any other accounting system — without touching the Flow Payroll UI.What’s new
  • A new Payroll journals group in the sidebar, served under /v1/journal.
  • Template endpoints to define how journals are built: create, clone, and update templates, manage pay-element and control-account mappings, configure split dimensions, and import mappings from systems such as Sage, Xero, Staffology, and IRIS.
  • Preview endpoints to render a template against a pay run before you rely on it, including CSV and PDF preview exports.
  • Journal endpoints to list journals, fetch the latest version for a pay run, download exports (Xero Manual Journal CSV, generic CSV, or PDF) via presigned URLs, and rebuild or reissue a journal after corrections.
  • Organisation-wide defaults via GET/PUT /org-journal-settings, plus export-option lookups for presets, date formats, and narration and reference tokens.
Get startedJournals are generated automatically as soon as payslips are calculated — start by creating a template with POST /v1/journal/templates, then browse the Payroll journals group in the sidebar for the full surface. For how templates, mappings, and splits behave in the product, read the Payroll journals guide.
Authentication

One set of credentials, many organisations

Your API credentials are no longer tied to a single organisation. The platform now separates who you are from which organisation you’re acting on: your token (sent as X-Auth-Token) proves your identity exactly as before, and the new X-Org-Id request header states which organisation the request applies to. One client_id/client_secret pair can now be granted access to many organisations — useful for bureaus, accountants, and group companies.What’s new
  • A new X-Org-Id request header, accepted on every endpoint, naming the organisation the request is for. Switching organisation is just sending a different value on the next request — the same token stays valid across all your organisations, with no re-authentication.
  • A new Authentication API group in the sidebar. Call GET /v1/authentication/my-organisations to list every organisation your credentials can act on — it works before an organisation is selected, so you can use it to build an organisation picker.
  • Nothing breaks for existing integrations: if your credentials access exactly one organisation, requests without X-Org-Id resolve to it automatically. With access to more than one, omitting the header falls back to your configured default organisation; with no default set, the request is rejected with 403 so you choose explicitly.
Get startedSend X-Org-Id explicitly on every request, even while your credentials access only one organisation — your integration then keeps working unchanged the day a second organisation is granted. See Authentication → Choosing an organisation for the resolution rules and error responses. To link additional organisations to your existing credentials, contact hello@flowpayroll.ai.
PensionsDeprecationsDocumentation

Pension API v1 retired from the docs

The reference now documents the Pension API v2 only. Every pension page (employee pensions, schemes, groups, providers, auto-enrolment) is on the v2 spec. If you still call v1 paths, move to their v2 equivalents.
Pay runsPayslips

More detail on payslips

Payslip base values and payslip summary items carry more fields, so an integration can rebuild a payslip line by line from the API without working out the figures itself. See the payslip endpoints in the Pay API overview.
Behaviour changeStatutory payHMRCCourt orders

One response envelope across the services

Statutory pay, HMRC DPS and court orders now return the same response wrapper as the Pay API, so one error handler and one pagination reader work across all of them. See Response format.
HMRCNew endpoints

P32, P45 and P60 over the API

The HMRC P-forms API grows from a single P45 read to the year-end and leaver forms.What’s new
  • P32: GET /p32/{year}, GET /p32/{year}/{month} and a PDF at GET /p32/{year}/{month}/pdf.
  • P45: a preview, a file download, finalised lists by tax year (optionally by payroll configuration), replacements, and POST /p45/send-notification to email them.
  • P60: by employee and tax year, a file download, preview and finalised lists, POST /p60/{employeeId}/{taxYear}/regenerate, and POST /p60/send-notification.
  • …/tax-years endpoints on each form list the years that have data.
EmployeesNew endpoints

Leavers in bulk, and cancelling a leaver

  • PUT /employees/leaver/bulk sets leaver details for many employees in one call, and DELETE /employees/leaver/bulk cancels them.
  • DELETE /employees/{employeeId}/leaver clears one employee’s leaver details.
  • GET /organisation/balances/paye-liability/tax-years lists the tax years with PAYE liability data.
  • The court orders API gains GET /orders/constants/{taxYear} and POST /orders/reconcile, and statutory pay gains a history per payment.
  • Payment configuration and payment files moved into their own Payment API spec.
AuthenticationDocumentation

Authentication guide

A dedicated Authentication page walks through getting a token with the client credentials flow and sending it on each request.
New APIPay runs

Recurring line items API

The Recurring line items API manages pay that repeats across pay runs: create a recurring item, attach employees to it, and read or adjust each employee’s schedule by tax year and period, down to a single occurrence.
Billing

Billing: invoice numbers, references and email

  • POST /invoices/{clientId}/{invoiceId}/email emails an invoice.
  • Configurable invoice number formats and invoice references, with the tokens you can use from GET /finance/invoice-reference-fields.
  • Filter invoices by invoice number.
  • Due date settings, and tag group and name on invoices formatted by tag.
PaymentsNew endpoint

Retry a failed payment file

  • POST /payment/file/run/{payrollConfigId}/year/{year}/frequency/{frequency}/period/{period}/retry regenerates a payment file that failed, and the file endpoint now returns an error response saying why generation failed.
  • BACS files carry the bank account name.
  • Bank details are validated on input, and fewer bank fields are required.
Pay runsHMRCNew endpoints

Year-end finalisation

  • POST /payroll/year-end/{taxYear}/{payrollConfigId}/finalise finalises a payroll configuration’s tax year.
  • GET /payroll/year-end/{taxYear}/{payrollConfigId} reads it back, and GET /payroll/year-end/{taxYear} lists every finalisation in the organisation for that year.
Statutory payNew endpoints

Statutory pay attachments and imports

  • Attach documents to a statutory payment with POST, GET and DELETE on /payments/employees/{employeeId}/{statutoryPayId}/attachments.
  • POST /payments/import/{employeeId} brings in statutory payments that began before the employee moved to Flow Payroll.
HMRCBehaviour change

FPS downloads, and DPS matching

  • FPS submissions report more statuses and carry pre-signed submissionDownloadUrl and previewDownloadUrl links to the XML.
  • HMRC DPS gains POST /notifications/retry-matching, which retries matching notifications to employees, and GET /notifications/counts.
EmployeesBehaviour changeNew endpoint

The payroll ID has its own endpoint

PUT /employees/{employeeId}/payroll-id changes an employee’s payroll ID. The payroll ID is still set on create and returned on every read, but it stopped being accepted on PUT /employees/{employeeId} on 14 April 2026,.
EmployeesBehaviour change

Validation errors you can translate

Each validation issue now carries reasonToken, a stable snake_case key, with reasonTokenArguments, beside the English reason. Build your own messages on the token; the wording of reason may change. The NI category field is now an enum of the HMRC-published letters.
Payments

Barclays BACS files and payment references

  • Payment configuration supports the Barclays BACS CSV format and lists supportedFileFormats for each method.
  • A payment reference template, with a date format, sets the reference printed on each payment.
EmployeesDirectorsBehaviour change

Opening balances, split three ways

The single set of year-to-date values on an employee became three, so a migration can say where each figure came from:
  • Previous employment, sent on create and update.
  • Opening balance, for earnings already paid in this employment.
  • Director opening balance, for the stretch a director has spent on the director basis.
Opening balances are sent on create and changed afterwards through a dedicated endpoint. Year-to-date earnings are still returned on GET.
EmployeesNew endpoint

Move an employee between payroll configurations

PUT /employees/{employeeId}/payroll-config changes the payroll configuration an employee is paid on. Marital status and phone number are no longer required on an employee, and the partner details fields were renamed to name and nationalInsuranceNumber.
BillingIntegrations

Billing: accounting integrations

The Billing API connects to an accounting provider.What’s new
  • Connect, list, switch tenant and disconnect under /integrations/{provider}.
  • Push clients, invoices and credit notes to the provider (…/sync), and download the provider’s PDF.
  • Import contacts, tax rates and account codes from the provider.
  • Organisation-wide pay element mappings, finance defaults, and invoice and credit note templates under /finance.
HolidayStatutory pay

Rolled-up holiday pay and statutory rates

  • The holiday configuration supports rolled-up holiday pay.
  • Statutory pay gains GET /statutory-rates/{taxYear}, GET /statutory-rates/available-years and POST /payments/reconcile.
EmployeesPay runsNew endpoints

Starter details and opening the next pay run

  • PUT /employees/{employeeId}/starter updates starter details on their own, split out of the main employee update.
  • POST /payroll/{payrollConfigId}/next opens the next pay run for a payroll configuration.
EmployeesBehaviour change

Tax code and NI category are optional when onboarding

Leave them out and Flow Payroll applies the default tax code for the tax year and an NI category based on the employee’s age. The unused isExemptFromNi flag was removed.
New APIHoliday

Holiday API

The Holiday API covers holiday entitlement end to end: an organisation holiday configuration, per-employee configuration, holiday taken, opening balances, the employee’s history, and the 52-week average rate.
BillingNew endpoints

Billing: credit notes and finance settings

  • Credit notes: draft, update, authorise, void, allocate to an invoice, and list by organisation or client.
  • Void an invoice, credit it in full, or act on many at once with POST /invoices/bulk-action.
  • Tax rates and account codes under /finance, and finance settings per client.
  • Unarchive a client.
New APIPensions

Pension API v2

The Pension API v2 adds auto-enrolment to the pension surface: assessments per pay run, employees whose enrolment is not complete, enrolment letters, postponement, and organisation-level auto-enrolment and postponement defaults.
Pay runsBehaviour change

Payslips by pay run page with a token

GET /payslip/payrun/{payrollConfigId}/{year}/{period} now pages with paginationToken rather than a page number, because payslips are now pre-processed. Line items gain PO numbers and attachments, removed with DELETE /payroll/line/{employeeId}/{lineId}/attachments/{attachmentId}.
Statutory payNew endpoints

Statutory pay: linking, average earnings and Alabaster

  • Link periods of incapacity for work with POST /calculations/employees/{employeeId}/linking, and read the calculation history.
  • Average weekly earnings, line items per payment, and a single payment by id.
  • Alabaster detection and recalculation for maternity pay where a pay rise lands in the relevant period.
New APIStatutory pay

Statutory pay API

Statutory pay moved out of the Pay API into the Statutory pay API: calculations, a preview, and payments per employee and per organisation that you can update and delete.
New APICourt orders

Court orders API

The Court orders API manages attachment of earnings orders: create and update orders, attach the order document, list an employee’s orders, and manage the payees deductions are paid to.
Organisation

Class 1 NI-able pay elements

Pay elements say whether they are Class 1 National Insurance liable.
BillingDocumentation

Attachments, and a new shape for the reference

  • Attach files to a line item with POST /payroll/line/{employeeId}/{lineId}/attachments and to an invoice with POST /invoices/{invoiceId}/attachments.
  • The reference is now organised as Organisation setup, Pay API and Bill API, each with an overview page.
New APIBilling

Billing API

The first version of the Billing API: clients, contacts and invoices, including closing an invoice, with invoice summary items that name the employee each charge relates to.
EmployeesHMRC

Employee codes and NI band results

  • employeeCode, an optional identifier of your own, on create, update and read.
  • NI results carry grossForEmployeeNi, grossForEmployerNi and bandResults.
  • EPS requests take the tax month.
EmployeesNew endpointsBehaviour change

Effective-dated tax codes, NI categories and loans

Tax codes and NI categories became histories rather than single values, so a change can take effect from a date.What’s new
  • POST /employees/{employeeId}/tax-code, GET …/tax-codes and DELETE …/tax-codes/{year}/{effectiveDate}.
  • The same three for NI categories, and GET /employees/{employeeId}/effective-tax-info for what applies on a date.
  • Start and end student loans and postgraduate loans with their own endpoints.
  • PUT /employees/{employeeId}/nino updates the National Insurance number.
  • Salary is no longer required on an employee, and the unused paymentType was removed.
New APIHMRC

HMRC DPS API

The HMRC DPS API retrieves the tax code, student loan and other notices HMRC sends, matches them to employees, and applies or ignores them one at a time or in bulk.
OrganisationPay runsNew endpoints

Several organisations at once, and paged payslips

  • GET /organisation/information/multiple returns several organisations’ details in one call.
  • GET /payslip/payrun/{payrollConfigId}/{year}/{period} takes pageSize and paginationToken.
EmployeesPensionsNew endpoints

Find an employee by NI number or payroll ID

  • GET /employees/by-ni/{nationalInsuranceNumber} and GET /employees/by-payroll-id/{payrollId}.
  • GET /pension-group/allowed-bases lists the pensionable pay bases a pension group can use.
Pay runs

Pay periods and payment periods

  • Pay runs and payslips carry payPeriodStartDate and payPeriodEndDate.
  • Payroll configurations take a payment period setup that says where each period starts.
Pay runsPensionsNew endpoints

Recurring line items, and pension exports

  • Line items can recur: read an employee’s with GET /payroll/line/{employeeId}/recurrent, and the lines in force for a range with GET /payroll/line/{employeeId}/effective-range.
  • The pension API gains pension calculations, CSV exports and export file retrieval per pay run.
HMRCNew endpoints

EPS resubmission, and the years that have data

  • POST /rti/eps/{taxYear}/manual/{submissionId}/resubmit resubmits an EPS.
  • GET /payroll/runs/tax-years-with-pay-runs and GET /rti/eps/tax-years-with-submissions list the years worth asking about.
  • EPS submission reads no longer return the raw XML.
HMRCPensions

P45 structure, and pension references

The P45 response took a new structure with week and month numbers, and a pension’s employer id became externalReferenceId.
HMRCNew endpoints

PAYE liability and payments

  • GET /organisation/balances/paye-liability/{taxYear} summarises what is owed to HMRC.
  • Record, update and delete PAYE payments against it under /organisation/balances/paye-payments/{taxYear}.
AuthenticationBehaviour change

OAuth 2.0 client credentials

API access moved to the OAuth 2.0 client credentials flow: exchange a client id and secret for an access token at the token endpoint, then send the token on each request. See Authentication.
New APIHMRC

HMRC P-forms API

The HMRC P-forms API starts with GET /p45/{employeeId}.
Pay runsEmployeesNew endpoints

Email payslips, and set leavers

  • POST /notification/email/payslip/{payrollConfigId}/{year}/{period} emails employees their payslips.
  • PUT /employees/{employeeId}/leaver sets leaver details.
  • payrollId on each employee in the list.
PaymentsPay runsNew endpoints

Payment files and payslip notes

  • GET /payment/file/run/{payrollConfigId}/year/{year}/frequency/{frequency}/period/{period} returns the payment file for a pay run.
  • POST /payslip/data/{employeeId}/{year}/{period} stores payslip notes and a payment date override.
Pay runsNew endpoints

Pay runs and payslips in bulk

  • GET /payroll/runs/{year} lists pay runs, and GET /payroll/{payrollConfigId}/{year}/{period}/employees the employees on one.
  • GET /payslip/payrun/{payrollConfigId}/{year}/{period} returns every payslip on a pay run, and GET /payslip/data/{employeeId} an employee’s payslips over a date range. forceGenerate regenerates on read.
  • Pay runs carry an Open status, lockedBy, lockedDate and totalBalances.
  • Payslip summary items carry the pay element id, tags and date, and base values include pension.
New APIPensions

Pension API

The first Pension API: pension providers, schemes and groups, associating employees with a pension, and monthly pension calculations.
PaymentsPay runs

Create pay runs, and payment configuration

  • POST /payroll/{payrollConfigId}/{year}/{period} creates a pay run.
  • A Payments API configures how employees are paid per payroll configuration, with GET /payment/available/config listing the options.
  • GET /clients lists your clients.
  • HMRC settings take a bank account, and payment date configuration takes whichWeekInPeriod for two- and four-weekly payrolls.
Employees

List employees

GET /employees lists employees with pagination. Employees gain salary, jobTitle and department.
OrganisationHMRCNew endpoints

Pay elements, balances and the EPS

  • Pay elements: GET, POST, PUT and DELETE on /organisation/pay-element. Line items take a pay element id.
  • Opening balances and running balances by tax year and month under /organisation/balances.
  • EPS: submit manually or from monthly balances, and read submissions by tax year under /rti/eps.
  • The FPS is submitted and read at /rti/fps/{payrollConfigId}/{taxYear}/{period}.
MCP server

MCP server

The Flow Payroll MCP server lets AI tools such as Claude and Cursor work with the Flow Payroll API and its documentation.
OrganisationNew endpoints

Organisation information and HMRC settings

  • GET and POST /organisation/information for the organisation’s details.
  • GET and POST /organisation/hmrc/settings, including the PAYE reference.
  • Add line items with POST /payroll/line/{employeeId}, and DELETE /payroll/line/{employeeId}/{lineId} to remove one.
  • Payslips carry the employer’s details.
EmployeesHMRCNew endpoint

Leavers, partners and working days

  • PUT /employee/{employeeId}/leaver updates leaver details.
  • Employees gain partner details and a working days configuration.
  • Seconded details are a single stayStatus enum, following HMRC’s FPS guidance.
Statutory payNew endpoints

Statutory pay

Calculate statutory pay for an employee or as a preview, and create and read statutory pay records, under /statutory.
HMRCNew endpoint

Preview the FPS, and read a submission

POST /payroll/{payrollConfigId}/{year}/{period}/hmrc/fps submits the FPS, or previews it with a query parameter, and GET on the same path returns a submission already made.
HMRCPay runs

FPS submission, and director appointment dates

  • Submit the FPS for a pay run over the API.
  • Director details take an appointment date, so appointments in past years are reported correctly.
Pay runs

Upload line items per employee

Line items are uploaded one employee per request, so each one validates on its own. Uploads take niType, respond with the payrollDate, and accept up to 100 items per request. Employees gain title and payrollId, and employee and payslip fields follow HMRC’s FPS.
New API

The Flow Payroll API

The first version of the API, and the start of this page. It covered payroll configurations, employees, clients, uploading pay lines, reading and locking a pay run, payslip data, and a gross-to-net calculator.