Settlement coherence across credit reversals, and a wire conformance gate
What changed
Two changes, both about trusting the numbers rather than a new surface to call.
Reversing a credit note’s allocation now returns the affected invoice to its true settlement status. When a credit note offsets an invoice in full, the invoice reads as credited. Reverse that allocation and the invoice no longer stays stuck on credited: it returns to sent if nothing has been paid, or partial if a payment still remains, with its outstanding balance restored in the same step.
curl -X POST https://api.speybooks.com/v1/credit-notes/cn_8f2a/allocations/cna_3d10/reverse \
-H "Authorization: Bearer $TOKEN" \
-H "Idempotency-Key: 7c3f...redacted" \
-d '{ "reason": "duplicate credit" }'
{
"success": true,
"data": {
"creditNoteId": "cn_8f2a",
"allocationId": "cna_3d10",
"invoiceId": "inv_4b71",
"invoiceStatus": "sent",
"invoiceOutstanding": "10000"
}
}
Separately, a check now runs on every deploy that verifies every money and rate field the API serves is an integer in its declared minor unit: amounts in pence, unit prices in micro-pounds, percentage rates in basis points. If any field were to serve a float or a string where an integer minor unit is required, the deploy stops before that change can reach you.
Why it matters
Your invoice list should never lie to you. The guarantee that an invoice returns to the right status after a credit reversal previously rested on the reversal being the clean inverse of the allocation. That coherence is now proven mechanically, over both the payment side and the credit side, so a reversed credit can never leave an invoice reporting credited while it is actually open and owing. You reverse, and the status and the outstanding balance you read back are the real ones, every time.
The wire conformance gate is about trusting the shape of the money itself. Money and rate values cross the API as integers in a fixed scale, never as floats, so nothing is ever lost to a binary rounding artefact in transit. That has been the rule for some time; what is new is that it is now checked at the door on every deploy. A field that drifted off the integer minor unit would once have needed a human to notice. Now it fails the deploy closed, so the drift never ships silently in the first place.
Proof and impact
Settlement coherence is enforced at the database state machine, which makes the reversal edges legal (migration 116), and is proven by the credit-note reversal test: it drives a real allocation to credit an invoice, reverses it through the live route, and reads the restored status back from the ledger for both the unpaid case (returns to sent) and the part-paid case (returns to partial). The status is not modelled, it is the one the service writes.
The wire conformance gate checks the declared type and minor unit of every money and rate field against the freshly generated API specification at deploy time, and aborts the deploy on any field it cannot account for or that carries the wrong scale. It witnesses the contract the API publishes; the runtime values themselves remain covered by the existing money handling.
invoiceOutstanding is returned as a string to preserve full precision on large balances.
Breaking changes: none. No endpoint, request, or response shape changed. These are guarantees tightened over the surface you already use.
Known issues
None.