Money2046 Agent API — Score Contract
Canonical reference for agents and LLMs consuming Money2046. Short version:
public/agents.md. Feeds: /data/v1/*.json (build-time generated, versioned,
static). Scoring: pure functions in src/lib/calc.js + src/lib/dispositions.js.
Primary object: advertised vs settled for machine payments. Card spend and
MYR corridors are secondary. CHAIN flow is gated by docs/chain-addendum.md
— no aggregate payment count in these feeds.
1. Dispositions
| Enum | Meaning |
|---|---|
SCHEMA_ONLY |
Schema/estimate values only, zero verified evidence. NOT fact. |
VERIFIED_FRESH |
Verified (certified-adapter payload or human review) within 30 days. Fact. |
STALE_REVERIFY |
Was verified, older than 30 days. Re-verify before reliance. |
UNAVAILABLE |
Not available/eligible in the stated jurisdiction (cards: myGate = unlikely → MY). |
INSUFFICIENT_EVIDENCE |
Cannot score — required fields or evidence missing. |
Freshness window: 30 days (FRESH_DAYS in src/lib/dispositions.js,
isFresh(record, 30) in src/lib/calc.js). Missing date ⇒ not fresh (fail closed).
2. Feeds
All feeds share: schema (versioned id), asOf (ISO build time), rule
(human-readable fail-closed rule), plus their payload. Pending evidence appears
only as a count in meta.
catalog.json (money2046.catalog.v1)
summary— counts: cards, routes, providers, verifiedQuotes, pendingQuotes, verifiedEvidence, pendingEvidencecards[]— schema fields (rates, friction, stake, caps, myGate, availability)status+disposition+evidence(verified evidence ref or null)
routes[]— id/corridor/job/rail/provider +disposition+summary/legs(per-field status tags preserved — treatpendingfields as estimates)providers[]— slug/entity/name/type/corridors/availabilityMY/cardIds
quotes.json (money2046.quotes.v1)
quotes[]— verified only, each withdispositionderived from freshness. Record fields: corridor, provider, method, rail, amount, currency, recipient_gets, total_cost, fx_rate_used, fee, speed_observed, eligibility_notes, protection, quote_source, captured_at, captured_by, consent, id.
card-evidence.json (money2046.card-evidence.v1)
evidence[]— verified only. Fields: cardId, cashbackRate, fxSpread, monthlyFee, annualFee, capturedAt, source, consent, id, importedAt, status.
observations.json (money2046.observations.v1)
observations[]— verified only. Currency-neutral rows: observationKind (QUOTE / SETTLEMENT / FAILURE), provenance, assetIn/assetOut, amountIn, optionalinstrumentId+component(FX / FUNDING / REWARD / FEE / SETTLE / FAIL), optionalmethod(X402 / CARD_*), quoteShown, optionaldiscovery(x402 bazaar coverage — never written intorate/fxRateApplied), payloadSha256,replay(Wise + x402 discovery QUOTE rows). Vault URIs never ship.- Row
disposition: VERIFIED_FRESHis freshness of that observation, not index eligibility. Resource counts on discovery rows are a page, not a census.
wallets.json (money2046.wallets.v1)
- Agent wallet + facilitator catalog. Every row
SCHEMA_ONLY. wallets[]capabilities from public docs (x402Client,spendLimits,networks).facilitators[]discovery URLs and claimed networks.- Human page:
/compare/wallets/.
agent-index.json (money2046.agent-index.v1)
- Agent payment outcomes.
profile($1 USDC task on Base) is the probe target, not a license to rank across the admit band. facilitators[]per probe facilitator: measured fee/settlevalue(including acceptedtaskAmount) or hole.- Cross-facilitator
rankingonly when ≥ 2 have verified x402 SETTLEMENT rows at the same accepted task amount. Other verified rows stay listed, with their amount, unranked. - Discovery QUOTE rows never enter this index.
- Human page:
/observatory/#agent-rails. - CHAIN joins never enter this index (
docs/chain-addendum.md).
Evidence permalinks
- Observation:
/evidence/{id}/— human rendering of the matching row in/data/v1/observations.json(lookup byid). Same object. No second evidence endpoint. - Route quote:
/receipts/{id}/— existing receipt permalink. - Human pages:
data-evidence-idon a resolvable<a>. Unlinked measurement →EVIDENCE LINK FAILEDat postbuild. Zero marks also fail. Each publishing surface declaresdata-evidence-surface(measurements≥1 mark;hole-onlyzero measured cells). Committed list:src/data/evidence-surfaces.json. Evidence-page self-links cannot satisfy/observatory. Listing-shape epoch counts may cite/epochs/#{epoch_id}(named exemption — not an observation or payment count).
registry-epochs.json (money2046.registry-epochs.v1)
- Listing-shape projection of versioned bazaar snapshots.
asOfis the latest epochcaptured_at(source time, not build time).- Per epoch:
epoch_id,captured_at,addressCount,listingCount(or a hole with reason),networkFamilies,facilitatorAddressCounts,publishable,disposition,added/retired,addresses. - Never ships transfer/USDC/payment counts or local paths.
- Does not put epoch ids onto
chain-flow.jsonand does not satisfy CHAIN §9. Human page:/epochs/.
chain-flow.json (money2046.chain-flow.v1)
- Public CHAIN surface. Allowlisted keys only:
disposition,reasons,attributionPrecision,epochIds,joinRule,asOf,epoch. - Until addendum gates pass:
INSUFFICIENT_EVIDENCE/UNJOINABLE/epochIds: []. - Extra keys (including
payToCount) fail the build. §4 values are pinned:attributionPrecision∈CALL_LEVEL|ADDRESS_NET|BATCH_NET|UNJOINABLE,joinRule===epoch_overlap,disposition∈ DISPOSITIONS,epochrequired,epoch.status∈none_ingested|refused. Definitional couplings:UNJOINABLE⟹ emptyepochIds;none_ingested⟹ emptyepochIds;VERIFIED_FRESH⟹ non-emptyepochIds. Human page:/observatory/#chain-flow.
onchain (money2046.onchain.v1, index money2046.onchain-index.v1)
Conditions an agent needs before it moves money or takes a position.
Human hub: /onchain/. Contract anchor: /onchain/#contract. Design:
docs/onchain.md. This is not the CHAIN payment-count surface and it
does not satisfy docs/chain-addendum.md.
- Index:
/data/v1/onchain/index.json. One feed per analysis:/data/v1/onchain/<slug>.json. - Publication order is fixed. It is not a size order.
- Each analysis is one object:
slug,question,why_agent,metric_kind(ratio|risk|quality|calendar),value,unit,as_of,sources([{ name, url }]),method_url,confidence(high|medium|low|none),limitations,status(OK|INSUFFICIENT_EVIDENCE). - The file adds only
schemaandrule. Extra keys fail the build. OKis a reading with sources, method, as-of, confidence, and limitations. It is notVERIFIED_FRESHand it is not a settlement.- Missing or failed inputs:
status: INSUFFICIENT_EVIDENCE,value: null,unit: null,confidence: none. The page still renders. - Cite
method_url(/onchain/<slug>/#method) andsources. These pages are hole-only. They are not/evidence/{id}/and they do not create a second evidence endpoint. - Banned: raw-volume boards, TVL rankings, market-cap rankings,
dominance shares, top-N lists, leaderboards.
scripts/assert_onchain.mjsruns the stem tripwire insrc/lib/onchain-guard.json every subtree, then the existing census tripwire.census-guard.jsis unchanged. - Slugs:
bsc-dex-volume-realism,aave-liquidation-cascades,aerodrome-incentive-efficiency,aerodrome-emission-mix,arb-unlock-risk,near-bridge-flow-composition,near-stable-flow,zcash-shielded-adoption,zcash-shield-direction. stablecoin-carry-pegis not published. Ask before placing it on/onchainor the observatory.
card-index.json (money2046.card-index.v1)
- Landed Card Spend Index (secondary).
profile($1,000/mo, 50% cross, $100 basket). cards[]per probe instrument: measured FXvalueor hole + named component holes.- Cross-card
rankingonly when ≥ 2 cards have verified FX at the basket. - Human page:
/observatory/#card-spend.
index.json (money2046.index.v1)
- Sufficiency-gated corridor index (MYR subgraph).
corridors[]each carrydisposition,value(ornull), namedmissinggates,reasons, and a labeledquotes[]series that never enters the index. - Human page:
/observatory/#corridors.
watch.json (money2046.watch.v1)
records[]— approved only.
score-examples.json (money2046.score-examples.v1)
score_wallet[]— first two wallets,SCHEMA_ONLYcapabilities from catalog.score_agent_refusal— honestINSUFFICIENT_EVIDENCEfor measured x402 outcomes.score_card[]— first two cards, computed live bycardReviewModelat build.score_route— computed live from the newest verified quote, or an honestINSUFFICIENT_EVIDENCErefusal when none exists.illustrative_verified_shape—illustrative: true, the exact shape a VERIFIED_FRESH response will take. Not real data.
openapi.json
OpenAPI 3.1: resources + Disposition enum + ScoreCardRequest/ScoreResponse
schemas. Live scoring endpoints are not deployed; the schemas describe the
contract the examples already implement.
3. score_card
Request shape (as mirrored by agents until a live endpoint exists):
{
"cardId": "cryptocom-indigo",
"monthly": 3000,
"crossPct": 0.5,
"opts": { "alreadyHoldStake": false }
}
Response shape:
{
"disposition": "SCHEMA_ONLY",
"score": {
"annual": 36000,
"grossCashback": 540,
"netCashback": 459,
"rewardHaircut": 81,
"fxCost": 144,
"fundingCost": 288,
"fees": 0,
"stakingCost": 0,
"net": 27,
"netLow": 24.84,
"netHigh": 29.16,
"costPerRm100": 99.93,
"band": 0.08,
"stages": { "funding": 288, "conversion": 144, "fees": 0, "rewards": 459, "staking": 0 }
},
"assumptions": ["..."],
"asOf": "2026-08-11T00:00:00.000Z",
"evidenceIds": []
}
Model (must match cardReviewModel):
- annual = monthly × 12
- grossCashback = annual × cashbackRate, capped by cashbackCapMonthly × 12
- rewardHaircut = grossCashback × rewardFriction (token-sell/withdrawal loss)
- fxCost = annual × crossPct × fxSpread
- fundingCost = annual × fundingFriction (MYR→USDT top-up)
- fees = monthlyFee × 12 + annualFee
- stakingCost = stakeUsd × myrPerUsd × 5%/yr, zero if
alreadyHoldStake - net = netCashback − fxCost − fees − fundingCost − stakingCost
- costPerRm100 = 100 + drag per RM100 (all-in: what RM100 of purchases effectively costs)
- band: ±8% verified, ±25% pending
Agent rules:
disposition !== "VERIFIED_FRESH"⇒ do not presentscore.netas fact.evidenceIds.length === 0⇒ no verified backing; say so.UNAVAILABLE⇒ refuse for thatjurisdiction.
4. score_route
{
"request": { "corridor": "USD-MYR", "provider": "Wise", "amount": 1000, "currency": "USD" },
"response": {
"disposition": "INSUFFICIENT_EVIDENCE",
"score": null,
"reason": "No verified quote for this corridor yet. Schema-only rates must not be used.",
"assumptions": [],
"asOf": "...",
"evidenceIds": []
}
}
When verified quotes exist: score = { recipient, rate, feePct } via
estimateRecipient — recipient_gets is NET; rate × amount; never deduct the
fee again (a known historical bug class in this codebase — do not reintroduce).
5. Ingest (propose evidence)
Agents cannot self-approve. Pipeline:
Card spend (observations ledger):
- CSV per
docs/card-capture-protocol.md(template:docs/card-capture-template.csv). node scripts/card_spend_import.mjs --file <csv>→ pending SETTLEMENT rows with vault hash.- Human gate:
node scripts/card_spend_import.mjs --verify <id>.consent=falseis REFUSED. - Verified rows flow into
observations.jsonandcard-index.jsonon next build.
Routes / legacy card evidence:
python3 scripts/quotes_import.py --file <csv>orscripts/card_evidence_import.py.- Human
--verify <id>. Next build publishes to feeds.
x402 discovery (automated): npm run observe:x402 via certified QUOTE adapter; QUOTE rows include replay + discovery. Settlement: SETTLEMENT capability + docs/x402-probe.md. CHAIN collector is local-only (scripts/census/x402_flow.py) until docs/chain-addendum.md gates pass.
Wise quotes (automated): npm run observe:wise via certified adapter; QUOTE rows include replay.
Env MONEY2046_DATA_DIR isolates any test/agent run from real data.
6. Error / refusal semantics
- No verified quote for a corridor →
INSUFFICIENT_EVIDENCE+reason. This is the correct answer, not a failure. - Unknown cardId →
INSUFFICIENT_EVIDENCE(or 404 when a live endpoint exists). - Missing scoring fields (cashbackRate absent, etc.) →
INSUFFICIENT_EVIDENCE, never a zero-filled score. - Never synthesize an
evidenceIdsentry. Empty array is honest.
Disclosure: Money2046 publishes receipts for agent payments: fee, time, fail, facilitator, chain — or INSUFFICIENT_EVIDENCE. Not financial advice. Evidence can be insufficient. We charge you in USD. Payment processing by Airwallex. Money2046 does not move customer funds and is not a wallet, an exchange, or a payment processor.