Business Requirements Document
The epics, features, user stories and acceptance criteria, each criterion with its own ID.
Revision 50, 16 October 2026. This is a read-only copy of the document kept in Claude Docs, with the same IDs and wording; a change is made there first, then copied here (scripts/criteria_from_brd.py makes the criteria file from it).
The service estimates a UK Self Assessment tax bill and what to pay by 31 January and 31 July, and offers it four ways: on its own web pages, through an API for apps with their own screens, as pages an app opens for its customers, and with figures read from HMRC instead of asked. An administrator decides, for the whole service and for each organisation, which calculations, tax years, ways of use and data sources are on.
1. Scope
The service works out an estimate of a person's Self Assessment tax bill for the tax years 2023 to 2024 up to 2027 to 2028, and what to pay by 31 January and 31 July. It never files a return or sends anything to HMRC.
In scope
- Income Tax for England, Wales, Northern Ireland and Scotland, Class 2 and Class 4 National Insurance, Capital Gains Tax, student loan repayments, the High Income Child Benefit Charge and pension charges, split into 29 parts an administrator can switch on or off.
- Payments on account, paying through a tax code, late filing and late payment penalties and interest.
- Four ways in: the service's own web pages, an API, question pages opened by another app, and figures read from HMRC and DVLA with the customer's permission.
- Optional capabilities: payment plan amounts, the tax so far this year, 8 Making Tax Digital for Income Tax helpers, document reading, customer accounts and 12 separate tax tools (Stamp Duty, VAT, Corporation Tax, payroll, Inheritance Tax and others).
- A journey planner for apps: fixed rules set up a customer's journey from what the app knows (Epic 5).
- Settings for the whole service and separately for each organisation that uses it.
- No customer data is sent to an AI model, anywhere in the service (decided on 16 October 2026).
Out of scope
- Filing a tax return, sending a Making Tax Digital update, or setting up a Budget Payment Plan with HMRC. HMRC offers no public API for the last, so the service gives the amount only.
- Advice on what a person should do; the service shows an estimate and the working.
- Welsh language pages (planned, not built).
Where the rules come from. Every rate, threshold and rule comes from GOV.UK, HMRC's manuals or legislation.gov.uk, and each is stored with its source. Figures not yet announced for 2027 to 2028, such as Scottish rates, use the previous year's and the result says so. A rule that could not be checked against one of those sources is left out rather than guessed.
How to read the requirements. Each epic lists features with an ID (for example ADM-03), a user story, and acceptance criteria written as Given / When / Then, each with its own ID (for example ADM-03.2) that the tests proving it name (NFR-14). "Way of use" means one of: full question pages (web), question pages inside another app (embedded), or API calculations (API).
2. Actors
Six kinds of people and systems use the service; the service administrator sets the rules everyone else works within.
| Actor | Who they are | What they do | How they sign in |
|---|---|---|---|
| Customer | A taxpayer: employee, sole trader, landlord, pensioner, investor | Answers questions, sees their estimate, works out a payment plan or tax so far | None for the estimate; GOV.UK One Login or email and password for an account; HMRC sign-in to let records be read |
| Service administrator | The team that runs the service | Sets the service's own settings, switches features on and off, creates organisations and their API keys, reads the audit log | Admin sign-in (hashed password) or admin API token |
| Organisation | A firm that uses the service for its customers: an accountant, a payroll provider, a software company, HMRC's Budget Payment Plan service | Has its own settings, address (/o/name), API keys and clients | API key |
| Organisation administrator | A person the service administrator names for one organisation | Changes that organisation's settings, creates and revokes its API keys | GOV.UK One Login or email and password |
| Calling app | An organisation's software | Calls the API, or opens the question pages for its customer and collects the result | The organisation's API key |
| Data source | HMRC APIs (employments, state benefits, CIS, Self Assessment account), HMRC internal services (Child Benefit, bank interest, student loan plan, Marriage Allowance), DVLA | Supplies figures in place of questions, with the customer's permission | OAuth token for the customer's own HMRC record; service credentials for DVLA |
3. At a glance
The four ways in (the calculator's own pages, an app with its own screens through the API, an app that sends its customers to the calculator's pages, and figures read from HMRC instead of asked) all use one calculation, under one set of settings.
Each way of use is opened or closed on its own; the parts, tax years and Scottish Income Tax apply to all of them, and records are read only where the settings allow it and the customer agrees.
4. Epic 1: Admin configuration
An administrator controls what the calculator does through one settings form, asked in this order: parts, tax years, ways of use, reading records, each part's answers, features, documents, the app's answers, the result, return addresses and notifications. The service has one set of settings (/admin/calculator); each organisation has its own (/admin/saas/organisations/{id}, or /account/organisations/{name} for its own administrators). Every setting is also in the admin API.
Rule for every setting: anything switched off cannot be reached and changes nothing else. An automated test checks every part, tax year, feature, capability and organisation feature, not a sample.
ADM-01 Parts of the calculator
As an administrator, I want to choose which of the 29 parts are used, so that customers are only asked what my service covers.
The parts: pay from employment; benefits and company cars; job expenses; leaving a job; self-employment; partnerships; UK property; pensions received; taxable state benefits; savings interest; dividends; foreign income; Capital Gains Tax; life insurance gains; trust income; other taxable income; pension contributions; Gift Aid; investment schemes and other reliefs; Blind Person's Allowance; Marriage Allowance; Married Couple's Allowance; High Income Child Benefit Charge; student loans; Scottish Income Tax; residence; payments on account; collecting through a tax code; late filing and payment penalties.
Ready-made sets: Full Self Assessment (all 29), Basic Income Tax (pay and pensions), Employees, Self-employed, Landlords, and Savers and investors. Choosing a set ticks its parts; the administrator can then change any tick.
- ADM-01.1 Given a part is off, when a customer goes to one of its question pages by address, then the page does not open, even part way through a journey.
- ADM-01.2 Given a part is off, when an API request sends one of its fields, then the request is refused with 422 and code
MODULE_NOT_ENABLED, naming the field. - ADM-01.3 Given a customer answered a part's questions before it was switched off, when the bill is worked out, then those answers are ignored.
- ADM-01.4 Given no part is ticked, when the administrator saves, then the error "Select at least one part of the calculator" is shown and nothing is saved.
- ADM-01.5 Given Marriage Allowance or Married Couple's Allowance is on, when the shared question is asked, then it offers only the choices of the parts that are on.
- ADM-01.6 Given a request or saved setting uses an older, broader name (
employment_extras,payments,allowances), when it is read, then it is treated as every finer part it became. - ADM-01.7
GET /api/v1/moduleslists every part, whether it is on, and its fields.
ADM-02 Scottish Income Tax
As an administrator, I want Scottish Income Tax as a switch on any set, so that I can offer a UK-wide service or one for England, Wales and Northern Ireland only.
- ADM-02.1 Given the switch is on, when a customer starts, then they are asked whether they pay Scottish Income Tax (an app sends
jurisdiction), and Scottish taxpayers get Scottish rates and bands. - ADM-02.2 Given the switch is off, when a bill is worked out, then everyone is taxed at the rates for England, Wales and Northern Ireland.
- ADM-02.3 Given the switch is off, when an API request sends
"jurisdiction": "scotland", then it is refused rather than give a Scottish taxpayer the wrong figure.
ADM-03 Tax years offered
As an administrator, I want to choose which tax years can be used, so that I can offer only the years my customers need.
- ADM-03.1 Given a year is off, when the tax year question is shown, then that year is not offered, and posting it is refused.
- ADM-03.2 Given a year is off, when the API, an app's journey, the tax so far or a Making Tax Digital forecast asks for it, then it is refused with code
TAX_YEAR_NOT_OFFERED, listing the years offered. - ADM-03.3 Given the latest year that has ended is off, when an API calculation is sent without
tax_year, then it is refused withTAX_YEAR_NOT_OFFEREDrather than worked out for that year; an app's journey without a year still starts, and the customer chooses one of the years offered. - ADM-03.4 Given a new tax year is added to the rates, when nobody has switched it off, then it is offered.
- ADM-03.5 Given only some years are on, when the start page is shown, then it names those years (a range when they follow on, otherwise each year), and names the year most people need only if it is offered.
- ADM-03.6 Given a year is off, then take-home pay does not offer it, and its API refuses it.
- ADM-03.7 Given no year is ticked, when the administrator saves, then "Select at least one tax year" is shown.
ADM-04 Ways of use
As an administrator, I want to open or close each way of use, so that I control how customers and apps reach the calculator.
| Way of use | Service default | Organisation default |
|---|---|---|
| Full question pages (web) | On | Off |
| Question pages inside another app (embedded) | Not available | Off |
| API calculations (API) | On | On |
- ADM-04.1 Given a way is closed, when a customer opens its pages, then they see "This service is not available" (404); when an app calls its API, then it gets 403.
- ADM-04.2 Given every way is unticked, when the administrator saves, then "Select at least one way to use the calculator" is shown.
- ADM-04.3 Given the service's own settings, when they are saved, then the embedded way stays off: only organisations can open pages inside an app.
ADM-05 Reading answers from records, per way of use
As an administrator, I want to choose in which ways of use answers may be read from HMRC and DVLA, so that, for example, the web pages read records but the API does not.
- ADM-05.1 Given reading is off for a way, when a customer or app uses that way, then nothing is read and every question is asked or must be sent.
- ADM-05.2 Given reading is on for the API and no data source is switched on, when an app calls
calculate/with-hmrc-data, then it gets 409 "No data sources are switched on. Send all figures in the request instead."
ADM-06 Data sources
As a service administrator, I want to switch each data source on or off, so that only sources with an agreement and a legal basis are used.
Sources: HMRC employments (pay, tax, benefits, student loan deductions), HMRC state benefits (State Pension, taxable benefits), HMRC CIS deductions, HMRC Self Assessment account (payments on account made), DVLA vehicle enquiry (company car CO2 and fuel), and HMRC internal services for Child Benefit, bank interest, student loan plan and Marriage Allowance.
- ADM-06.1 Given a source is off, when a part it fills is set to read records, then that part's questions are asked instead.
- ADM-06.2 Given a source's connection is not configured, when the data sources page is shown, then it says the source is unavailable.
- ADM-06.3
GET /api/v1/data-sourcesshows which sources are on.
ADM-07 Each part's answers: ask, check or read
As an administrator, I want to choose for each part that a source can fill whether answers are asked, read and checked, or read without asking.
Parts a source can fill: pay, benefits and company cars, self-employment (CIS), pensions, taxable state benefits, savings interest, Marriage Allowance, High Income Child Benefit Charge, student loans and payments on account.
- ADM-07.1 Given
ask, then nothing is read for that part and the customer enters it. - ADM-07.2 Given
check, when records are read, then the answers are filled in on the question pages for the customer to check and change. - ADM-07.3 Given
auto, when a question is answered completely from records, then it is skipped but still listed on Check your answers; anything that could not be read is asked. - ADM-07.4
GET /api/v1/modulesshows each part's choice asread_automatically.
ADM-08 What else can be worked out (capabilities)
As an administrator, I want to switch each extra capability on for each way of use, so that, for example, payment plans are on the pages but not the API.
Capabilities: payment plan amounts; tax so far this year; 8 Making Tax Digital helpers; and each tax tool (need a tax return, PAYE check, CGT on a UK home, salary or dividends, SDLT, LBTT, LTT, Corporation Tax, VAT registration, VAT return, VAT Flat Rate Scheme, cancelling VAT registration, payroll, Inheritance Tax, partnership profits, trust Income Tax, Child Benefit, Universal Credit).
- ADM-08.1 Given a capability is on for a way, when that way is closed or the capability's feature is off, then the capability is not offered.
- ADM-08.2 Given a capability is off for a way, when a customer or app uses it that way, then its pages return 404 and its API returns 404 or 403, and its links are not shown.
- ADM-08.3 Given the service's settings, when the service starts for the first time, then payment plans are on for the web and the API.
ADM-09 Features for the whole service
As a service administrator, I want to switch whole features on and off without a restart (/admin/features).
- ADM-09.1 Given a feature is off, then all its pages and API routes return 404 and its links disappear.
- ADM-09.2 Given a feature is switched back on, then its finer settings are as they were.
- ADM-09.3 Given a feature was never installed (
FEATURES), then the calculator gives exactly the same answers as with it.
ADM-10 Features for one organisation
As an administrator, I want to switch a feature off for one organisation, so that it is closed for that organisation's customers, apps and API key only.
Features that can be switched off per organisation: payment plans, tax so far, Making Tax Digital helpers, document reading, each of the 12 tax tools, and the journey planner. Customer accounts and the demonstration are service-wide.
- ADM-10.1 Given a feature is off for an organisation, when its customers, its app or its key reach the feature, then it is closed, while other organisations are unaffected.
ADM-11 Documents customers can upload
As an administrator, I want to choose which parts customers can fill in by uploading a document.
Documents: a P60 (pay), a dividend statement, a letting agent's statement (property), a pension contributions statement, and a bank or building society interest statement.
- ADM-11.1 Given a part is on for documents, when a customer uploads the document, then the answers it holds are filled in for them to check, and the document is not kept. It is read on the service itself and never sent to an AI model.
- ADM-11.2 Given no reader is configured, then the upload option is not offered.
ADM-12 Answers an app sends (organisations)
As an organisation administrator, I want to choose whether customers check answers my app sends: skip the questions those answers fill completely, or check (show them filled in).
- ADM-12.1 Given
skip, when a journey starts with answers, then fully answered questions are skipped. - ADM-12.2 Given
check, or the journey setsconfirm_prefill: true, then those questions are shown filled in.
ADM-13 What the result shows
As an administrator, I want to choose between the bill with all the working (full) and the bill and what to pay without the working (summary).
- ADM-13.1 Given
summary, when a result page is shown (the calculator, tax so far or a Making Tax Digital helper), then the working is left out and the totals and payment dates are kept. API responses always carry the whole calculation.
ADM-14 Apps customers can be sent back to (organisations)
As an organisation administrator, I want to list the web addresses of my apps, so that customers are only sent back to them.
- ADM-14.1 Given a journey's
continue_urlorback_urlis not on a listed host, when the app starts the journey, then it is refused with 422 and code INVALID_RETURN_URL. - ADM-14.2 Given a return address does not start with https://, when a journey is started, then it is refused (http only for this computer, when the service runs locally or in its tests). App hosts are saved as host names.
ADM-15 Telling the app when a customer finishes (organisations)
As an organisation administrator, I want to give a web address to notify, so that my app does not have to keep asking whether a result is ready.
- ADM-15.1 Given a notify address, when a customer finishes, then a signed notification (
<kind>.completed, Standard Webhooks signature) is sent with only the journey and result IDs, no figures or personal details. - ADM-15.2 Given the app does not answer with 2xx within 10 seconds, then it is tried again after 1 minute, 10 minutes, 1 hour and 6 hours, then marked failed.
- ADM-15.3 Given the address is not https, not on a listed app host, or on a private network, then it is refused. The signing secret is shown once.
ADM-16 Organisations and their API keys
As a service administrator, I want to create organisations, name their administrators, create and revoke API keys, and suspend an organisation.
- ADM-16.1 Given a key is created, then it is shown once and stored only as a SHA-256 hash.
- ADM-16.2 Given a key is revoked or the organisation suspended, when it is used, then every request is refused.
- ADM-16.3 Given an organisation exceeds its requests a minute, then it gets 429 with
Retry-After; given a monthly quota, then requests past it get 429 "This organisation has used its requests for this month", with Retry-After set to the start of the next month. - ADM-16.4 Given any organisation's key, then it can only see that organisation's clients and results.
ADM-17 Organisation administrators
As an organisation administrator, I want to sign in and manage my organisation's settings and keys myself (/account/organisations).
- ADM-17.1 Given I administer an organisation, then I can change only that organisation's settings and keys.
- ADM-17.2 Given an app with the organisation's key, when it reads
GET /api/v1/saas/settings, then it sees the settings and cannot change them.
ADM-18 Audit log
As a service administrator, I want every change recorded.
- ADM-18.1 Given any settings, feature, data source or organisation change, then the audit log records who, when, from which address, and the settings before and after (
GET /api/v1/admin/audit).
ADM-19 Rates
As a service administrator, I want to see the effect of a rate change before it is published, and to reload rates.
- ADM-19.1 Given a what-if rate, when I run
POST /api/v1/admin/rates/what-if, then a customer's bill is worked out on a copy of the rates and shown beside the bill at today's rates; the live rates are never changed. - ADM-19.2 Given the rates file has been updated, when rates are reloaded, then new calculations use them, and the load fails if any figure has lost its source.
5. Epic 2: Standalone calculator
A customer uses the calculator on its own web pages, one question per page, and sees their bill with the working. The service's own pages start at /; each organisation's customers start at /o/{organisation}, under that organisation's settings. This way of use is open when "Full question pages" is ticked (ADM-04).
Admin switches that shape this epic: the parts (ADM-01), Scottish Income Tax (ADM-02), tax years (ADM-03), reading records on the web (ADM-05, ADM-07), documents (ADM-11), the result (ADM-13) and the capabilities offered on the pages (ADM-08).
CAL-01 Start page
As a customer, I want to know what the service does and what I need, before I start.
- CAL-01.1 Given the service's settings, when the start page is shown, then it lists what is estimated, names the tax years offered ("the tax years from 6 April 2023 to 5 April 2028", or each year when there are gaps), the latest completed tax year most people need (only when it is offered), and that it takes 10 to 20 minutes.
- CAL-01.2 It warns that this is an estimate and does not file a return or tell HMRC anything, and links to the take-home pay calculator.
- CAL-01.3 Given an organisation's address, when its start page is shown, then it carries the organisation's name, the parts it uses (when not all) and the years it offers.
CAL-02 Tax year
As a customer, I want to choose the tax year my return is for.
- CAL-02.1 Given the years offered, when the question is shown, then only those years are listed.
- CAL-02.2 Given a year whose figures are not all announced (for example Scottish rates for 2027 to 2028), when the bill is shown, then it uses the year before's figures and says which.
CAL-03 Where the customer pays tax
As a customer, I want to say whether I pay Scottish Income Tax or live abroad, so that the right rates apply.
- CAL-03.1 Given Scottish Income Tax is on, then the customer is asked whether they pay it; given it is off, the question is not asked.
- CAL-03.2 Given the residence part is on, then non-residents and split years are asked about; given it is off, everyone is treated as UK resident.
CAL-04 Types of income
As a customer, I want to tick the kinds of income I had, so that I am only asked about those.
- CAL-04.1 Given a part is off, then its kind of income is not offered.
- CAL-04.2 Given "None of these" is ticked with another box, then the page is refused (400) with an error asking the customer to select 'None of these' only; this works without JavaScript.
CAL-05 Question pages
As a customer, I want one clear question per page in the order that applies to me.
There are 59 question pages in all; a customer sees only those for the income they ticked and the parts that are on.
- CAL-05.1 Given an earlier required page is unanswered, when a later page is opened by its address, then the customer is redirected (303) to the first page still to answer.
- CAL-05.2 Given no session, when a question page is opened, then the customer is sent to
/calculate/start. - CAL-05.3 Given an answer is missing or invalid, when the page is submitted, then the page title starts with "Error: ", an error summary links to each field, and the message is shown at the field, as the GOV.UK Design System sets out.
- CAL-05.4 Every page has a back link, works without JavaScript and carries a CSRF token.
CAL-06 Filling answers from HMRC records
As a customer, I want to let the service read my figures from HMRC, so that I do not type them.
- CAL-06.1 Given reading is on for the web and a part is set to
checkorauto, when the customer chooses to use their records, then they sign in and give permission first, and only figures about them are read. - CAL-06.2 Given
check, then the figures are filled in on the question pages for the customer to check and change. - CAL-06.3 Given
auto, then questions answered completely are skipped and still listed on Check your answers; figures that could not be read are asked. - CAL-06.4 Given a source is unavailable, then its questions are asked and nothing fails.
CAL-07 Filling answers from a document
As a customer, I want to upload a P60 or statement to fill a page in.
- CAL-07.1 Given documents are on for the part and a reader is configured, when the customer is on that part's question page, then they can upload a PDF of the document. The answers are filled in for them to check, and the document is not kept.
- CAL-07.2 Given the figures for that part came from HMRC records, then no upload is offered.
- CAL-07.3 Given a file that is not a PDF, then the error is "The selected file must be a PDF".
- CAL-07.4 The link only appears on the question page itself, in the order of the questions.
CAL-08 Check your answers
As a customer, I want to see all my answers before the bill is worked out, and change any of them.
- CAL-08.1 Given the customer reaches the end, then every answer is listed with a change link, including answers read from records.
- CAL-08.2 Given an answer is changed, then the customer returns to Check your answers, with any new questions that answer brings asked first.
CAL-09 The result
As a customer, I want to see what I owe or am owed, when to pay, and how it was worked out.
- CAL-09.1 The result shows the estimated bill for the tax year, or the repayment due, or that there is nothing to pay.
- CAL-09.2 "What to pay and when": total tax, tax already paid or deducted, the balance, any amount collected through the tax code, the first and second payments on account for next year with their dates, and the total to pay by 31 January.
- CAL-09.3 Given the result shows
full, then "How your tax is worked out" lists income, reliefs, allowances, adjusted net income, Income Tax by band, reductions, charges (High Income Child Benefit Charge, pension savings, Gift Aid, remittance basis), Class 2 and Class 4 National Insurance, Capital Gains Tax and student loan repayments; givensummary, it is left out. - CAL-09.4 The result also says whether Making Tax Digital applies, and shows things to know, ways the customer may be able to pay less (Marriage Allowance, reducing payments on account, student loans) and what happens next.
- CAL-09.5 Given the Making Tax Digital helpers are on for the web, then the result adds a panel on whether Making Tax Digital applies, with a link to the helper. The result also links to change the answers, start a new estimate, sign in to file a return, and the rates used.
CAL-10 Time out and privacy of answers
As a customer, I want my answers to stay private and not be lost without warning.
- CAL-10.1 Given 13 minutes without use, then the HMRC timeout warning offers to stay signed in; at 15 minutes the session ends and its answers can no longer be used (CAL-10.2).
- CAL-10.2 Answers are kept only in the customer's session: in memory, or in the service's database when it has one. They can no longer be used 15 minutes after the customer last used the service, are deleted by the clean-up that runs every 5 minutes, and are never sent to HMRC.
CAL-11 Take-home pay
As a customer, I want a quick figure for take-home pay from a salary, pension or business profit (/take-home-pay).
- CAL-11.1 Given a salary for an offered year, then the result shows Income Tax, National Insurance, any student loan and what is left, weekly, monthly and yearly. Only the tax years offered are listed. The same is at
GETorPOST /api/v1/calculate/quick.
CAL-12 Rates and help pages
- CAL-12.1
/tax-ratesshows every rate and allowance for a year, each with its source. - CAL-12.2 Every page links to cookies, the accessibility statement, privacy, terms, and a form to report a problem.
6. Epic 3a: App with its own screens calls the API to calculate
The app asks its own questions and sends the answers; the service only calculates and returns the result as JSON. An organisation's app calls /api/v1/saas/... with its API key, under its organisation's settings; the public API at /api/v1/... uses the service's settings. This way of use is open when "API calculations" is ticked (ADM-04).
Admin switches that shape this epic: which parts the API accepts (ADM-01), Scottish Income Tax (ADM-02), tax years (ADM-03), whether the API is open (ADM-04), and which capabilities and tools are on for the API (ADM-08, ADM-10).
API-01 Calling as an organisation
As an app developer, I want to call the API with my organisation's key, so that my organisation's settings apply and its usage is counted.
- API-01.1 Given a valid key, when the app calls
/api/v1/saas/calculate, then the organisation's parts, years and capabilities apply. - API-01.2 Given a missing, revoked or unknown key, or a suspended organisation, then the call is refused.
- API-01.3 Given more calls a minute than the organisation's limit, then 429 with
Retry-After;GET /api/v1/saas/usageshows the month's calls and limits. - API-01.4
GET /api/v1/saas/settingsshows the organisation's settings, read only.
API-02 The full calculation
As an app developer, I want to send whatever figures I have and get the whole calculation back (POST /api/v1/calculate or /api/v1/saas/calculate).
- API-02.1 Given no field is required, when only some figures are sent, then the calculation uses them, and without
tax_yearit is for the latest tax year that has ended onas_of(default today), and the result says so. - API-02.2 Given no
tax_yearand the latest year that has ended is switched off, then 422 withTAX_YEAR_NOT_OFFERED, asking fortax_year. - API-02.3 Given an unknown field name, then 422 lists every problem; nothing is calculated.
- API-02.4 Given a field of a part that is off, then 422 with
MODULE_NOT_ENABLED; given a year that is off, then 422 withTAX_YEAR_NOT_OFFERED. - API-02.5 Given the API is closed in the settings, then 403 "Calculations through the API are switched off".
- API-02.6 The response carries the tax for the year by type, the working, the balance, payments on account and the payment schedule, notes and recommendations, and where the rates came from.
API-03 One part at a time
As an app developer, I want to calculate from the least I have, for example pay alone.
- API-03.1
GET /api/v1/moduleslists every part, whether it is on, its fields and how its answers are read. - API-03.2 Given
{"tax_year": "2025/26", "salary_gross": 30000}sent toPOST /api/v1/modules/employment/calculate, then the calculation uses only that part's fields. - API-03.3 Given the part is off, then 403 naming the part.
API-04 Checking a request
- API-04.1
POST /api/v1/validatereturns whether the request is valid, the tax year it would use, and its errors and warnings, without calculating.
API-05 Reference data
- API-05.1
GET /api/v1/tax-yearslists the supported years;GET /api/v1/tax-rates?tax_year=2025-26returns every rate for a year with its source. - API-05.2
GETorPOST /api/v1/calculate/quickreturns take-home pay from a salary.
API-06 Extra calculations through the API
As an app developer, I want the capabilities my organisation has switched on for the API.
| Capability | Endpoint (public / organisation) |
|---|---|
| Payment plan amount | POST /api/v1/payment-plan / /api/v1/saas/payment-plan |
| Tax so far this year | POST /api/v1/tax-so-far / /api/v1/saas/tax-so-far |
| Making Tax Digital helpers | POST /api/v1/mtd/{status, quarterly, deadlines, penalty-points, late-payment, check, year-end, explain, categories} / /api/v1/saas/mtd/... |
| Tax tools | POST /api/v1/{tool}/{slug} / /api/v1/saas/{tool}/{slug}, for example /api/v1/vat/register |
| Penalties and interest | POST /api/v1/penalties-interest/estimate |
| Company car benefit | POST /api/v1/benefits/company-car |
- API-06.1 Given a capability is off for the API, or its feature is off for the organisation, then its endpoint is refused and no figure is returned.
- API-06.2 Given a tool's API, then it takes the same answers as its question pages, as JSON, and checks them the same way;
GET /api/v1/{tool}lists its tools with an example request.
API-07 Turning records into figures
- API-07.1
POST /api/v1/ingest/open-bankingand/api/v1/ingest/cis-statementturn bank transactions or CIS statements into calculation fields; bank transactions that cannot be recognised for certain are listed for review rather than used.
API-08 Tracking clients through the year (organisations)
As an accountant's app, I want to keep each client's forecast up to date.
- API-08.1
POST /api/v1/saas/clientsadds a client under the organisation's own reference (never a NINO or UTR) with their profile; updates, history and deletion are under/api/v1/saas/clients/{id}. - API-08.2 Given income so far is added, then a new forecast is returned and kept in the history.
- API-08.3 Given a client is deleted, then everything held about them is deleted.
API-09 A stable contract
- API-09.1 Given a release, when it removes an operation or a returned field, or makes a field newly required, then the release pipeline blocks it unless the change is approved and recorded.
- API-09.2 Full documentation is at
/docsand/openapi.json.
7. Epic 3b: App without screens uses the calculator's screens
The app has no tax questions of its own. It starts a journey with whatever it already knows, sends its customer to the calculator's pages, and collects the result when the customer comes back. Only organisations can do this, and only when "Question pages inside another app" is ticked in their settings (ADM-04).
Admin switches that shape this epic: the parts asked (ADM-01, ADM-02), tax years (ADM-03), whether the embedded way is open (ADM-04), reading records inside an app (ADM-05, ADM-07), which journey kinds are on inside an app (ADM-08), whether customers check the app's answers (ADM-12), the result (ADM-13), return addresses (ADM-14) and notifications (ADM-15).
EMB-01 Start a journey
As an app developer, I want to start a journey with what I know about the customer and get a link to send them to (POST /api/v1/saas/journeys).
Request: kind, prefill (fields the app already has), continue_url (required), back_url (optional), confirm_prefill (optional). Response: journey_id, kind, start_url, expires_at.
Kinds: tax_estimate (the bill for a tax year), payment_plan, tax_so_far, the Making Tax Digital helpers mtd_status, mtd_quarterly, mtd_check, mtd_year_end and mtd_explain, and each of the 18 tax tool calculations, named after its capability (for example vat_return, property_tax_england, corporation_tax, payroll, benefits_universal_credit).
- EMB-01.1 Given the embedded way is closed, then 403 "Question pages inside another app are switched off".
- EMB-01.2 Given a kind other than
tax_estimatethat is not switched on inside an app, then 403 naming the kind. - EMB-01.3 Given
prefillholds an unknown field, an invalid value or a field of a part that is off, then 422 and no journey is made. - EMB-01.4 Given
continue_urlorback_urlis not on one of the organisation's app hosts, then 422INVALID_RETURN_URL.
EMB-02 The customer opens the link
As a customer, I want to go straight into the questions from the app I use.
- EMB-02.1 Given a new journey, when the customer opens
start_url, then a session starts under the organisation's settings and the journey is tied to that browser. - EMB-02.2 Given a
back_url, then the first question's back link and "Go back to {organisation}" on every page go to it; given none, the first question has no back link. The customer never lands on the calculator's own start page. - EMB-02.3 Given the customer selects the service name or the logo in the header inside an app, then the page reloads. The customer never leaves the app's journey by them.
- EMB-02.4 Given the link is opened in a second browser, then "This link has already been used" (409) is shown, with a pointer back to the app.
- EMB-02.5 Given the journey has expired (60 minutes by default) or does not exist, then "This link has expired" (404) is shown.
- EMB-02.6 Given the organisation closes the embedded way or the kind after the journey was made, then "This service is not available" is shown.
- EMB-02.7 Given the same browser opens the link again, then the customer carries on where they left off.
EMB-03 Questions with the app's answers
As a customer, I do not want to be asked again what the app already told the calculator.
- EMB-03.1 Given the settings say
skipandconfirm_prefillis not set, then questions the app's answers fill completely are skipped, and still listed on Check your answers. - EMB-03.2 Given the settings say
check, or the journey setsconfirm_prefill: true, then those questions are shown filled in for the customer to check. This applies to every kind of journey. - EMB-03.3 Given reading records is on inside an app, then records are read as each part's choice says (ADM-07), after the customer gives permission, and only for what the app did not send (DAT-04).
- EMB-03.4 Only the organisation's parts and years are asked, as on the standalone pages (CAL-02 to CAL-08).
EMB-04 Sending the result back
As a customer, I want to see my estimate and choose whether to send it to the app.
- EMB-04.1 Given the result page, when the customer selects "Continue", then the result is saved and they go to
continue_url?resultId=.... - EMB-04.2 Given a
back_url, then the result page also offers "Go back to {organisation} without sending this estimate", and nothing is saved.
EMB-05 Collecting the result
As an app developer, I want the figures I need without reading the whole calculation (GET /api/v1/saas/results/{resultId}).
- EMB-05.1 The result carries
kind, therequestused, asummary(for a tax estimate: the tax for the year, the balance and what to pay when), and the wholecalculation,forecast,tax_so_faror helperresultfor its kind. - EMB-05.2 Given another organisation's key, then the result is not found: an organisation only sees its own journeys and results.
- EMB-05.3 Given 24 hours have passed (default), then the result has expired.
- EMB-05.4
GET /api/v1/saas/journeys/{id}returns the status:not_started,startedorcompleted, with theresult_idonce completed.
EMB-06 Being told the customer has finished
- EMB-06.1 Given a notify address (ADM-15), when the customer selects Continue, then the app receives
<kind>.completedwith the journey and result IDs, signed, and collects the figures with its key.
8. Epic 3c: Figures read from HMRC and other APIs instead of asked
With the customer's permission, the service reads figures HMRC and DVLA already hold and uses them in place of questions or request fields. It works in all three ways of use: an app calling the API with the customer's HMRC token, the standalone pages, and the pages inside an app. Each source is read only if the administrator has switched it on (ADM-06), reading is on for that way of use (ADM-05), and the part it fills is set to check or auto (ADM-07).
| Source | Fills | Replaces the question about |
|---|---|---|
| HMRC Individuals Employments Income API | Pay from employment, benefits, student loans | Pay, tax taken off, benefits in kind, student loan deducted |
| HMRC Individuals State Benefits API | Pensions, taxable state benefits | State Pension, taxable benefits |
| HMRC CIS Deductions API | Self-employment | CIS deductions |
| HMRC Self Assessment Accounts API | Payments on account | Payments on account already made |
| DVLA Vehicle Enquiry Service | Benefits and company cars | CO2 emissions and fuel type, from the registration number |
| HMRC internal services | Child Benefit, savings, student loans, Marriage Allowance | Child Benefit received, UK interest, student loan plan, Marriage Allowance status |
Admin switches that shape this epic: the data sources on (ADM-06), reading per way of use (ADM-05), each part's choice of ask, check or read (ADM-07), and the parts on (ADM-01): nothing is read for a part that is off.
DAT-01 An app asks the service to read the figures
As an app developer, I want to send the customer's NINO and HMRC token and only the figures I have, and let the service fill in the rest (POST /api/v1/saas/calculate/with-hmrc-data; the public /api/v1/calculate/with-hmrc-data takes the token as a Bearer header).
- DAT-01.1 Given figures the app sent, then they are always used; read figures only fill fields the app left out.
- DAT-01.2 Given a read figure belongs to a part that is off or set to
ask, then it is not used. - DAT-01.3 The response returns the
calculation, every figure read with its source and whether it wasused, and the sources that wereunavailable. - DAT-01.4 Given no data source is on for the API, then 409 "No data sources are switched on. Send all figures in the request instead."
- DAT-01.5 Given an invalid NINO, then 422
INVALID_NINO; given no customer token, then 422; given anAuthorizationheader that is not a Bearer token, then 401. - DAT-01.6 Given a source fails or does not answer, then the calculation still runs on what was sent and read, and the source is listed as unavailable.
DAT-02 The customer chooses to fill answers from records (pages)
As a customer, I want the service to fill in figures from my HMRC record, so that I do not have to find my P60.
- DAT-02.1 Given reading is on for the way of use and at least one part can be read, when the customer has chosen the tax year, then they are asked "Do you want us to fill in some answers from HMRC records?", with the parts that can be filled listed and their National Insurance number asked.
- DAT-02.2 Given "Yes", then the customer signs in and gives permission before anything is read; given "No, I will enter everything myself", then every question is asked.
- DAT-02.3 Given a figure was read, then it is labelled as from HMRC records; given the customer changes it, then the label is removed.
DAT-03 Check or read without asking
- DAT-03.1 Given a part is set to
check, then read figures are filled in on that part's pages for the customer to confirm or change. - DAT-03.2 Given a part is set to
auto, then pages fully answered from records are skipped, and the figures are listed on Check your answers with a change link; anything not read is asked.
DAT-04 Reading inside an app
- DAT-04.1 Given a journey inside an app and reading on for the embedded way, then the same choice and permission step as DAT-02 is offered, under the organisation's settings.
- DAT-04.2 Given the app also sent some figures, then records are offered only for what it did not send, and a figure the app sent is never replaced, as through the API (DAT-01); given it sent everything the records could fill, then records are not offered (records are offered at all only where a switched-on source can fill a part the settings read).
DAT-05 Reading for the extra capabilities
- DAT-05.1 Tax so far: given pay is read from records, then the pay and tax so far come from HMRC's Real Time Information after permission (API:
ninoandhmrc_access_tokeninPOST /api/v1/tax-so-far). - DAT-05.2 Making Tax Digital "explain HMRC's estimate": HMRC's own tax calculation is read with permission and explained line by line.
- DAT-05.3 Customer accounts: pay and tax so far and the Self Assessment account's bills and credit are read with permission, and the customer checks them before they are saved.
DAT-06 What is never done
- DAT-06.1 Nothing is ever written to HMRC.
- DAT-06.2 Only the customer's own record is read, with their consent (HMRC OAuth scope
read:self-assessment); DVLA data is public register information found by a registration number the customer gives. - DAT-06.3 Read figures are used for that estimate only. On the pages they are kept in the customer's session and deleted with it (CAL-10); nothing read through the API is kept; only a customer account the customer chose to keep holds them.
9. Epic 4: Optional capabilities and tools
Beyond the bill for a tax year, the service offers extra capabilities. Each one is a feature the service administrator installs and switches on (ADM-09), an organisation can switch off for itself (ADM-10), and is switched on separately for each way of use (ADM-08). So the same capability can be on the pages and off in the API, or the other way round.
OPT-01 Payment plan amounts
As a customer, I want a fixed weekly or monthly amount that pays my next Self Assessment bills, so that I can set up a Budget Payment Plan.
Ways of use: pages (/payment-plan, /o/{organisation}/payment-plan), inside an app (journey kind payment_plan), API (POST /api/v1/payment-plan, /api/v1/saas/payment-plan).
- OPT-01.1 Given the bills due on 31 January and 31 July, and this year's forecast, then the amount is the lowest fixed amount, rounded up to the nearest pound, so that the money collected by each deadline covers everything due by then.
- OPT-01.2 The forecast uses, in order of preference: the customer's own estimate, a seasonal adjustment, a blend of this year and last, this year scaled up, then last year; the other methods give the lowest and highest forecast.
- OPT-01.3 Given a new Direct Debit, then the first collection is 5 working days after the request (3 if the customer has paid HMRC by Direct Debit from the same bank before), and dates on weekends or GOV.UK bank holidays move as GOV.UK sets out.
- OPT-01.4 Given a bill due before the first collection, then it is listed as "pay another way".
- OPT-01.5 Given the customer has overdue tax, then they are shown the Time to Pay route instead, as GOV.UK requires them to be up to date first.
- OPT-01.6 The service only gives the amount: HMRC has no public API to set up a plan, so the customer is told how to set it up in their HMRC online account. Nothing is stored for an API call.
OPT-02 Tax so far this year
As a customer, I want to know how much tax has built up since 6 April and what the whole year may come to.
Ways of use: pages (/tax-so-far), inside an app (kind tax_so_far), API (POST /api/v1/tax-so-far).
- OPT-02.1 Given figures since 6 April, then they are forecast to the whole year with a low to high range, the year's tax is worked out with the calculator, and the tax so far is the year's tax times income so far divided by the forecast income.
- OPT-02.2 Given tax already paid (PAYE, CIS, student loan deductions), then it is taken off to show what has built up.
- OPT-02.3 Every step is shown on the result page and returned to apps in
explanation.
OPT-03 Making Tax Digital for Income Tax helpers
As a sole trader, landlord or accountant, I want help alongside my Making Tax Digital software, without anything being sent to HMRC.
| Helper (capability) | What it does | Inside an app |
|---|---|---|
When it starts (mtd_status) |
Whether and from when the customer must use Making Tax Digital (qualifying income over £50,000 from April 2026, £30,000 from April 2027, £20,000 from April 2028), and exemptions | Yes |
Quarterly tax so far (mtd_quarterly) |
The tax after each quarterly update and what to put aside | Yes |
Deadlines and penalties (mtd_deadlines) |
Deadlines, penalty points and late payment penalties, with email reminders | No |
Checks before sending (mtd_check) |
Checks an update before the customer sends it | Yes |
Year-end picture (mtd_year_end) |
The year's position, then the calculator's own questions | Yes |
HMRC's calculation explained (mtd_explain) |
Reads HMRC's estimate with permission and explains it | Yes |
Records sorted into categories (mtd_categories) |
Sorts bank transactions into HMRC's categories | No |
Client list (mtd_clients) |
Accountants' list of clients and their Making Tax Digital status (organisations only) | No |
- OPT-03.1 Given a helper is used, then nothing is ever sent to HMRC.
- OPT-03.2 Given a customer used one helper, when they open the next in the same visit, then the update periods (and, for the same update, its businesses, turnover and residential finance costs) are filled in for them to check.
- OPT-03.3 Given a customer with an account asks for reminders, then emails are sent 14 and 3 days before each deadline.
OPT-04 Tax tools
As a customer or adviser, I want quick answers on other taxes, each with its own question pages and API.
| Feature | Capabilities | What it works out |
|---|---|---|
Need a tax return (filing_check) |
1 | Whether a return is needed for the year that has ended, and why, with deadlines |
PAYE check (paye_check) |
1 | Whether an employee or pensioner paid too much or too little through PAYE, like a P800 |
CGT on a UK home (property_cgt) |
1 | Capital Gains Tax to report and pay within 60 days of selling a UK home that was not always the main home |
Salary or dividends (director_pay) |
1 | Five ways a director can take a company's profit, and what they keep |
Tax on buying property (property_tax) |
3 | Stamp Duty Land Tax, Land and Buildings Transaction Tax, Land Transaction Tax |
Corporation Tax (corporation_tax) |
1 | Corporation Tax with Marginal Relief, and when to pay and file |
VAT (vat) |
4 | Registration, a VAT return, the Flat Rate Scheme, cancelling a registration |
Payroll (payroll) |
1 | An employee's pay for a week or month: PAYE, National Insurance, loans, pension, employer cost |
Inheritance Tax (inheritance_tax) |
1 | Tax on an estate with thresholds, exemptions, Business and Agricultural Relief and gifts |
Partnership profits (partnership_return) |
1 | A partnership's profit shared between up to 8 partners, with each partner's estimated tax |
Trust Income Tax (trust_return) |
1 | A trust's Income Tax for the SA900 return |
Benefits (benefits) |
2 | Child Benefit with the High Income Child Benefit Charge, and an estimate of Universal Credit with the benefit cap |
- OPT-04.1 Given a tool, then it asks one question per page, shows Check your answers and a result, and is at the service's address and at
/o/{organisation}/.... - OPT-04.2 Given the tool's API, then it takes the same answers as JSON and checks them the same way.
- OPT-04.3 Given an organisation switches a tool on inside its app, then its app can start a journey of that tool's kind with any of the answers the API takes; the customer answers the rest, selects "Continue to {organisation}", and the app collects the result: a summary (heading, main figure, data) and the whole result (EMB-01 to EMB-06).
Rule for every tool: a rule that could not be checked on GOV.UK, in HMRC's manuals or in legislation is left out rather than guessed. It is a policy for writing the tools, not a test (NFR-14).
OPT-05 Document reading
As a customer, I want to upload a document instead of typing its figures (see ADM-11, CAL-07).
- OPT-05.1 Given an upload, then figures are filled in for the customer to check, and the document is not kept.
- OPT-05.2 Given any upload, then the document is read on the service itself and never sent to an AI model.
- OPT-05.3 Given a digital P60, then the pay and tax are read from the row "In this employment", never "Total for year", and the tax year from "Tax year to 5 April". These are the rows HMRC's P60 specification sets out (RD1, HMRC 01/25).
- OPT-05.4 Given another statement (dividends, interest, letting agent, pension contributions), then a figure is read where a line starts with its usual wording. A figure not found is left for the customer to type.
- OPT-05.5 Given a scan, a photo or a PDF without text, then nothing is read, and the customer is asked to download the document from their employer's or provider's online service, or type the figures. There is no OCR.
- OPT-05.6 Given HMRC records gave a part's figures, then no document is asked for (structured data first).
- OPT-05.7 The test site reads only its sample documents, in the same way.
OPT-06 Customer accounts
As a customer, I want to keep track of my tax during the year.
- OPT-06.1 Given a customer signs in with GOV.UK One Login or creates an account (email and a password of at least 12 characters, not commonly used), then they answer a few questions and see their forecast tax, next bill and the amount to put aside.
- OPT-06.2 Given they upload a bank statement CSV, then only transactions recognised for certain are used, the rest are listed to check, and only totals are kept.
- OPT-06.3 Given the amount to pay changes by £10 and 10% or more, then the customer is emailed.
- OPT-06.4 Given they ask, then they can download everything held about them as JSON, or delete their account; given 2 years without use, the account is deleted.
- OPT-06.5 Given 5 wrong passwords, then the address is locked for 15 minutes, whether or not an account exists.
10. Epic 5: Journey planner for apps
An organisation's app describes its customer and what it wants worked out, and the journey planner sets up the customer's journey: which calculation, which tax year, which questions, whether HMRC records are offered, and whether the customer checks the app's answers. It follows fixed, written rules and gives the reason for every decision. No AI model is involved, and nothing the app sends leaves the service.
It is a separate feature beside the calculator. A plan can only narrow the organisation's own settings for that one customer, never widen them. The calculator's own pages and every other journey are unchanged.
Admin switches that shape this epic: the feature for the whole service (ADM-09), the feature for one organisation (ADM-10), where the organisation's app can use it (ADM-08: API calculations for plans only, question pages inside the app for plans that start a journey), and every setting the plan narrows (ADM-01 to ADM-07).
PLN-01 Asking for a plan
As an app developer, I want to describe my customer and see how their journey would be set up, without starting it (POST /api/v1/saas/journey-plans).
The app can send:
- the purpose
- the tax year, or which year (
last_completed,currentornext) - the kinds of income the customer has, and whether that list is complete
- whether the customer pays Scottish Income Tax
- whether the customer agreed in the app to their HMRC records being read
- whether the customer should check the app's answers
- any figures the app already has
Acceptance criteria:
- PLN-01.1 Given the planner is on for the organisation's API, then the response gives the calculation, the tax year, the parts, the question pages the customer will see as far as is known, whether records are offered and which, whether the customer checks the answers, the prefill, and a list of decisions, each with its reason.
- PLN-01.2 Given the planner is off for the organisation's API, then 403.
PLN-02 Starting a planned journey
As an app developer, I want the planner to set up and start the customer's journey in one call (POST /api/v1/saas/journey-plans/start).
- PLN-02.1 Given the planner is on inside the organisation's app, then the response gives the plan and the journey: its
start_urland when it expires. The customer is sent there, and the result comes back as from any journey (EMB-02 to EMB-06). - PLN-02.2 Given
continue_urlorback_urlis not on one of the organisation's app hosts, then 422INVALID_RETURN_URL. - PLN-02.3 Given the planner is off inside the organisation's app, then 403.
PLN-03 The calculation
- PLN-03.1 Given a purpose of
tax_estimate, or any kind of journey switched on inside the organisation's app (EMB-01), then that is the journey's calculation. - PLN-03.2 Given an unknown purpose, then 422 listing the purposes; given one switched off inside the app, then 403.
PLN-04 The tax year
- PLN-04.1 Given the app names a year, or describes it with
which_year, then that year is used if it is offered. Otherwise 422TAX_YEAR_NOT_OFFERED, listing the years offered. - PLN-04.2 Given nothing about the year, and only one year offered, then that year is used.
- PLN-04.3 Given nothing about the year, and several years offered, then the customer chooses one of them on the first question.
PLN-05 The questions
- PLN-05.1 Given the app lists the customer's kinds of income and says the list is complete, then other kinds of income are not asked about. The income question is answered with that list, and its pages follow.
- PLN-05.2 Given the app lists some income, or sends figures, without saying the list is complete, then the customer is shown the income question with what the app knows ticked, to add any other.
- PLN-05.3 Given the app sent figures for a part, then that part is always kept.
- PLN-05.4 Given a kind of income the organisation does not use, then it is not asked about, with that reason.
- PLN-05.5 Given a page of a part the plan left out, when the customer opens it by its address, then it does not open.
PLN-06 HMRC records
Records are offered only when all of these hold:
- PLN-06.1 The customer agreed in the app.
- PLN-06.2 The organisation reads records inside its app.
- PLN-06.3 A switched-on source can fill something the app did not send.
Otherwise the records question is not shown, with the reason. The customer still signs in and gives permission at HMRC before anything is read.
PLN-07 Checking the app's answers
- PLN-07.1 Given the app asks for it, or the organisation's settings show the app's answers to check, then the customer sees them filled in (EMB-03).
- PLN-07.2 Otherwise, questions the app's answers fill are skipped.
PLN-08 Never wider, and no AI
- PLN-08.1 Given any plan, then the customer's settings are the organisation's with parts removed and, when the plan says so, records not read. A plan never adds a part, a tax year, a way of use or a data source.
- PLN-08.2 Given any request, then it is decided by the written rules only. Nothing is sent to an AI model, and the only service outside the calculator reached is HMRC, for the records the customer agreed to (PLN-06, PLN-11).
PLN-09 Switching it on and off
- PLN-09.1 Given the feature is switched off for the service, then its API returns 404.
- PLN-09.2 Given it is switched off for one organisation, then that organisation's app gets 403 and the others are unaffected.
- PLN-09.3 Given the feature is on, then the calculator's own pages, other organisations' journeys and journeys started without a plan work exactly as without it.
PLN-10 Questions for an app with its own screens
As an app developer with my own screens, I want the planner to tell me which questions to ask, one page at a time, and then work out the result (POST /api/v1/saas/journey-plans/questions).
- PLN-10.1 Given the planner is on for the organisation's API calculations, when the app sends what it knows, the answers so far and the pages the customer has been through, then the response is the next page to show: each question's name, type, label, hint, whether it is required, its choices and the questions each choice brings, and the API field it fills.
- PLN-10.2 Given the plan answered a page from what the app knows, then that page is not given, unless the customer is to check the app's answers.
- PLN-10.3 Given the customer's answers on a page are wrong, then the same page comes back with what is wrong, worded as the calculator's own pages word it.
- PLN-10.4 Given nothing is left to ask, then the response holds the whole calculation and the figures an app usually needs.
- PLN-10.5 Given a purpose other than the tax estimate, then 422: a tax tool's own API lists its questions.
- PLN-10.6 Given the planner is off for the organisation's API calculations, then 403.
PLN-11 HMRC records for an app with its own screens
- PLN-11.1 Given the plan offers records and the app sends the customer's NINO and HMRC token, then the records are read once and fill only what neither the app nor the customer gave. The response lists them, and the page they fill is given for the customer to check.
- PLN-11.2 Given the app sent a figure, then a figure from the records never replaces it.
11. Switch matrix
What the calculator works out is chosen once for all ways of use (parts, Scotland, tax years); how it is reached is chosen per way of use (open or closed, reading records, each capability). "Yes" means the setting applies in that way of use; "—" means it does not apply there.
| Setting | Full question pages | Inside an app | API | Chosen per way of use? | Set by |
|---|---|---|---|---|---|
| Parts (29) and ready-made sets (ADM-01) | Questions not asked | Not asked; prefill fields refused | Fields refused (422) | No | Service and each organisation |
| Scottish Income Tax (ADM-02) | Question not asked | Not asked | jurisdiction: scotland refused |
No | Service and each organisation |
| Tax years (ADM-03) | Not offered | Refused | Refused (422) | No | Service and each organisation |
| Way of use open (ADM-04) | Yes | Yes (organisations only) | Yes | Yes | Service and each organisation |
| Reading records (ADM-05) | Yes | Yes | Yes | Yes | Service and each organisation |
| Data sources (ADM-06) | Yes | Yes | Yes | No | Service and each organisation |
| Each part's answers: ask, check, read (ADM-07) | Yes | Yes | Yes | No | Service and each organisation |
| Payment plan amounts (ADM-08) | Yes | Yes | Yes | Yes | Service and each organisation |
| Tax so far (ADM-08) | Yes | Yes | Yes | Yes | Service and each organisation |
| Making Tax Digital helpers, each (ADM-08) | Yes | 5 of the 8 | Yes | Yes | Service and each organisation |
| Tax tools, each (ADM-08) | Yes | Yes | Yes | Yes | Service and each organisation |
| Whole features (ADM-09, ADM-10) | Yes | Yes | Yes | No | Service; each organisation can switch off its own |
| Documents per part (ADM-11) | Yes | Yes | — | No | Service and each organisation |
| App's answers: skip or check (ADM-12) | — | Yes | — | No | Organisations only |
| Result: full or summary (ADM-13) | Yes | Yes | — (always full) | No | Service and each organisation |
| Return addresses, notify address (ADM-14, ADM-15) | — | Yes | — | No | Organisations only |
| API keys, rate limit, quota (ADM-16) | — | Yes (to start journeys) | Yes | No | Service administrator |
| Journey planner (Epic 5) | — | Yes (plans that start a journey) | Yes (plans only) | Yes | Organisations only; the service can switch the feature off |
Example configurations. The test site's example organisations show the main patterns:
| Organisation | Ways open | Parts | Records read | Capabilities | Result |
|---|---|---|---|---|---|
| Example Accountants (standalone pages) | Pages, API | All 29 | Pages and API; pay read without asking, other parts checked | Payment plans, tax so far, all MTD helpers, all tax tools | Full |
| Example Payroll App (pages inside an app, basic tax) | Inside an app, API | Basic Income Tax: pay, pensions, Scotland | Inside the app only; pay checked | Tax so far inside the app and API | Summary |
| Example Budget Payment Plan Service (an app's own journey) | Inside an app, API | All 29 | None | Payment plans inside the app and API | Full |
| Example Bookkeeping App (Making Tax Digital) | Inside an app, API | All 29 | None | 5 MTD helpers inside the app; all through the API | Full |
| Example Tax API (API only, app has its own screens) | API | Employees set | API; employee parts read without asking | Tax so far through the API | Full |
12. Non-functional requirements
Every release must pass all of these automatically before it reaches the live site; the release pipeline records each approval in a log.
| ID | Area | Requirement | How it is checked |
|---|---|---|---|
| NFR-01 | Accuracy | Every rate and threshold is stored with its GOV.UK, HMRC or legislation source; a figure without a source stops the rates loading | Rates check on load; a test compares the rates file with its saved sources |
| NFR-02 | Accuracy | The calculation matches hand-worked results | 186 hand-worked customers, 496 threshold checks, 25,000 random customers against a separately written calculation, GOV.UK's own worked examples |
| NFR-03 | Accuracy | At least 95% of the calculation code is covered by tests | Coverage measured on every release (97.5% on 12 October 2026) |
| NFR-04 | Configuration | A switched-off setting is unreachable and changes nothing else | A test for every part, tax year, feature, capability and organisation feature |
| NFR-05 | Accessibility | WCAG 2.2 AA, following the GOV.UK Design System; every page works without JavaScript except the developer reference at /docs | Automated scans of journeys and /docs in every release; audited with the NVDA screen reader on 112 pages; an independent audit is still to come |
| NFR-06 | Security | CSRF tokens on every form, security headers, sessions ending after 15 minutes idle, passwords hashed with PBKDF2-SHA256 (600,000 iterations), saved figures encrypted with AES-256-GCM, API keys stored as SHA-256 hashes, per-organisation rate limits | Static security scan, known-vulnerability scan of libraries and a secrets scan on every release; security tests |
| NFR-07 | Privacy | Estimate answers are kept only in the customer's session, which ends 15 minutes after last use and is deleted within 5 minutes after that; they are never sent to HMRC; journeys expire after 60 minutes and results after 24 hours; uploaded documents are not kept; an organisation only sees its own data | Tests; privacy notice; data protection impact assessment before live use |
| NFR-08 | Performance | Pages answer within 1.5 seconds and the main API calls within 1.0 second at the 95th percentile | Timed on every release (start page, question start, tools, rates, /docs, calculate, Child Benefit) |
| NFR-09 | Stability for apps | No release removes an API operation or returned field, or makes a field newly required, without approval | API contract test against a saved snapshot of 101 paths |
| NFR-10 | Audit | Every settings, feature, data source and organisation change is logged with who, when, where from, and before and after | Audit log, admin API |
| NFR-11 | Change control | Every change goes on a branch, is reviewed, then passes the release pipeline before it is merged and published | Pipeline report and approval log for every release |
| NFR-12 | Branding | Unless HMRC runs the service, it appears under its own name, without the crown, GOV.UK logotype, GDS Transport typeface or GOV.UK colours | Branding tests; accessibility scan of the footer |
| NFR-13 | Privacy | No customer data is sent to an AI model: documents are read on the service itself, and the journey planner uses fixed rules | Tests prove the removed document reader sends nothing; code review of every release |
| NFR-14 | Traceability | Every acceptance criterion has an ID and at least one test that names it and passes; a criterion no test names, or a test naming an unknown criterion, stops the release | Release pipeline stage reading each test's criteria; a traceability report in every run (213 criteria; the tools' rule in OPT-04 is a policy, not a test) |
13. Assumptions and open questions
The requirements above describe the service as built and tested on the test site; going live depends on the assumptions below, and the open questions need a decision.
Assumptions
- Reading HMRC records for real customers needs production access to HMRC's APIs and HMRC's fraud prevention headers; the test site uses dummy HMRC, DVLA, One Login and Notify services.
- GOV.UK One Login and GOV.UK Notify are for government services only. If a company runs the service, it needs its own sign-in and email provider.
- Figures for 2027 to 2028 that are not yet announced use 2026 to 2027's, and the result says so; they are replaced when announced.
- No company runs the service yet: the operator's name, number and registered office are settings, empty until it does.
Open questions
- Resolved: App figures versus HMRC records: inside an app, records were not offered at all once the app sent any figure, while the API used them to fill the gaps. Resolved on 16 October 2026: in both, records fill only what the app did not send and never replace the app's figures (DAT-01, DAT-04).
- Resolved: Start page year: it named the latest completed year even when switched off. Resolved on 16 October 2026, together with the API's default year and take-home pay (ADM-03, API-02, CAL-11).
- Resolved: Tax tools inside an app. Resolved on 16 October 2026: every tool can be handed to the calculator by an app and give its result back (OPT-04, EMB-01). The first question's back link now goes to the app (EMB-02), and the app's request to check its answers applies to every kind (EMB-03).
- Resolved: Should documents be read by an AI model? Decided on 16 October 2026: no customer data goes to an AI model, and the Claude reader was removed (OPT-05, NFR-13).
- Resolved: Which reader reads real documents? Decided on 16 October 2026: structured data first, then digital PDFs read on the service. OCR only if research shows customers mostly have paper (OPT-05).
- Resolved: Should the planner serve apps with their own screens? Decided on 16 October 2026: yes, one page of questions at a time, then the calculation (PLN-10, PLN-11).
- Resolved: The header link inside an app. Decided on 16 October 2026: it reloads the page (EMB-02).
- Resolved: Link every acceptance criterion to a test. Decided on 16 October 2026: each criterion has an ID, each test names the criteria it proves, and a release is blocked while any criterion has no passing test (NFR-14).
- Open: Is there research on whether customers mostly have P60s and statements as PDFs or on paper? It decides whether OCR on the service is needed.
- Open: Who will run the service and be the data controller?
- Open: When will the Welsh language version be built?
- Open: Where will the live service be hosted, and when will HMRC production credentials be requested?
- Open: Who will carry out the independent accessibility audit and the testing with disabled people?
14. Sources
The calculation rests on 56 GOV.UK, HMRC and legislation sources, each stored beside the figures it supports (app/data/tax_rates.json); each tax tool lists its own at the top of its rules. HMRC, DVLA and Notify API specifications are saved in the code's reference/ folder. The main ones behind this document: