API documentation
Integrate plannng's retirement projection engine into your platform. This guide covers authentication, data modelling, and running projections via the REST API.
Base URL
https://api.plannng.co/api/v1
Authentication
All API requests (except registration and login) require a Bearer token in the Authorization header.
Register
Create a new account. Returns a JWT token and client profile.
{
"name": "Jane Smith",
"email": "jane@example.com",
"password": "securepassword"
}
| Field | Type | Notes |
|---|---|---|
| name | string | required |
| string | required | |
| password | string | required - 8 to 72 characters |
Log in
Authenticate with email and password. Returns a JWT token.
{
"email": "jane@example.com",
"password": "securepassword"
}
Response:
{
"data": {
"token": "eyJhbGciOiJIUzI1NiIs...",
"client": {
"id": "c_abc123",
"name": "Jane Smith",
"email": "jane@example.com",
"emailVerified": true,
"persons": [],
"createdAt": "2026-01-15T10:30:00Z",
"updatedAt": "2026-01-15T10:30:00Z"
}
}
}
Using the token
Include the token as a Bearer token in subsequent requests:
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
Refresh token
Exchange a valid token for a new one with the current subscription tier. Call this after subscription changes take effect.
Current client
Returns the authenticated client's profile, including linked persons.
Permanently delete the authenticated client and all associated data.
Password management
Change password for the authenticated client. Requires currentPassword and newPassword.
Send a password reset email. Requires email.
Reset password using a token from the reset email. Requires token and newPassword.
Email verification
Verify an email address using the token sent during registration.
Resend the verification email for the authenticated client.
Response format
All successful responses are wrapped in a data envelope:
{
"data": { ... }
}
Errors return an error object with a machine-readable code and human-readable message:
{
"error": {
"code": "not_found",
"message": "Plan not found"
}
}
Standard HTTP status codes are used: 200 success, 201 created, 204 no content (deletes), 400 validation error, 401 unauthorized, 403 forbidden, 404 not found, 409 conflict.
Persons
A person represents an individual in the plan - typically the client, and optionally their partner. Each person has their own date of birth, tax jurisdiction, and can be assigned to plan elements.
List all persons for the authenticated client.
Create a new person.
{
"firstName": "Jane",
"lastName": "Smith",
"dateOfBirth": "1980-06-15",
"taxJurisdiction": "rUK",
"lsaUsed": "0",
"otherAnnualIncome": "0"
}
| Field | Type | Notes |
|---|---|---|
| firstName | string | required |
| lastName | string | required |
| dateOfBirth | string | required - format YYYY-MM-DD |
| taxJurisdiction | string | rUK or Scotland |
| lsaUsed | string | Decimal - Lifetime Savings Allowance already used |
| otherAnnualIncome | string | Decimal - annual income not modelled as plan elements |
Get a person by ID.
Update a person. Same fields as create, all required.
Delete a person.
Plans
A plan is the top-level container for a client's financial scenario. It holds elements, stages, and budgets. Each plan has a target retirement date.
List all plans. Returns each plan with its elements and stages.
Create a new plan.
{
"name": "Main retirement plan",
"retirementMonth": 4,
"retirementYear": 2045
}
| Field | Type | Notes |
|---|---|---|
| name | string | required |
| retirementMonth | integer | required - 1 to 12 |
| retirementYear | integer | required - minimum 1900 |
Get a plan by ID, including all elements and stages.
Update a plan's name.
Delete a plan and all its elements, stages, and budgets.
Duplicate a plan. Optionally pass {"name": "Copy of plan"} in the body.
Life stages
Stages divide a plan into named time periods - for example "Working", "Semi-retired", "Fully retired". Elements can reference stages for their start and end dates, so moving a stage boundary automatically shifts the elements tied to it.
List all stages for a plan, in order.
Create a new stage.
{
"name": "Semi-retired",
"startMonth": 1,
"startYear": 2040,
"endMonth": 3,
"endYear": 2045,
"isRetirement": false,
"afterStageId": 1
}
| Field | Type | Notes |
|---|---|---|
| name | string | required |
| startMonth | integer | required - 1 to 12 |
| startYear | integer | required - minimum 1900 |
| endMonth | integer | 1 to 12 |
| endYear | integer | minimum 1900 |
| isRetirement | boolean | Marks this stage as the retirement stage |
| afterStageId | integer | Insert after this stage in the ordering |
Get a stage by ID.
Update a stage. Requires name, startMonth, and startYear.
Delete a stage.
Plan elements
Elements are the building blocks of a plan. Each element represents a financial instrument or cash flow in the client's life. There are five types:
- income - employment, rental, or other income with start and end dates
- expense - living costs, bills, and other outgoings
- investment - ISA, GIA, or savings accounts, each with their own tax treatment
- pension - PCLS drawdown or UFPLS, with crystallisation and tax-free lump sum handling
- asset - non-income holdings like property or vehicles, tracked for net worth
List all elements for a plan.
Create a new element.
Example: ISA investment
{
"name": "Stocks and shares ISA",
"type": "investment",
"subType": "ISA",
"personId": "p_abc123",
"startingValue": "85000",
"startMonth": 4,
"startYear": 2026,
"growthRate": {
"mode": "percentage",
"period": "annual",
"value": "5.0"
},
"contribution": {
"amount": "1000",
"period": "monthly"
},
"drawdownOrder": 2
}
Example: PCLS drawdown pension
{
"name": "Workplace pension",
"type": "pension",
"subType": "PCLS_DRAWDOWN",
"personId": "p_abc123",
"startingValue": "320000",
"startMonth": 4,
"startYear": 2026,
"growthRate": {
"mode": "percentage",
"period": "annual",
"value": "4.5"
},
"contribution": {
"amount": "500",
"period": "monthly",
"endMonth": 3,
"endYear": 2045
},
"pclsPercentage": "25",
"pclsTargetId": "elem_isa_123",
"drawdownOrder": 3,
"drawdownStartMonth": 4,
"drawdownStartYear": 2045
}
The pclsPercentage sets the tax-free lump sum as a percentage of the fund at crystallisation. pclsTargetId is the element that receives the lump sum (for example, an ISA or savings account). Alternatively, use pclsAmount for a fixed amount.
Example: employment income
{
"name": "Salary",
"type": "income",
"personId": "p_abc123",
"startingValue": "65000",
"startMonth": 4,
"startYear": 2026,
"endMonth": 3,
"endYear": 2045,
"growthRate": {
"mode": "percentage",
"period": "annual",
"value": "2.5"
}
}
Common fields
| Field | Type | Notes |
|---|---|---|
| name | string | required |
| type | string | required - income, expense, investment, pension, asset |
| subType | string | For investments: ISA, GIA, SAVINGS. For pensions: PCLS_DRAWDOWN, UFPLS. For expenses: BUDGET |
| personId | string | The person this element belongs to |
| startingValue | string | required - decimal as string |
| startMonth | integer | required - 1 to 12 |
| startYear | integer | required |
| endMonth | integer | 1 to 12 |
| endYear | integer | When the element ends |
| growthRate | object | required - see below |
| contribution | object | Regular contribution - see below |
| drawdownOrder | integer | Priority for drawdown (lower = drawn first) |
Growth rate
Every element requires a growth rate. All three fields are required.
| Field | Type | Notes |
|---|---|---|
| mode | string | percentage or absolute |
| period | string | monthly or annual |
| value | string | Decimal as string - e.g. "5.0" for 5% annual |
Contribution
| Field | Type | Notes |
|---|---|---|
| amount | string | required - decimal as string |
| period | string | required - monthly or annual |
| endMonth | integer | 1 to 12 - when contributions stop |
| endYear | integer | When contributions stop |
Stage-linked dates
Instead of fixed dates, element start, end, contribution end, drawdown start, and liquidation dates can be linked to a stage boundary. Use the *StageId and *StageEdge fields:
{
"startDateStageId": 2,
"startDateStageEdge": "start",
"endDateStageId": 3,
"endDateStageEdge": "end"
}
When a stage's dates change, all elements linked to it shift automatically.
Liquidation
Elements can have a one-off liquidation event - for example, selling a property or crystallising a pension.
| Field | Type | Notes |
|---|---|---|
| liquidationMonth | integer | 1 to 12 |
| liquidationYear | integer | When the liquidation occurs |
| liquidationPercent | string | Percentage to liquidate (e.g. "100") |
| liquidationAmount | string | Fixed amount to liquidate |
| liquidationTargetId | string | Element ID to receive the proceeds |
| gainPercentage | string | Capital gain percentage for CGT calculation (GIA only) |
Pension-specific fields
| Field | Type | Notes |
|---|---|---|
| pclsPercentage | string | Tax-free lump sum as % of fund (typically "25") |
| pclsAmount | string | Fixed tax-free lump sum amount (alternative to percentage) |
| pclsTargetId | string | Element ID to receive the PCLS |
| drawdownStartMonth | integer | When drawdown begins (1 to 12) |
| drawdownStartYear | integer | When drawdown begins |
Get an element by ID.
Update an element. Same required fields as create.
Delete an element.
Budgets
A budget is a named collection of expense line items attached to a plan. When an expense element has subType: "BUDGET" and a budgetId, the engine uses the budget's total as the expense amount.
List all budgets for a plan.
Create a new budget.
{
"name": "Monthly living costs",
"items": [
{
"name": "Mortgage",
"amount": "1200",
"growthRate": {
"mode": "percentage",
"period": "annual",
"value": "0"
},
"endMonth": 6,
"endYear": 2040
},
{
"name": "Utilities",
"amount": "250",
"growthRate": {
"mode": "percentage",
"period": "annual",
"value": "3.0"
}
}
]
}
Each budget item has a name, amount (decimal as string), and its own growthRate. Items can optionally have an end date, after which they stop contributing to the total.
Get a budget by ID.
Update a budget. Replaces the full item list.
Delete a budget.
Projections
A projection runs the deterministic engine against a plan - a month-by-month simulation using fixed growth rates across the full time range.
{
"startMonth": 4,
"startYear": 2026,
"endMonth": 3,
"endYear": 2070
}
| Field | Type | Notes |
|---|---|---|
| startMonth | integer | required - 1 to 12 |
| startYear | integer | required - 1900 to 2200 |
| endMonth | integer | required - 1 to 12 |
| endYear | integer | required - 1900 to 2200 |
| overrides | object | What-if overrides - see below |
Response
The response contains a summary, an array of monthlySnapshots, and effectiveDates for each element.
{
"data": {
"summary": {
"totalMonths": 528,
"finalNetWorth": "1245000.00",
"finalInflationAdjustedNetWorth": "780000.00",
"totalIncomeGenerated": "2850000.00",
"totalExpensesIncurred": "1920000.00",
"totalTaxPaid": "485000.00",
"totalTaxFreeIncome": "120000.00",
"totalDrawdown": "340000.00"
},
"monthlySnapshots": [
{
"date": "2026-04",
"totalNetWorth": "405000.00",
"inflationAdjustedNetWorth": "405000.00",
"totalIncome": "5416.67",
"totalExpenses": "3200.00",
"totalTax": "890.00",
"totalTaxFreeIncome": "0",
"netIncomeAfterTax": "1326.67",
"netCashFlow": "1326.67",
"totalDrawdown": "0",
"remainingLSA": "1073100.00",
"elements": [
{
"elementId": "elem_123",
"name": "Stocks and shares ISA",
"type": "investment",
"value": "86000.00"
}
],
"personTaxDetails": [
{
"personId": "p_abc123",
"personName": "Jane Smith",
"taxableIncome": "5416.67",
"taxFreeIncome": "0",
"netIncome": "4526.67",
"taxDue": "890.00",
"bands": [
{
"bandName": "Basic rate",
"rate": "0.20",
"income": "4370.83",
"tax": "874.17"
}
]
}
]
}
],
"effectiveDates": [
{
"elementId": "elem_123",
"name": "Stocks and shares ISA",
"type": "investment",
"startDate": "2026-04",
"endDate": "2070-03",
"contributionEndDate": "2070-03",
"drawdownStartDate": "2045-04"
}
]
}
}
Each monthly snapshot includes per-element values, per-person tax breakdowns with band detail, and aggregate totals. The inflationAdjustedNetWorth reports net worth in today's purchasing power.
Monte Carlo simulations
A simulation runs the Monte Carlo engine - multiple stochastic iterations with randomised monthly returns, aggregated into percentile bands. Simulations run asynchronously; you submit a job and poll for results.
Submit a simulation
Returns 202 Accepted with a job ID.
{
"startMonth": 4,
"startYear": 2026,
"endMonth": 3,
"endYear": 2070,
"simulations": 1000,
"annualVolatility": 15.0,
"targetMonth": 3,
"targetYear": 2060
}
| Field | Type | Notes |
|---|---|---|
| startMonth | integer | required - 1 to 12 |
| startYear | integer | required |
| endMonth | integer | required - 1 to 12 |
| endYear | integer | required |
| simulations | integer | required - 10 to 10,000 |
| annualVolatility | number | required - 0 to 100 (percentage) |
| targetMonth | integer | 1 to 12 - for success rate calculation |
| targetYear | integer | Target date for success rate |
| overrides | object | What-if overrides - see below |
Response:
{
"data": {
"jobId": "job_xyz789",
"type": "simulation",
"status": "pending"
}
}
Poll for results
Use the jobs endpoint to check status and retrieve results once complete. The result contains P10/P25/P50/P75/P90 percentile bands for net worth at each month, plus a success rate if a target date was provided.
Jobs
Simulations and other long-running operations are processed as background jobs. Poll the job endpoint to check status.
List all jobs for the authenticated client.
Get the status and result of a specific job.
{
"data": {
"jobId": "job_xyz789",
"planId": "plan_abc123",
"type": "simulation",
"status": "completed",
"createdAt": "2026-04-12T14:00:00Z",
"startedAt": "2026-04-12T14:00:01Z",
"completedAt": "2026-04-12T14:02:15Z",
"result": { ... }
}
}
Job statuses: pending, running, completed, failed. If failed, the response includes an errorMessage.
What-if overrides
Both projections and simulations accept an overrides object. This lets you run scenarios without modifying the underlying plan data - change an element's value, shift a stage boundary, or adjust a person's income, then compare the results.
{
"startMonth": 4,
"startYear": 2026,
"endMonth": 3,
"endYear": 2070,
"overrides": {
"elementOverrides": [
{
"elementId": "elem_123",
"startingValue": "100000",
"growthRate": {
"mode": "percentage",
"period": "annual",
"value": "4.0"
}
}
],
"stageOverrides": [
{
"stageId": 2,
"startMonth": 1,
"startYear": 2043
}
],
"personOverrides": [
{
"personId": "p_abc123",
"otherAnnualIncome": "5000"
}
]
}
}
Element overrides
Override any element field for the duration of the run. Only elementId is required; include only the fields you want to change. Use remove* flags (e.g. removeContribution: true) to clear optional fields.
Stage overrides
Shift stage boundaries. Requires stageId; include startMonth, startYear, endMonth, endYear as needed.
Person overrides
Adjust a person's lsaUsed or otherAnnualIncome for the run.
Subscription and usage
API access is metered by subscription tier. Check your current tier and daily usage.
Get the authenticated client's subscription details, including tier and expiry date.
Get daily usage per feature.
{
"data": [
{
"feature": "projection",
"dailyUsed": 12,
"dailyLimit": 100
},
{
"feature": "simulation",
"dailyUsed": 3,
"dailyLimit": 20
}
]
}
Questions about the API? Get in touch at business@plannng.co