Changelog

Every feature, fix, and improvement — documented as we ship.

LINES OF CODE
150,731
RELEASES
208
API ENDPOINTS
216
API 71,505 Portal 40,174 Docs 11,541 Site 27,511 32 API resources
Build v5.91.0 Lock 608dbce9 OpenAPI Canonical

Most software ships updates without telling you what changed. You find out when something breaks or feels different.

Accounting software shouldn't work that way. When your books are involved, you deserve to know exactly what changed, when, and why.

Every entry here is written by a human, not generated. If we ship it, we document it.

Feature New capability
Fix Bug resolved
Improvement Existing feature enhanced
Breaking API or behaviour change
v5.91.0 26 June 2026 Latest

Test Harness Teardown Fixes

Two test harnesses were creating scratch organisations in the production database and not cleaning them up. The root cause was the same in both cases, and the fix is the same discipline the erasure service has always used.

What changed

The retention-purge and credit-note allocation race harnesses both had teardown functions that issued a database sanction and a delete as separate statements on an autocommit connection. The sanction is a transaction-local marker that permits the organisation delete to proceed; on an autocommit connection each statement runs in its own implicit transaction, so the marker expired before the delete ran. The delete was silently refused and the scratch organisation stayed in the database.

In the credit-note race harness, the teardown also pre-deleted child rows before attempting the organisation delete. Because the cascade on every organisation-scoped foreign key has been unconditional since migration 119, the pre-deletes were redundant and introduced additional failure surface: a throw on any child delete would abort the teardown before the organisation delete was ever reached.

Both teardowns now wrap the sanction and the delete in a single explicit transaction, matching the pattern the erasure service has used from the start. The credit-note teardown drops the redundant child pre-deletes and relies on the cascade. Both add a catch block that logs failures rather than swallowing them, so a future teardown problem surfaces in the test output rather than accumulating silently.

Why it matters

Leaked scratch organisations were accumulating in the live database on every test run. They were empty and caused no financial or data integrity issue, but they appeared in the admin panel alongside real accounts and cluttered operational views. More importantly, a silent teardown failure is a diagnostic gap: if a guard or a cascade breaks, the failure should be loud, not invisible.

The fix closes the gap. The harnesses now clean up correctly on every run, and a teardown failure produces a console error rather than a missing row.

How it is proven

The retention-purge suite (six cases) was run twice against the live database after the fix landed. After the second run, zero rows matched the scratch organisation name and slug patterns. The same verification was applied to the credit-note race suite (one case). Both passed.

The deployment pipeline ran all 408 executable proof anchors across 36 test harnesses. Zero violations. The erasure guard, the cascade behaviour, and the transaction-local sanction semantics are all covered by existing anchors.

35 scratch organisations accumulated since the harnesses shipped were purged directly, using the same per-organisation sanction pattern, after confirming each had zero transactions, zero invoices, and no company number.

Breaking changes

None.

v5.90.0 26 June 2026

Settings, In One Language

Settings, In One Language

What changed

The general, developer and billing settings pages now read in the same visual language as the dashboard and the organisation page: the banded layout, the mono section labels, one palette where a colour means the same thing everywhere, and the same quiet register the rest of the app uses. Nothing about how these pages behave has changed. Every field, every form, every action does exactly what it did before. The saving behaviour, the two-factor setup, the API key and webhook flows, and the Stripe billing controls are untouched. What changed is how the surface reads, not what it does.

Alongside the settings work, a corporation tax first-year period now shows its own start and end dates, where a first-year split could previously show the company year end in place of each period’s own boundary.

Why it matters

Settings used to read as a different product from the rest of the app: cards and borders and a palette that did not match the dashboard you spend your time on. Now the whole surface speaks once. The same calm band rhythm, the same legible labels, the same colour carrying the same meaning on every screen. A state that needs your attention is the colour of attention; a figure that is settled reads as settled. You stop having to relearn the page each time you cross from the dashboard into settings.

The discipline that makes a reskin safe is that it stays a reskin. The real risk in restyling a working page is that you quietly change what it does. Here, every data path was carried across untouched and then checked to be identical to the page it replaces, so the trust you already had in these screens transfers wholesale. The billing view still resolves to exactly one state per visit, two-factor still enables only against a verified code, and a developer secret is still shown once and never again.

The corporation tax fix is small, and it is the kind that matters: a date you read off your books should be the date that governs you. A company whose first year splits into two corporation tax periods now sees each period’s own start and end, rather than the shared year end repeated, so the period you are reading is the period that is real.

How it is proven

A reskin earns trust by proving it changed nothing it should not. Each settings page was rebuilt with its data layer held constant: every read, every write and every piece of state preserved verbatim from the page it replaces, then diffed to confirm the behaviour is identical and only the presentation moved. Each page was checked in both light and dark themes before it shipped.

