v5.75.0 17 June 2026 Feature

Credit Note Read Surface: Retrieving and Listing Credit Notes

Why This Matters

Until now you could create, issue, allocate and reverse a credit note, but you could not read one back. The API could change a credit note’s world without letting you see the result. Anything that needed to show a credit note, reconcile what it had credited, or display a customer’s outstanding credits had to reach past the API into the database.

This release adds the read surface. You can retrieve a single credit note as a complete document, with its lines, its full allocation history, and the invoices it credits, and you can list and filter credit notes for an organisation. The credit-note lifecycle is now observable through the same API that drives it.


Retrieving a credit note

A new endpoint returns one credit note as the full document object: the header totals, the lines, the settlement block, and the invoices the note currently credits.

  • Header and lines. Header totals (subtotal, VAT, total) are in pence. Each line carries its quantity, unit price, VAT rate, and net, VAT and line total, read from the values the issue path computed and stored, where the line total always equals net plus VAT.

  • Settlement, the full history not just the position. The settlement block reports allocatedTotal and remaining over the live (non-reversed) allocations, and an entries list of every allocation the note has ever carried, live and reversed alike, each flagged with isLive. A reversed entry keeps its original amount and records who reversed it, when, and why. You see the current state and the trail that produced it in one read.

  • Related invoices. The invoices the note currently credits are listed as typed ids with links, derived from the live allocation set, so a reversed allocation drops out of the relation as you would expect.

  • Cross-organisation isolation. A credit note belonging to another organisation returns 404, not a permission error that would confirm its existence. This is enforced by row-level security and asserted directly by the read route test.


Listing credit notes

A second endpoint returns a paginated list, filtered by type (sales or purchase, default sales), status, contact, and an issue-date range. The collection is wrapped under data.creditNotes, and meta carries page, perPage, total and pages.

Each row is a lighter projection: the header totals, the contact summary, the status, and the live allocated and remaining totals. The lines and the allocation entries stay on the single-note object, so a list stays cheap to fetch and a full reconciliation is one note away.


Money and rates at their exact unit

This is the part worth dwelling on, because it is where a financial API usually loses precision quietly.

A final money amount needs penny resolution. A unit price or a tax rate needs more. If a system stores both at penny resolution, a half-point VAT rate or a sub-penny unit price is rounded the moment it is read, and the line arithmetic stops reconciling. SpeyBooks serves each field at the resolution it actually needs, all as integers, never as floating point:

  • Amounts are integer pence.
  • Unit prices are integer micro-pounds, millionths of a pound. A unit price of GBP 500.000000 is 500000000. A sub-penny price such as GBP 4.259500 survives exactly as 4259500, where rounding it to pence would lose it.
  • VAT rates are integer basis points, hundredths of a percent. 20% is 2000 and 17.5% is 1750. A half-point rate is exact, where an integer percent would mangle it.
  • Quantities are full-scale decimal strings, for example "1.0000".

The benefit is that what you read reconstructs what was entered, with no silent rounding between the two. The read route test pins this: 17.5% serialises as 1750, and a sub-penny unit price serialises as 4,259,500 micro-pounds while its rounded line amount stays 426 pence. Exact rate, exact price, correct money, all in the same response.


Operational Impact

  • A credit note and its full settlement can be displayed and reconciled through the API, with no database access.
  • The allocation history is visible on every credit note: live and reversed allocations, each with its reason and the user who reversed it, so a correction is auditable from the read alone.
  • Credit notes can be listed and filtered by type, status, contact and date, with stable pagination metadata.
  • Unit prices and VAT rates come back at full precision, so a half-point rate or a sub-penny price reconciles against the line total instead of drifting by a penny.
  • Both endpoints are read-only and additive. No existing integration changes.

Versioning

No change to the internal axiom registry. The read surface adds no axioms and no invariants; it reads what the write path already guarantees. No public API surface was removed or changed in a breaking way. Two endpoints were added, both read-only.


Files Changed

Backend:

  • Credit note service
    • Two read methods: the single-note fetch and the filtered, paginated list
  • Credit note routes
    • The two read endpoints, with the response shapes and the settlement serialiser
  • Tests
    • A new read route test (full-object serialisation, the half-point rate and sub-penny price cases, the live-and-reversed settlement, cross-organisation 404, malformed and wrong-type id rejection, and the list shape with its filters)

Frontend / Docs site:

  • The API reference gains the two read endpoint pages, generated from the OpenAPI specification. No portal UI change.

Known Issues

  • The permitted next states of a credit note (its available transitions) are not yet served on the object. They join the read additively when the first mutating route that needs them ships. Reading a credit note today tells you its current status, not the moves allowed from it.

The read surface makes the credit-note lifecycle observable through the API, and sets up the credit-note void surface still to come.