API documentation
Everything another system can call on this service. Replace YOUR-SERVICE-URL in the examples with this site's own address.
Signing in with a key
Staff create a key on the API keys page and send it on every request:
Authorization: Bearer nxs_live_…Keys can be revoked at any time. Public verification pages need no key. Scheduled jobs use the cron secret header x-cron-secret.
Choosing the XRP ledger
Anything recorded to the ledger goes to the app-wide setting on the admin overview page. A key can be set to always use the live ledger or the test ledger instead, and a single call can pick one directly:
x-xrpl-network: testnetAccepted values are mainnet and testnet; you can also send network in the request body or as a query string. Responses include the ledger that was used, and test transactions appear on test.xrpscan.com.
Every write request should also include a unique x-idempotency-key. Retrying the same request returns the original transaction instead of submitting another.
Only one anchor is submitted at a time per ledger. A request that arrives while another anchor is mid-flight returns 409 with state: "busy" — retry shortly. A request that fails before anything is sent to the ledger returns 400 with state: "failed_before_submit", which guarantees no XRP was spent. Once a transaction is accepted, the response is always a success containing the transaction hash; any later bookkeeping problem appears in a warnings list.
Receiving events
How AWC Nexus pushes loan and payment activity into this registry.
POST /functions/v1/ingest
Receives loan, party, payment, schedule, document, risk, file and version events from AWC Nexus.
Access: Signed with the ingestion secret
Send header x-nexus-signature: sha256=<hex>, an HMAC-SHA256 of the exact request body using INGEST_SIGNING_SECRET. Repeat event_id values return duplicate: true. Events for a loan that is not mirrored yet return 202 deferred.
Example request
# Node.js — sign the exact body you send
import { createHmac } from "crypto";
const body = JSON.stringify({
event_id: "8f1c2b6e-0f3e-4a1b-9c77-2b2d6a4e1111",
event_type: "loan.upserted",
occurred_at: new Date().toISOString(),
data: {
source_loan_id: "0b6f6a52-51a1-4f3a-9c4c-3f6f1a2b7c10",
loan_number: "AWC-10241",
loan_type: "bridge",
loan_amount: 450000,
current_loan_balance: 431200,
interest_rate: 10.75,
term_months: 18,
maturity_date: "2027-03-01",
registry_tier: "certified_active",
stage: "servicing",
status: "current",
start_date: "2025-09-01",
certified_at: "2025-09-05T14:00:00Z",
property: { city: "Austin", state: "TX" }
}
});
const signature = createHmac("sha256", process.env.INGEST_SIGNING_SECRET)
.update(body)
.digest("hex");
await fetch("https://YOUR-SERVICE-URL/functions/v1/ingest", {
method: "POST",
headers: {
"content-type": "application/json",
"x-nexus-signature": `sha256=${signature}`
},
body
});Example response
{ "ok": true, "event_id": "8f1c2b6e-0f3e-4a1b-9c77-2b2d6a4e1111" }
// repeat of the same event_id
{ "ok": true, "duplicate": true }
// loan not mirrored yet (HTTP 202)
{ "ok": false, "deferred": true, "reason": "loan not found" }POST /api/public/ingest
The same endpoint under its native path.
Access: Signed with the ingestion secret
Example request
curl -X POST https://YOUR-SERVICE-URL/api/public/ingest \
-H "content-type: application/json" \
-H "x-nexus-signature: sha256=$SIGNATURE" \
-d '{"event_id":"…","event_type":"payment.upserted","data":{
"source_loan_id":"0b6f6a52-51a1-4f3a-9c4c-3f6f1a2b7c10",
"source_payment_id":"c31e…","payment_number":3,
"due_date":"2026-01-01","payment_date":"2026-01-01",
"amount":4031.25,"status":"paid"}}'Example response
{ "ok": true, "event_id": "…" }POST /functions/v1/ingest — party.upserted / party.identifiers.changed
Sends a borrower or lender along with every email, phone, name variation (Bob / Robert) and up to 10 EINs. Matching emails, phones or EINs automatically attach the record to the same person.
Access: Signed with the ingestion secret
Names are stored as variations but never used on their own to merge two people. EINs must be 9 digits; anything past the 10th is ignored.
Example request
curl -X POST https://YOUR-SERVICE-URL/functions/v1/ingest \
-H "content-type: application/json" \
-H "x-nexus-signature: sha256=$SIGNATURE" \
-d '{"event_id":"…","event_type":"party.upserted","data":{
"source_user_id":"7c1e…","full_name":"Robert McIntosh",
"emails":["bob@example.com","robert@company.com"],
"phones":["+1 (555) 010-2233"],
"names":["Bob McIntosh","Bobby McIntosh"],
"eins":["12-3456789","98-7654321"]}}'Example response
{ "ok": true, "event_id": "…" }
// party not mirrored yet (HTTP 202)
{ "ok": false, "deferred": "party not yet mirrored" }Read and write API
Scores, registry records and note ownership. Requires a key.
GET /api/public/score/loan/{loanId}
Current Nexus score for one loan.
Access: API key
Add acting_role=admin to include the score breakdown.
Example request
curl "https://YOUR-SERVICE-URL/api/public/score/loan/0b6f6a52-51a1-4f3a-9c4c-3f6f1a2b7c10?acting_role=admin" \
-H "Authorization: Bearer nxs_live_…"Example response
{
"loan": {
"id": "a1d2…", "source_loan_id": "0b6f6a52-…",
"loan_number": "AWC-10241",
"registry_tier": "certified_active",
"certified_at": "2025-09-05T14:00:00Z"
},
"scores": [
{
"borrower_id": "5c7b…", "total_score": 862,
"payment_score": 372, "asset_score": 245,
"verification_score": 165, "tenure_score": 80,
"weight_multiplier": 1, "is_archived": false,
"last_updated": "2026-09-12T07:00:11Z"
}
],
"subscores_visible": true
}GET /api/public/score/borrower/{userId}
Aggregate score for one borrower across their certified loans.
Access: API key
Example request
curl https://YOUR-SERVICE-URL/api/public/score/borrower/5c7b1f90-6d1e-4a55-9a1d-77f0c2d3e001 \
-H "Authorization: Bearer nxs_live_…"Example response
{
"borrower_id": "5c7b1f90-…",
"aggregate_score": 848,
"loan_count": 3,
"updated_at": "2026-09-12T07:00:19Z"
}GET /api/public/score/history/{loanId}
Score history for one loan over time.
Access: API key
Example request
curl "https://YOUR-SERVICE-URL/api/public/score/history/0b6f6a52-51a1-4f3a-9c4c-3f6f1a2b7c10" \
-H "Authorization: Bearer nxs_live_…"Example response
{
"loan_id": "0b6f6a52-…",
"history": [
{ "borrower_id": "5c7b…", "total_score": 830, "recorded_at": "2026-07-01T07:00:04Z" },
{ "borrower_id": "5c7b…", "total_score": 862, "recorded_at": "2026-08-01T07:00:06Z" }
]
}POST /api/public/score/recalculate
Recalculates a score on demand.
Access: API key
Pass source_loan_id or source_user_id.
Example request
curl -X POST https://YOUR-SERVICE-URL/api/public/score/recalculate \
-H "Authorization: Bearer nxs_live_…" \
-H "content-type: application/json" \
-d '{"source_loan_id":"0b6f6a52-51a1-4f3a-9c4c-3f6f1a2b7c10",
"acting_user_id":"9ab3…","acting_role":"admin"}'Example response
{
"ok": true,
"loan_id": "a1d2…",
"scores": [{ "borrower_id": "5c7b…", "total_score": 862 }]
}GET /api/public/registry/loan/{loanId}
Registry record and ledger proof details for one loan.
Access: API key
Example request
curl https://YOUR-SERVICE-URL/api/public/registry/loan/0b6f6a52-51a1-4f3a-9c4c-3f6f1a2b7c10 \
-H "Authorization: Bearer nxs_live_…"Example response
{
"loan": {
"loan_number": "AWC-10241",
"registry_tier": "certified_active",
"version_number": 7,
"last_anchored_at": "2026-09-12T08:00:31Z",
"public_share_token": "d4b0c8f2-…"
},
"anchor": {
"merkle_root": "9f2a…",
"xrpl_tx_hash": "A1B2C3…",
"ledger_index": 91234567
}
}POST /api/public/registry/anchor
Anchors the current registry state to the ledger.
Access: API key
Example request
curl -X POST https://YOUR-SERVICE-URL/api/public/registry/anchor \
-H "Authorization: Bearer nxs_live_…" \
-H "x-xrpl-network: testnet" \
-H "x-idempotency-key: registry-2026-09-18-01" \
-H "content-type: application/json" -d '{}'Example response
{
"ok": true,
"loans_anchored": 42,
"merkle_root": "9f2a…",
"xrpl_tx_hash": "A1B2C3…"
}POST /api/public/registry/anchor-modification
Anchors a single loan modification with its reason and changed fields.
Access: API key
Example request
curl -X POST https://YOUR-SERVICE-URL/api/public/registry/anchor-modification \
-H "Authorization: Bearer nxs_live_…" \
-H "content-type: application/json" \
-d '{"source_loan_id":"0b6f6a52-…",
"reason":"Maturity extended 6 months",
"changed_fields":{"maturity_date":["2027-03-01","2027-09-01"]},
"acting_user_id":"9ab3…","acting_role":"admin"}'Example response
{ "ok": true, "xrpl_tx_hash": "D4E5F6…", "version_number": 8 }POST /api/public/tokenization/accept-offer
Accepts an offer on a note listing and moves ownership shares.
Access: API key
Example request
curl -X POST https://YOUR-SERVICE-URL/api/public/tokenization/accept-offer \
-H "Authorization: Bearer nxs_live_…" \
-H "content-type: application/json" \
-d '{"offerId":"7e21a4c9-…","acting_user_id":"9ab3…","acting_role":"admin"}'Example response
{
"ok": true,
"participation_id": "b77c…",
"buyer_percentage": 25,
"listing_status": "partially_sold"
}POST /api/public/tokenization/anchor-participation
Anchors an ownership split to the ledger.
Access: API key (acting_role must be admin)
Example request
curl -X POST https://YOUR-SERVICE-URL/api/public/tokenization/anchor-participation \
-H "Authorization: Bearer nxs_live_…" \
-H "content-type: application/json" \
-d '{"participationId":"b77c…","loanNumber":"AWC-10241",
"buyerName":"Cedar Capital LLC","percentage":25,
"acting_role":"admin"}'Example response
{ "ok": true, "xrpl_tx_hash": "F7A8B9…" }Public verification
Open to anyone holding a share link. No key required.
GET /api/public/verify/{token}
Basic proof that a loan is in the registry.
Access: None
Example request
curl https://YOUR-SERVICE-URL/api/public/verify/d4b0c8f2-1a3e-4c22-9f00-5d7a1b2c3d4eExample response
{
"loan_number": "AWC-10241",
"registry_tier": "certified_active",
"property_city": "Austin", "property_state": "TX",
"certified_at": "2025-09-05T14:00:00Z",
"version_number": 7,
"last_anchored_at": "2026-09-12T08:00:31Z",
"xrpl_tx_hash": "A1B2C3…"
}GET /api/public/certified-verify/{token}
Full certified record: terms, payment record, standing and audit timeline.
Access: None
Example request
curl https://YOUR-SERVICE-URL/api/public/certified-verify/d4b0c8f2-1a3e-4c22-9f00-5d7a1b2c3d4eExample response
{
"loan_number": "AWC-10241",
"nexus_score": 862,
"lien_score": 88,
"effective_ltv": 0.64,
"loan_amount": 450000,
"current_loan_balance": 431200,
"interest_rate": 10.75,
"term_months": 18,
"maturity_date": "2027-03-01",
"payments_made": 12, "payments_on_time": 12,
"payments_late": 0, "payments_missed": 0,
"total_paid": 48375,
"audit_timeline": [
{ "at": "2026-09-12T08:00:31Z", "event": "registry_anchored", "xrpl_tx_hash": "A1B2C3…" }
]
}GET /api/public/passport/{passportId}
Public borrower passport, respecting the borrower's sharing choices.
Access: None
Example request
curl https://YOUR-SERVICE-URL/api/public/passport/31f9c7aa-2b44-4d0e-9a11-88c3e5f60002Example response
{
"full_name": "Cedar Ridge Holdings LLC",
"member_since": "2023-04-11",
"aggregate_nexus_score": 848,
"aggregate_score_updated_at": "2026-09-12T07:00:19Z",
"show_loan_history": true,
"loans": [
{ "loan_number": "AWC-10241", "registry_tier": "certified_active",
"property_state": "TX", "loan_amount": 450000 }
]
}How bank verification works (Plaid)
Under the hood, bank account verification is powered by Plaid, the same bank-connection network used by major banks and fintech apps. Your platform never touches Plaid directly — no Plaid account, no SDK, no separate contract or fees. You make two calls to Nexus Ledger: one to get a verification link, and one to read back the result.
- You request a link for one borrower or lender via create-session, passing their ID, legal name and where they should land afterwards.
- The person opens your link on nexusledger.tech/connect-bank. The page shows your platform's name, then walks them through Plaid's standard bank login — the same trusted flow used by Venmo and thousands of apps. They pick their bank and sign in with their online banking credentials, which go only to their bank through Plaid. We never see them.
- We verify ownership by pulling the account holder's legal name and address straight from the financial institution and comparing it against the name on file for that person. A match score is recorded; only scores of 0.8 or higher count as verified.
- You read the result via the status endpoint (or receive a webhook): a simple verified flag, the bank's name and the last four digits of the account. The person is also sent back to your redirect_url automatically.
What never crosses the API: bank login credentials, full account or routing numbers, balances, or transactions. Verification links expire after 24 hours and can only be used once. A failed name match is recorded as a failed verification — the account is stored but marked is_verified_owner: false, and loans for that borrower stay in the stricter Tier 2 scoring treatment until a matching account is linked.
Bank account verification (hosted)
Confirm a borrower or lender really owns the bank account behind their payments. You do not need your own bank-data provider — we host the whole flow.
POST /api/public/bank-verification/create-session
Creates a one-time, hosted verification link for one borrower or lender. Send them the URL; they connect their bank on our domain.
Access: Bearer key
Links last 24 hours and can only be completed once. redirect_url must be https and is where the person is sent afterwards. Your platform never handles bank logins, account numbers or routing numbers.
Example request
curl -X POST https://nexusledger.tech/api/public/bank-verification/create-session \
-H "Authorization: Bearer nxs_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"source_user_id": "3f2b1c8e-77aa-4c31-9e02-1d5f9a0b2c34",
"legal_name": "Robert McIntosh",
"platform_name": "AWC Nexus",
"redirect_url": "https://app.awcnexus.com/loans/10241/bank-linked"
}'Example response
{
"session_id": "0f4c...",
"session_token": "bvs_9a41c2...",
"verification_url": "https://nexusledger.tech/connect-bank?session=bvs_9a41c2...",
"expires_at": "2026-09-28T18:10:00.000Z",
"status": "pending"
}GET /api/public/bank-verification/session/{session_token}
Read by the hosted page itself. Returns the session state and a fresh bank-connection token.
Access: None — the token is the credential
You normally never call this; it is documented so the flow is auditable.
Example request
curl https://nexusledger.tech/api/public/bank-verification/session/bvs_9a41c2...Example response
{
"found": true,
"status": "pending",
"display_name": "Robert McIntosh",
"platform_name": "AWC Nexus",
"link_token": "link-production-...",
"config_error": null
}POST /api/public/bank-verification/exchange
Called by the hosted page when the account holder finishes connecting their bank. Completes the ownership check and records the verified account.
Access: None — the session token is the credential
You normally never call this either; it is documented so the flow is auditable. A session can only be completed once — a second call returns an error.
Example request
curl -X POST https://nexusledger.tech/api/public/bank-verification/exchange \
-H "Content-Type: application/json" \
-d '{"session":"bvs_9a41c2...","public_token":"public-production-..."}'Example response
{
"status": "verified",
"is_verified_owner": true,
"institution_name": "Chase",
"mask": "4821",
"identity_match_score": 1,
"redirect_url": "https://app.awcnexus.com/loans/10241/bank-linked"
}
// owner name did not match the name on file
{
"status": "failed",
"is_verified_owner": false,
"identity_match_score": 0.33,
"reason": "The bank lists a different owner than the name on file"
}GET /api/public/bank-verification/status/{source_user_id}
Whether this party has a bank account confirmed to be in their own name, with safe metadata only.
Access: Bearer key
Poll after sending someone to a verification link, or before allowing them to send or receive loan payments. Never returns account or routing numbers.
Example request
curl https://nexusledger.tech/api/public/bank-verification/status/3f2b1c8e-77aa-4c31-9e02-1d5f9a0b2c34 \
-H "Authorization: Bearer nxs_live_xxx"Example response
{
"source_user_id": "3f2b1c8e-77aa-4c31-9e02-1d5f9a0b2c34",
"is_verified": true,
"accounts": [
{
"id": "a71f...",
"institution_name": "Chase",
"mask": "4821",
"account_name": "Total Checking",
"is_verified_owner": true,
"identity_match_score": 1,
"linked_at": "2026-09-27T18:04:11.000Z"
}
]
}GET /api/public/score/loan/{source_loan_id}
Loan tier and collusion outcome ride along with the score record.
Access: Bearer key
loan_tier is Tier1_AssetBacked by default. A loan becomes Tier2_P2P_Uncollateralized with collusion_risk_score 100 when borrower and lender resolve to the same person, share a bank account, share a verified account owner, or no borrower account has passed ownership verification. Tier 2 paper can lift a borrower at most 15 points above the neutral 500 baseline, and any payment funded by the lender within the previous 14 days is excluded from scoring entirely.
Example request
curl https://nexusledger.tech/api/public/score/loan/0b6f6a52-51a1-4f3a-9c4c-3f6f1a2b7c10 \
-H "Authorization: Bearer nxs_live_xxx"Example response
{
"loan": {
"loan_number": "AWC-10241",
"loan_tier": "Tier1_AssetBacked",
"collusion_risk_score": 0
},
"scores": [{ "borrower_id": "3f2b...", "total_score": 724 }]
}How scoring treats verified and flagged loans
- Every loan starts as Tier1_AssetBacked and earns full four-pillar points (Payment 400 / Asset 300 / Verification 200 / Tenure 100).
- A loan drops to Tier2_P2P_Uncollateralized with a collusion risk score of 100 when borrower and lender resolve to the same person, share a bank account, share a verified account owner, or the borrower has no verified account on file.
- Tier 2 loans can lift a borrower at most 15 points above the neutral 500 baseline, across their entire credit history.
- Any payment funded by the lender within the previous 14 days is excluded from scoring entirely (circular-flow detection).
- Ownership verification uses the hosted bank-verification flow above — no raw account or routing numbers ever cross the API.
Usage and billing
- Every billable call is logged against your API key: ledger anchors at $0.10 each and bank verifications at $1.50 each, plus a 15% service fee.
- Charges are drawn from a prepaid balance. Add funds, save a card and set an automatic top-up on the billing page in your account.
- When the balance runs out, billable calls are refused with 402 and {"state":"insufficient_funds"}. Reads, scoring and verification look-ups are never charged and keep working.
- The billing page shows totals by month, the latest 50 entries, and a date-range CSV export of everything.
Diagnostics
Connectivity and configuration checks. Safe to call at any time.
GET /api/public/test-xrpl-connection
Health check for ledger connectivity. Reports which ledger a given network selection resolves to, the wallet address and balance, and whether anchoring is switched on.
Access: None
Never submits a transaction and never exposes signing material. Send ?network=testnet or ?network=mainnet to check a specific ledger; without it, the app-wide setting is used.
Example request
curl "https://nexusledger.tech/api/public/test-xrpl-connection?network=testnet"Example response
{
"connected": true,
"network": "testnet",
"network_id": 1,
"endpoint": "https://s.altnet.rippletest.net:51234/",
"endpoint_matches_network": true,
"account": "rnCsykkpxu32HHP9btveihQ9U3zCE3XCYu",
"balance_xrp": 100,
"anchoring_enabled": true,
"signing_modules_loaded": true
}Scheduled jobs
Run automatically each day; can also be triggered manually.
POST /api/public/cron/nexus-score-daily-refresh
Recalculates every certified loan score. Runs daily at 07:00 UTC.
Access: Cron secret or API key
Example request
curl -X POST https://YOUR-SERVICE-URL/api/public/cron/nexus-score-daily-refresh \
-H "x-cron-secret: $CRON_SECRET"Example response
{ "ok": true, "loans_scored": 42, "failures": 0 }POST /api/public/cron/anchor-registry
Anchors the day's registry state to the ledger. Runs daily at 08:00 UTC.
Access: Cron secret or API key
Example request
curl -X POST https://YOUR-SERVICE-URL/api/public/cron/anchor-registry \
-H "x-cron-secret: $CRON_SECRET"Example response
{ "ok": true, "loans_anchored": 42, "xrpl_tx_hash": "A1B2C3…" }Errors
- 400 — the request was malformed or an id was not a valid identifier.
- 401 — the key, signature or cron secret was missing or wrong.
- 202 — accepted but held, waiting on a loan record that has not arrived.
- 404 — no matching record, or the share link expired or was revoked.
- 500 — something failed on this service; the response includes a message.