The release crossed the sixteen-phase deploy pipeline with the executable anchor suite green and zero violations. Because the settings work adds no behaviour, those anchors are themselves the assurance that nothing beneath the surface moved: they ran unchanged and stayed green. The corporation tax date fix carries its own anchor: a test that pins each period to its own start and end, asserts the two periods in a split read different ends rather than the shared year end repeated, and confirms a straight twelve-month period is unaffected. That anchor fails if the dates are ever bound back to the shared year end, so the correction cannot quietly regress. The underlying payment dates these periods drive were already governed by the end-of-month rule proven over a closed set of thirty-one dates (migration 123) and were not changed by this release; what changed is that each period now reports its own boundary.

Operational impact

The settings surface changes in appearance only. No endpoint, request or response shape changed. Endpoint count: 215, unchanged. There are no breaking changes.

Deferred

Two pieces from 5.89.0 remain the next steps, unchanged by this release. A company whose first period runs a little over twelve months still has its corporation tax deadlines held back until the first-year split lands. This release corrects the dates those periods display, but the two deadlines themselves still wait for the split. And a company that began recording transactions before it set its accounting reference date still cannot set or correct it in settings, and sees its company details read-only until the change path for an established company arrives.

v5.89.0 24 June 2026

Your Accounting Period, From Companies House

Your Accounting Period, From Companies House

What changed

In Settings, a limited company now sets its accounting period by confirming itself from Companies House, rather than choosing a year end from a short dropdown of common dates. You enter your company number, the app reads your company back to you, the name, registered office, status, incorporation date and accounting reference date, and you confirm it. A sole trader sees a single line instead: your accounting period follows the tax year, so there is nothing to set here. The old Financial Year End dropdown, which could not express most real year ends and wrote nothing your corporation tax deadline depended on, is gone.

A limited company that has not yet confirmed its accounting reference date cannot post invoices, record dividends, or import transactions. The ledger boundary must be set before the ledger can be used. This is now enforced at the database layer, not left to the application to check.

A sole trader registered through the same flow is seeded with the correct UK tax year period automatically. A limited company is born with no period at all, which is the honest state: a company number and an accounting reference date exist only once you confirm them from Companies House, so no default is written on your behalf.

Why it matters

The dashboard in 5.88.0 prompts you to confirm your accounting reference date before it will seal a corporation tax deadline against it. This is where you do that, and the way you do it is the point. Your year end is read from Companies House, the record that actually decides it, and confirmed by you. It is never typed, and it is never inferred from a default. The date your corporation tax deadline rests on is therefore one you confirmed from the source that governs it, not a guess the app made on your behalf.

It can be set only before you record any activity. Once your ledger has a posting, your accounting period is a boundary that decides which transactions fall in which year, and moving it would re-bucket what you have already recorded. So the control sets it once, while the ledger is still empty, and it is fixed from your first transaction onward. That is a deliberate limit, not a missing feature: a period boundary is not a thing a posted ledger should let you move by hand.

The posting block for an unconfirmed limited company is the same limit, expressed at the ledger rather than the UI. Even if something reaches the database directly, the rule holds.

And a sole trader is never shown a Companies House lookup, because a sole trader has no company number and no corporation tax period. The screen you see is the one that fits the business you declared, and nothing else.

How it is enforced

The registration branch. A limited company is now born with no accounting period row and no tax year hint in its settings. A sole trader receives both, exactly as before. The branch is on the declared legal form, read at registration and written once. Neither default is applied to the other entity type.

The posting block. A database trigger fires before every transaction insert. If the inserting organisation is a limited company that has not yet confirmed an accounting period, the insert is refused and the caller receives a clear precondition code. The trigger sits at the ledger, so it catches every route that writes to it: invoices, credit notes, dividends, opening balances, and any future writer, without the application needing to remember to check.

{
  "error": {
    "code": "conflict",
    "message": "Set the accounting reference date before posting transactions."
  }
}

The trigger’s rule is “never confirmed”, meaning a company that has never sealed an accounting period, not merely one with no period currently open. This distinction matters once period close and rollover land: a company that has confirmed a period and is legitimately between periods is not blocked. The block falls only on companies that have never confirmed their year end.

The trigger’s security context mirrors the existing closed-period trigger. It runs under the application role’s tenant context, so its reads are scoped to the inserting organisation and it cannot cross-read another tenant’s data.

The confirmation flow. The lookup reads, the confirm writes, and only the confirm writes. Entering your company number and looking it up is advisory: it fetches your company and shows it to you, and it changes nothing. The single act that sets your period is the confirmation, so what is sealed is always what you saw and agreed to, never a value a stale or altered client could slip past you.

The control offers to set only when the period can still be set. Whether it is settable is read from the same condition the seal itself enforces, the ledger being at its starting point, so the screen never offers you an action the boundary would then refuse. If a transaction is recorded in the moment between the confirmation being shown and you confirming it, the set declines and the screen settles to its fixed state rather than raising an error, because by then the refusal is the correct answer.

{
  "error": {
    "code": "organisation_not_empty",
    "message": "The accounting period can only be set before any transactions are recorded."
  }
}

The view you see is gated on the legal form you declared, read as a fact at the data layer (migration 122). A declared company gets the Companies House flow, a declared sole trader gets the tax-year line, and a business whose form is not yet declared is treated as a company rather than being told, falsely, that it is a sole trader. No screen asserts a tax basis you did not choose.

The seal accepts only a year end that could be one. It must be a real month-end, since an accounting reference date always is, and it must fall within twelve months of the period start. A company whose first period runs a little over twelve months, which is the common case, therefore stays honestly empty rather than sealing a single span that is not yet a valid period, and waits for the first-year split still to come.

How it is proven

The accounting period flow is backed by anchors that open real database connections against the live schema, set a tenant context and roll back, so they prove the read-back, the settable condition and the refusal the way production runs them, not the way a mock would. They pin the company that is read and confirmed, the period that is sealed only at the starting point, the refusal once activity exists, and the form gate that keeps a sole trader out of the company flow.

The posting block is proven by nine anchors in the registration period-seed suite, all exercised against the live schema, with a tenth anchor deferred and named below. Four arms prove the block directly at the ledger: a limited company with no accounting period is refused and receives the precondition response; a sole trader is admitted; a limited company that has confirmed its year end is admitted; and an organisation whose legal form is not yet declared is admitted. Two further anchors prove the precondition routes to the correct HTTP status through each posting path, so the distinction between a missing period and a closed period, both carried in the same error class, is verified end to end. The registration branch is proven two-sided: a sole trader registration produces exactly one tax-year period row; a limited company registration produces none.

The corporation tax payment date the sealed period drives is proven over a closed set of thirty-one dates, every month end across a leap and a non-leap year plus ordinary, boundary and overflow days, so the end-of-month rule is exhausted rather than sampled (migration 123). The declared legal form it reads is a data-layer fact (migration 122). The posting block sits at the database layer (migration 124).

The release crossed the sixteen-phase deploy pipeline and the documentation pipeline’s six gates with 374 executable anchors green and zero violations, every migration registering itself against the live database on apply.

Operational impact

Settings gains the accounting period control in place of the removed dropdown. Your declared legal form is now returned by the organisation endpoint, so the app reads it from one place rather than inferring it from billing state:

curl https://app.speybooks.com/api/v1/organisation \
  -H "Authorization: Bearer $SPEYBOOKS_TOKEN"
{
  "data": {
    "entityType": "limited_company",
    "companyNumber": "SC000000"
  }
}

The dividends and opening balances routes now surface a clean precondition response rather than a server error when a limited company without a confirmed period attempts to post. No other response shapes changed. There are no breaking changes.

Endpoint count: 215, unchanged.

Deferred

Four things are honestly not finished. A company that began recording transactions before it set its accounting period cannot yet set or correct it here, because the period can only be sealed while the ledger is empty. Until the change path for an established company lands, such a company sees its company details as read-only and its corporation tax deadline stays honestly absent rather than sealed on a default. The first-year split, where a period a little over twelve months becomes two corporation tax periods with two deadlines, is still the next piece, so a company in that position sees its accounting reference date confirmed but no deadlines in the tax timeline yet. The incorporation date in the confirmation is shown in the source format pending a pass to British long form. And one further proof anchor is owed: the dashboard’s attention-count behaviour on a company with no period is confirmed by derivation but does not yet have its own executable anchor, which is why nine of the suite’s ten checks run today and the tenth is carried.

Until these land, nothing is sealed on an unconfirmed or invalid date. The control would rather show you read-only details, or an honest gap, than a year end it cannot stand behind.

v5.88.0 24 June 2026

The Verifiable Dashboard

The Verifiable Dashboard

What changed

Your dashboard is rebuilt, and it is the surface the new navigation has been making room for. It now reads from a single summary of your finances in which every figure carries its own status: a number is either proven against your ledger or shown as honestly absent, with the reason for the absence stated in plain words. Nothing on the dashboard is an estimate dressed as a fact. Across the top sits your position, and below it a running balance with the month ahead drawn as a forecast, your aged receivables, a worklist of what needs attention, and a tax timeline of your VAT and corporation tax deadlines. Any figure can be opened to show the derivation behind it, the working that produced the number rather than just the number.

Why it matters

