Early access

Router API

Quote a trade, build an unsigned transaction for your wallet, sign it yourself, send it, and read the receipt. JSON over HTTPS.

Building and sending need an API key. Once your request is approved, sign in at developer.moonyversal.com and create your own keys there. Quotes, the currency list and receipts answer without one, at a small allowance.

Request early access

Basics

Base URL: https://router.moonyversal.com/api/v2

Requests and responses are JSON. Amounts are decimal strings in whole units ("5" is five dollars of USDF), so nothing is lost to rounding. Each asset is USDF, USDC, or a Flipcash currency’s mint address. Tickers are not unique, so currencies go by mint; GET /tokens lists them all.

Keys and limits

Send your key with every request, as Authorization: Bearer <key> (or x-api-key: <key>).

KeyWhat it can do
mny_test_…Quote and build, with every build simulated. Send is refused with 403 test_key, so you can build your integration with nothing at risk.
mny_live_…Everything, including send.

Keep your key on your server. Never put it in a browser app, a public repository or a URL. We store only a fingerprint of it, so a lost key is replaced, not recovered.

Limits are counted per key, per minute. Every response carries RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset. Past the limit the answer is 429 rate_limited with Retry-After in seconds.

How a trade works

Quote what you would pay and get. Build an unsigned transaction for your wallet. Sign it with your own key. Send it back and the Router sees it through to confirmation, or broadcast it yourself. Read the receipt. The Router never signs, never sees a key and never holds funds.

Currencies GET /tokens

$ curl https://router.moonyversal.com/api/v2/tokens
Response, shortened (Oct 7, 2026)
{
"tokens": [
{ "ticker": "USDF", "name": "Flipcash Dollar", "mint": "5AMAA9JV9H97YYVxx8F6FsCMmTwXSuTTQneiup4RYAUQ",
"decimals": 6, "kind": "dollar", "priceUsd": 1, "tradeReady": true },
{ "ticker": "MNY", "name": "Moony", "mint": "4muAfB6m1P7C3Znad1VUsoqYFwvGQRkWGpJ3A4vupxz6",
"decimals": 10, "kind": "currency", "priceUsd": 0.0999, "tradeReady": true },
…
],
"total": 209,
"source": "moonyversal-router"
}

kind is dollar (USDF, USDC) or currency. tradeReady: false means the Router will not trade it right now, and tradeNote says why.

Quote GET /quote

ParameterMeaning
payWhat you pay: USDF, USDC or a currency mint. Required.
receiveWhat you get. Required.
amountHow much of pay, in whole units, as a decimal string. Required.
slippageBpsHow far below the expected amount you will accept, in basis points. Default 30 (0.3%).
partnerFeeBpsYour share of the buy fee, in basis points, from 0 to 50. Default: the share set on your key. Needs a key with a fee wallet. See Fees.
$ curl -G https://router.moonyversal.com/api/v2/quote \
-H "Authorization: Bearer $ROUTER_KEY" \
-d pay=USDF -d receive=4muAfB6m1P7C3Znad1VUsoqYFwvGQRkWGpJ3A4vupxz6 -d amount=5
Response (a real quote with a 0.5% partner share, Oct 7, 2026)
{
"kind": "buy",
"route": ["USDF", "MNY"],
"pay": { "asset": "5AMAA9JV9H97YYVxx8F6FsCMmTwXSuTTQneiup4RYAUQ", "symbol": "USDF",
"amount": "5.000000", "base": "5000000" },
"receive": { "asset": "4muAfB6m1P7C3Znad1VUsoqYFwvGQRkWGpJ3A4vupxz6", "symbol": "MNY",
"expected": "49.5186984656", "min": "49.3701423702", "minBase": "493701423702" },
"fees": [ { "label": "Router buy fee", "bps": 50, "amount": "0.025000", "asset": "USDF" },
{ "label": "Partner fee", "bps": 50, "amount": "0.025000", "asset": "USDF" } ],
"priceImpactPct": 0.01,
"slippageBps": 30,
"quotedAt": "2026-10-07T23:34:08.679Z",
"source": "moonyversal-router"
}

