Skip to content

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.

  1. Call POST /v1/account/session/challenge with { "walletAddress": "0x..." }. The response holds a challenge, the message to sign, and an expiresAt five minutes away.
  2. Sign message with the wallet as a standard EIP-191 Ethereum signed message.
  3. Call POST /v1/account/session with the walletAddress, the challenge and the signature. The response holds an accessToken that lasts one hour, so expiresIn is 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/execute
  • POST /v1/deposit
  • POST /v1/earn/receipts
  • POST /v1/safe-locks/receipts
  • POST /v1/mandate/sign
  • POST /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.