Our deprecation policy
Deprecating a field means we have stopped recommending it, not that we have stopped supporting it.- A deprecated field keeps working, with its existing behaviour, until its removal date. This applies wherever the field appears — a deprecated request field is still accepted and still has the same effect, and a deprecated response field is still populated with the same value. Deprecating a field never changes what it does.
- Most deprecations are input only. We stop asking you to send a field long before we stop returning it, and often we never stop returning it: a field deprecated as input is still populated on every response after its removal date. Only the fields marked Responses stop coming back.
- Every field gets at least 90 days between deprecation and removal. We set the removal date when we deprecate the field, and publish it here and in What’s new.
- We announce every deprecation in What’s new as it ships, so the changelog is the one place to watch.
- A field is only ever removed on the date published here. If we need to move a date, we move it later, never earlier.
How to read this
Each service has its own table. Every row is one deprecated field.
Where a field and its replacement can both be sent in the same request, the notes under each table say which one wins.
The Flow Payroll API has 200 deprecated fields, in the Billing, Court orders, Pay API, Pensions and Reports services. Every other service in this reference has none. The earliest removal is 7 November 2026.
Billing
Why, and what to send instead
errors— Each entry is a copy of onevalidationIssuesentry:fieldis the same,reasonTextisreason,reasonIdisreasonTokenandreasonParametersisreasonTokenArguments. Billing writeserrorson every response, asnullwhen there are none, until the removal date.metadata— A copy ofcontent.metadata: the date formats and, on a paginated list, the cursor.paginationTokenandhasMorePages— The same cursor, in the place every Flow Payroll service puts it. Pass it back as thepaginationTokenquery parameter; the rows stay where they are incontent.data.pageSize— Echoes thepageSizequery parameter, which is not changing. ApageSizeof 0 or less is refused with a 400.textId— Keeps the value billing sent before, which is not always the same astoken: client reads keep their old camel-case ids (clientsListedSuccessfully), and unexpected failures keepservice_error_occurred. Match ontoken, which names each case on its own.message.textis not changing.textParameters— Keeps the pairs billing sent before, such asPageSizeandPaginationTokenon a client list andRequestIdon a failure. The request id is also on theX-Request-Idresponse header.nextPageToken— The same cursor under the name every other list uses. Pass it back as thepageTokenquery parameter, which is not changing.
Endpoints that carry these fields
Endpoints that carry these fields
Returned on responses from:
DELETE /clients/{clientId}DELETE /clients/{clientId}/credit-notes/{creditNoteId}DELETE /clients/{clientId}/credit-notes/{creditNoteId}/allocations/{allocationId}DELETE /clients/{clientId}/credit-notes/{creditNoteId}/attachments/{attachmentId}DELETE /finance/accounts/{code}DELETE /finance/tax-rates/{taxType}DELETE /invoices/{clientId}/{invoiceId}/attachments/{attachmentId}GET /clientsGET /clients/provider-contactsGET /clients/{clientId}GET /clients/{clientId}/credit-notesGET /clients/{clientId}/credit-notes/{creditNoteId}GET /clients/{clientId}/credit-notes/{creditNoteId}/external-pdfGET /clients/{clientId}/credit-notes/{creditNoteId}/line-itemsGET /clients/{clientId}/finance-settingsGET /clients/{clientId}/invoicesGET /clients/{clientId}/invoices/{invoiceId}/external-pdfGET /credit-notesGET /finance/accountsGET /finance/accounts/provider-accountsGET /finance/attachment-limitsGET /finance/credit-note-templateGET /finance/finance-defaultsGET /finance/invoice-reference-fieldsGET /finance/invoice-templateGET /finance/line-item-description-fieldsGET /finance/pay-element-mappingsGET /finance/tax-ratesGET /finance/tax-rates/provider-ratesGET /invoicesGET /invoices/{clientId}/{invoiceId}GET /invoices/{clientId}/{invoiceId}/line-itemsPATCH /clients/{clientId}/unarchivePATCH /invoices/{clientId}/{invoiceId}POST /clientsPOST /clients/import-from-providerPOST /clients/{clientId}/credit-notes/{creditNoteId}/allocationsPOST /clients/{clientId}/credit-notes/{creditNoteId}/attachmentsPOST /clients/{clientId}/credit-notes/{creditNoteId}/authorisePOST /clients/{clientId}/credit-notes/{creditNoteId}/syncPOST /clients/{clientId}/credit-notes/{creditNoteId}/voidPOST /clients/{clientId}/invoices/{invoiceId}/syncPOST /clients/{clientId}/syncPOST /credit-notesPOST /finance/accountsPOST /finance/accounts/import-from-providerPOST /finance/accounts/{code}/archivePOST /finance/accounts/{code}/unarchivePOST /finance/tax-ratesPOST /finance/tax-rates/import-from-providerPOST /finance/tax-rates/{taxType}/archivePOST /finance/tax-rates/{taxType}/unarchivePOST /invoices/bulk-actionPOST /invoices/{clientId}/{invoiceId}/attachmentsPOST /invoices/{clientId}/{invoiceId}/emailPOST /invoices/{clientId}/{invoiceId}/full-creditPOST /invoices/{clientId}/{invoiceId}/refresh-line-item-mappingsPOST /invoices/{clientId}/{invoiceId}/voidPUT /clients/{clientId}PUT /clients/{clientId}/credit-notes/{creditNoteId}PUT /clients/{clientId}/finance-settingsPUT /finance/accounts/{code}PUT /finance/credit-note-templatePUT /finance/finance-defaultsPUT /finance/invoice-templatePUT /finance/pay-element-mappingsPUT /finance/tax-rates/{taxType}PUT /invoices/{clientId}/{invoiceId}
Court orders
Why, and what to send instead
percentageRate— An alias ofpercentageon a deduction from earnings order (DEO), onPUT /orders/{orderId}and on the body ofPOST /ordersfor aCmsDeo. It is read only whenpercentageis not sent; when both are sent,percentagewins. The two are always stored equal, anddetails.percentageRateon a DEO response keeps being returned, matchingpercentage, after the removal date.
Endpoints that carry these fields
Endpoints that carry these fields
Accepted on requests to:
PUT /orders/{orderId}
Pay API
Why, and what to send instead
grossForEmployeeNi,grossForEmployerNi,niandemployerNi— Sent on an employee’sopeningBalanceDirector.{year}. SendniBandResultkeyed by NI category letter instead: the scalar totals cannot express a director who paid NI under more than one letter in a year. WhenniBandResultis present these totals are ignored; when it is absent they are attributed to the employee’s latest effective NI letter for the year, which is lossy. They keep being returned on responses, as the sum across letters, after the removal date.pension— Sent on an employee’sopeningBalance.{year}. SendpensionContributionsByTypeat migration time so FPS year-to-date reporting splitsNetPayArrangementfromReliefAtSourcecorrectly. When that breakdown is present this field is ignored on input; it keeps being returned on responses, as the sum of the breakdown, after the removal date.salary— Deprecated as input onPUT /employees/{employeeId}andPATCH /employees/{employeeId}only.POST /employeesstill accepts it unchanged, as the starting salary, which becomes the first salary version effective from the employee’s start date. Record every later change withPUT /employees/{employeeId}/salary, which keeps an effective-dated history the pay run pro-rates from. While it is still accepted onPUTandPATCH, a value that differs from the salary in force today updates the most recent salary version in place rather than adding a new one, so it cannot express a change from a date;nullor absent leaves the history untouched. It keeps being returned on every employee response, as the annual salary in force today, after the removal date. Read the full history fromsalaryHistoryon the employee, or fromGET /employees/{employeeId}/salary.
Endpoints that carry these fields
Endpoints that carry these fields
Accepted on requests to:
PATCH /employees/{employeeId}POST /employeesPUT /employees/{employeeId}PUT /employees/{employeeId}/opening-balance
DELETE /employees/{employeeId}DELETE /employees/{employeeId}/leaverDELETE /employees/{employeeId}/ni-categories/{year}/{effectiveDate}DELETE /employees/{employeeId}/tax-codes/{year}/{effectiveDate}GET /employees/by-ni/{nationalInsuranceNumber}GET /employees/by-payroll-id/{payrollId}GET /employees/{employeeId}PATCH /employees/{employeeId}POST /employeesPOST /employees/{employeeId}/ni-categoryPOST /employees/{employeeId}/postgraduate-loan/endPOST /employees/{employeeId}/postgraduate-loan/startPOST /employees/{employeeId}/student-loan/endPOST /employees/{employeeId}/student-loan/startPOST /employees/{employeeId}/tax-codePUT /employees/{employeeId}PUT /employees/{employeeId}/leaverPUT /employees/{employeeId}/ninoPUT /employees/{employeeId}/opening-balancePUT /employees/{employeeId}/payroll-configPUT /employees/{employeeId}/payroll-idPUT /employees/{employeeId}/starter
Pensions
Why, and what to send instead
paginationToken— The same cursor, in the place every Flow Payroll service puts it. Pass either one back as thepaginationTokenquery parameter until the removal date, then only the new one exists. The rows stay where they are, incontent.data.items.errors— Each entry is a copy of onevalidationIssuesentry:fieldis the same,reasonTextisreason,reasonIdisreasonTokenandreasonParametersisreasonTokenArguments.metadata— A copy ofcontent.metadata: the date formats and, on a paginated list, the cursor.autoEnrolmentPensionGroupandmanualEnrolmentPensionGroup— These two fields expose how the association is stored rather than the employee’s pension. ReadpensionGroupfor the group in force andisAutoEnrolledfor how the employee came to be enrolled. Unlike the Pay API deprecations, these stop being returned at the removal date.textId— A copy ofmessage.token.message.textis not changing.textParameters— A copy ofmessage.tokenArguments.organisationId— A pension group always belongs to the organisation in your access token, so this field tells you nothing the request did not already state. Read theorganisationIdof the enclosing resource where one is present.isSalarySacrificeonPensionGroupV2— Derived on responses: true only whenpensionTypeisSalarySacrifice.pensionTypealso distinguishesReliefAtSourcefromNetPayArrangement, which this flag cannot.isSalarySacrificeonSubmitPensionGroupRequest— Read only whenpensionTypeis omitted, and it cannot expressNetPayArrangement. SendpensionTypeon every create and update.
Endpoints that carry these fields
Endpoints that carry these fields
Accepted on requests to:
POST /pension-groupPUT /pension-group/{id}
DELETE /auto-enrolment-exemption/{employeeId}DELETE /employee-pension/{employeeId}DELETE /employee-pension/{employeeId}/additional-voluntary-contributions/{taxYear}/{period}DELETE /employee-pension/{employeeId}/contribution-overrides/{taxYear}/{period}DELETE /pension-group/{id}DELETE /pension-schema/{id}DELETE /postponement/{employeeId}GET /auto-enrolment-exemption/{employeeId}GET /auto-enrolment/assessments/payrun/{payrollConfigId}/{year}/{frequency}/{period}GET /auto-enrolment/complete/payrun/{payrollConfigId}/{year}/{frequency}/{period}GET /auto-enrolment/enrolment-information/outstandingGET /auto-enrolment/enrolment-information/sentGET /auto-enrolment/not-auto-enrolmentsGET /auto-enrolment/not-completeGET /employee-pension/batchGET /employee-pension/letters/jobs/{jobId}GET /employee-pension/{employeeId}GET /employee-pension/{employeeId}/additional-voluntary-contributionsGET /employee-pension/{employeeId}/additional-voluntary-contributions/{taxYear}/{period}GET /employee-pension/{employeeId}/contribution-overridesGET /employee-pension/{employeeId}/contribution-overrides/{taxYear}/{period}GET /employee-pension/{employeeId}/membership-endsGET /employee-pension/{employeeId}/opt-out-noticesGET /employee-pension/{employeeId}/refund-previewGET /export-file-retrieval/pension-calculation/year/{year}/period/{period}/frequency/{frequency}/payroll-config/{payrollConfigId}GET /organisationGET /organisation/pension-membership-endsGET /pension-calculation/by-year/{year}/by-month/{month}GET /pension-calculation/csv/year/{year}/period/{period}/frequency/{frequency}/payroll-config/{payrollConfigId}GET /pension-calculation/export/year/{year}/period/{period}/frequency/{frequency}/payroll-config/{payrollConfigId}GET /pension-calculation/pension-provider/{pensionProviderId}/by-year/{year}/by-month/{month}GET /pension-group/allowed-basesGET /pension-group/{id}GET /pension-information/{employeeId}GET /pension-providerGET /pension-provider/slug/{slug}GET /pension-provider/{id}GET /pension-schema/organisationGET /pension-schema/{id}GET /postponement/{employeeId}GET /provider-sync/member-changesGET /provider-sync/member-changes/summaryGET /provider-sync/reconciliationGET /provider-sync/returnsGET /provider-sync/{payrollConfigId}/{year}/{frequency}/{period}GET /provider-sync/{payrollConfigId}/{year}/{frequency}/{period}/issuesGET /provider-sync/{payrollConfigId}/{year}/{frequency}/{period}/sendablePOST /auto-enrolment/assessments/previewPOST /employee-pensionPOST /employee-pension/letters/{letterType}POST /employee-pension/{employeeId}/enrolment-letterPOST /employee-pension/{employeeId}/opt-out-noticesPOST /pension-calculationPOST /pension-calculation/exportPOST /pension-groupPOST /pension-schemaPOST /provider-sync/member-changes/{changeId}/acknowledgePOST /provider-sync/member-changes/{changeId}/applyPOST /provider-sync/member-changes/{changeId}/deletePOST /provider-sync/member-changes/{changeId}/dismissPOST /provider-sync/{payrollConfigId}/{year}/{frequency}/{period}/approve-paymentPOST /provider-sync/{payrollConfigId}/{year}/{frequency}/{period}/refresh-membersPOST /provider-sync/{payrollConfigId}/{year}/{frequency}/{period}/sendPUT /auto-enrolment-exemptionPUT /employee-pension/{employeeId}/additional-voluntary-contributions/{taxYear}/{period}PUT /employee-pension/{employeeId}/contribution-overrides/{taxYear}/{period}PUT /organisation/pension-default/auto-enrolment-settingsPUT /organisation/pension-default/postponement-settingsPUT /pension-group/{id}PUT /pension-schema/{id}PUT /postponement
Reports
Why, and what to send instead
webhookUrl— Sent indeliverywhen creating a schedule. Subscribe a Webhooks API endpoint toreport_export.completed(andreport_export.failed) instead: every export run sends one, scheduled or queued throughPOST /exports, carrying therunIdandscheduleIdto route on, and the webhooks service signs, retries and logs it like every other event. The event links toGET /exports/{runId}rather than carrying a download link. Until the removal date a schedule created with awebhookUrlstill receives the legacyX-Flow-SignaturePOST exactly as before; after it the field is ignored on input and no longer returned on schedules.webhookSecret— Only ever returned once, on creating a schedule with awebhookUrl, to verify the legacyX-Flow-Signatureheader. An endpoint created through the Webhooks API carries its own signing secrets, with rotation. Stops being returned whenwebhookUrlis removed.
Endpoints that carry these fields
Endpoints that carry these fields
Accepted on requests to:
POST /report-definitions/{definitionId}/schedules
GET /report-definitions/{definitionId}/schedulesPOST /report-definitions/{definitionId}/schedules
Deprecated request shapes
The tables above list fields. One deprecation is a whole request body shape rather than a field, so it is tracked here by hand.
Until the removal date the bare object is read as
finalSubmissionDetails with no late reason and behaves exactly as it did before. Nothing in the response changes.
Migrating away from a deprecated field
1
Work back from the earliest removal date
Take the earliest date in the tables above and check what your integration does with those fields. Anything you send is a code change; anything you read is a parsing change.
2
Switch what you send
For the fields marked Input only or Input and responses, send the replacement in the Use instead column instead. Then stop sending the deprecated field alongside it — sending both is supported until removal, but the notes above define which value the API uses, and dropping the old field removes the ambiguity.
3
Leave your response parsing alone unless the field says Responses
A field marked still returned keeps coming back after its removal date, so there is nothing to change in what you read. Only the fields marked Responses or Input and responses stop being populated — for those, read the replacement and make sure your parsing tolerates the old field being missing rather than failing on it.
4
Subscribe to the changelog
Every deprecation and every removal date is announced in What’s new. Subscribe to its RSS feed so a new date reaches you without checking this page.
Need more time than a removal date allows? Email us before the date — we would rather move it than break your integration.