A dashboard is the first thing you see and the thing you make decisions from, so the question that matters is whether you can trust a number without going to check it. Most dashboards cannot answer that: a headline figure is a query result, and you have no way to tell a solid number from a hopeful one. This one answers it on every tile. A figure that can be proven against your ledger shows that it is proven. A figure that cannot, because a period is not open or a scheme is not set, says so, rather than showing a zero or a guess you would mistake for real.

The honest absence is the point. You are never quietly handed a number the system itself cannot stand behind, and when a figure does carry its mark you can act on it without opening the books to confirm it. That is a different promise from a prettier chart, and it is the one a set of accounts should make.

The tax timeline is derived, not typed. Your VAT and corporation tax deadlines are computed from your own filing data and HMRC’s published rules, and only the deadlines that apply to you appear: corporation tax if you are a company, VAT if you are registered. The dates are derived correctly, including the month-end cases the obvious calculation gets a day wrong.

How it is enforced

Every figure is proven or absent, never ambiguous. A figure reaches the dashboard only as one of three things: proven, an estimate clearly marked as one, or a stated absence carrying its reason. A figure that arrives malformed, claiming to be both at once or neither, is rejected before it can render, so a number that has lost its proof cannot slip onto the surface wearing the look of one that kept it.

A deadline appears only where you have declared the fact it rests on. The legal form your business takes is recorded as a declared fact at the data layer (migration 122), and the timeline reads it: corporation tax for a company, VAT for a registered business. Where the fact is not yet declared, the dashboard prompts you to complete it rather than sealing a deadline on a guess. A wrong date carrying a confident mark is worse than an honest gap, so the gap is what you get until the fact is in.

The corporation tax payment date follows HMRC’s end-of-month rule exactly. The payment date is nine months and one day after your year end, but HMRC dates a year end that falls on the last day of a month to the first day of the tenth month that follows, which a plain nine-months-and-one-day sum lands a day early for a year end on the last day of a thirty-day month or of February. The date is now derived by the end-of-month rule itself (migration 123), so it agrees with HMRC for every year end, not just the ones the shortcut happens to get right.

How it is proven

The dashboard summary is backed by eight executable anchors that open real database connections against the live schema, set a tenant context, and roll back, so they prove the assembled figures and the tenant isolation the way production runs them, not the way a mock would. They pin the proven and absent states of each figure, the rejection of a malformed one, isolation between tenants on the assembly path, and the deadline derivation for each legal form.

The corporation tax payment date is proven separately over a closed set of thirty-one dates: every month end across a leap and a non-leap year, plus ordinary, boundary and overflow days. The space is bounded, so the rule is exhausted rather than sampled, and there is no curated subset for an error to hide in (migration 123).

The whole release crossed the sixteen-phase deploy pipeline with 339 executable anchors green and zero violations, every migration registering itself against the live database on apply.

Operational impact

The dashboard now reads from one summary it can prove, with a verification signal that rides the top bar on every page, not only the dashboard, and turns the moment your ledger stops reconciling, so a loss of integrity surfaces wherever you are working rather than waiting to be found. The reporting surface gains the dashboard summary and a standalone ledger verification check, taking the public API to 213 endpoints. There is no change to your data and there are no breaking changes.

Deferred

Two things are honestly not finished. A company’s first year usually runs a little over twelve months, which under HMRC’s rules splits into two corporation tax accounting periods with two separate deadlines; today the timeline surfaces the nearest single deadline, and the first-year split is the next piece of this work. And your accounting reference date, the date your financial year ends, is not yet editable in the app, so a company whose year end is not yet confirmed sees a prompt to confirm it rather than a deadline sealed from a default. That prompt is the honest stand-in until the editable date and the split land. Until they do, no deadline is ever sealed on an unconfirmed date.

v5.87.0 22 June 2026

The Mobile Navigation

The Mobile Navigation

What changed

On a narrow screen the drawer is gone. Navigation now runs along a bar fixed to the bottom of the screen, where the destinations you reach for most, your dashboard, your bank, your invoices, sit as tabs, with a single Create button raised at the centre. Your organisation and your account stay along the top. The rest of the menu, the less frequent destinations and your settings, the theme and signing out, gather in sheets that rise from the bottom when you ask for them. On a wide screen nothing changes: the top bar from the last release is exactly as it was.

Why it matters

A phone is held in one hand and worked by thumb, and the thumb reaches the bottom of the screen, not the top. The drawer the last release shipped was honest, it held every destination, but it asked for a tap to open and then a reach up into a panel for the thing you wanted. A bottom bar puts the common path where the hand already is. The three destinations you move between all day are one tap away in the thumb’s arc, and the action you reach for most, creating something, is the raised button at the centre, the largest target in the bar and the one nearest the thumb.

