Integrate with AI

Prompt copied

Paste it into Claude Code, Cursor, Copilot… from your project's root. Your assistant reads Float's docs, asks for your website or brand kit, then builds Float into your app in your branding.

  1. Have your API key (for your own screen built on the API) or your referral code (for the embedded widget) handy — the assistant first asks which way you want to integrate, then asks for the one it needs. Don't paste a key into the prompt.
  2. Paste the prompt into your AI assistant, opened at your project's root.
  3. Answer its questions — your website or brand kit first — and review what it builds.
Prompt
You are integrating Float (https://www.myfloat.cc) into my product. Float swaps Canton Coin (CC)
to and from ~2,000 assets at the best rate across its liquidity partners, and I earn 0.2% of the
CC side of every order opened with my API key or my referral code.

Step 1. Read these first, completely, and treat them as the source of truth for every endpoint,
field, state and error code:
  - https://www.myfloat.cc/docs/api.md   (the API reference)
  - https://www.myfloat.cc/openapi.json  (OpenAPI 3.1 spec)
  - https://www.myfloat.cc/docs/ai.md    (the reference widget spec and starter code)
  - https://www.myfloat.cc/llms.txt      (index of Float's docs, with the key facts)
  - https://www.myfloat.cc/downloads/float-widget-starter.zip  (the Next.js starter to adapt)

Step 2. Before writing any code, ask me these questions, ONE AT A TIME, and wait for each answer:
  1. Integration type: "How do you want to integrate Float?
       (A) API — your own swap screen in your app, built on Float's API with your API key: full
           control of the look and flow; needs a small server-side proxy.
       (B) Widget — Float's ready-made swap screen embedded on your page with your referral code:
           no API key, no backend, live in minutes."
     My answer decides everything below. If I am unsure, explain the trade-off in two lines and
     recommend (A) when my app has a backend, (B) for a static site or no-code.
  2. Branding: "What's your website URL or brand kit (logo, colours, fonts)?"
     Use it to match my branding: read my site's colours and fonts (or the kit) and set them
     as CSS variables — for (A) the whole widget, for (B) the section, heading and buttons around
     the embed (Float's screen inside the iframe keeps Float's own look).
  3. For (A) — API key: "Paste your Float API key from the partner dashboard."
     Store it ONLY in a server-side environment variable named FLOAT_API_KEY (.env.local or my
     host's secret store). Never put it in client code, a NEXT_PUBLIC_/VITE_ variable, a URL,
     logs, or the repository. Add .env* to .gitignore if needed.
     For (B) — referral code: "Paste your Float referral code (partner dashboard → Referral
     links)." It is public and safe in client code; never ask me for an API key in (B).
  4. Ideas (optional, I may skip): "Any ideas for how you want Float in your product?"
  5. Placement: "Where in your app should it live?" (a page, a modal, a tab…), or I describe it.

Step 3, if I chose (A) API. Build the integration end to end:
  - Set up the repo or app structure as needed for my stack.
  - A small server-side proxy route that holds FLOAT_API_KEY and calls Float's /v1 API
    (base https://api.myfloat.cc/v1): GET /assets, POST /quote,
    POST /order (with an Idempotency-Key per intended order), GET /order/{claimToken},
    POST /order/{claimToken}/refund-address. Allowlist exactly these paths. The browser only
    ever calls my proxy — Float's API is server-side only and does not allow browser calls
    from my domain.
  - The UI, following the REFERENCE WIDGET spec on https://www.myfloat.cc/docs/ai.md exactly
    (two tabs Get CC / Withdraw CC, token picker, live quote and routes, Exchange, deposit step
    with countdown and QR, status tracking, every state and error message), in my branding,
    usable at 375 px wide and with the keyboard.
  - A webhook receiver in my backend (e.g. POST /api/float-webhook) that verifies the
    Float-Signature header (t=…,v1=…: HMAC-SHA256 with the endpoint secret over "<t>.<raw body>",
    constant-time compare, accept either v1 during a rotation, reject t older than 5 minutes) on
    the RAW body before parsing, de-duplicates on the event id, answers 2xx within 10 s, and
    handles order.state_changed (data.order is the public order object), payout.updated and
    webhook.test. Register it with my key via POST /v1/webhooks once the app is deployed on a
    public https URL; store the returned secret (shown once) as the server-side env var
    FLOAT_WEBHOOK_SECRET; then send a test event with POST /v1/webhooks/{id}/test. The widget
    still polls GET /order/{claimToken} for the order on screen (every 10–30 s, 5 s while
    state is preparing_deposit_address) and stops at a final state.
  - Tests, or a manual test checklist, and a README (how to run, where the key goes).

Step 3, if I chose (B) Widget. Build the embed exactly as section "10. Embed widget" of
https://www.myfloat.cc/docs/api.md describes:
  - The iframe https://www.myfloat.cc/bridge?embed=1&ref=<my referral code>, plus dir / asset /
    amount when my placement calls for it (e.g. a "Withdraw CC" entry point → dir=out). Give it
    a title, width 100%, no border, loading="lazy".
  - The height listener, accepting messages only when e.origin === "https://www.myfloat.cc"
    exactly and e.data.source === "float-widget", so the iframe never scrolls inside my page.
  - A section in my branding around it at my chosen placement, usable at 375 px wide, plus a
    plain referral link (https://www.myfloat.cc/bridge?ref=<code>) as a fallback button.
  - No API key, no proxy and no webhook: orders are attributed by the referral code and I see
    them and my earnings in the partner dashboard.

Step 4. Check it and hand over. For (A): run it, load the asset list and a live quote through
the proxy (no real order), check 375 px and keyboard use, then tell me what you built, which env
vars to set on my host, and how to register the webhook once the app is deployed. For (B): load
the page, check the embed shows, resizes with its content and works at 375 px, then tell me what
you built and where.

Rules:
  - Use rateType "float" by default.
  - Amounts are decimal strings: never do money arithmetic in floating point; show amountIn
    exactly as returned; round received amounts down for display.
  - Render the API's error sentence as text, never as HTML (no innerHTML); build UI from data
    with textContent / your framework's text binding.
  - Identify an asset by symbol + network (the pair), never by a ticker suffix like "-SOL". Omit
    network (do not send null) when the asset row has none.
  - Withdraw CC (CC → X): the user sends exactly amountIn CC to depositAddress — no memo, ever.
    The PAYOUT side may need a destination tag / memo: when the asset row or the quote says
    destinationExtraIdNeeded, show the field (label: destinationExtraIdName) with the warning
    "Sending to an exchange? Add your destination tag/memo, or the funds may be lost." and send
    it as destinationExtraId on the quote and the order.
    Get CC (X → CC): if depositExtraId is not null, the user must include it as the memo/tag.
  - Never create a real order while testing unless I say so; quotes and the asset list are safe.
  - If the docs and the spec ever disagree with what the API returns, trust the API and tell me.

You may start from Float's starter (Next.js App Router), printed in full on
https://www.myfloat.cc/docs/ai.md and downloadable from
https://www.myfloat.cc/downloads/float-widget-starter.zip — adapt it to my stack and branding.

Float partner API

Float swaps Canton Coin (CC) to and from about 2,000 assets, at the best rate across Float's liquidity partners. This API lets your app do it for your users: quote a swap, open an order, show your user where to send, and follow the order until it is done. You earn 0.2% of the CC side of every order you route.

The document at the API host's root, /openapi.json, is a different product (Slay's wallet API) — Float's spec is /v1/openapi.json.

1. Overview

Two directions, one API:

DirectionYour userdirection
Get CCsends an asset (USDT, BTC, ETH, …) and receives CC in their Canton walletin
Withdraw CCsends CC and receives an asset at an address on its chainout

Opening an order moves no money. It returns a deposit address and an exact amount; your user sends from their own wallet, and an order nobody pays simply expires.

There is no sandbox. GET /v1/assets and POST /v1/quote are always safe to call. POST /v1/order opens a real order at a swap partner — harmless until someone pays it, but do not send a deposit while testing unless you mean to swap, and do not open orders in automated tests.

Integration flow in five steps

  1. List currencies — GET /v1/assets?all=1 once, for your token picker (logos included).
  2. Estimate — POST /v1/quote as the user types; show amountOut and the routes.
  3. Create the exchange — POST /v1/order from your server, with your API key and an Idempotency-Key.
  4. Show the deposit — depositAddress, the exact amountIn, the network and a countdown to depositDeadline.
  5. Track it — receive order.state_changed webhooks, and/or poll GET /v1/order/{claimToken} every 10–30 seconds, until state is final.
text
 User            Your app (browser)        Your server               Float API /v1          Swap partner
  |  pick token, amount  |                       |                          |                     |
  |--------------------->|  /api/float/quote --->|  POST /quote (key) ----->|---- asks every ---->|
  |                      |<---- best + routes ---|<-------------------------|<-------------------- |
  |  address, "Exchange" |                       |                          |                     |
  |--------------------->|  /api/float/order --->|  POST /order (key,       |                     |
  |                      |                       |   Idempotency-Key) ----->|---- opens order --->|
  |                      |<-- depositAddress, amountIn, depositDeadline ----|                     |
  |  sends exact amountIn from own wallet ------------------------------------> Float (CC→X) or partner (X→CC)
  |                      |  every 10-30 s ------>|  GET /order/{token} ---->|                     |
  |<---- "Done" ---------|<-- state: completed --|<-------------------------|                     |
mermaid
sequenceDiagram
  participant U as User
  participant A as Your app
  participant S as Your server (holds the key)
  participant F as Float /v1
  U->>A: token, amount
  A->>S: quote
  S->>F: POST /quote
  F-->>S: best route + alternatives
  U->>A: address, Exchange
  A->>S: create order
  S->>F: POST /order (Bearer key, Idempotency-Key)
  F-->>S: depositAddress, amountIn, depositDeadline, claimToken
  U->>F: sends exactly amountIn (CC→X) / to the partner (X→CC)
  loop every 10–30 s until final
    S->>F: GET /order/{claimToken}
  end

Endpoints

MethodPathWhat it does
GET/v1/assetsList or search the assets Float can swap (?all=1 for a picker)
POST/v1/quoteIndicative price across every partner
POST/v1/orderCreate an order; returns where the user sends
GET/v1/order/{claimToken}Read an order (status)
POST/v1/order/{claimToken}/refund-addressAdd the user's refund address (X → CC) or refund party (CC → X)
GET/v1/r/{code}Resolve a referral code (counts a click)
GET POST PATCH DELETE/v1/webhooks…Register and manage webhook endpoints (API key required) — see Webhooks
GET/v1/openapi.jsonThis API as OpenAPI 3.1

Every request and response is JSON. Any other path under /v1 is 404 not_found.

2. Authentication

Authorization: Bearer fl_live_… — optional on every endpoint, required in practice for orders you want to earn on.

Create a key: sign in at https://www.myfloat.cc/partners → API for developers → Create new API key. The secret is shown once (only a hash is stored). You can restrict a key to up to 32 IP addresses, create as many as you like, and revoke any of them.

With a keyWithout a key
Orders are attributed to you (0.2%)Unattributed, unless the order carries a referral ref
Rate-limited per keyRate-limited per IP: 12 requests a minute
Idempotency-Key accepted on POST /orderIdempotency-Key refused (400 idempotency_requires_key)

A key that is present but unknown, revoked or used from an IP it does not allow is 401 invalid_key — never silently treated as anonymous.

Server-side only

  • Keys are server secrets. Keep the key in a server-side environment variable (for example FLOAT_API_KEY). Never put it in a web page, a mobile app bundle, a URL, or your repository. It is only ever read from the Authorization header.
  • Call the API from your backend. The API is built for server-to-server use. Browser calls from your own domain are not supported: CORS does not allow them, so the browser blocks your page from reading the answer. Put a small proxy route on your server (the starter has one) and have your front end call that. /v1 answers carry no Access-Control-Allow-Origin or Access-Control-Allow-Credentials header; the one exception is GET /v1/openapi.json, which any origin may read (Access-Control-Allow-Origin: *).
  • No-code alternative: the embed widget runs on Float's domain inside an iframe and is attributed with a referral code, which is safe to publish.

Rate limits

CallerLimit
With an API keyPer key, per minute: 60 POST /quote, 20 POST /order (and /refund-address), 300 other requests. Counted across all of Float's servers.
Without a key12 requests a minute per IP, approximate (counted per edge instance). A server calling without a key shares that one budget across all of its users — use a key.

Keyed responses carry X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset (unix seconds the window ends); keyless ones carry the first two. Over the limit: 429 rate_limited with Retry-After in seconds — wait, then retry the identical request. Windows are fixed minutes. /v1/openapi.json is not limited. /v1/webhooks calls are limited to 30 a minute per IP (test events: 6 a minute). Need more? Ask support — every quote fans out to every partner.

Idempotency-Key

A timeout on POST /order does not tell you whether an order opened. Send an Idempotency-Key header — a UUID per intended order — and retry with the identical body:

Retry withResult
same key, same bodythe same order, 201, header Idempotent-Replayed: true — no second order
same key, different body409 idempotency_key_reused
same key while the first is still opening409 idempotency_in_progress with Retry-After: 2 — retry the same request

Keys need an API key, are remembered per partner (all your keys share one namespace) for 24 hours, and are 1–255 visible ASCII characters (400 idempotency_key_invalid otherwise). "Same body" means the same JSON values (key order and whitespace do not matter; "0.01" and 0.01 differ). A request that fails frees its key. A replayed order is read fresh, so its state is current.

3. Currencies — GET /v1/assets

Assets at least one partner will swap into CC, best-covered first.

QueryDefault
q—Case-insensitive match on ticker or name. Exact ticker first, then prefix, then anything.
limit50Clamped to 1–200.
offset0Page past limit. total is the number of matching rows.
network—Only rows on this chain, any spelling (BSC, BNB, bep20); none = rows with no known chain.
all—all=1 returns every row in compact form (cached 10 minutes); other parameters are ignored. Use this for a token picker.
bash
curl "https://api.myfloat.cc/v1/assets?q=USDT&limit=2"
json
{
  "landingAsset": "CC",
  "total": 18,
  "offset": 0,
  "assets": [
    { "symbol": "USDT", "venues": ["houdiniswap", "rocketx", "letsexchange", "exolix", "thechange"], "name": "Tether",
      "network": "TRON", "networkName": "Tron",
      "icon": "https://www.myfloat.cc/icons/tokens/usdt.webp", "networkIcon": "https://www.myfloat.cc/icons/chains/tron.png" },
    { "symbol": "USDT-APT", "venues": ["letsexchange"], "name": "TetherUS",
      "network": "APTOS", "networkName": "Aptos",
      "icon": "https://www.myfloat.cc/icons/tokens/usdt.webp", "networkIcon": "https://www.myfloat.cc/icons/chains/aptos.png" }
  ]
}

?all=1 rows carry only symbol, name, network, networkName, icon, networkIcon and, where they apply, destinationExtraIdNeeded / destinationExtraIdName.

Field
symbolThe ticker to send as from (Get CC) or to (Withdraw CC). Some are chain-suffixed (USDT-APT, USDC-SOL), but not consistently (USDT on Tron has no suffix, and a token can have both a plain and a suffixed row). Identify an asset by the pair symbol + network, never by parsing a suffix.
nameDisplay name. May be absent.
networkFloat's code for the row's chain (TRON, ETHEREUM, BSC, …). Absent when unknown. Send it as network on /quote and /order (omit the key when the row has none).
networkNameDisplay name of the chain, or null.
iconFloat-hosted 64×64 WebP token image (absolute URL): the token's own, else its base token's (USDT-APT → USDT), else the chain logo, else null — draw a lettered tile.
networkIconFloat-hosted 64×64 chain logo (PNG or WebP), or null. Use it as the badge on the token logo and on network chips.
destinationExtraIdNeeded, destinationExtraIdNameOnly on rows whose payouts take a destination tag / memo: true, and what to call it ("Destination tag", "Memo", "Memo (comment)"). Show the field on Withdraw CC. This flag (and the same one on the quote) is authoritative.
venuesPartner ids that list it. Informational.

Limits (min / max) depend on the route and the size, so they come from POST /v1/quote (minIn, maxIn, or the 409 message), not from this list.

Each asset is listed once. A chain-suffixed ticker that is only another spelling of a pinned asset on the same chain (USDT-TRC20 beside USDT on Tron, USDC-ERC20 beside USDC on Ethereum, VELO-XLM beside VELO on Stellar…) is left out of /v1/assets. Those tickers are still accepted on /quote and /order and quote exactly like the plain one.

Destination tag / memo. Rows whose payouts take a tag or memo (an exchange's shared XRP, XLM, TON, ATOM, EOS, HBAR… deposit address) carry destinationExtraIdNeeded: true and destinationExtraIdName ("Destination tag" for XRP, "Memo (comment)" on TON, else "Memo"); both are absent on every other row. The flag is authoritative — read it from the row rather than keeping your own list; the list below is a snapshot. On 20 Oct 2026 (GET /v1/assets?all=1): XRP, XLM, HBAR, ATOM, EOS, INJ, KAVA, OSMO, TIA, RUNE, LUNA, SEI, DYDX, STX, ALEX, ALEX-STX, AKT, SCRT, XPR, POLYX, NIBI, ORAI, PI, VELO and the TON tokens (GRAM, GRAM-TON, NOT, DOGS, HMSTR, MAJOR, MAJOR-TON, REDO, JUSDT, USDT-TON). See Destination tag / memo.

Networks and pinned chains

Many assets live on several chains. network on /quote and /order names the chain of the non-CC asset — what the user sends on (in) or is paid on (out). CC is always on Canton. Any common spelling is accepted and answered with Float's code:

Float's codeAlso accepted (examples)
BITCOINBTC
ETHEREUMETH, ERC20, ERC-20
TRONTRX, TRC20
BSCBEP20, BNB, BINANCE, BNB Smart Chain
SOLANASOL, SPL
POLYGONMATIC, POL
ARBITRUMARB, ARBITRUM ONE
OPTIMISMOP
BASE
AVALANCHEAVAX, AVAXC, C-CHAIN
HYPEREVMHYPEEVM, HYPE-EVM — not HYPERCORE (Hyperliquid's L1)
TON, XRP, LITECOIN, DOGECOIN, ZKSYNC, COSMOS …LTC, DOGE, RIPPLE, ZKSYNC-ERA, COSMOSHUB-4 …

There are about 140 chains. Unknown names keep their own spelling, upper-cased.

  • With network: only quotes on that chain are considered. A different chain is never substituted (a deposit on the wrong chain is lost). No partner on that chain is 409 conflict.
  • A chain-suffixed ticker (USDC-SOL, USDT-TRC20) is the plain asset on that chain, exactly as if network were sent. A network that names a different chain is 400 invalid_request.
  • Without network: an asset Float pins to a chain (below) is quoted on that chain at every partner (networkChoice: "float_default"). Any other asset uses each partner's own default chain, which can differ between partners (networkChoice: "venue_default"). If the chain matters, send network — the picker row's network is the right value.
Pinned assetChain
ETH, USDC, DAI, LINK, UNI, AAVE, WBTC, USDE, PYUSD, TEL, XCN, SPA, VOOI, PROMPT, TOKEN, EDGE, H, SPX, FORT, LADYS, ORDER, SWFTC, CRO, KCS, TLOSETHEREUM
USDT, TRX, USDDTRON
BNB, BABYDOGE, USD1BSC
SOL, SOON, ZEUS, ACS, ARX, USDGSOLANA
MATIC, TRADEPOLYGON
BTC, AVAX, LTC, XRP, DOGE, DOT, ADA, ATOM, XMR, BCH, XLM, ETCtheir own chain (BITCOIN, AVALANCHE, LITECOIN, XRP, DOGECOIN, POLKADOT, CARDANO, COSMOS, MONERO, BITCOINCASH, STELLAR, ETC)
ARB, OP, HYPE, MNT, TAIKO, BRISE, WAVES, ELA, XPR, ORAI, 0G, VELOARBITRUM, OPTIMISM, HYPEREVM, MANTLE, TAIKO, BITGERT, WAVES, ELASTOS, XPR, ORAICHAIN, 0G, STELLAR

The order opens on the chain its quote was priced on and records it (network on the order). If the partner would open it on any other chain, it is refused with 409 venue_unavailable before anything is sent.

Build a token picker

GET /v1/assets?all=1 is everything a picker needs: about 2,000 rows with Float-hosted token logos and chain logos. Load it once (through your proxy), filter in the browser.

  • Popular first: USDT, USDC, BTC, ETH, SOL, BNB, XRP, TRX, DOGE, LTC, TON, AVAX — then the rest A–Z.
  • Search by ticker or name: exact ticker, then prefix, then anything.
  • Network filter (chips or a column) built from the rows' network / networkName / networkIcon.
  • Token logo with chain badge: icon with networkIcon in the corner; a lettered tile when either is null or fails to load.
  • Return symbol and network and quote with them. Key rows (React keys, favourites, recent picks) on the pair symbol + network, not on the ticker alone or a suffix like -SOL.
html
<input id="q" placeholder="Search by name or ticker">
<div id="nets"></div>
<ul id="list"></ul>
<script type="module">
  // Your server proxies this to GET /v1/assets?all=1 (never call Float from the browser).
  const { assets } = await (await fetch("/api/float/assets?all=1")).json();
  const POPULAR = ["USDT", "USDC", "BTC", "ETH", "SOL", "BNB", "XRP", "TRX", "DOGE", "LTC", "TON", "AVAX"];
  const rows = assets.filter((a) => a.symbol !== "CC");
  let net = "all";

  // Built with createElement / textContent: names and symbols are data, never HTML.
  const el = (tag, props = {}, ...kids) => {
    const e = Object.assign(document.createElement(tag), props);
    e.append(...kids); // strings become text nodes
    return e;
  };
  const tile = (label) => el("span", { className: "tile", textContent: (label[0] ?? "?").toUpperCase() });
  const img = (src, label, size) => {
    if (!src) return tile(label);
    const i = el("img", { src, width: size, height: size, alt: "" });
    i.onerror = () => i.replaceWith(tile(label));
    return i;
  };

  // Network chips: one per chain, biggest first.
  const nets = new Map();
  for (const a of rows) if (a.network) {
    const n = nets.get(a.network) ?? { ...a, n: 0 };
    n.n++; nets.set(a.network, n);
  }
  const chip = (label, value, icon) =>
    el("button", { onclick: () => { net = value; render(); } }, ...(icon ? [icon, " "] : []), label);
  document.querySelector("#nets").replaceChildren(
    chip("All", "all"),
    ...[...nets.values()].sort((x, y) => y.n - x.n).slice(0, 20)
      .map((n) => chip(n.networkName ?? n.network, n.network, img(n.networkIcon, n.networkName ?? n.network, 16)))
  );

  function pick(a) {
    // What /v1/quote needs: { from (or to): symbol, network }
    console.log("picked", { symbol: a.symbol, network: a.network ?? undefined });
  }

  function render() {
    const q = document.querySelector("#q").value.trim().toLowerCase();
    const rank = (a) => { const s = a.symbol.toLowerCase(); return s === q ? 0 : s.startsWith(q) ? 1 : 2; };
    const pop = (a) => { const i = POPULAR.indexOf(a.symbol); return i < 0 ? 99 : i; };
    const list = rows
      .filter((a) => net === "all" || a.network === net)
      .filter((a) => !q || a.symbol.toLowerCase().includes(q) || (a.name ?? "").toLowerCase().includes(q))
      .sort((a, b) => (q ? rank(a) - rank(b) : pop(a) - pop(b)) || a.symbol.localeCompare(b.symbol))
      .slice(0, 200);
    document.querySelector("#list").replaceChildren(...list.map((a) =>
      el("li", {}, el("button", { onclick: () => pick(a) },
        el("span", { className: "face" },
          img(a.icon, a.symbol, 32),
          el("span", { className: "badge" }, ...(a.networkIcon ? [img(a.networkIcon, a.networkName ?? "", 14)] : []))),
        el("b", { textContent: a.symbol }), " ", a.name ?? "", " ",
        el("small", { textContent: a.networkName ?? "" })))));
  }
  document.querySelector("#q").oninput = render;
  render();
</script>

A complete React version with keyboard support is in the starter (components/TokenPicker.tsx).

The image maps. The images are on www.myfloat.cc, and you may hotlink them or copy them into your own app. Both maps are public JSON, served with Access-Control-Allow-Origin: *:

  • https://www.myfloat.cc/data/token-icons.json — { "generatedAt": "…", "size": 64, "icons": { "USDT": "/icons/tokens/usdt.webp", … } }. Keys are upper-case tickers; paths are relative to https://www.myfloat.cc. 64×64 WebP.
  • https://www.myfloat.cc/data/chain-logos.json — { "generatedAt": "…", "size": 64, "chains": { "tron": { "name": "Tron", "logo": "/icons/chains/tron.png", "networks": ["TRON", "TRX", "TRC20", "TRC-20"] }, … }, "networks": { "TRON": "tron", "TRX": "tron", … } }. networks maps every network code or alias to a chain id; logo is a 64×64 PNG or WebP path, or null.

/v1/assets already resolves both for you; read the maps only if you want to bundle the images.

4. Estimate — POST /v1/quote

A price that binds nothing. There is no quote id and no expiry: POST /v1/order re-quotes when it opens the order. Debounce it (about 500 ms) as the user types — each quote asks every partner.

json
{ "direction": "in", "from": "USDT", "amount": "100", "rateType": "float" }
FieldRequired
directionno"in" = asset → CC (default). "out" = CC → asset.
fromon inThe asset the user sends.
toon outThe asset the user receives.
amountyesString, > 0. in: amount of from. out: amount of CC.
rateTypeno"float" or "fixed". Default "fixed" — anything else is fixed. Send "float" (below).
networknoChain of the non-CC asset (see Networks). May be omitted or null (or "") — all three mean the asset's default chain.
destinationExtraIdno, out onlyThe payout address's destination tag / memo. Send it on the quote too: partners that cannot carry it are left out of the ranking. See Destination tag / memo.
timezone / tzOffsetnoThe end user's IANA zone / Date.getTimezoneOffset(); passed to partners whose risk checks ask for it.

Float or fixed? A floating rate follows the market until the deposit confirms; a fixed rate is locked for the order. Fewer partners offer fixed rates and they price the risk in: in a live comparison on 17 Oct 2026, 100 USDT → CC fixed paid about 1.7% less than floating (936.73 vs 953.67 CC). Recommend "float" and offer fixed only if your users ask for it.

Response 200 (live, 17 Oct 2026):

json
{
  "direction": "in", "from": "USDT", "to": "CC",
  "amountIn": "100",
  "amountOut": "953.672096269999997276",
  "venueOut": "953.672096269999997276",
  "markupBps": 60, "venueBaseFeeBps": 20, "totalFeeBps": 151,
  "venue": "thechange",
  "network": "TRON", "venueNetwork": "TRX", "networkChoice": "float_default",
  "settleSeconds": null, "warning": null,
  "needsRefundAddress": false,
  "alternatives": [
    { "venue": "exolix", "amountOut": "950.140842", "network": "TRON", "note": "0.37% less than The Change",
      "deltaPct": 0.37, "totalFeeBps": 187, "venueNetwork": "TRX", "needsRefundAddress": false }
  ],
  "venueSpread": 0.003702797097462964,
  "unavailable": [
    { "venue": "letsexchange", "reason": "venue returned no output amount", "minIn": "120" },
    { "venue": "rocketx", "reason": "rocketx settles USDT on ETHEREUM, not TRON" }
  ],
  "suggestedMin": null,
  "rateType": "float", "flatFee": "5", "flatFeeAsset": "out",
  "minIn": "30.050000000000000711", "maxIn": "2400032",
  "indicative": true
}
Field
amountOutWhat the user receives, after every fee (partner, network, Float's). Show this.
venueThe winning partner's id. alternatives are the others, best first; let the user pick one and pass it as venue on the order.
alternatives[].noteA short comparison with the winner, naming it as people know it ("0.37% less than The Change"). Display text only — its wording may change; do not parse it.
alternatives[].deltaPctThe same comparison as a number: how much less this route delivers than the winner, in percent (0.37); 0 on a tie, null if it cannot be computed. Branch on this, not on note.
network / venueNetworkThe chain this price is on — Float's code / the partner's spelling.
networkChoicerequested (you sent network or a suffixed ticker), float_default (Float's pinned chain), venue_default (each partner's own).
needsRefundAddressGet CC only: this partner (RocketX, or HoudiniSwap at a fixed rate) opens the order only once it has the user's refund address on the source chain. Collect it and send refundAddress with the order. Also on each alternative. Never affects ranking.
destinationExtraIdNeeded / destinationExtraIdNameWithdraw CC: whether payouts on this chain commonly need a tag / memo, and what this route calls it — null when this route cannot pass one. Also on each alternative. false / null on Get CC. Never affects ranking.
totalFeeBpsThe all-in cost against market, already inside amountOut. markupBps is Float's share, venueBaseFeeBps the partner's published base fee — null when the partner publishes none (display it as "—", never as 0).
flatFee / flatFeeAssetA flat fee the partner reports, in the input (in) or output (out) asset. Already inside amountOut; display only.
minIn / maxInThe winning route's limits, in the input asset. Round minIn up and maxIn down for display.
unavailablePartners that did not quote, with the reason (and minIn when size was the problem).
suggestedMinWhen nothing quoted because the amount was too small, the smallest minimum reported.
warningA sentence when the best price is notably worse than market. Show it.
indicativeAlways true.

Partner ids you may see in venue, alternatives and unavailable, with the name to show: letsexchange → YNOT, thechange → The Change, exolix → Exolix, houdiniswap → HoudiniSwap, rocketx → RocketX, squid → Squid, stealthex → StealthEX. Which are enabled changes over time; treat an unknown id as a name to show as-is.

Too small or too large is 409 conflict, and the limit is only in the message — show error to the user (as text — see Errors):

json
{ "error": "That amount is below every venue's minimum. The lowest is 30.0392 USDT.", "code": "conflict" }
{ "error": "That amount is above every venue's maximum. The most any will take is 29.38 BTC.", "code": "conflict" }

5. Create exchange — POST /v1/order

Re-quotes, opens the order at the partner, and returns where the user sends. Call it from your server with your key and an Idempotency-Key.

http
POST /v1/order
Authorization: Bearer fl_live_YOUR_KEY_HERE
Idempotency-Key: 6f1c0d7e-4b8a-4f0e-9a51-2a8b3c4d5e6f
Content-Type: application/json

{
  "direction": "out",
  "to": "BTC",
  "amount": "1000",
  "rateType": "float",
  "destination": "bc1qexampleaddress…",
  "venue": "exolix",
  "refundParty": "alice::12207d5f1c3b9a0e4f6d8c2b1a0e9f8d7c6b5a4f3e2d1c0b9a8f7e6d5c4b"
}
FieldGet CC (in)Withdraw CC (out)
direction"in""out"Default "in".
fromrequired—The asset the user sends.
to—requiredThe asset the user receives.
amountrequiredrequiredString > 0. in: amount of from. out: amount of CC.
destinationrequiredrequiredWhere the user is paid. in: their Canton party ID (name::1220…, 16+ hex after ::). out: an address on the asset's chain (the partner validates it). Cannot be changed later.
rateTypeoptionaloptionalSend "float".
networkoptionaloptionalThe chain of the non-CC asset — send the picker row's network. Omitted, null or "": the asset's default chain, as on /quote.
venueoptionaloptionalOpen at this partner (from the quote). If it is no longer quoting this pair at this size the order is refused (409 conflict) — never swapped for another.
refundAddressoptional (needed when needsRefundAddress)—The user's address on the source chain, for a failed swap's refund. Checked for that chain's format.
refundParty—optional, recommendedThe user's own Canton party. Every automatic refund of the order's CC goes there. Ask for it whenever the user may pay from an exchange. Must be able to receive CC (a transfer preapproval) or the order is 422.
refoptionaloptionalA referral code, used only when no valid key is sent.
timezone / tzOffsetoptionaloptionalAs on /quote.
destinationExtraId— (400)optionalThe payout address's destination tag / memo, when it needs one (an exchange's XRP, XLM, TON, ATOM… address). String; an integer is accepted for an XRP tag. Validated per chain. See below.

Sending refundAddress on out, or refundParty on in, is 400 invalid_request. Float's own deposit addresses are refused as destination or refundParty.

Response 201 — the order (Withdraw CC, waiting for the user's CC):

json
{
  "claimToken": "3k0x9q…(64 characters)",
  "direction": "out", "from": "CC", "to": "BTC",
  "amountIn": "1000", "expectedOut": "0.00124738",
  "venue": "exolix",
  "depositAddress": "float-deposit-012::12207d5f1c3b9a0e4f6d8c2b1a0e9f8d7c6b5a4f3e2d1c0b9a8f7e6d5c4b",
  "depositExtraId": null,
  "deliversTo": "bc1qexampleaddress…",
  "status": "pending", "state": "awaiting_deposit",
  "network": "BITCOIN", "venueNetwork": "BTC",
  "custodialState": "awaiting_deposit",
  "receivedCc": null, "receivedAt": null, "settledCc": null,
  "settledTxId": null, "venueTxId": null, "refundTxId": null,
  "createdAt": "2026-10-17T10:00:00.000Z",
  "deadline": 1792231200000,
  "depositDeadline": "2026-10-17T10:30:00.000Z",
  "depositGraceUntil": "2026-10-17T10:40:00.000Z",
  "transactions": [
    { "kind": "customer_deposit", "chain": "canton", "asset": "CC", "amount": "1000", "from": null,
      "to": "float-deposit-012::12207d5f…", "txId": null, "explorerUrl": null, "state": "pending", "at": null }
  ],
  "refundParty": "alice::12207d5f1c3b9a0e4f6d8c2b1a0e9f8d7c6b5a4f3e2d1c0b9a8f7e6d5c4b",
  "destinationExtraId": null,
  "depositParty": "float-deposit-012::12207d5f1c3b9a0e4f6d8c2b1a0e9f8d7c6b5a4f3e2d1c0b9a8f7e6d5c4b",
  "depositValidForThisOrderOnly": true,
  "depositAddressNote": "Valid for this order only"
}

A Get CC order looks the same with from the asset, to "CC", depositAddress the partner's address on the source chain, depositGraceUntil: null, and refundAddress, refundAddressRequired, refundAddressAtVenue (and, when Float pays the user from a per-order wallet, payoutParty and payoutFromThisOrderOnly — informational; the user never sends there).

How your user pays

Withdraw CC (CC → X): the user pays Float.

  • depositAddress is this order's own Float deposit address — a Canton party used by this order alone. Show depositAddressNote ("Valid for this order only") next to it; the user must not reuse it.
  • Send exactly amountIn CC. No memo, ever — depositExtraId is always null on CC → X. Float forwards the CC to the swap partner itself, with whatever reference that partner needs.
  • Send before depositDeadline: Float's window is 30 minutes from createdAt (earlier if the partner's own deadline is earlier). A transfer already in flight is still accepted until depositGraceUntil, 10 minutes later.
  • Short (less than amountIn): the whole payment is refunded and the order cancelled — there are no top-ups.
  • Over (more than amountIn): the order goes ahead for amountIn and the excess is refunded.
  • Late (after the grace, or to an order that is already closed): refunded.
  • Refunds go to the order's refundParty when it has one, otherwise back to the sending party. A refund to an exchange's shared deposit party is held for a person (a transfer there without the user's own memo can be lost) — which is why you should collect refundParty. A payment of less than 5 CC to a deposit address is treated as spam and is not refunded.
  • While state is preparing_deposit_address, depositAddress is "" and depositPreparing is true: show "Preparing your deposit address…" and keep polling (every 5 s); it usually takes a minute or two.

Get CC (X → CC): the user pays the swap partner.

  • depositAddress is the partner's address on the source chain (network). Send exactly amountIn of from on that chain before depositDeadline (the partner's deadline, or 30 minutes when it names none). There is no grace: the partner, not Float, is receiving.
  • When depositExtraId is not null, the user must include it as the memo / tag / destination tag on the source chain, or the partner cannot match the payment.
  • The partner's rules apply to a wrong amount or a late payment. If the route needed a refund address, a failed swap's deposit goes back there.
  • CC is delivered to destination (the user's Canton party), which must be able to receive CC — a party without a transfer preapproval is refused at creation (422 unprocessable).

On both: show the figures from the response (amountIn, depositAddress, network, depositDeadline), not what you sent; show depositDeadline as a countdown computed from that field (not from when your page loaded); and store claimToken server-side — it is a bearer capability: whoever holds it can read the order.

Destination tag / memo — destinationExtraId

On Withdraw CC the payout address may need a destination tag or memo: an exchange's XRP, XLM, TON, ATOM, EOS, HBAR… deposit address is shared by all its customers, and the tag is how the exchange credits the right one. A payout sent to an exchange without its tag can be lost. A personal wallet needs none.

The field. destinationExtraId — optional, a string (a JSON integer is also accepted for an XRP tag) — on POST /v1/quote and POST /v1/order, direction: "out" only, next to destination. Blank is the same as absent. On "in" it is 400 invalid_request (that payout is a Canton party, which takes no tag). It is part of the order body, so it is part of the Idempotency-Key identity: a retry with a different or missing tag is 409 idempotency_key_reused.

Validation (400 invalid_request, with the reason), by the payout chain:

ChainCalledFormat
XRP LedgerDestination tagdigits only, 0 – 4294967295 (uint32)
Stellar (XLM, and tokens on it)Memotext up to 28 characters, or a numeric ID up to 18446744073709551615
TON (and jettons such as USDT on TON)Memo (comment)text up to 64 characters
Cosmos Hub (ATOM) and other Cosmos chainsMemotext up to 64 characters
EOS, Hedera (HBAR), BNB Beacon (BEP2)Memotext up to 64 characters
Bitcoin, Ethereum and EVM chains, Tron, Solana, Litecoin, Dogecoin…—refused: these addresses take no tag

Every tag: 1–64 visible ASCII characters, no spaces.

Which assets need one: GET /v1/assets rows and quotes carry destinationExtraIdNeeded and destinationExtraIdName (above). When destinationExtraIdNeeded is true, show the field, labelled with destinationExtraIdName, and this warning: "Sending to an exchange? Add your destination tag/memo, or the funds may be lost." An order without a tag is still accepted (the user may be sending to a personal wallet).

Partners. With a tag in the request, a partner that cannot deliver it for that asset (RocketX and Squid cannot pass payout tags) is left out of the quote and listed in unavailable with the reason; the best route that can carry it wins. A tag is never silently dropped: no order opens on a partner that cannot carry it. Naming such a partner in venue is 409 venue_unavailable. If no partner can carry it, the quote / order is 409 conflict ("No partner can pay XRP out with a destination tag or memo right now…"), with unavailable listing why.

On the order. destinationExtraId is echoed on Withdraw CC orders (the create response and GET /v1/order/{claimToken}): the tag the partner was given, or null. Absent on Get CC orders. It cannot be added or changed after the order opens — open a new order instead.

Pay from your own Canton wallet

If your app is a Canton wallet, or integrates one, Withdraw CC is one step for your user: send the CC straight from their wallet.

Every Float deposit address has a TransferPreapproval, so a standard Canton Coin transfer to depositAddress completes directly — there is no accept step on Float's side and no memo.

  1. POST /v1/order with direction: "out"; keep claimToken.
  2. Read depositAddress, amountIn and depositDeadline (if state is preparing_deposit_address, poll until the address appears).
  3. Submit a CC transfer from the user's party to depositAddress for exactly amountIn, before depositDeadline. Set refundParty to the user's party when you create the order, so any refund comes straight back to the same wallet.
  4. Wait for the order.state_changed webhook, or poll GET /v1/order/{claimToken}, until state is final.
ts
// Wallet-agnostic sketch: `wallet.transferCC` is your wallet's own transfer call
// (a Canton Network Token Standard transfer / TransferPreapproval_Send).
const order = await float.createOrder({ direction: "out", to: "USDC", network: "ETHEREUM", amount: "500",
  rateType: "float", destination: userEvmAddress, refundParty: user.partyId });
const ready = await waitFor(() => float.getOrder(order.claimToken), (o) => o.depositAddress !== "");
await wallet.transferCC({ from: user.partyId, to: ready.depositAddress, amount: ready.amountIn });
await waitFor(() => float.getOrder(order.claimToken), (o) => ["completed", "refunded", "failed", "expired"].includes(o.state));

For the transfer itself, see the Splice documentation on the Canton Network Token Standard (CIP-0056) transfer API and TransferPreapproval: https://docs.sync.global/.

When a partner will not open the order

A partner that quoted the route but will not open it answers 409 venue_unavailable, with its id in venue. Nothing was opened or sent. Mark that route unavailable for this attempt and open the next one from alternatives (pass it as venue), or re-quote:

json
{ "error": "The Change can't open this swap right now. Pick another route — nothing has been sent.", "code": "venue_unavailable", "venue": "thechange" }

A partner's own 4xx (for example an address it will not pay) is 400 venue_refused with its message — usually something the user can fix.

6. Exchange status — GET /v1/order/{claimToken}

Returns the same order object. Each read refreshes the order from its partner, so state moves forward between reads. An unknown token is 404 not_found.

Webhooks or polling: with a webhook registered, read the order when an order.state_changed event arrives (its data.order is this same object) and keep a slow poll as a fallback. Without one, poll every 10–30 seconds while the user is watching (every 5 s during preparing_deposit_address), and stop at a final state. Reads have their own, generous limit (300 a minute per key).

Drive your UI from state — one field, ten values. status and custodialState are internal and kept only for compatibility; do not branch on them.

text
[awaiting_refund_address | preparing_deposit_address →] awaiting_deposit → confirming → exchanging → sending → completed
                                                                                       ↘ failed | refunded | expired
stateMeaningWhat your app should do
awaiting_refund_addressGet CC: the route needs the user's source-chain refund address before the partner order opens. No deposit address yet.Ask for the address; POST /refund-address. Do not ask the user to send anything. Unanswered, the order expires after 24 hours.
preparing_deposit_addressWithdraw CC: Float is creating this order's deposit address. depositAddress is "".Show "Preparing your deposit address…"; poll every 5 s.
awaiting_depositWaiting for the user's payment.Show the deposit step and the countdown. After depositDeadline (with nothing received) say "Deposit window closed — don't send now".
confirmingThe payment was seen and is being confirmed / forwarded.Show progress; nothing for the user to do.
exchangingThe partner is swapping.Show progress.
sendingThe payout to the user is on its way.Show progress.
completedThe user has been paid. Final.Show "Done" with expectedOut (or settledCc).
refundedThe payment went back to the user. Final.Show where it went (refundParty, or the sending wallet).
failedThe swap will not complete. Final — except that when Float holds the user's deposit and is returning it, the order moves on to refunded.Tell the user Float is on it; offer support@myfloat.cc with the order link.
expiredThe deposit window closed with nothing received. Final."Nothing was received. Nothing was sent and nothing is owed — start a new order."

confirming, exchanging and sending partly come from the partner's own status word, read live. Until a partner reports progress — or when it does not answer — an order shows awaiting_deposit, so treat the in-progress states as progress information and only the final states as facts.

transactions lists every on-chain transfer behind the order, oldest first:

Field
kindcustomer_deposit (the user pays), venue_payment (Float pays the partner, CC → X), venue_payout (the partner pays out), customer_payout (Float pays the user, X → CC), refund
chain, asset, amountWhere and what. chain is canton or the partner's chain code.
from, to, txIdParties / addresses and the transaction id, when known.
explorerUrlA link to show ("View transaction"), when known — Canton transfers link to ccview.io.
statepending, confirmed, failed or cancelled.
atISO time, when known.

Other order fields: receivedCc / receivedAt (what Float received), settledCc / settledTxId (Float's payout to the user on Get CC), venueTxId (Float's payment to the partner on Withdraw CC), refundTxId — Canton update ids a user can look up, or null. deadline is depositDeadline in unix ms, kept for compatibility.

6b. Webhooks

Float can tell your server when something changes, so you do not have to poll GET /v1/order/{claimToken}:

EventSent when
order.state_changedan order attributed to you (created with your API key, or through one of your referral links) changes state
payout.updatedone of your affiliate payouts is created or changes status
webhook.testyou press Send test event (or call POST /v1/webhooks/{id}/test) — always delivered to that endpoint, whatever its events

Polling still works and stays the source of truth: a webhook tells you that an order moved; GET /v1/order/{claimToken} always tells you where it is now.

Register an endpoint

In the dashboard: API for developers → Webhooks → Add endpoint. Or with your API key:

bash
curl -X POST https://api.myfloat.cc/v1/webhooks \
  -H "Authorization: Bearer fl_live_…" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://example.com/hooks/float"}'
json
{
  "endpoint": {
    "id": "whe_3f9c0a…",
    "url": "https://example.com/hooks/float",
    "status": "active",
    "pausedReason": null,
    "pausedAt": null,
    "secretHint": "whsec_…Q2aZ",
    "previousSecretValidUntil": null,
    "consecutiveFailures": 0,
    "failingSince": null,
    "lastSuccessAt": null,
    "lastFailureAt": null,
    "createdVia": "api_key:3f2b9c1e-7a4d-4e8b-9c0f-5d6e7a8b9c0d",
    "createdAt": "2026-10-17T12:00:00.000Z",
    "updatedAt": "2026-10-17T12:00:00.000Z",
    "events": ["order.state_changed", "payout.updated"]
  },
  "secret": "whsec_hY3…Q2aZ",
  "notice": "Store this signing secret now — it is shown only in this response. …"
}
Endpoint field
statusactive or paused. pausedReason (manual, or why Float paused it) and pausedAt are set only while paused, otherwise null.
consecutiveFailures, failingSinceFailed real deliveries in a row, and when the current run of failures began (null when the last delivery succeeded). Drive auto-pause.
lastSuccessAt, lastFailureAtThe last successful / failed delivery of a real event. Test events change none of the health fields.
createdViadashboard, or api_key:<key id> — the id (a UUID) of the API key that registered the endpoint, as listed in the dashboard; never the key itself.
createdAt, updatedAtISO 8601 UTC. updatedAt is the last change to the endpoint itself.
eventsThe subscribable event types: order.state_changed and payout.updated — every endpoint receives all of them. GET /v1/webhooks also returns the same list as eventTypes. webhook.test is not in that list: it is sent only by POST /v1/webhooks/{id}/test, and always delivered there, whatever events says.

The secret is in this response and no other. Store it with your other credentials. If you lose it, rotate it (Rotating the secret).

CallWhat it does
GET /v1/webhooksyour endpoints (never the secrets)
POST /v1/webhooks { url }register; returns the secret once
GET /v1/webhooks/{id}one endpoint
PATCH /v1/webhooks/{id} { url?, paused? }change the URL; paused: true pauses, paused: false resumes
DELETE /v1/webhooks/{id}remove it (its undelivered events are dropped)
POST /v1/webhooks/{id}/rotate-secretnew secret, returned once; the old one keeps working for 24 h
POST /v1/webhooks/{id}/testsend a webhook.test now and get the receiver's status code back
GET /v1/webhooks/{id}/deliveriesthe last 50 delivery attempts, with status codes

The dashboard uses the same calls under /api/float/partner/webhooks, authenticated by your signed-in session.

Rules for the URL. https:// only; no user name or password in it; no IP addresses in private, loopback, link-local or other reserved ranges; no localhost, .local, .internal or single-label names; no cloud metadata addresses; not a Float host. Up to 5 endpoints per account, each URL once. Every endpoint receives every event type; ignore types you do not handle (new types may be added).

API key or dashboard? Both can manage endpoints, so an integration (or an AI doing the integration from these docs) can set itself up with only the key. Because an endpoint receives your orders' claim tokens, every change made with a key is emailed to your account address straight away, naming the key and the URL. If you did not make it, revoke the key and delete the endpoint. Restricting your key to your servers' IPs (dashboard → the key → API access restrictions) also applies to these calls.

What you receive

Every delivery is a POST with a JSON body and these headers:

HeaderValue
Content-Typeapplication/json; charset=utf-8
Float-Signaturet=<unix seconds>,v1=<hex> — see Verify the signature
Float-Event-Idthe event's id — the same on every retry
Float-Event-Typee.g. order.state_changed
Float-Delivery-Attempt1 on the first try, 2 on the first retry, …
User-AgentFloatWebhooks/1.0 (+https://myfloat.cc/docs)

Every body has the same envelope:

FieldMeaning
idevt_… — unique per event, identical on every retry. De-duplicate on this.
typethe event type
created_atwhen the change was seen (ISO 8601 UTC) — not when this POST was made
datathe event's content, below

order.state_changed

json
{
  "id": "evt_7c1e5b0a9f2d4c3b8a1e6f0d",
  "type": "order.state_changed",
  "created_at": "2026-10-17T12:04:31.512Z",
  "data": {
    "previous_state": "awaiting_deposit",
    "order": {
      "claimToken": "…",
      "direction": "in",
      "from": "BTC",
      "to": "CC",
      "amountIn": "0.01",
      "expectedOut": "7624.1",
      "venue": "letsexchange",
      "depositAddress": "bc1q…",
      "depositExtraId": null,
      "deliversTo": "yourparty::1220…",
      "status": "pending",
      "state": "confirming",
      "network": "BITCOIN",
      "venueNetwork": "BTC",
      "…": "every other field of GET /v1/order/{claimToken}"
    }
  }
}

data.order is the object GET /v1/order/{claimToken} returns — same serializer, same fields, one schema — built from what Float has stored (a GET can be a step ahead where it asks the exchange partner live). Read data.order.state; previous_state is what Float last told you (null on the first event for an order). The states:

awaiting_refund_address, preparing_deposit_address, awaiting_deposit, confirming, exchanging, sending, then one of the final states completed, refunded, failed, expired.

  • You may not see every state. An order that moves through several between two of Float's looks (they are about a minute apart) produces one event, e.g. awaiting_deposit → completed. On orders where the exchange partner holds every leg, confirming / exchanging / sending come from that partner's live status and are usually skipped in events (a GET may still show them). Act on state, never on a sequence.
  • state never goes backwards in events.
  • Final states are final, with one documented exception: a custodial order can go failed → refunded once Float returns the deposit.
  • The first event for an order can arrive up to a minute or two after you created it. Orders you created before registering any endpoint that were already finished by then produce no event.

payout.updated

json
{
  "id": "evt_0b9d…",
  "type": "payout.updated",
  "created_at": "2026-10-17T12:10:02.004Z",
  "data": {
    "previous_status": "requested",
    "payout": {
      "id": "…",
      "amountCc": "125.400000",
      "destination": "yourparty::1220…",
      "status": "sent",
      "failureReason": null,
      "createdAt": "2026-10-17T12:09:40.118Z",
      "updatedAt": "2026-10-17T12:10:01.870Z"
    }
  }
}

payout carries the fields the dashboard's payout list shows, plus updatedAt. previous_status is null for a payout's first event.

webhook.test

json
{
  "id": "evt_…",
  "type": "webhook.test",
  "created_at": "2026-10-17T12:00:00.000Z",
  "data": { "endpoint_id": "whe_…", "message": "This is a test event from Float. …" }
}

Signed exactly like a real event. Use it to check your verification code.

Verify the signature

Float-Signature is t=<unix seconds>,v1=<hex>. The hex is HMAC-SHA256 keyed with your endpoint's secret (the whole whsec_… string, as text) over:

<t> + "." + <the raw request body>

To verify: take the raw body exactly as received (not re-serialised JSON), compute the HMAC for the header's t, compare it in constant time with each v1 value (there are two for 24 h after a rotation — accept if either matches), and reject a t more than 5 minutes from your clock (replays).

Node

js
import crypto from "node:crypto";

// rawBody: the request body as a Buffer or string, byte-for-byte as received.
export function verifyFloatWebhook(rawBody, header, secret, toleranceSeconds = 300) {
  if (!header) return false;
  let t = null;
  const sigs = [];
  for (const part of header.split(",")) {
    const i = part.indexOf("=");
    if (i < 0) continue;
    const k = part.slice(0, i).trim();
    const v = part.slice(i + 1).trim();
    if (k === "t" && /^\d+$/.test(v)) t = Number(v);
    else if (k === "v1") sigs.push(v);
  }
  if (t === null || sigs.length === 0) return false;
  if (Math.abs(Math.floor(Date.now() / 1000) - t) > toleranceSeconds) return false;
  const expected = crypto.createHmac("sha256", secret).update(`${t}.`).update(rawBody).digest();
  return sigs.some((s) => {
    const got = Buffer.from(s, "hex");
    return got.length === expected.length && crypto.timingSafeEqual(got, expected);
  });
}

With Express, keep the raw body for this route:

js
app.post("/hooks/float", express.raw({ type: "application/json" }), (req, res) => {
  if (!verifyFloatWebhook(req.body, req.get("Float-Signature"), process.env.FLOAT_WEBHOOK_SECRET)) {
    return res.status(400).end();
  }
  const event = JSON.parse(req.body.toString("utf8"));
  // de-duplicate on event.id, queue the work, answer fast:
  res.status(200).end();
});

Python

python
import hashlib
import hmac
import time


def verify_float_webhook(raw_body: bytes, header: str, secret: str, tolerance: int = 300) -> bool:
    """raw_body: the request body bytes exactly as received."""
    if not header:
        return False
    t, sigs = None, []
    for part in header.split(","):
        k, sep, v = part.strip().partition("=")
        if not sep:
            continue
        if k == "t" and v.isdigit():
            t = int(v)
        elif k == "v1":
            sigs.append(v)
    if t is None or not sigs or abs(int(time.time()) - t) > tolerance:
        return False
    expected = hmac.new(secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256).hexdigest()
    return any(hmac.compare_digest(expected, s) for s in sigs)

(Flask: request.get_data(); Django: request.body; FastAPI: await request.body().)

Answering, retries and timeouts

  • Success is any 2xx within 10 seconds. Answer first, do the work afterwards (a queue), or slow work will turn into retries.
  • Redirects are not followed — a 3xx counts as a failure. Register the final URL.
  • Float never reads your response body.
  • Anything else is retried with exponential backoff — about 30 s, 1 min, 2, 4, 8, 16, 32 min, 1 h, 2 h, then every 4 h (±10 % jitter) — for 24 hours from the event, with a last try at the 24-hour mark. Then that event is given up for that endpoint (it shows as failed in the delivery log; the order itself is unaffected — GET /v1/order/{claimToken} still has it).
  • Auto-pause. An endpoint that fails 10 times in a row over at least 12 hours, with no success in between, is paused and your account email is told. While paused it receives nothing new; events that were already queued for it keep their 24 hours and go out as soon as you resume it. Fix the receiver, send a test event, then resume (PATCH … {"paused": false} or the dashboard). You can pause an endpoint yourself the same way.

Ordering and idempotency

  • Idempotency: the event id (also in Float-Event-Id) is the same on every retry. Store the ids you have processed and ignore repeats — a delivery can arrive twice if your 2xx was lost on the way back.
  • Ordering: for one order, events reach an endpoint in the order they happened: a later event waits while an earlier one for the same order is still being retried. Across different orders there is no ordering. If you ever need certainty, compare data.order.state with what you stored, or fetch the order.

Test events

Dashboard: Send test event on the endpoint. API: POST /v1/webhooks/{id}/test — it sends one webhook.test immediately and answers with what your server said:

json
{ "eventId": "evt_…", "delivered": true, "statusCode": 200, "error": null, "durationMs": 182 }

Test events work on paused endpoints, are delivered whatever the endpoint's events, are never retried, never count toward auto-pause, and appear in the delivery log. They do not change the endpoint's health: lastSuccessAt, lastFailureAt, consecutiveFailures and failingSince move only on real events. Limited to 6 a minute.

Rotating the secret

POST /v1/webhooks/{id}/rotate-secret (or Rotate secret) returns a new secret, once. For the next 24 hours every delivery is signed with both: Float-Signature: t=…,v1=<new>,v1=<old> — so a receiver still holding the old secret keeps verifying while you deploy the new one. After 24 h only the new secret signs. previousSecretValidUntil on the endpoint says when the overlap ends.

Delivery log

GET /v1/webhooks/{id}/deliveries (and the dashboard) lists the last 50 attempts, newest first:

json
{ "attempts": [
  { "eventId": "evt_…", "eventType": "order.state_changed", "attempt": 2,
    "delivered": false, "statusCode": 503, "error": "HTTP 503", "durationMs": 412,
    "at": "2026-10-17T12:05:02.000Z" }
] }

error is Float's own description (HTTP 503, timeout — no answer within 10 s, network — …, HTTP 302 — redirects are not followed), never your server's response.

7. Refunds — POST /v1/order/{claimToken}/refund-address

The claim token is the only credential. Body: { "address": "…", "acceptOut": "…" } (acceptOut optional).

Get CC — the user's refund address on the source chain.

Order is…Result
waiting for a refund addressStored, and the partner order opens on a fresh quote from the same partner and chain: 200 { "opened": true, "venueHasAddress": true, "message": "…", "order": { … } } — the order now has its deposit address.
… and the fresh quote pays more than 0.5% less than the user saw409 requote_needed with requote: { venue, previousOut, freshOut }. Nothing opened. Show freshOut; to accept, re-send with "acceptOut": "<freshOut>".
already openStored for support (opened: false); the partner keeps what it was given at creation.
expired or finished409 conflict.

Withdraw CC — the user's Canton refund party. { "address": "name::1220…" } sets or changes refundParty. It moves nothing and opens nothing: 200 { "opened": false, "venueHasAddress": false, "message": "…", "order": { …, "refundParty": "…" } }. Allowed only until a refund of the order has started; afterwards (or on a finished order) it is 409 conflict. A party without a transfer preapproval is 422.

This call counts against the per-key order limit, because it can open a partner order.

The deposit-window rules, in one place

What happenedWithdraw CC (user pays Float)Get CC (user pays the partner)
Exact amount, on timeProcessedProcessed
Less than amountInRefunded in full, order cancelledThe partner's rules
More than amountInamountIn processed, excess refundedThe partner's rules
After depositDeadline, before depositGraceUntilStill processed— (no grace)
After the grace, or after the order closedRefundedThe partner's rules
Nothing receivedexpired after the graceexpired when the partner reports it
Refunds go torefundParty, else the sending party (held for a person if that is an exchange's shared party)refundAddress when the route has one

8. Errors

Every error is JSON:

json
{ "error": "Enter an amount.", "code": "invalid_request" }

error is a sentence for a person and may be reworded — show it, but branch on code.

Display errors as text, never innerHTML. Put error on the page with textContent (or your framework's ordinary text binding), never innerHTML or dangerouslySetInnerHTML. Float's own sentences never repeat what you sent — an asset Float does not list is answered 409 conflict with exactly Unknown asset. Use a symbol from GET /v1/assets. — but treat every error as untrusted text all the same.

HTTPcodeMeaningWhat your app should do
400invalid_requestMissing or bad field: no JSON body, no asset, amount not > 0, bad network, a suffixed ticker that disagrees with network, bad destination / refundAddress / refundParty, CC as the non-CC assetShow error; fix the input
400venue_refusedThe partner rejected the order (its message is included)Show it; usually the address or amount — fix and retry, or pick another route
400idempotency_key_invalidIdempotency-Key is not 1–255 visible ASCII charactersUse a UUID
400idempotency_requires_keyIdempotency-Key sent without an API keySend your key (or drop the header)
401invalid_keyThe key is unknown, revoked, or used from an IP it does not allowCheck the key and its IP list in the dashboard
404not_foundUnknown claim token, referral code or path—
409conflictNo route at this size (limit in error), an asset Float does not list ("Unknown asset. Use a symbol from GET /v1/assets."), the chosen venue is not quoting it, or the order is expired / finished / already refundingShow error; re-quote, adjust the amount, or pick a symbol from /v1/assets
409venue_unavailableThe partner that quoted this route would not open the order. Body has venue. Nothing was openedMark that route unavailable; open the next from alternatives, or re-quote
409requote_neededAdding a refund address would open the order more than 0.5% worse. Body has requoteShow requote.freshOut; re-send with acceptOut to accept
409idempotency_key_reusedSame Idempotency-Key, different bodyUse a new key for a new order
409idempotency_in_progressThe first request with this key is still opening the orderRetry the same request after Retry-After
422unprocessableThe Canton party (destination on Get CC, refundParty on Withdraw CC) has no transfer preapprovalAsk the user to enable receiving in their wallet, then retry
429rate_limitedToo many requestsWait Retry-After seconds; retry the identical request
502upstream_errorA partner did not answer, or the order could not be recordedRe-quote and retry; on POST /order, retry with the same Idempotency-Key
503unavailableFloat is switched off, deposits are paused ("Deposits are paused right now…"), every deposit address is in use, a deposit address could not be prepared, or the key could not be checked. Nothing was openedShow error; retry later

The /v1/webhooks calls also use unauthorized (401, no fl_ key sent — these calls need one), forbidden (403), invalid_url (400), too_many_endpoints and duplicate_url (409) and internal (500). Your client should treat any unknown code by its HTTP status class.

9. Earnings

  • 0.2% (20 bps) of the swap's CC amount — the CC the partner delivers on Get CC, the CC the user sends on Withdraw CC. It comes out of Float's own fee: your user gets exactly the price they would get on myfloat.cc.
  • Attribution is decided when the order opens and never changes: your API key if the request carries a valid one, otherwise a referral code (ref). GET /v1/r/{code} resolves a referral code to { "ref": "<code>", "partner": "<your app name>" } and counts one click on it (404 not_found for an unknown code); the embed widget calls it for you.
  • Each attributed order writes one earning: accrued when the order opens → payable once the order completes (Float's background jobs promote it, usually within minutes) → paid when included in a payout; void if the order fails, is refunded or expires. Only payable is withdrawable.
  • Payouts are requested in the dashboard (https://www.myfloat.cc/partners → Withdrawal) and sent in CC, automatically, from Float's affiliate wallet to the payout Canton party you set in Settings (it must be able to receive CC — a transfer preapproval). The minimum payout is 10 CC by default; below it Withdraw is disabled. Ask for any amount from the minimum up to your payable balance and exactly that is sent (earnings are taken oldest first, and the last order's earning is split if needed; leave the amount empty to withdraw everything payable), and a new payout waits behind any earlier payout that is still held (pending_funds or unknown).
  • Dashboard figures (the partner dashboard's earnings summary): earnedCc = paidCc + pendingCc + availableCc + settlingCc, where paidCc is CC actually sent to you, pendingCc is in payouts that are requested, pending_funds or unknown, availableCc is payable (withdrawable now), and settlingCc is earned on completed orders and not yet payable.
  • Payout states: sent; pending_funds (the affiliate wallet is being topped up — the amount is held and paid automatically, retried every 5 minutes); failed (nothing moved; the earnings are payable again — for example your party cannot receive CC yet); unknown (Float could not confirm the transfer; the amount stays held, never paid twice, until Float checks it — contact support@myfloat.cc).

10. Embed widget

The no-code alternative: Float's own swap screen in an iframe on your page, attributed with your referral code — never an API key. Create a code in the dashboard (Referral links).

html
<iframe src="https://www.myfloat.cc/bridge?embed=1&ref=YOUR_CODE" style="width:100%;border:0" title="Swap to CC"></iframe>
<script>
  addEventListener("message", (e) => {
    if (e.origin === "https://www.myfloat.cc" && e.data?.source === "float-widget" && e.data.type === "height")
      document.querySelector("iframe[title='Swap to CC']").style.height = e.data.height + "px";
  });
</script>
Parameter
embed=1Drops Float's header and footer, and posts messages (its height, the direction, the order's state — never its claim token) to the parent page.
refYour referral code (letters, digits, _, -; up to 64). Orders are attributed to you; each visit counts one click (once per browser tab).
dirin (Get CC, default) or out (Withdraw CC).
assetThe non-CC asset to start with, e.g. USDT.
amountThe amount to start with, e.g. 250. Without it the widget starts at 250 USDT on Get CC and 500 CC on Withdraw CC. An amount or asset you pass wins, and is kept when the user switches between Get CC and Withdraw CC.
orderA claimToken: reopens that order's status screen (for "view your order" links).

Use www.myfloat.cc exactly — the bare domain redirects there. A plain referral link is the same page without embed=1: https://www.myfloat.cc/bridge?ref=YOUR_CODE.

Messages from the widget

With embed=1 the widget posts these to the parent page with postMessage (target origin "*", so your listener must check e.origin === "https://www.myfloat.cc" and e.data.source === "float-widget" and ignore anything else):

typeMessageSent
height{ source: "float-widget", type: "height", height: <pixels> }whenever the content's height changes
direction{ source: "float-widget", type: "direction", dir: "in" | "out" }on load, and whenever the user switches between Get CC (in) and Withdraw CC (out)
order_created{ source: "float-widget", type: "order_created", direction: "in" | "out" }when an order opens
order_state{ source: "float-widget", type: "order_state", state }when an order is shown (on load of ?order=… or right after it opens) and on every change of its state
  • state is the order's public state, the same lifecycle as the API: awaiting_refund_address, preparing_deposit_address, awaiting_deposit, confirming, exchanging, sending, then completed, refunded, failed or expired (Exchange status).
  • The widget never posts the order's claim token (nor any other identifier that can act on the order). Anything on your page can read these messages, so they carry nothing that could view or change an order. The order link stays with the user, inside the widget.
  • No secrets and no personal data are posted — no addresses, no amounts, no API key.
  • New message types may be added; ignore types you do not handle.
html
<script>
  const FLOAT_ORIGIN = "https://www.myfloat.cc";
  const frame = document.querySelector("iframe[title='Swap to CC']");
  addEventListener("message", (e) => {
    if (e.origin !== FLOAT_ORIGIN || e.data?.source !== "float-widget") return;
    const m = e.data;
    switch (m.type) {
      case "height":        frame.style.height = m.height + "px"; break;
      case "direction":     document.body.dataset.floatDir = m.dir; break;                 // "in" | "out"
      case "order_created": trackOrderOpened(m.direction); break;                         // your own code, e.g. analytics
      case "order_state":   if (m.state === "completed") showThanks(); break;             // your own code
    }
  });
</script>

11. Code examples

The whole flow — assets → quote → create order (with Idempotency-Key) → show the deposit → poll → done. Your API key goes only in the server-side FLOAT_API_KEY environment variable. The examples below never contain a real key; fl_live_YOUR_KEY_HERE is a placeholder.

curl

bash
BASE=https://api.myfloat.cc/v1
FLOAT_API_KEY=fl_live_YOUR_KEY_HERE   # server-side secret — never in a browser or a repo

# 1. Currencies (for a picker)
curl -s "$BASE/assets?all=1" | head -c 400

# 2. Estimate
curl -s -X POST "$BASE/quote" -H "Authorization: Bearer $FLOAT_API_KEY" -H "content-type: application/json" \
  -d '{"direction":"out","to":"BTC","amount":"1000","rateType":"float"}'

# 3. Create the order (one Idempotency-Key per intended order; reuse it on retries)
IDEM=$(uuidgen)
curl -s -X POST "$BASE/order" -H "Authorization: Bearer $FLOAT_API_KEY" -H "content-type: application/json" \
  -H "Idempotency-Key: $IDEM" \
  -d '{"direction":"out","to":"BTC","amount":"1000","rateType":"float","destination":"bc1q…","refundParty":"user::1220…"}'
# → show depositAddress, amountIn, depositDeadline; keep claimToken

# 4. Status, every 10–30 s until state is completed | refunded | failed | expired
curl -s "$BASE/order/$CLAIM_TOKEN" -H "Authorization: Bearer $FLOAT_API_KEY"

JavaScript (Node 18+, fetch)

js
// float.mjs — server-side only. Run: FLOAT_API_KEY=fl_live_YOUR_KEY_HERE node float.mjs
import { randomUUID } from "node:crypto";

const BASE = "https://api.myfloat.cc/v1";
const KEY = process.env.FLOAT_API_KEY; // <-- your key, from the environment only

async function float(path, { method = "GET", body, idempotencyKey } = {}) {
  for (let attempt = 0; ; attempt++) {
    const res = await fetch(BASE + path, {
      method,
      headers: {
        accept: "application/json",
        ...(KEY ? { authorization: `Bearer ${KEY}` } : {}),
        ...(body ? { "content-type": "application/json" } : {}),
        ...(idempotencyKey ? { "idempotency-key": idempotencyKey } : {}),
      },
      body: body ? JSON.stringify(body) : undefined,
    });
    const json = await res.json();
    if (res.ok) return json;
    // Retry what is safe to retry: rate limits, "still opening", and 5xx on an idempotent call.
    const retryable = res.status === 429 || json.code === "idempotency_in_progress" || (res.status >= 500 && (method === "GET" || idempotencyKey));
    if (retryable && attempt < 3) {
      await new Promise((r) => setTimeout(r, Number(res.headers.get("retry-after") ?? 2) * 1000));
      continue;
    }
    throw Object.assign(new Error(json.error), { code: json.code, status: res.status, body: json });
  }
}

// 1. Currencies
const { assets } = await float("/assets?all=1");
const btc = assets.find((a) => a.symbol === "BTC" && a.network === "BITCOIN"); // symbol + network identify a row

// 2. Estimate (float rate)
const quote = await float("/quote", { method: "POST", body: { direction: "out", to: btc.symbol, network: btc.network, amount: "1000", rateType: "float" } });
console.log(`1000 CC → ${quote.amountOut} BTC via ${quote.venue}`);

// 3. Create the order — one Idempotency-Key per intended order, reused on every retry.
const idempotencyKey = randomUUID();
let order;
try {
  order = await float("/order", {
    method: "POST",
    idempotencyKey,
    body: { direction: "out", to: "BTC", network: btc.network, amount: "1000", rateType: "float",
            venue: quote.venue, destination: "bc1q…user-address", refundParty: "user::1220…" },
  });
} catch (e) {
  if (e.code === "venue_unavailable") { /* drop quote.venue, retry with quote.alternatives[0].venue and a NEW key */ }
  throw e;
}

// 4. Show the deposit
console.log(`Send exactly ${order.amountIn} ${order.from} to ${order.depositAddress} before ${order.depositDeadline}` +
  (order.depositExtraId ? ` with memo ${order.depositExtraId}` : "")); // CC → X: depositExtraId is always null

// 5. Poll until final
const FINAL = ["completed", "refunded", "failed", "expired"];
while (!FINAL.includes(order.state)) {
  await new Promise((r) => setTimeout(r, order.state === "preparing_deposit_address" ? 5_000 : 15_000));
  order = await float(`/order/${order.claimToken}`);
  console.log(order.state);
}

Python (3.9+, requests)

python
# float_flow.py — server-side only. pip install requests
# Run: FLOAT_API_KEY=fl_live_YOUR_KEY_HERE python float_flow.py
import os, time, uuid
from decimal import Decimal
import requests

BASE = "https://api.myfloat.cc/v1"
KEY = os.environ.get("FLOAT_API_KEY")  # <-- your key, from the environment only

def call(path, method="GET", body=None, idempotency_key=None):
    headers = {"accept": "application/json"}
    if KEY:
        headers["authorization"] = f"Bearer {KEY}"
    if idempotency_key:
        headers["idempotency-key"] = idempotency_key
    for attempt in range(4):
        r = requests.request(method, BASE + path, json=body, headers=headers, timeout=30)
        data = r.json()
        if r.ok:
            return data
        retryable = r.status_code == 429 or data.get("code") == "idempotency_in_progress" or (
            r.status_code >= 500 and (method == "GET" or idempotency_key))
        if retryable and attempt < 3:
            time.sleep(int(r.headers.get("retry-after", "2")))
            continue
        raise RuntimeError(f'{r.status_code} {data.get("code")}: {data.get("error")}')

# 1. Currencies
assets = call("/assets?all=1")["assets"]
usdt = next(a for a in assets if a["symbol"] == "USDT" and a.get("network") == "TRON")  # symbol + network identify a row

# network is optional: include the key only when the row has one (omitted = the asset's default network)
net = {"network": usdt["network"]} if usdt.get("network") else {}

# 2. Estimate — amounts are decimal strings: parse with Decimal, never float
q = call("/quote", "POST", {"direction": "in", "from": "USDT", **net, "amount": "100", "rateType": "float"})
print("100 USDT ->", Decimal(q["amountOut"]).quantize(Decimal("0.0001")), "CC via", q["venue"])

# 3. Create the order (Get CC: destination is the user's Canton party)
order = call("/order", "POST", {
    "direction": "in", "from": "USDT", **net, "amount": "100", "rateType": "float",
    "venue": q["venue"], "destination": "user::1220…",
    **({"refundAddress": "T…user-tron-address"} if q["needsRefundAddress"] else {}),
}, idempotency_key=str(uuid.uuid4()))

# 4. Show the deposit
memo = f' with memo/tag {order["depositExtraId"]}' if order["depositExtraId"] else ""
print(f'Send exactly {order["amountIn"]} {order["from"]} on {order["network"]} to {order["depositAddress"]}{memo} before {order["depositDeadline"]}')

# 5. Poll until final
while order["state"] not in ("completed", "refunded", "failed", "expired"):
    time.sleep(5 if order["state"] == "preparing_deposit_address" else 15)
    order = call(f'/order/{order["claimToken"]}')
    print(order["state"])

Amounts are decimal strings

Every amount is a decimal string. Partner figures can carry binary-float artifacts ("953.672096269999997276"), so:

  • parse with a decimal type (Decimal in Python, a decimal library or BigInt scaling in JavaScript) — never do money arithmetic in floating point;
  • show what the user must send (amountIn) exactly as returned — never rounded;
  • round what they receive (amountOut, expectedOut) down for display: about 4 decimals for CC, 2 for stablecoins, 8 for BTC, 6 otherwise;
  • round a minimum (minIn) up and a maximum (maxIn) down.

12. Versioning and changelog

/v1 is stable. Within /v1 Float only adds: new endpoints, new optional request fields, new response fields, new error codes. Your client must ignore fields it does not know. Anything breaking ships under a new version (/v2) with notice. The original unversioned paths under /api/float still work but are not documented; use /v1.

Newest first.

DateChange
2026-10-21Embed widget: order_created no longer carries claimToken — the widget never posts the order's claim token (or anything else that can act on an order) to the host page.
2026-10-20Error sentences never repeat your input; an asset Float does not list is 409 conflict "Unknown asset. Use a symbol from GET /v1/assets." alternatives[].deltaPct, and alternatives[].note names the winner as people know it ("The Change"). /v1/assets lists each pinned asset once (USDT-TRC20, USDC-ERC20… still accepted). /v1 answers carry no CORS credentials (/v1/openapi.json stays readable from any origin). The OpenAPI servers list is https://api.myfloat.cc/v1 only. Embed widget: posts direction, order_created and order_state messages to the host page besides height; default amounts per direction. Partner payouts send exactly the amount asked, from the minimum up to the payable balance.
2026-10-17Public reference, OpenAPI 3.1 at /v1/openapi.json and www.myfloat.cc/openapi.json. /v1/assets rows carry Float-hosted icon, networkIcon and networkName.
2026-10-17Withdraw CC orders always get their own deposit address (depositValidForThisOrderOnly), no memo; 30-minute deposit window plus 10-minute grace (depositDeadline, depositGraceUntil).
2026-10-17Pinned chains (networkChoice: "float_default"), 409 venue_unavailable.
2026-10-17Webhooks: order.state_changed, payout.updated, webhook.test; /v1/webhooks endpoints (API key).
2026-10-17Destination tag / memo: destinationExtraId on Withdraw CC quotes and orders; destinationExtraIdNeeded / destinationExtraIdName on /assets rows and quotes.
2026-10-17Base URL is https://api.myfloat.cc/v1.
2026-10-16/v1 launched: per-key rate limits, Idempotency-Key, one state field, refundAddress / refundParty.