Projection API
The engine behind every Hundred Summers figure, available to other software: send a plan, get back the year-by-year projection the app would show — UK income tax (including Scotland), National Insurance, pensions and their tax-free cash, ISAs and other savings, the State Pension, property, debts and inheritance tax — with the assumptions it used and a fingerprint of the engine that produced it.
Status: a proof of concept, version 0.1. Access is included in the Adviser plan, so a firm's own systems (a CRM, client portal or back office) can run plans through the same engine; other design partners by arrangement. Ask for a key at contact@hundredsummers.co.uk with a line on what you would build. Expect it to change as we learn from you.
Privacy: nothing is kept
The API is stateless. The plan you send is run in memory and the answer goes straight back; nothing is stored, and request bodies are never logged (we record only which key called, the response status and the time taken). Names are removed before the engine sees the plan and none are returned. Please send no names, contact details or other identifiers. This doesn't touch the app itself: our members' plans stay on their own devices, and the API holds none of them.
Endpoints
| Request | Key | What it does |
|---|---|---|
GET /api/v1/status | No | The API version and the engine fingerprint now running. |
POST /api/v1/projection | Yes | Runs a plan and returns the projection. |
Send the key as Authorization: Bearer <key> and the body as JSON (Content-Type: application/json), at most 256 KB. Each key may make up to 60 requests a minute. Call it from your server, never from a web page: a key in page code is public, so browsers are not allowed to call it.
A worked example
A 45-year-old on £60,000 with a workplace pension and an ISA, retiring at 65. The request:
curl https://hundredsummers.co.uk/api/v1/projection \
-H "Authorization: Bearer $HS_API_KEY" \
-H "Content-Type: application/json" \
-d @plan.json{
"facts": {
"currentAge": 45,
"retirementAge": 65,
"planToAge": 92,
"compensation": {
"baseSalary": 60000
},
"taxOptimisation": {
"niQualifyingYears": 24
},
"preRetirementExpense": 32000,
"retirementExpense": 28000,
"pensionPots": [
{
"id": 1,
"label": "Workplace pension",
"value": 150000,
"annualContrib": 3000,
"employerContrib": 3000,
"riskProfile": "moderate"
}
],
"savingsAccounts": [
{
"id": 2,
"label": "Stocks and shares ISA",
"value": 40000,
"annualContrib": 4000,
"growthRate": 5,
"taxWrapper": "isa",
"assetClass": "equity"
}
]
},
"options": {
"money": "today",
"currentYear": 2026
}
}The answer, computed by the engine when this page was built (in 2026 pounds, as asked): spending is covered in every year; at 65 the pensions hold £331,979 and the savings £280,836; lifetime tax comes to £324,748. Some of the rows:
| Age | Pay | State Pension | From pensions | From savings | Spending | Income tax | Pensions | Savings | Net worth |
|---|---|---|---|---|---|---|---|---|---|
| 45 | £60,000 | £0 | £0 | £0 | £32,000 | £10,232 | £150,000 | £40,000 | £190,000 |
| 64 | £60,000 | £0 | £0 | £0 | £32,000 | £12,141 | £325,625 | £304,161 | £629,786 |
| 65 | £0 | £0 | £0 | £28,000 | £28,000 | £0 | £331,979 | £280,836 | £612,815 |
| 80 | £0 | £12,548 | £0 | £15,739 | £28,000 | £288 | £443,605 | £58,563 | £502,168 |
| 92 | £0 | £12,548 | £18,517 | £0 | £28,000 | £3,065 | £387,438 | £0 | £387,438 |
And the summary, as returned:
{
"summary": {
"spendingCovered": true,
"firstShortfall": null,
"atRetirement": {
"age": 65,
"year": 2046,
"netWorth": 612815,
"pensions": 331979,
"savingsAndInvestments": 280836
},
"atPlanEnd": {
"age": 92,
"year": 2073,
"netWorth": 387438,
"pensionsAndSavings": 387438,
"inheritanceTaxEstimate": 114245
},
"lifetimeUnfundedSpendingToday": 0,
"lifetimeTaxToday": {
"incomeTax": 264519,
"nationalInsurance": 60229,
"capitalGainsTax": 0,
"savingsAndDividendTax": 0,
"total": 324748
}
}
}The plan you send
facts is a plan in the app's own format, and only currentAge, retirementAge and planToAge are required. Anything you leave out takes the value of a new, empty plan in the app, and empty means empty: no pensions, savings, property or spending, and no State Pension unless you give either statePension (a forecast, £ a year) or taxOptimisation.niQualifyingYears. Nested parts such as compensation merge field by field, so { "baseSalary": 60000 } keeps the other compensation defaults. The quickest way to see every field is to build a plan in the app, turn off backup encryption in Settings, save it to a file, and send that file as the body as it is: the API accepts the app's plan file and runs its active plan.
| Option | Values | Default |
|---|---|---|
options.money | "nominal" (each year's pounds) or "today" (deflated at the plan's inflation) | "nominal" |
options.currentYear | The year the plan starts in. Set it to make a result repeatable from one year to the next. | This year |
options.rows | false returns the summary alone | true |
The answer
summary says whether spending is covered every year (and, if not, when it first falls short), the position at retirement and at the end of the plan, and lifetime tax and lifetime unfunded spending in today's pounds. assumptions lists every rate the engine resolved (inflation, growth, allowances, charges), basis the year and money terms, and rows one entry a year, the first being today's position:
| Field | Meaning |
|---|---|
year | Calendar year. |
age | Age during that year. The first row is today's position, before any flows. |
retired | True from the retirement age. |
employmentIncome | Salary and bonus from every role that year, after any phased-retirement step. |
statePension | State Pension paid. |
definedBenefitIncome | Final-salary (defined benefit) pensions paid. |
otherIncome | Other income lines as entered (rental and other taxable income). |
annuityIncome | Income from any annuity bought. |
pensionDrawdown | Taken from defined contribution pensions, gross (before income tax). |
savingsDrawdown | Taken from savings and investments. |
totalIncome | All income and withdrawals that year. |
spending | Planned living spending (working-years or retirement budget, with any later-life additions). |
totalExpenses | Everything paid out: spending, debt repayments, one-off costs and the like. |
pensionContributions | Paid into pensions, by the person and any employer. |
savingsContributions | Paid into savings and investments. |
incomeTax | Income tax on earnings, pensions and other income. |
nationalInsurance | Employee or self-employed National Insurance. |
capitalGainsTax | Capital gains tax on investments and property. |
savingsAndDividendTax | Tax on savings interest and dividends. |
studentLoanRepayment | Student loan repaid. |
unfundedSpending | Spending the plan could not pay for that year. Above zero means the plan runs short. |
pensions | Defined contribution pensions at the end of the year. |
savingsAndInvestments | Savings and investments at the end of the year. |
property | Property value at the end of the year. |
liabilities | Mortgages and other debts outstanding. |
netWorth | Everything owned less everything owed. |
inheritanceTaxEstimate | Inheritance tax if the estate passed that year, on the plan's estate settings. |
Reproducing a result
Every answer carries engine.fingerprint, a hash of the engine's source code. The engine is deterministic: the same request, with options.currentYear set, to an engine with the same fingerprint gives the same answer, to the pound. A different fingerprint means the engine changed, usually because a tax rule or published figure did; every change is recorded on What's new and every assumption is published in the Methodology. The fixed-rule arithmetic is checked case by case in the validation pack.
Errors
| Status | Code | When |
|---|---|---|
| 400 | no_plan, bad_json, bad_option | No plan in the body, the body isn't JSON, or an option is out of range. |
| 401 | unauthorised | No key, or a key that is wrong or revoked. |
| 405, 404 | method_not_allowed, not_found | The wrong method, or an unknown endpoint. |
| 413, 415 | too_large, unsupported_media_type | A body over 256 KB, or not sent as JSON. |
| 422 | invalid_plan, engine_error | A required age is missing or out of range, or the engine cannot run the plan. |
| 429 | rate_limited | More than 60 requests in a minute; wait for the Retry-After seconds. |
Terms for the proof of concept
- Education, not advice. The API returns a projection on stated assumptions, not a recommendation. If you use it in advice to your own customers, that advice, and any regulation that applies to it, is yours.
- UK rules only, as set out in the Methodology. Every projection is uncertain; the summary is not a guarantee.
- No service level. The API may change, pause or stop, and keys may be withdrawn, with notice where we can give it.
- Send no names, contact details or other identifiers. Nothing you send is stored.
- Our Terms of Service apply otherwise.
Questions, or a plan the engine gets wrong? contact@hundredsummers.co.uk