The less frequent things are not lost, they are gathered. One button opens the full menu, another opens your account, and both arrive as a sheet rising from the bottom rather than a panel sliding in from the side, so they too land under the thumb rather than across the screen from it. The rule from the last release still holds: nothing the wide screen offers is out of reach on a narrow one. What changed is that the reach is now a short one.

Proof and impact

The navigation draws only on the colour system, so its contrast meets the WCAG AA standard against its surface in both the light and the dark theme, the same checkable guarantee the colour system carries: sample a control and its background and the ratio holds. The raised Create button is held to that same standard, its label legible against its fill in both themes. The destination you are on is marked by weight and by a filled shape, not by colour alone, so the cue survives a colourblind reader or a glance in poor light, the same principle the rest of the interface follows.

Every control in the bar and in the sheets meets the minimum touch-target size, so the whole of navigation is worked by thumb, not only by a cursor. While a sheet is open it holds keyboard focus, it returns focus to where you were when it closes, and it closes on the escape key or a tap outside, so a keyboard or screen-reader user is never left stranded behind it. The organisation you are working in is announced as such to a screen reader, not left as a bare mark. The bar reserves room for the phone’s own home indicator and holds its place through the browser’s collapsing toolbars, so it stays seated wherever the page is scrolled.

There is one mobile navigation now, not two behind a setting: the drawer is retired, not hidden. There is no change to the API and no change to your data. This is the interface you see, not the kernel beneath it. There are no breaking changes.

What is next

The frame is now complete on every width, the top bar on a wide screen and the bottom bar on a phone. What it sets up is unchanged from the last release: the verification state, already carried on a wide screen, brought to where a phone can always see it too, and the dashboard beneath it filling out. The Create menu and the full menu already mark what is on its way, so when those arrive they take a place that is already waiting for them. That is next in line.

v5.86.0 22 June 2026

The Top Navigation

The Top Navigation

What changed

The left sidebar is gone. Navigation now runs along a bar at the top of the application, built on the Heather colour system the last releases put in place. The destinations you use most sit in the bar, the rest gather under a single More menu, and your organisation and your account controls sit at the right. On a narrow screen the bar folds to the brand and one menu button, and a drawer slides in carrying every destination and every action the bar holds, so nothing is lost when the width runs out.

Why it matters

A sidebar spends a fixed strip of every screen on navigation whether you are using it or not, and on a phone it spends space you do not have. Moving navigation to the top hands that width back to the work, the invoices, the ledger, the import you actually came here for, and it does so without hiding anything: the common destinations stay in view, and the less frequent ones are one click away under More rather than scattered down a list.

The change that matters most is on a small screen. A bar that merely shrank would strand the controls that did not fit, and the actions you reach for least, your settings, the theme, signing out, are exactly the ones a careless collapse drops first. Here the collapse is deliberate. Below the breakpoint the bar becomes the brand and a menu button, and the drawer behind that button carries the full set: every destination, your organisation, settings, the theme switch and signing out. The rule the drawer is built to hold is simple: nothing the bar offers on a wide screen is out of reach on a narrow one.

Proof and impact

The navigation draws only on the colour system, so its contrast meets the WCAG AA standard against its surface in both the light and the dark theme, the same checkable guarantee the colour system carries: sample a control and its background and the ratio holds. The destination you are on is marked by weight and by a filled shape, not by colour alone, so the cue survives a colourblind reader or a glance in poor light, the same principle the rest of the interface follows. Every control in the bar and in the drawer meets the minimum touch-target size, so the drawer is worked by thumb on a phone, not only by a cursor. While the drawer is open it holds keyboard focus, and it returns focus to where you were when it closes, so a keyboard or screen-reader user is never left stranded behind it.

There is no change to the API and no change to your data. This is the interface you see, not the kernel beneath it. There are no breaking changes.

What is next

The top bar is now the frame the rest of the interface hangs from. The width it returns and the structure it sets up are what the next work builds on: a verification status surfaced where you can always see it, and a fuller dashboard beneath it. That is next in line.

v5.85.0 21 June 2026

The Heather Colour System: Category Hues Applied

The Heather Colour System: Category Hues Applied

What changed

The four category hues defined in the last release are now in use. Where the interface needs to tell one category from another, an account type, a contact type, the provenance and scope of a categorisation rule, a matched key in an import, a plan tier, it now draws on four quiet hues, a teal, a steel blue, a plum and a rose, with the category always named in words and the hue sitting beside the name as a second cue. The last interactive blues have retired onto heather, so a single accent identity now carries the whole surface. With this, category, status and accent speak one shared language, the one the last release set out.

Why it matters

