Reference
API
Base URLs, authentication, response format and the main endpoints of the Floatless API.
On this page
The Floatless API is the backend the Floatless app talks to. This page covers its two deployments, sessions, the response format, limits and the main routes.
Base URLs
| Network | Base URL | Chain ID |
|---|---|---|
| Monad mainnet | https://api-mainnet.floatless.xyz |
143 |
| Monad testnet | https://api.floatless.xyz |
10143 |
Each deployment has its own database, so records on one network never show up on the other. Floatless Savings is live on the mainnet API, and the testnet API returns a preview of it. Regulated dollars, the /v1/compliance routes, work on the testnet API only. Spending rules work on both. Mainnet and testnet covers the split for app users.
Check the network
Every response carries an x-floatless-chain-id header with the chain ID the server runs on. The app refuses to use a server whose chain ID doesn't match its own network, and your client should do the same. Every response also carries an x-request-id, which error bodies repeat as requestId. You can send your own X-Request-Id to tag a request.
curl -i https://api-mainnet.floatless.xyz/health/ready
These are the headers that matter here.
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
x-request-id: pDU-xw9EAmblDfKxWoN04
x-floatless-chain-id: 143
This is the body, with its productEvidence field left out.
{
"ready": true,
"scope": "service_dependencies_only",
"checks": {
"database": "ready",
"monadRpc": "ready",
"chainId": 143,
"expectedChainId": 143,
"fundingMandateRegistry": "ready",
"kuruRouter": "ready",
"kuruMarketData": "ready",
"cleanverse": "not_required",
"morpho": "configured"
}
}
/health/ready answers 200 when every dependency is ready and 503 when one isn't. It checks the database, the Monad RPC and chain ID, the spending-rules registry, the Kuru exchange's router and market data, and Cleanverse where regulated dollars are configured. As its scope field says, it checks dependencies only, so it doesn't prove payments work end to end. On testnet the same call returns chain ID 10143, "cleanverse": "ready" and "morpho": "unconfigured".
Sessions
Send a session token as a bearer token on every route that needs one.
Authorization: Bearer <token>
How you get the token depends on how the account signs in.
Passkey accounts
A passkey account proves it controls its wallet by signing a one-time message.
- Call
POST /v1/account/session/challengewith{ "walletAddress": "0x..." }. The response holds achallenge, themessageto sign, and anexpiresAtfive minutes away. - Sign
messagewith the wallet as a standard EIP-191 Ethereum signed message. - Call
POST /v1/account/sessionwith thewalletAddress, thechallengeand thesignature. The response holds anaccessTokenthat lasts one hour, soexpiresInis 3600.
Each challenge works once. When the token expires, repeat the three steps. A new passkey account registers the same way with POST /v1/account/passkey/challenge and POST /v1/account/passkey, which also takes the passkey's credential ID and returns a session.
Email accounts
Email accounts sign in through Privy. The app sends its Privy access token as the bearer token, and the API checks it with Privy. The first time it sees a new Privy user, the API creates the Floatless account and an embedded wallet for it. From then on, the API signs for that wallet through Privy when the user confirms an action in the app. Keys and security explains what that means for your money.
Responses and errors
A successful response wraps its result in an envelope. Some routes that create a record answer 201 instead of 200.
{ "success": true, "data": {} }
An error uses the same envelope with an error object instead.
{
"success": false,
"error": {
"code": "AUTHENTICATION_REQUIRED",
"message": "A valid bearer session is required.",
"requestId": "OzWUKy1zf0qvo_WaFX2NT"
}
}
Branch on code. The message is written in plain words, so you can show it to people. A 422 VALIDATION_ERROR adds details with the field problems, and a declined regulated transfer adds reasons. Some errors also carry a top-level data with context.
A few routes don't use the envelope. /health and /health/ready return plain JSON, and DELETE /v1/goals/:id answers 204 with no body. When Cleanverse can't be used, the compliance config, registration link, deposit address and report routes answer 503 with "success": false and a data object whose status is unavailable.
| Status | Code | When it happens |
|---|---|---|
| 401 | AUTHENTICATION_REQUIRED |
The route needs a session and none was sent |
| 401 | AUTHENTICATION_INVALID |
The header isn't a bearer token, or the token is invalid or expired |
| 403 | ACCOUNT_NOT_ACTIVE |
The account behind the token isn't active |
| 422 | VALIDATION_ERROR |
The request body failed validation |
| 428 | IDEMPOTENCY_KEY_REQUIRED |
The route needs an Idempotency-Key and didn't get a valid one |
| 409 | IDEMPOTENCY_KEY_REUSED |
The key was already used with a different body |
| 409 | IDEMPOTENCY_REQUEST_IN_PROGRESS |
The first request with this key is still running |
| 429 | RATE_LIMITED |
Too many requests from your IP address |
| 500 | INTERNAL_ERROR |
An unexpected failure on the server |
Idempotency keys
Routes that record payments, savings receipts or spending rules need an Idempotency-Key header, so a retry never records the same thing twice.
POST /v1/send/executePOST /v1/depositPOST /v1/earn/receiptsPOST /v1/safe-locks/receiptsPOST /v1/mandate/signPOST /v1/mandate/registration
A key is 16 to 128 characters long. It starts with a letter or digit, and the rest can be letters, digits, ., _, : or -. Keys are scoped to your account and the route, and kept for 24 hours.
- Sending the same key and body again returns the first response. Field order in the JSON body doesn't matter.
- The first response is kept even when it's an error below 500, so after fixing a rejected request, send it with a new key.
- Sending the same key with a different body gets 409
IDEMPOTENCY_KEY_REUSED. - If the first request failed with a server error, the key is released and you can retry with it.
The app derives each key from what it records, such as the transaction hash for a payment or earn:<transaction hash> for a savings receipt.
Rate limits
Limits count requests per IP address over a one-minute window.
| Routes | Requests per minute |
|---|---|
Routes under /v1/account/, which include sign-in |
20 |
POST /v1/waitlist |
5 |
Everything else, including /health |
120 |
Over the limit, the API answers 429 RATE_LIMITED with a Retry-After header that says how many seconds to wait. In a browser, only pages on the API's allowed origins can read its responses. Requests without an Origin header, such as from a server or curl, aren't affected.
Endpoints
Paths are relative to the base URL. Routes need a session unless they're marked public.
Status and sign-in
All of these are public.
| Route | What it does |
|---|---|
GET /health |
Liveness check that returns { "message": "OK" } |
GET /health/ready |
Checks the service's dependencies, as described above |
POST /v1/account/passkey/challenge |
Returns a one-time message to sign before registering a passkey account |
POST /v1/account/passkey |
Registers the passkey account and returns a session |
POST /v1/account/session/challenge |
Returns a one-time message to sign in to an existing passkey account |
POST /v1/account/session |
Exchanges the signed message for a one-hour session |
Account, balances and history
| Route | What it does |
|---|---|
GET /v1/account |
Your Floatless account |
GET /v1/portfolio |
The live onchain balance of each supported asset, plus your Floatless Savings position on mainnet |
GET /v1/portfolio/availability |
Spendable balances only |
GET /v1/activity |
Your history, newest first, 50 entries a page. Pass ?before=<id> with the last id for the next page |
Spending rules and goals
| Route | What it does |
|---|---|
GET /v1/mandate |
Your active spending rules, or null |
POST /v1/mandate/preview |
Turns proposed rules into the exact typed data to sign, with the next version number |
POST /v1/mandate/sign |
Saves the rules you signed. Needs an Idempotency-Key |
POST /v1/mandate/registration |
Links the onchain registration after checking its event. Needs an Idempotency-Key |
GET /v1/goals |
Your savings goals |
POST /v1/goals |
Creates a goal |
PATCH /v1/goals/:id |
Changes a goal |
DELETE /v1/goals/:id |
Archives a goal |
GET /v1/goals/:id/history |
A goal's change history |
Payments
| Route | What it does |
|---|---|
GET /v1/ens/resolve?name= |
Resolves an ENS name to { name, address, source }. source is monad, evm or ethereum, the first record the name set in that order. Refuses a contract wallet that exists only on Ethereum |
POST /v1/send/quote |
Runs every check before you sign and returns ALLOW or DECLINE with reasons. When allowed, it adds the gas limit, gas price and maximum network fee. A quote lasts up to 60 seconds |
POST /v1/send/execute |
Records a settled payment after verifying the transfer onchain. Needs an Idempotency-Key |
GET /v1/send/:id |
One recorded payment receipt |
POST /v1/deposit |
Records a transfer into your wallet after verifying it onchain. Needs an Idempotency-Key |
GET /v1/proof/:receiptId |
A receipt with its decision and its history entry |
Floatless Savings
| Route | What it does |
|---|---|
GET /v1/earn |
Your savings: live with your position on mainnet, preview on testnet, or unavailable with a reason |
POST /v1/earn/receipts |
Records a savings deposit or withdrawal after checking the vault's event onchain. Needs an Idempotency-Key |
Scheduled releases
| Route | What it does |
|---|---|
POST /v1/safe-locks/quote |
Runs the payment checks for a release of amount AUSD to beneficiary and returns ALLOW or DECLINE with reasons, plus the SafeLock address. Nothing is signed |
GET /v1/safe-locks |
The releases you created, newest first, with each one's live state on Monad: amounts in base units, unlockAt, released, releasable, and who released it when that's recorded |
GET /v1/safe-locks/:lockId |
One of your releases by its bytes32 ID |
POST /v1/safe-locks/receipts |
Records a create or release after checking SafeLock's event onchain. A release by any wallet is accepted and attributed to Chainlink CRE or a wallet. Needs an Idempotency-Key |
These routes answer 503 SAFE_LOCK_UNAVAILABLE on a network without SafeLock, which today means testnet.
Regulated dollars
These work on the testnet API only.
| Route | What it does |
|---|---|
GET /v1/compliance/config |
Public. Cleanverse's environment and the regulated assets it lists |
GET /v1/compliance/apass |
Your wallet's Cleanverse identity status: active, inactive or unavailable |
POST /v1/compliance/apass/magic-link |
Your personal Cleanverse registration link |
POST /v1/compliance/preflight |
Runs the Cleanverse check for a transfer and records the decision |
GET /v1/compliance/deposit |
Your deposit address for turning USDC into aUSDC |
GET /v1/compliance/reports/:activityId |
A short-lived download link for the Travel Rule report of a settled regulated payment |
curl https://api.floatless.xyz/v1/compliance/config
{
"success": true,
"data": {
"status": "available",
"environment": "uat",
"enforcement": "configured",
"chain": "monad",
"assets": [
{
"symbol": "ausdc",
"address": "0xaC0893567D43C3E7e6e35a72803df05416C1f20D",
"decimals": 6,
"origin": {
"symbol": "usdc",
"address": "0x534b2f3A21130d7a60830c2Df862319e593943A3",
"decimals": 6
},
"accessCore": "0x8F118338a1fa41E7Fa86Be19A4e8B99Ed58A6EcC",
"apassAddress": "0xbA82D189540CaC9DC6FF46B6837CaC1BFdEC58B9"
}
]
}
}
The API also has routes the app uses internally, such as embedded-wallet signing under /v1/wallet, and routes for experimental features. They aren't covered here and aren't meant for other clients.
To ask questions about Floatless from an AI assistant, see Use Floatless in ChatGPT and Claude.