/v2/pension relative to the gateway base URLs on the API reference introduction — for example, https://api.sandbox.flowpayroll.ai/v2/pension in sandbox.
Key concepts
- Pension provider — the pension company itself. A provider has a name, a URL-friendly slug, a flag indicating whether schemes with it require a payment source, and
exportSupported, which says whether a completed pay run produces an export file for its schemes. TheManualprovider is the one that does not: choose it for a scheme you file with your provider yourself. Providers are set up in the product (go to Settings and open the Pension tab); the API exposes them read-only. - Pension scheme — your organisation’s arrangement with a provider. A scheme records its payment source, duty start date, last re-enrolment date, and an optional external reference. Scheme endpoints use the
/pension-schemapath. - Pension group — a set of contribution rules within a scheme: the pension type (relief at source, salary sacrifice, or net pay arrangement), the contribution basis, employee and employer factors, a National Insurance savings factor, and an optional external reference. One group can be marked as the organisation’s default.
- Employee pension association — the link between an employee and a pension group, with an enrolment date and an option to send enrolment letters.
- Additional voluntary contribution: an extra employee contribution on top of the scheme contribution, for one pay run only. Held per employee, tax year, and period.
- Contribution override: replaces the group’s contribution settings for one pay run only, for example an unpaid-leave month. Any additional voluntary contribution still applies on top. Held per employee, tax year, and period.
- Membership end: the record of a membership ending: why it ended, when, and what each side is owed back. It survives the association being removed, which is what keeps a refund retrievable afterwards.
- Auto-enrolment assessment — the AE result a pay run produces for each employee: the assessment outcome, worker age, earnings, and effective assessment date.
- Postponement — a deferral of auto-enrolment, defined as a start-date definition and an eligibility-date definition (a value plus a frequency). Defaults are set at organisation level and can be overridden per employee.
- Pension calculation — an employee’s contribution amounts for a specific period, retrievable per month or per provider and exportable as a CSV or provider-format file.
- Itemised contributions: how contributions reach a payslip: the pension service pushes one payroll line per contribution, so the payslip shows a pension row per contribution, and additional voluntary contributions, per-period overrides and opt-out refunds through payroll all become possible.
- Provider submission: what was sent to a scheme’s connected pension provider for one pay run: the enrolments, the contribution schedule and the payment approval, each with its status, the provider’s reference and the reason if it did not land. A scheme becomes sendable by being linked to a connection in the Integrations API; NEST is the first provider.
API structure
Pension providers
Providers are read-only through the API: list them withGET /pension-provider, retrieve one with GET /pension-provider/{id}, or look one up by its slug with GET /pension-provider/slug/{slug}. Create and manage providers in the product — go to Settings and open the Pension tab.
Pension schemes
Create, update, retrieve, and delete schemes at/pension-schema and /pension-schema/{id}. GET /pension-schema/organisation returns every scheme in your organisation, each with its provider and groups embedded. Creating a scheme requires a pensionProviderId — find it with GET /pension-provider.
Pension groups
Manage contribution groups at/pension-group and /pension-group/{id}. Before creating a group, call GET /pension-group/allowed-bases to see the contribution bases the service accepts.
Set externalReferenceId on a group to record the identifier your provider uses for it — a worker group or category code, for example. It is optional, and it is returned on every group, including the groups embedded in GET /pension-schema/organisation, so you can map our groups onto yours without a second lookup.
A group’s
externalReferenceId is stored and returned for your own reconciliation. It is the scheme-level externalReferenceId that provider export files use as the employer reference.Employee enrolment
POST /employee-pension associates an employee with a pension group, and GET /employee-pension/{employeeId} reads the association. GET /employee-pension/batch fetches several employees at once via repeated employeeId query parameters. POST /employee-pension/{employeeId}/enrolment-letter triggers an enrolment letter for an employee.
Ending a membership
DELETE /employee-pension/{employeeId} is the single removal surface, and what you pass to it decides what is recorded:
The reason is a record of what happened, not a switch: any ending stops further automatic assessment until the employer’s next cyclical re-enrolment date, and a worker whose membership ended within 12 months of that date is left out of that cycle by default. Whether an opt-out falls inside the statutory opt-out period is the employer’s assessment, not something the service enforces.
Call
GET /employee-pension/{employeeId}/refund-preview first. It is side-effect-free and returns exactly what a refund would pay back, per settled period, so an operator can see the figure before confirming.
Endings stay readable long after the association is gone: GET /employee-pension/{employeeId}/membership-ends for one employee, and GET /organisation/pension-membership-ends for the organisation’s refunds report over a date range.
A refund covers the member’s contributions only. Employer contributions are repaid by the scheme to the employer directly, so they never appear on a payslip. The membership-end records carry both figures, which is what makes the organisation report reconcilable against what the provider actually returned.
Additional voluntary contributions and per-period overrides
Both are addressed per employee, tax year, and pay period, both are idempotent (a repeatedPUT replaces the value), and both apply to open pay runs only. The period’s payslip is re-processed automatically, so the change shows up without you triggering anything.
- Additional voluntary contributions at
/employee-pension/{employeeId}/additional-voluntary-contributions/{taxYear}/{period}(GET,PUT,DELETE), withGET /employee-pension/{employeeId}/additional-voluntary-contributionslisting them in pay-period order. An additional voluntary contribution sits on top of the employee’s normal contribution. On a relief-at-source scheme the payslip deduction is 80% of the gross amount you set, because the provider claims the basic-rate relief. - Contribution overrides at
/employee-pension/{employeeId}/contribution-overrides/{taxYear}/{period}(GET,PUT,DELETE), withGET /employee-pension/{employeeId}/contribution-overrideslisting them. An override replaces the group settings for that one period, and any additional voluntary contribution still applies on top. The auto-enrolment minimum binds per pay reference period, so an override below it is refused unless you sendacknowledgeBelowAutoEnrolmentMinimum.
Auto-enrolment
GET /auto-enrolment/assessments/payrun/{payrollConfigId}/{year}/{frequency}/{period} returns the assessments a pay run produced, and GET /auto-enrolment/complete/payrun/{payrollConfigId}/{year}/{frequency}/{period} lists employees whose enrolment is complete for that pay run. GET /auto-enrolment/not-complete and GET /auto-enrolment/not-auto-enrolments list employees with outstanding or no auto-enrolment, optionally including employee details. POST /auto-enrolment/assessments/preview runs a stateless assessment preview from the request body alone — supply a date of birth, earnings with their frequency, and the pay reference period dates, and it returns the worker category (EligibleJobholder, NonEligibleJobholder, EntitledWorker, or NotEligible) with its consequences, without reading or storing any employee data.
Enrolment information duty
You have six weeks from someone’s enrolment date to give them their enrolment information (The Pensions Regulator, detailed guidance 5 and 10). Two endpoints report on that duty. They are exact complements: every membership appears on one list or the other, never both and never neither. An empty outstanding list therefore means everyone has been informed.GET /auto-enrolment/enrolment-information/outstanding lists who is still owed theirs, worst first: failed sends, then overdue, then still due. GET /auto-enrolment/enrolment-information/sent lists who has already had theirs, most recently given first, with sentDate and sentBy.
sentDate is the day the information was given, not the day an email was dispatched. It is the day the member’s opt-out period runs from (detailed guidance 7, paragraphs 17 and 18).
Someone holding both a manual and an automatic membership appears once for each. Each is its own enrolment, with its own date and its own letter.
Both endpoints are paginated. Follow content.metadata.paginationToken until it is absent rather than treating a short page as the last. The outstanding list returns nothing for an organisation that has declared it issues the information itself. The sent list still answers, because Flow can always report what it sent.
Postponement
PUT /postponement sets an employee’s postponement, GET /postponement/{employeeId} reads it, and DELETE /postponement/{employeeId} removes it.
Organisation defaults
GET /organisation returns your organisation’s pension defaults: the default pension group, default postponement definitions, and the auto-enrolment mode. Update them with PUT /organisation/pension-default/postponement-settings and PUT /organisation/pension-default/auto-enrolment-settings.
Pension calculations and exports
Read calculations withGET /pension-calculation/by-year/{year}/by-month/{month}, or narrow to one provider with GET /pension-calculation/pension-provider/{pensionProviderId}/by-year/{year}/by-month/{month}. POST /pension-calculation records a calculation for an employee and period. For a pay period, fetch a CSV via GET /pension-calculation/csv/year/{year}/period/{period}/frequency/{frequency}/payroll-config/{payrollConfigId}, or request provider export files with POST /pension-calculation/export and list the generated files — including their file paths — through GET /pension-calculation/export/year/{year}/period/{period}/frequency/{frequency}/payroll-config/{payrollConfigId} or the equivalent /export-file-retrieval/pension-calculation/... endpoint. A scheme on the Manual provider (exportSupported: false on GET /pension-provider) never has export files: read its contributions through the calculation endpoints instead, and use the payroll_run.completed webhook to know when a period’s figures are final.
Provider submissions
A scheme linked to a provider connection can be sent directly instead of, or as well as, by export file. NEST is the only provider supported today; schemes with any other provider continue through the export files. Completing a pay run produces the export files and stops there: a submission only happens when you ask for one, and approving the payment is a second, separate call. The pay run is addressed in the path, as/provider-sync/{payrollConfigId}/{year}/{frequency}/{period}.
-
GET /provider-sync/{payrollConfigId}/{year}/{frequency}/{period}/sendablelists every scheme in the pay run and whether each can be sent right now. A scheme that cannot be sent is listed withsendable: falseand areasonTokenrather than hidden:pension_scheme_not_connectedfor one that delivers by export file,connection_not_callablefor one whose connection has to be finished or repaired. An empty list means the pay run holds no pension calculations at all. -
POST /provider-sync/{payrollConfigId}/{year}/{frequency}/{period}/sendsubmits the run. PassschemeIdto send one scheme andoperationTypeto send one part, which is how a part that did not land is sent again on its own; with both parts, enrolments go first, because a member the provider has never seen cannot appear on a contribution schedule. Naming aschemeIdthe pay run does not hold returns 404. A 2xx means the request was understood, not that anything was sent. Readschemesto find out what the send decided, scheme by scheme:Submitted,Skipped(not connected, or nothing outstanding),Blocked(the connection needs finishing or repairing) orFailed, each with areasonTokenand a readablereasonMessage.submissionscarries the pay run’s submission ledger as it stands, filtered to the scheme you asked for; follow it with the next endpoint. Do not read an emptysubmissionsas success — a scheme that delivers by export file sends nothing and says so inschemes. Sending the same unchanged data twice is deduplicated rather than submitted again, while a corrected run goes as a new submission, which is how the provider expects corrections to an open schedule. -
GET /provider-sync/{payrollConfigId}/{year}/{frequency}/{period}lists the pay run’s submissions per scheme, one record peroperationType:pension.members.enrol,pension.contributions.submitandpension.payment.approve. Each carriesstatus,memberCount,totalAmount, the provider’sproviderReferenceonce issued (the schedule reference, for contributions), anderrorTokenwitherrorMessageif it did not land. -
GET /provider-sync/{payrollConfigId}/{year}/{frequency}/{period}/issues?schemeId&operationTypereturns every reason the provider refused one submission, each with the record it relates to, the provider’s own code and its own wording. A submission record carries the first page and the total; use this for the rest. -
POST /provider-sync/{payrollConfigId}/{year}/{frequency}/{period}/refresh-membersre-reads named members from their employee records and writes their details back onto the pay run, so a refusal about a person, such as a National Insurance number added after the run was locked, can be corrected without re-running payroll. Amounts, periods and payment dates are never touched. Name up to 250 members by employee id, asissuesreports them. -
POST /provider-sync/{payrollConfigId}/{year}/{frequency}/{period}/approve-paymenttells the provider to collect the submitted schedule. It is accepted only once the contribution schedule hasstatusSucceededand aproviderReference; otherwise it returns 400 with a plain-English reason. It is never called on your behalf. -
GET /provider-sync/returns?type&pageSize&paginationTokenlists what providers sent back, newest first:pension.opt-outs.received,pension.stopped-contributions.received,pension.refunds.received,pension.schedules.receivedandpension.setup.received, each with the full detail asdocument. Keep followingcontent.metadata.paginationTokenwhile it is non-null. -
GET /provider-sync/reconciliation?schemeIdcompares the scheme with its provider: members enrolled in Flow Payroll, opt-outs and stopped contributions the provider reported and how many were applied or could not be matched, the refunds the provider reported and their total (the provider’s own figure), and when the last return arrived. -
GET /provider-sync/member-changes?status&pageSize&paginationTokenis the queue of what providers reported about members, paged the same way. Nothing here has been applied: reading from a provider never changes payroll on its own, so each waits forPOST /provider-sync/member-changes/{changeId}/apply,/dismiss,/acknowledgeor/delete.GET /provider-sync/member-changes/summarygives the count for a badge.
status values are the provider connection’s own: Pending (queued or waiting for a retry), AwaitingProvider (accepted by the provider, still processing), Succeeded, Failed, NeedsAttention (stopped for a reason a retry alone will not fix) and Cancelled, plus SubmitFailed when the submission could not be built at all, for example pension_missing_payment_source or pension_unsupported_frequency. What happens between those statuses, including the retry schedule, pausing after repeated failures and the two-day result deadline, is described once on the Integrations API overview.
A reported opt-out is applied automatically: the member’s membership ends on the date the provider gives, through the same flow as
DELETE /employee-pension/{employeeId}, with reason OptOut and no payroll refund, because the provider refunds the member directly. A reported stopped contribution ends the membership as MembershipCeased. Refund amounts the provider reports are recorded for you to reconcile, never re-applied to payroll.Employee pension information
GET /pension-information/{employeeId} returns a single employee’s full pension picture in one call: their pension composition, latest auto-enrolment determination, and any postponement.
Typical workflow
- Look up your provider (
GET /pension-provider/slug/{slug}) — set it up in the product first if it does not exist — then create a scheme withPOST /pension-schema. - Check
GET /pension-group/allowed-bases, then add one or more groups withPOST /pension-group, marking one as the default. - Set your organisation’s postponement and auto-enrolment defaults via the
/organisation/pension-defaultendpoints — see the pension defaults guide. - Enrol employees with
POST /employee-pension, sending enrolment letters where required. - Run your pay run, then read its AE results with
GET /auto-enrolment/assessments/payrun/...and follow up on anyone listed byGET /auto-enrolment/not-complete. - After the period, download the contribution CSV or request an export with
POST /pension-calculation/exportand retrieve the generated files for your provider. - If the scheme is linked to a provider connection, send the run instead with
POST /provider-sync/{payrollConfigId}/{year}/{frequency}/{period}/send, follow it withGET /provider-sync/{payrollConfigId}/{year}/{frequency}/{period}, and approve the payment withPOST /provider-sync/{payrollConfigId}/{year}/{frequency}/{period}/approve-paymentonce the schedule is accepted.
Notes and best practices
- Scheme endpoints use the path segment
pension-schema(singular), notpension-scheme— copy paths exactly as documented. - Assessments, CSVs, and exports are addressed by payroll configuration, tax year, frequency, and period, so keep those identifiers from your pay run to hand.
- Provider export files are described by records with a file path, provider slug, export group, and file type — generate them with the export
POST, then list them with either retrieval endpoint. - Postponement definitions are a value plus a frequency at both organisation and employee level; an employee-level definition set via
PUT /postponementtakes the place of the defaults for that employee. - Send
pensionTypewhen you create or update a group, and readpensionTypeandpensionGroupback —isSalarySacrifice,organisationIdon a group, and the splitautoEnrolmentPensionGroup/manualEnrolmentPensionGroupfields are deprecated and go on 7 December 2026. The four response fields stop being returned on that date, so update your parsing as well as your writes. See Deprecations. - Every response uses the shared envelope. Read
message.token,validationIssuesandcontent.metadata: the olderrorslist, the top-levelmetadata, andmessage.textId/message.textParametersare copies of them, deprecated and removed on 30 December 2026. On a paginated list, read the cursor fromcontent.metadata.paginationToken;content.data.paginationTokenis a deprecated copy. The rows stay incontent.data.items. See Deprecations. - Reading pension amounts off a payslip has one rule: take them from the payslip’s pension summary items or from
forPeriod.pension, never from both. See reading pension on a payslip. - Additional voluntary contributions and per-period overrides are keyed by tax year and period, so they are written against a specific pay run and only take effect while that run is open.
- A provider submission never starts on its own, payment approval is never implied by a send, and a change a provider reports never applies itself. Call
GET /provider-sync/{payrollConfigId}/{year}/{frequency}/{period}/sendablebefore offering a send, check theschemesoutcomes on the send itself rather than assuming a 2xx sent something, and expectNeedsAttentionsubmissions to need a person: theerrorTokensays what to fix before sending again. Where the refusal names members rather than the file,POST /provider-sync/{payrollConfigId}/{year}/{frequency}/{period}/refresh-membersre-reads them so the run does not have to be re-run. - The contribution due date is read from the provider’s own published schedules and matched on the earnings period, so sending late does not change it. A scheme can carry
contributionDueDateOffsetDaysfor a provider that has published no schedule yet; without either, a submission is refused withpension_unknown_contribution_due_daterather than sent with a guess. - The export files are still produced for a linked scheme on every pay run, so the file route remains available as a fallback.
- Removing an enrolment without an
optOutDateorreasonrecords an administrative removal. It still stops further automatic assessment until the next cyclical re-enrolment date, so pass the date or the reason when the employee actually opted out — the reason is what the record shows and what you evidence in an audit.
Authentication
Every request needs anX-Auth-Token header, and multi-organisation credentials also send X-Org-Id — see Authentication. Responses use the shared envelope described in Response format.