Telling categories apart is a different job from signalling status, and it needs a different kind of colour. Status colours make claims, paid, owed, needs attention, and the heather accent draws your eye to what matters, so none of them is free to also mean “this is an asset” or “this is a verified rule” without muddying both. The category hues are deliberately quiet and deliberately without verdict: a hue here tells you two things belong to the same group, never that either is good, settled or wrong. That separation is what lets you scan a column of account types or rule scopes at a glance and trust that the colour is grouping, not judging.

The principle from the last release holds without exception here. No category is ever carried by colour alone: every type, scope and provenance level is named in text, with the hue only ever the second cue, so the grouping survives a colourblind reader, a poor screen or a quick glance. The hues also keep their distance from the marks reserved for proof. A rule labelled verified shows its scope in a quiet hue; it does not borrow the standing that belongs to a proven figure. A category cue groups, it never certifies.

Proof and impact

Every category hue meets the WCAG AA contrast standard against the surface it sits on, in both the light and the dark theme, and that is checkable rather than asserted: sample any hue and its background and the ratio holds. The rule that no category is carried by colour alone is a rule of the colour specification, inherited by every new control, so it cannot be dropped one screen at a time, and a hue that does not clear its contrast target does not enter the system.

There is no change to the API and no change to your data. This is the interface you see, not the kernel beneath it. There are no breaking changes.

What is next

This completes the category, status and accent migration, the user-facing heart of the colour system. The surface that carries your status, your accents and your categories is now one documented palette, and it becomes the foundation the rest of the interface is built on from here. A few elements in the dark theme are still being brought fully onto the system and can read with slightly lower contrast until they are; they carry no status or meaning, and that polish is next in line.

v5.84.0 20 June 2026

The Heather Colour System: Meaning Never by Colour Alone

The Heather Colour System: Meaning Never by Colour Alone

What changed

The interface now runs on one documented colour system built on heather, the deep purple that is SpeyBooks’ accent throughout: buttons, links, the active item in the sidebar, chart fills, and the marks that draw your eye to what matters. Green has been retired. The figures, status marks, and accents that used to be green now use heather or a neutral content colour, so that colour signals a role rather than decorating a surface. Underneath, a palette of four quiet category hues is now defined. It is the groundwork for telling categories apart, a key type or a rule scope, without leaning on the colours that already carry meaning.

Why it matters

A colour in an accounting interface is a claim. Green reads as good or settled, red as wrong or owed, and an interface that uses those colours loosely makes claims it cannot stand behind. Under this system every colour means one thing and means it everywhere, so what you read is consistent from the dashboard to the ledger to a report, and a colour never tells you something the numbers do not.

The heart of it is this: no status is ever carried by colour alone. A paid invoice is labelled paid and an overdue one labelled overdue, with the colour sitting on top of the words as a second cue, never the only one. That is what keeps the interface legible if you are colourblind, reading on a poor screen, or glancing quickly, and it holds because it is a property of the system rather than a courtesy applied screen by screen. The surface should feel as trustworthy as the books are proven, and that begins with never being misled by a colour.

Proof and impact

Every colour in the system meets the WCAG AA contrast standard against the background it sits on, and body text on the page background meets the stricter AAA level. This is checkable, not asserted: sample any colour and the surface behind it, and the ratio holds. A colour that does not clear its target does not enter the system, and the rule that meaning is never carried by colour alone is built into the specification a new control inherits, so it cannot be forgotten one page at a time.

There is no change to the API and no change to your data. This is the interface you see, not the kernel beneath it. There are no breaking changes.

What is next

Two parts of the system are deliberately still to come, each shipping as its own release. The four category hues are defined but not yet applied: the key pills and the scope tabs that will carry them are next, with every category still named in text so the hue stays a secondary cue. And a few interactive blues remain in the interface; they retire onto heather in the same line of work, so that a single accent identity carries the whole surface rather than two.

v5.83.0 20 June 2026

Scheduled Retention Purge: Disposal at Window Close

Scheduled Retention Purge: Disposal at Window Close

What changed

The scheduled equivalent of organisation erasure, left disarmed in 5.82.0, is now armed. SpeyBooks can dispose of an organisation automatically once its seven-year custodial window has closed and it has been deactivated, without an administrator initiating each one by hand. Every disposal now records whether it was made by an administrator or by the scheduler, so the two are told apart in the record that outlives the data. The schedule itself is a separate, deliberate switch: the capability ships armed, and begins running only when it is turned on.

Why it matters

A custodial commitment to hold records for exactly seven years, no longer, is only as good as the thing that lets them go at the end. Doing that by hand for every organisation does not scale, and it makes the most destructive act in the system depend on someone remembering to perform it. This release makes the discharge of the commitment something the system does on its own, on the same gated and provable terms as the manual route, so storage limitation is honoured without a person in the loop for each case.