expected is what you should get; min is the least the transaction will accept, enforced on chain. base and minBase are the same amounts in the token’s smallest units. Every fee is its own line in fees.

kindThe trade
buyUSDF to a currency.
sellA currency to USDF.
usdc-buyUSDC to a currency, through USDF, in one transaction.
usdc-sellA currency to USDC, through USDF, in one transaction.
routedOne currency to another, in one transaction.
usdf-to-usdcUSDF to USDC, one for one.
usdc-to-usdfUSDC to USDF, one for one.

A quote is a price, not a hold. Build quotes again at the moment it builds, and the transaction carries that quote’s minimum.

Build POST /swap/build

FieldMeaning
walletThe address that will sign and pay. It is the transaction’s fee payer. Required.
pay, receive, amountAs in the quote. Required.
slippageBpsAs in the quote. Default 30.
partnerFeeBpsAs in the quote. Default: the share set on your key.
simulateSimulate the transaction as your wallet before returning it. Default true.
$ curl https://router.moonyversal.com/api/v2/swap/build \
-H "Authorization: Bearer $ROUTER_KEY" \
-d '{ "wallet": "<your wallet>", "pay": "USDF",
"receive": "4muAfB6m1P7C3Znad1VUsoqYFwvGQRkWGpJ3A4vupxz6", "amount": "5" }'
Response
{
"transaction": "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA…",
"format": "legacy",
"feePayer": "<your wallet>",
"blockhash": "<recent blockhash>",
"lastValidBlockHeight": …,
"sizeBytes": …,
"builtAt": …, "expiresInSeconds": 60, "expiresAt": …,
"quote": { … },
"simulation": { "ok": true, "unitsConsumed": … },
"send": "/api/v2/swap/send",
"receipt": "/api/v2/receipt/<signature>",
"source": "moonyversal-router"
}

transaction is the unsigned transaction, base64, in Solana’s legacy format. simulation is the network’s verdict when it ran as your wallet: if it would fail, ok is false and message says why, before anything is signed. A wallet that cannot pay is refused outright with insufficient_balance, for example “You need USDF to make this swap. Use USDC or buy USDF first.”

A built transaction lives about 60 seconds. Its blockhash expires. If signing is slow (a hardware wallet, a person approving), build again once expiresAt has passed. Sending an expired transaction answers 409 expired_blockhash and nothing is broadcast.

Sign

With any Solana signer. In JavaScript, with @solana/web3.js:

import { Transaction } from "@solana/web3.js";
const tx = Transaction.from(Buffer.from(build.transaction, "base64"));
tx.sign(keypair); // a server key
// in a browser: const signed = await wallet.signTransaction(tx);
const signedTransaction = tx.serialize().toString("base64");

You can decode the transaction and check it before you sign. The Router never asks for a key.

Send POST /swap/send

$ curl https://router.moonyversal.com/api/v2/swap/send \
-H "Authorization: Bearer $ROUTER_KEY" \
-d '{ "signedTransaction": "<signed, base64>" }'
Response
{
"signature": "<signature>",
"status": "confirmed",
"slot": …,
"receipt": "/api/v2/receipt/<signature>",
"source": "moonyversal-router"
}

Send checks the blockhash is still valid, simulates (set simulateFirst: false to skip), broadcasts, and waits for confirmation, sending again while it waits. status: "pending" means it did not confirm in that window: read the receipt before building another trade, so you never trade twice. You can also broadcast the signed transaction yourself; the receipt works either way.

Receipt GET /receipt/<signature>

$ curl https://router.moonyversal.com/api/v2/receipt/<signature>
Response
{
"signature": "<signature>",
"status": "confirmed",
"slot": …,
"blockTime": …,
"swap": {
"kind": "buy",
"sold": { "mint": "5AMAA9JV9H97YYVxx8F6FsCMmTwXSuTTQneiup4RYAUQ", "symbol": "USDF", "amount": … },
"bought": { "mint": "4muAfB6m1P7C3Znad1VUsoqYFwvGQRkWGpJ3A4vupxz6", "symbol": "MNY", "amount": … },
"valueUsd": …
},
"fees": [ { "label": …, "usdf": … }, { "label": "Network fee", "lamports": … } ],
"source": "moonyversal-router"
}

Read from the chain, after the fact: what was sold, what was bought, the dollar value, and every fee. A signature the chain does not know yet answers 404 not_found; after a send, ask again until it appears.

Fees

Fee
Calling the APIFree.
BuysUp to 1%, inside the transaction: the Router’s 0.5% (“Router buy fee”) plus your share of up to 0.5% (“Partner fee”). Your users never pay more than 1% on a buy.
Your shareYou set it: a default on your key, or partnerFeeBps on any quote or build, from 0 to 50 basis points, the same for every currency. The buyer pays it straight to your wallet’s USDF account in the same transaction; the Router never holds it. Charge less to pass the saving to your users.
SellsNo Router fee and no partner fee. Flipcash’s reserve charges its own fee on every sell, whoever builds it; the quote shows it as its own line.
USDF and USDCSwitching between them is one for one.
Network feesPaid in SOL by the signing wallet, for now.

Coming soon Trade without holding SOL: the Router will pay the network fee and be repaid inside the same transaction.

Your fee wallet PUT /key/partner-fee

Set the wallet that receives your partner fee yourself, with your key, and change it whenever you like. The wallet needs a USDF account first: send it any amount of USDF once. The Router never creates that account, so your users never pay for it.

FieldMeaning
walletThe address whose USDF account receives your share. Required the first time.
bpsYour default share in basis points, 0 to 50. Default 50. Any quote or build can still pass partnerFeeBps.
$ curl -X PUT https://router.moonyversal.com/api/v2/key/partner-fee \
-H "Authorization: Bearer $ROUTER_KEY" \
-d '{ "wallet": "<your wallet>", "bps": 50 }'
Response
{
"id": "key_…", "name": "…", "env": "live",
"fees": {
"routerFeeBps": 50,
"partnerFee": { "wallet": "<your wallet>", "bps": 50, "updatedAt": "…" },
"partnerFeeMaxBps": 50
},
"source": "moonyversal-router"
}

GET /key shows your key’s current settings; DELETE /key/partner-fee removes the partner fee, so buys carry the Router’s fee alone. A change takes effect within a minute. Whoever holds your key can change where your share goes, so keep it secret.

Errors

Every error is { error, code, retryable, hint? }, and the HTTP status means what it says. retryable tells you whether the same request can succeed later.

StatuscodeMeaningRetry
400invalid_inputThe request itself: a missing field, a bad amount, a bad wallet address, a partnerFeeBps outside 0 to 50 or on a key with no fee wallet.no
400insufficient_balanceThe wallet cannot pay for this trade.no
400simulation_failedThe built transaction fails when simulated as your wallet; the message says why.no
400failed_on_chainSent, landed, and reverted.no
401invalid_keyThe key is malformed, unknown or revoked.no
401key_requiredBuild and send need a key.no
403forbidden_routeThe pair is outside what your key may trade.no
403test_keyA test key cannot send.no
404unknown_assetpay or receive is not USDF, USDC or a Flipcash currency mint.no
404not_foundThe chain does not know this signature yet.no
409expired_blockhashThe transaction outlived its blockhash. Nothing was sent; build again.yes
429rate_limitedPast your limit. Wait for Retry-After.yes
500internalSomething failed on our side.yes
502send_failedThe network did not take the transaction. The same signed bytes can be sent again while the blockhash is valid.yes
503no_liquidityA market condition, not a request error. Try later.yes

Ready to build? Request a key, start with a test key, and switch to a live one when your integration works.

Request early access