GET endpoints over plain HTTP — no authentication, no framework, no middleware stack. The server defaults to port 8787 and is overridable with the PORT environment variable. Every response you receive follows the six contracts below without exception; read them once and you will not be surprised by any individual route.
Base URL
Route Index
Six Response Contracts
These rules apply to every route. They are what makes the responses safe to consume at scale.1. Money Is a Decimal String in Drops
One XRP is 10⁶ drops. Every monetary quantity in every response is a plain decimal string of drops — never a JSON number. XRPLNUMBER fields carry up to 19 significant digits and may arrive in scientific notation. Parsing them into a JavaScript number destroys precision silently; BigInt("1e17") throws outright. Use decimal.js at precision 40.
"periodicPayment": "3333346.017258355048".
2. Absent Means Zero
rippled omits any field whose value equals the type default. A vault carrying no unrealised loss has noLossUnrealized key at all in the raw ledger object — not "0". Orma normalises this in one place (num() in src/num.mjs) and guarantees that every numeric field in every response is always emitted, defaulting to "0" when the ledger omits it.
Two deliberate exceptions use
null to mean “not known” rather than “zero”:- On
broker-history,debtBefore,debtAfter,coverBefore, andcoverAfterarenullwhenbrokerStateKnownisfalse. An impairment does not touch theLoanBrokerobject, so the transaction genuinely does not report the book. Emitting"0"would read as an empty book — a different and worse claim. - On the
pledgeblock of/api/mpt/:mptId/navand/api/mpt/:mptId/resolve,valueHeld,valueReported, andoverstatementarenullwhen the resolved document carries nounitValue. An unvaluable pledge is not a pledge worth zero.
3. Every 200 Carries a Ledger Stamp
serverTime and ledgerIndex are prepended to every successful response body. serverTime is the ledger close time converted from the Ripple epoch (offset 946684800) — not the server’s wall clock. Two fields read in the same response come from the same ledger.
GET /api/health builds these two fields itself rather than going through stamp(), because it reports on the reader’s state before the reader has necessarily connected.
4. Default List Order Is Worst-First by Grade
GET /api/vaults sorts by gradeNumeric ascending. It does not sort by navDivergenceBps.
navDivergenceBps only becomes non-zero once a loss has been declared. A manager sitting on an overdue book who has declared nothing reports a divergence of zero — a divergence sort would put the most dangerous facility at the bottom of the list. In the live capture, Kestrel Bridge Financing II sorts first at gradeNumeric: 26 with navDivergenceBps: 0, above Meridian Trade Finance I at gradeNumeric: 89 with navDivergenceBps: 1961. That inversion is the thesis, not a sorting bug: Meridian declared its loss and holds cash against every remaining claim; Kestrel has two distressed loans and reports par.
5. Errors
Every error response uses this envelope:
A vault id must match
[A-Fa-f0-9]{64} and an MPT issuance id [A-Fa-f0-9]{48}. Lowercase hex is accepted and upper-cased before lookup.
6. CORS and Public Cache Headers
Every response carriesAccess-Control-Allow-Origin: *, Access-Control-Allow-Headers: *, and Access-Control-Allow-Methods: GET,POST,OPTIONS unconditionally. OPTIONS returns 204.
The default cache policy is Cache-Control: no-store. Three routes opt into Cache-Control: public, max-age=4:
GET /api/vaults/:vaultId/navGET /api/mpt/:mptId/navGET /api/mpt/:mptId/resolve
max-age=4 matches the 4 000 ms poll interval in src/poll.mjs, so a cache can never serve a figure older than one reader tick.