The terms are deliberately strict, because the act cannot be undone. An organisation is eligible for automatic disposal only when two things are both true: its seven-year window has closed, measured from its last closed accounting period, and it has been deactivated. Deactivation is not a convenience here, it is a safety property. A deactivated organisation cannot open a new accounting period, so its window cannot quietly re-open between the moment the scheduler judges it eligible and the moment it disposes of it. A live organisation, however old its records, is never selected. The asymmetry is the whole point. The customer’s statutory duty is to keep these records for six years; SpeyBooks holds them for seven, a deliberate year beyond it. Holding longer still is only a storage cost. Disposing early erodes the margin SpeyBooks committed to, and disposing before the sixth year destroys records the customer is still required to keep, which cannot be undone. So the schedule errs the only safe way.

Until 5.82.0, a disposal record could not say whether a disposal was an administrator’s decision or an automatic one. They are now distinguished in the surviving record, so an audit of disposals can tell a deliberate administrative erasure from a scheduled one at a glance.

How it is enforced

Eligibility asks the same question, the same way, as the manual route. The scheduler decides closure with the very component the manual erasure uses, computed from the latest closed accounting period on the database clock. An organisation with no closed period produces an indeterminate window and is never selected, exactly as the manual route refuses it. The schedule cannot reach a more permissive verdict than a person could, because it is the same verdict.

Both conditions are required, and the deactivation condition freezes the window. Disposal proceeds only when the window is closed and the organisation is deactivated. The deactivated condition does double duty: it is the operator’s confirmation that the organisation is finished, and it is what makes the eligibility check safe against a window re-opening underneath it, since a deactivated organisation cannot add the period that would re-open it.

Each disposal reuses the manual routine, not a parallel copy of it. The scheduler does not reimplement the irreversible act. It selects, then hands each organisation to the same two-phase, recoverable, data-layer-guarded routine that backs the manual erasure, so the disposal a schedule performs is identical in every safety property to one an administrator performs. One organisation is disposed of per pass, in order, and a failure on one does not stop the rest.

Every run leaves a receipt, even an empty one. Each pass of the scheduler records what it did: how many organisations were eligible, how many were disposed of, skipped, or failed. A run that finds nothing due still writes its receipt, so a quiet schedule is provably a schedule that ran and found nothing, never a schedule that has silently stopped. (Enforced at the data layer, migration 121.)

Automatic and administrative disposals are distinguished in the record. The disposal record now carries which kind of disposal it was, and the database requires that mark to be present on every organisation disposal and absent on everything else. A scheduled disposal cannot be recorded as an administrative one by omission, because the database refuses a disposal record that does not say which it is. (Enforced at the data layer, migration 121.)

How it is proven

Every guarantee above ships with an executable check that runs on each deploy, against the live database rather than a mock. The new anchors cover each reason an organisation is held back, an active organisation, a deactivated one still inside its window, a closed window on an organisation that was never deactivated, and one with no accounting period at all, and they prove the distinguishing mark lands correctly: a scheduled disposal is recorded as scheduled, an administrative one as administrative, in the surviving record.

The release went out with all sixteen pipeline phases green and zero violations: 292 executable checks across 27 files, the surface-money and schema-coverage and provenance gates all clean, and the migration self-registered and verified. The change rests on one database migration, applied under the same review the destructive routine earns, with a dry run proving every gate against the live schema before the real apply.

Operational impact

There is no change to the API surface; this is a scheduled operation, not an endpoint. The disposal record gains a field recording whether a disposal was administrative or scheduled, set on every organisation disposal from now on, and existing administrative disposals are classified as such. A successful scheduled disposal removes every dependent record in one cascade and leaves its disposal record behind, the same as the manual route. There are no breaking changes.

Deferred

The schedule arms the capability but does not start it. Turning the schedule on is a separate, deliberate act, and at this stage no organisation is yet old enough to be eligible, so there is no urgency to start it. The external timestamp proof on the disposal record, an OpenTimestamps receipt, remains deferred to its own release, as in 5.82.0. One boundary is recorded and held: where a customer holds capital items that carry a longer statutory retention than seven years under the VAT Capital Goods Scheme, that obligation sits with the customer as the controller of their records, and a setting to flag such organisations and hold them back from the schedule will ship before any customer in that position is taken on.

v5.82.0 20 June 2026

Organisation Erasure: Post-Window Disposal

Organisation Erasure: Post-Window Disposal

What changed

SpeyBooks can now erase an organisation and all of its records, deliberately and irreversibly, and only after that organisation’s seven-year custodial window has closed. The erasure leaves a disposal record that outlives the data it removes, so the fact and timing of a disposal stay provable after the organisation is gone. The hard-delete route disabled in an earlier release stays disabled, and now points the caller at this sanctioned routine.

