Skip to main content
Orma exposes eleven 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

Orma is self-hosted. There is no hosted cloud URL. The service runs against XRPL Devnet because XLS-65 vaults and XLS-66 lending are not yet amended into Mainnet.
All data comes from XRPL Devnet. The network field is hard-coded to "devnet" in every response. health.source tells you whether the data came from a live Devnet read ("devnet") or from the recorded fixture ring ("fixtures").

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. XRPL NUMBER 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.
A real captured value that breaks a float: "periodicPayment": "3333346.017258355048".
For chart pixel positions, Number(x) after dividing to a human scale (e.g. dividing drops by 1 000 000 to get XRP) is acceptable. For anything displayed as a monetary figure, it is not.

2. Absent Means Zero

rippled omits any field whose value equals the type default. A vault carrying no unrealised loss has no LossUnrealized 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, and coverAfter are null when brokerStateKnown is false. An impairment does not touch the LoanBroker object, so the transaction genuinely does not report the book. Emitting "0" would read as an empty book — a different and worse claim.
  • On the pledge block of /api/mpt/:mptId/nav and /api/mpt/:mptId/resolve, valueHeld, valueReported, and overstatement are null when the resolved document carries no unitValue. 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 carries Access-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/nav
  • GET /api/mpt/:mptId/nav
  • GET /api/mpt/:mptId/resolve
These routes exist to be called by parties with no relationship to Orma — that is the point of writing a URL into a token. They serve read-only public ledger state, and 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.
The metadata that carries the NAV URL is written into the MPTokenIssuance at creation and can never be rewritten. The contract at the end of those three routes is therefore effectively permanent: fields may be added, but nothing may ever be renamed, retyped, or removed.