Values
Four fields on a Loan, a Receipt or a Client carry an instant, and each ends in_at. Every other date-bearing field carries a civil date, whatever its name: anchor, value_date, due_on and the on an event body takes.
Five values carry a fixed form on the wire.
One issuance body carries every form, and a refused request answers the fifth.
facility_id names the Facility the Loan is drawn from, which binds the amount, the price, the Client, the product and the window. decision_id names the Credit decision the Loan was issued under, which binds nothing at all.
Both are part of what a reused Reference restates: a repeat that moves either is refused as a conflict, the same way a repeat that moves the principal is.
Fields
A Reference is your own identifier for one record you asked the Platform to write, and the first request carrying it answers201. Every write on the loan book carries one: an issuance, a reported Receipt, each of the nine irreversible acts on a Loan, and a Receipt reversal. A Product configuration carries none — the Platform composes its identifier — which is why creating one takes an idempotency key instead. Every path begins /v1/tenants/{tenant_id}/, and a key acts for that one Tenancy.
A retry after a lost response is the same act named twice. Sending the identical request again answers 200 with what was recorded, so a caller that never saw its first answer can ask again without recording a second cancellation, a second write-off or a second reversal.
Each identifier is chosen by one side, has one shape, and behaves one way on a repeat.
A statement the retry omits is compared as
none, so dropping a field the first request carried is refused rather than replayed.
A repeat is compared against what is recorded now, not against the first request. So after a Client’s label is corrected, the original registration body no longer agrees and is refused 409, while the corrected label registers as the ordinary retry.
Attribution
Attribution is who performed an irreversible act and on what basis, both stated by you. The nine acts on a Loan and a Receipt reversal each require anactor and a basis, and a request omitting either is refused 422 naming the field.
The Platform records both beside the fact, echoes them on every read of it, and reads neither. No figure moves because of what either says, and there is no vocabulary of admissible reasons for an act on a Loan: a list of write-off grounds supplied by us would be read by an auditor as which criterion you satisfied, which is worse than text you wrote and answer for.
Neither is ever derived from the key that carried the request. The only actor a credential could name is your integration, and every fact on your book is already yours — the person who clicked the wrong button is inside your own system, on the far side of a boundary the Platform cannot see.
A fact recorded before Attribution existed carries none. The
act object is absent in whole rather than present with blanks, so its absence reads as recorded-before and never as unknown-who.
A Receipt reversal is the one act that also requires a reason, from a closed set of three: chargeback, returned_transfer, operator_error. A reason outside it is refused 422. All three restore the same debt in the same way, from the Receipt’s own value date; the field exists so that a year later the record can say which it was, and no figure reads it.
The set is closed here and absent on a Loan’s acts because the two are different statements. A reversal has three causes and each is a fact about what the money did. A write-off’s ground is a credit judgement, and nobody shares a vocabulary for one.
Context
Context is a flat object you attach to a record you create, which the Platform stores verbatim, echoes wherever that record is read in full, and never reads itself. A Loan carries it underterms, a Receipt beside the Receipt, and an act inside the act object on its event. The Loans list does not carry it, because that list carries no terms at all — it exists to find a Loan, and reading the Loan is what answers with everything it holds.
An issuance, a reported Receipt and each of the nine acts accept one under the field
context. Nothing indexes it, searches it or derives from it, and it is immutable once recorded — a repeat under the same Reference that attaches a different Context is refused 409 like any other disagreement.
The Platform answers it back with its keys in sorted order. Every key and every value survives exactly; the order you sent them in does not, because a JSON object carries none.
Context on an issuance
Pages
A collection is served in pages behind an opaque cursor, read from two query parameters.
A page answers
cursor when the walk continues and omits it at the end of the listing. Read the end from the absence of a cursor and never from the length of a page — the two are not the same question, and two routes make the difference visible:
- The feed answers a cursor on every page that served anything, including the last, because a feed is walked again tomorrow and a listing is walked once. Catching up is a page of no entries with a cursor.
- The loans listing under
?state=filters an answer the Platform derives rather than stores, so it walks the book under a published bound of ten timeslimitrows examined. A page can be short, or empty, and still carry a cursor: it means the walk continues, not that another matching Loan exists.
400 under invalid_cursor rather than served from a guessed position.
Walking the pages of a collection while records arrive under you serves every entry that existed for the whole walk exactly once, and never one twice. An entry created part-way through may or may not appear, and never displaces one that was already there.
Five collections page today, all of them on the loan book, and the source ledger pages under a scheme of its own. The other listings — Clients, Products, Facilities and the published Credit policies — answer whole and will page under these same two parameters.
Derivation
A read that answers as of a date names it in the query parameteron. A request without it is refused, because the Platform chooses no date for a lending read. A date that has not arrived is answered.
Each figure is either computed at issuance and stored, or derived by the read that asks for it.
Invariants
Five properties hold across every figure the Platform reports.- Derived. Every figure is derived from the facts on record.
- Not accumulated. No figure is stored as a running total.
- Not edited. A wrong fact is withdrawn rather than changed, and every figure is re-derived without it.
- As-of. The Platform derives a dated figure for the date your request names, and for no other.
- Versioned. A Product change never disturbs a Loan already issued.
Delivery
The Platform opens no connection toward you. Every answer travels on a request you make, and no callback URL, webhook or notification exists to configure. Nothing reaches you late as a result, because of who writes. Every fact the Platform holds arrived from you, through your key or through your own people in the Console, so there is no fact it could send that you do not already hold. And no figure turns over on its own: a Period is not marked overdue at midnight, it is overdue as of the date your request names. Four things a webhook would be for, and what you do instead.
Polling on an interval you choose is what this replaces, and a date that has not arrived is answered, so a read can run ahead of the day it asks about.
The feed
The feed is the recorded events of your Loans, in the order the Platform wrote them down, behind the cursor every collection pages with. Each entry names the Loan, what happened, the civil date the fact is about and the moment it was recorded. A withdrawal is not among them. Retracting a fact records a withdrawal against that fact rather than a new event on the Loan, so the feed carries the acceleration and not its retraction — read the Loan the entry names, where the fact stands carrying itsretraction.
GET /loan-events?limit=2
- The order is recording order and not the dates the facts are about. A write-off you record today for a judgement taken in November appears at the end, where it was written, which is what makes “what changed since Tuesday” answerable.
- The cursor is answered whenever a page served anything, including the last one, so a caller that has caught up still holds a position to return with. Catching up is a page of no entries.
- The feed carries recorded facts only. A Loan’s state, its Arrears and its closure are derived, and are derived again whenever the evidence moves, so there is no moment at which any of them changed for the feed to publish. An entry tells you which Loan to read.
Restrictions
Seven refusals follow from the conventions above.- No Reference guards Product creation, which writes a second Product on every request rather than replaying.
- No Reference is spent by a request the Platform refused, so the corrected request records under the same one.
- No Reference crosses a family: the nine acts on a Loan share one namespace, Receipt reversals hold another, and neither reaches the Reference an issuance or a Receipt was recorded under.
- No figure the Platform cannot state arrives as
null: the field is omitted, andanomaliesnames why. - No amount is converted, because two currencies never meet in one operation.
- No per-Loan read answers a date before a Loan’s anchor, neither the Arrears nor the Schedule, and the
payment-optionsread leaves such a Loan out. - No collection sorts on request. Each carries the one order it publishes, and two narrow: the Loans list by
client_id,configuration_idandstate, and the Facilities list byclient.