Why it matters

Erasing a company’s accounting records is the most destructive thing this system does, and the one it has to get right. Too early, and you destroy records the company is legally required to keep. Carelessly, and you leave orphaned data behind, or no proof the disposal ever happened. This release makes erasure a gated, recoverable, and provable act rather than a delete.

Your records are held for a full seven years, measured from your last closed accounting period. That is SpeyBooks’ custodial commitment, a year above the six years HMRC requires of you, held on your behalf. SpeyBooks will not dispose of them a day before that window closes, and that holds even against an administrator. There is no path that erases an organisation while its window is open, and an organisation that has never closed a period cannot be erased at all, because an irreversible act does not proceed on a window it cannot prove has passed.

With the deactivation shipped in 5.81.0, this closes the organisation lifecycle from both ends. Deactivate to lock an organisation out while keeping every record; erase only once the custodial window has run its course. The two are the same commitment seen from each side: SpeyBooks holds your records on your behalf for exactly as long as it must, and no longer.

How it is enforced

The window is decided in one place, on the database clock. The seven-year window is computed from your latest closed accounting period, with the arithmetic and the closed comparison run inside the database against its own clock, so the verdict cannot drift on an application server’s time. An organisation with no closed period produces an indeterminate window, never a silently closed one, and is refused. The same component will decide closure for the scheduled purge that follows later, so both answer the question identically.

The act runs in two committed phases, not one. The first phase records the disposal and the organisation’s terminal audit chain head, and commits, before anything is removed. The second authorises and carries out the delete, and every dependent record, across forty-nine linked tables, is removed in a single cascade. Splitting the act in two is what makes it recoverable: if it is interrupted after the record is written but before the delete completes, a re-run finds the durable record and resumes at the delete. There is no half-erased state, and no disposal recorded twice.

The delete is guarded at the data layer, not in the calling code. A database-level control refuses any organisation delete that is not explicitly authorised for that exact organisation, and only for the life of a single transaction. The one delete that succeeds is the one the routine sanctions, for the one organisation it sanctions; a stray or mistaken delete from any other path is refused by the database itself. Enforcing the rule below the application is deliberate, so that code written later cannot forget it. (Enforced at the data layer, migration 119.)

The disposal record survives the disposal. Every erasure leaves a record carrying the organisation’s terminal audit chain head and the lawful basis and timing of the disposal. The record holds no link back to the organisation, so it survives the cascade that removes everything else. Anyone holding a prior export of their data can confirm it hashes forward to that head, so a disposal stays verifiable after the organisation no longer exists.

How it is proven

Every guarantee above ships with an executable check that runs on each deploy, against the live database rather than a mock. Nine anchors cover the within-window and no-period refusals and that they write nothing, the post-window disposal and its single surviving record, recovery from an interrupted run, the already-erased response, and the guard refusing an unauthorised delete and passing an authorised one. The suite commits for real rather than rolling back, because the recovery property only exists if the first phase is durable across a failure of the second.

The release went out with all sixteen pipeline phases green and zero violations: 294 executable checks in total, the surface-money and schema-coverage and provenance gates all clean, the documentation regenerated to 212 endpoints, and the service reloaded healthy. No database migration and no manual step were required; the schema this routine stands on landed in an earlier release.

A within-window request is refused before anything is touched, and tells the operator when disposal becomes available:

curl -X POST https://api.speybooks.com/v1/admin/orgs/org_8f2a.../erase \
  -H "Authorization: Bearer $TOKEN"
{
  "success": false,
  "error": {
    "code": "RETENTION_WINDOW_OPEN",
    "message": "Erasure refused. The organisation's records are within the seven-year retention window, which closes on 2031-03-31. SpeyBooks holds these records on the customer's behalf until then. To lock the organisation out now, deactivate it instead."
  }
}

Operational impact

The API surface goes from 211 to 212 endpoints. The erase action is staff-only, under Admin, and writes an administrative audit entry on every outcome, refusal as well as success. A successful erasure disposes of every dependent record in one cascade and leaves its disposal record behind. There are no breaking changes.

Deferred

The external timestamp proof on the disposal record, an OpenTimestamps receipt, is deferred to its own release; until then the audit chain head stands as the verifiable anchor, and existing records will be backfilled when it ships. A scheduled equivalent that disposes automatically at window close is planned, gated on a VAT Capital Goods Scheme determination, and stays disarmed until that is settled.

How we version

SpeyBooks follows Semantic Versioning — a system designed for predictability.

X Major — Breaking changes (rare)
Y Minor — New features
Z Patch — Bug fixes

See something broken? Check our live bug tracker.

© 2026 Spey Systems Ltd