{
  "openapi": "3.1.0",
  "info": {
    "title": "Float partner API",
    "version": "1.0.0",
    "summary": "Swap ~2,000 assets into Canton Coin (CC) and back, at the best rate across Float's liquidity partners.",
    "description": "Float's versioned partner API (`/v1`). Quote a swap, open an order, show your user where to send, and poll the order until it is final.\n\n**This is Float's spec.** The document at the Worker root (`/openapi.json`) is a different product (Slay's wallet API) and does not describe these endpoints.\n\nFull reference, with the integration flow, the payment rules and code examples: https://www.myfloat.cc/docs/api (Markdown: https://www.myfloat.cc/docs/api.md).\n\n- **Server-side only.** Call the API from your backend. Keys are server secrets; browser calls from your own domain are not supported (CORS).\n- **Amounts are decimal strings.** Parse them as decimals; round only for display.\n- **Use `rateType: \"float\"`.** The default is `fixed`, which priced about 1.7% worse in a live comparison (17 Oct 2026).\n- **Webhooks.** Register an endpoint with `POST /webhooks` (API key) and receive `order.state_changed`, `payout.updated` and `webhook.test`, signed with `Float-Signature: t=…,v1=…` (HMAC-SHA256 over `<t>.<raw body>`). See the `webhooks` section of this document. Polling `GET /order/{claimToken}` stays the source of truth.\n- **Destination tag / memo.** CC → X payouts to an exchange's XRP, XLM, TON, ATOM, EOS, HBAR… address need the exchange's tag: send it as `destinationExtraId` on the quote and the order. `/assets` rows and quotes say when an asset needs one (`destinationExtraIdNeeded`, `destinationExtraIdName`).",
    "license": {
      "name": "Proprietary — use with a Float partner account",
      "identifier": "LicenseRef-Float-Partner"
    },
    "contact": {
      "name": "Float support",
      "email": "support@myfloat.cc",
      "url": "https://www.myfloat.cc/docs/api"
    }
  },
  "externalDocs": {
    "description": "Float partner API reference",
    "url": "https://www.myfloat.cc/docs/api"
  },
  "servers": [
    {
      "url": "https://api.myfloat.cc/v1",
      "description": "Production."
    }
  ],
  "security": [
    {},
    {
      "bearerAuth": []
    }
  ],
  "tags": [
    {
      "name": "Currencies",
      "description": "The assets Float can swap, with Float-hosted token and chain images."
    },
    {
      "name": "Estimate",
      "description": "Indicative prices across every partner."
    },
    {
      "name": "Exchange",
      "description": "Create an order, read it, add a refund address."
    },
    {
      "name": "Referrals",
      "description": "Referral codes (for links and the embed widget)."
    },
    {
      "name": "Meta",
      "description": "This document."
    },
    {
      "name": "Webhooks",
      "description": "Manage the endpoints Float POSTs events to. **Requires an API key** (these calls are not anonymous). Every change made with a key is emailed to the account owner. Up to 5 endpoints per account; every endpoint receives every event type. The events themselves are in this document's `webhooks` section."
    }
  ],
  "paths": {
    "/assets": {
      "get": {
        "tags": [
          "Currencies"
        ],
        "operationId": "listAssets",
        "summary": "List or search the assets Float can swap",
        "description": "Assets at least one enabled partner will swap into CC, best-covered first. `?all=1` returns every row in a compact form for a token picker (cached 10 minutes); the other parameters are then ignored.\n\n`icon` and `networkIcon` are absolute URLs to Float's own 64×64 images on https://www.myfloat.cc (never a venue's URL), or null — draw a lettered tile then.\n\nLimits (min / max) are per route and size, so they come from `POST /quote` (`minIn`, `maxIn`), not from here.\n\nEach asset is listed once: a chain-suffixed ticker that is only another spelling of a listed one (`USDT-TRC20` beside `USDT` on Tron, `USDC-ERC20` beside `USDC` on Ethereum) is left out. Such tickers are still accepted on `POST /quote` and `POST /order`.\n\n`destinationExtraIdNeeded` on a row is authoritative: when it is true, CC → X payouts of that asset take a destination tag / memo.",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Case-insensitive match on ticker or name. Exact ticker first, then prefix, then anything.",
            "example": "usdt"
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            },
            "description": "Rows per page. Clamped to 1–200."
          },
          {
            "name": "offset",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Rows to skip."
          },
          {
            "name": "network",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Only rows on this chain, any spelling (`BSC`, `BNB`, `bep20`). `none` = rows with no known chain.",
            "example": "TRON"
          },
          {
            "name": "all",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "1"
              ]
            },
            "description": "`1` returns every row (compact)."
          }
        ],
        "responses": {
          "200": {
            "description": "A page of assets, or (with `all=1`) every asset in compact form.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/AssetPage"
                    },
                    {
                      "$ref": "#/components/schemas/AssetCatalog"
                    }
                  ]
                },
                "examples": {
                  "search": {
                    "summary": "GET /assets?q=USDT&limit=2",
                    "value": {
                      "landingAsset": "CC",
                      "total": 18,
                      "offset": 0,
                      "assets": [
                        {
                          "symbol": "USDT",
                          "venues": [
                            "houdiniswap",
                            "rocketx",
                            "letsexchange",
                            "exolix",
                            "thechange"
                          ],
                          "name": "Tether",
                          "icon": "https://www.myfloat.cc/icons/tokens/usdt.webp",
                          "network": "TRON",
                          "networkName": "Tron",
                          "networkIcon": "https://www.myfloat.cc/icons/chains/tron.png"
                        },
                        {
                          "symbol": "USDT-APT",
                          "venues": [
                            "letsexchange"
                          ],
                          "name": "TetherUS",
                          "icon": "https://www.myfloat.cc/icons/tokens/usdt.webp",
                          "network": "APTOS",
                          "networkName": "Aptos",
                          "networkIcon": "https://www.myfloat.cc/icons/chains/aptos.png"
                        }
                      ]
                    }
                  },
                  "all": {
                    "summary": "GET /assets?all=1 (first rows)",
                    "value": {
                      "landingAsset": "CC",
                      "total": 1958,
                      "assets": [
                        {
                          "symbol": "BTC",
                          "name": "Bitcoin",
                          "network": "BITCOIN",
                          "networkName": "Bitcoin",
                          "icon": "https://www.myfloat.cc/icons/tokens/btc.webp",
                          "networkIcon": "https://www.myfloat.cc/icons/chains/bitcoin.png"
                        },
                        {
                          "symbol": "OBSCURE",
                          "name": "Some token",
                          "networkName": null,
                          "icon": null,
                          "networkIcon": null
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/InvalidKey"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "502": {
            "$ref": "#/components/responses/UpstreamError"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      }
    },
    "/quote": {
      "post": {
        "tags": [
          "Estimate"
        ],
        "operationId": "getQuote",
        "summary": "Get an indicative price",
        "description": "Asks every enabled partner and returns the best route (`venue`, `amountOut`) and the others (`alternatives`). Binds nothing: there is no quote id and no expiry, and `POST /order` re-quotes when it opens the order.\n\nToo small or too large: `409 conflict` with the limit in `error` only (e.g. \"That amount is below every venue's minimum. The lowest is 30.0392 USDT.\"). A successful quote also returns `minIn` / `maxIn` for the winning route.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/QuoteRequest"
              },
              "examples": {
                "in": {
                  "summary": "USDT → CC",
                  "value": {
                    "direction": "in",
                    "from": "USDT",
                    "amount": "100",
                    "rateType": "float"
                  }
                },
                "out": {
                  "summary": "CC → BTC",
                  "value": {
                    "direction": "out",
                    "to": "BTC",
                    "amount": "1000",
                    "rateType": "float"
                  }
                },
                "network": {
                  "summary": "USDC on Solana → CC",
                  "value": {
                    "direction": "in",
                    "from": "USDC",
                    "network": "SOLANA",
                    "amount": "250",
                    "rateType": "float"
                  }
                },
                "tag": {
                  "summary": "CC → XRP to an exchange address, with its destination tag",
                  "value": {
                    "direction": "out",
                    "to": "XRP",
                    "amount": "1000",
                    "rateType": "float",
                    "destinationExtraId": "104729"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The best route and its alternatives.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Quote"
                },
                "examples": {
                  "in": {
                    "summary": "USDT → CC",
                    "value": {
                      "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,
                      "destinationExtraIdName": null,
                      "destinationExtraIdNeeded": 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
                    }
                  },
                  "out": {
                    "summary": "CC → BTC",
                    "value": {
                      "direction": "out",
                      "from": "CC",
                      "to": "BTC",
                      "amountIn": "1000",
                      "amountOut": "0.00124738",
                      "venueOut": "0.00124738",
                      "markupBps": 60,
                      "venueBaseFeeBps": null,
                      "totalFeeBps": 135,
                      "venue": "exolix",
                      "network": "BITCOIN",
                      "venueNetwork": "BTC",
                      "networkChoice": "float_default",
                      "settleSeconds": null,
                      "warning": null,
                      "needsRefundAddress": false,
                      "destinationExtraIdName": null,
                      "destinationExtraIdNeeded": false,
                      "alternatives": [
                        {
                          "venue": "rocketx",
                          "amountOut": "0.00122983",
                          "network": "BITCOIN",
                          "note": "1.41% less than Exolix",
                          "deltaPct": 1.41,
                          "totalFeeBps": 274,
                          "venueNetwork": "BTC",
                          "needsRefundAddress": false,
                          "destinationExtraIdName": null,
                          "destinationExtraIdNeeded": false
                        },
                        {
                          "venue": "thechange",
                          "amountOut": "0.00119245",
                          "network": "BITCOIN",
                          "note": "4.40% less than Exolix",
                          "deltaPct": 4.4,
                          "totalFeeBps": 569,
                          "venueNetwork": "BTC",
                          "needsRefundAddress": false,
                          "destinationExtraIdName": null,
                          "destinationExtraIdNeeded": false
                        }
                      ],
                      "venueSpread": 0.07676890763039336,
                      "unavailable": [],
                      "suggestedMin": null,
                      "rateType": "float",
                      "flatFee": null,
                      "flatFeeAsset": null,
                      "minIn": "477.007935",
                      "maxIn": "7990265.157",
                      "indicative": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "`invalid_request`: no JSON body, no asset, amount not a positive number, `network` not a network name, a suffixed ticker that disagrees with `network`, or CC as the non-CC asset.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "amount": {
                    "summary": "No amount",
                    "value": {
                      "error": "Enter an amount.",
                      "code": "invalid_request"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/InvalidKey"
          },
          "409": {
            "description": "`conflict`: no route for this pair at this size (the limit is only in `error`), or an asset Float does not list — then `error` is exactly \"Unknown asset. Use a symbol from GET /v1/assets.\" Error sentences never repeat your input; show them as text.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "tooSmall": {
                    "summary": "Below every minimum",
                    "value": {
                      "error": "That amount is below every venue's minimum. The lowest is 30.0392 USDT.",
                      "code": "conflict"
                    }
                  },
                  "tooLarge": {
                    "summary": "Above every maximum",
                    "value": {
                      "error": "That amount is above every venue's maximum. The most any will take is 29.38 BTC.",
                      "code": "conflict"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "502": {
            "$ref": "#/components/responses/UpstreamError"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      }
    },
    "/order": {
      "post": {
        "tags": [
          "Exchange"
        ],
        "operationId": "createOrder",
        "summary": "Create an order (exchange)",
        "description": "Re-quotes, opens the order at the partner, and returns where the user sends. **Moves no money**: the user sends from their own wallet, and an order nobody pays expires.\n\n**What to show the user**\n- **CC → X (`direction: out`)**: send exactly `amountIn` CC to `depositAddress` — this order's own Float deposit address — before `depositDeadline`. **No memo, ever** (`depositExtraId` is always null): Float forwards the CC to the partner itself. Less than `amountIn` is refunded and the order cancelled; more is processed and the excess refunded. A transfer still in flight is accepted until `depositGraceUntil` (10 minutes later).\n- **X → CC (`direction: in`)**: send exactly `amountIn` of `from` on `network` to `depositAddress` — the swap partner's address — before `depositDeadline`. When `depositExtraId` is not null the user **must** include it as the memo / tag on the source chain.\n\nUse an `Idempotency-Key` (needs an API key) so a retry after a timeout returns the same order instead of opening a second one.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OrderRequest"
              },
              "examples": {
                "out": {
                  "summary": "CC → BTC",
                  "value": {
                    "direction": "out",
                    "to": "BTC",
                    "amount": "1000",
                    "rateType": "float",
                    "destination": "bc1qexampleaddress…",
                    "refundParty": "alice::12207d5f1c3b9a0e4f6d8c2b1a0e9f8d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3a2f"
                  }
                },
                "tag": {
                  "summary": "CC → XRP to an exchange (destination tag)",
                  "value": {
                    "direction": "out",
                    "to": "XRP",
                    "amount": "1000",
                    "rateType": "float",
                    "destination": "rExchangeDepositAddress…",
                    "destinationExtraId": "104729",
                    "refundParty": "alice::12207d5f1c3b9a0e4f6d8c2b1a0e9f8d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3a2f"
                  }
                },
                "in": {
                  "summary": "USDT (Tron) → CC",
                  "value": {
                    "direction": "in",
                    "from": "USDT",
                    "network": "TRON",
                    "amount": "100",
                    "rateType": "float",
                    "destination": "alice::12207d5f1c3b9a0e4f6d8c2b1a0e9f8d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3a2f",
                    "refundAddress": "TXyzUserRefundAddress…"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The order. With `Idempotent-Replayed: true` it is the order an earlier request with the same `Idempotency-Key` opened.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "Idempotent-Replayed": {
                "description": "`true` when this is a replay of an earlier request with the same Idempotency-Key.",
                "schema": {
                  "type": "string",
                  "enum": [
                    "true"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Order"
                },
                "examples": {
                  "out": {
                    "summary": "CC → BTC, awaiting the user's CC",
                    "value": {
                      "claimToken": "3k0x9q…(64 characters)",
                      "direction": "out",
                      "from": "CC",
                      "to": "BTC",
                      "amountIn": "1000",
                      "expectedOut": "0.00124738",
                      "venue": "exolix",
                      "depositAddress": "float-deposit-012::12207d5f1c3b9a0e4f6d8c2b1a0e9f8d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3a2f",
                      "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::12207d5f1c3b9a0e4f6d8c2b1a0e9f8d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3a2f",
                          "txId": null,
                          "explorerUrl": null,
                          "state": "pending",
                          "at": null
                        }
                      ],
                      "refundParty": "alice::12207d5f1c3b9a0e4f6d8c2b1a0e9f8d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3a2f",
                      "destinationExtraId": null,
                      "depositParty": "float-deposit-012::12207d5f1c3b9a0e4f6d8c2b1a0e9f8d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3a2f",
                      "depositValidForThisOrderOnly": true,
                      "depositAddressNote": "Valid for this order only"
                    }
                  },
                  "in": {
                    "summary": "USDT → CC, awaiting the user's USDT",
                    "value": {
                      "claimToken": "7p2m4d…(64 characters)",
                      "direction": "in",
                      "from": "USDT",
                      "to": "CC",
                      "amountIn": "100",
                      "expectedOut": "953.672096",
                      "venue": "thechange",
                      "depositAddress": "TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE",
                      "depositExtraId": null,
                      "deliversTo": "alice::12207d5f1c3b9a0e4f6d8c2b1a0e9f8d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3a2f",
                      "status": "pending",
                      "state": "awaiting_deposit",
                      "network": "TRON",
                      "venueNetwork": "TRX",
                      "custodialState": "awaiting_venue",
                      "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": null,
                      "transactions": [
                        {
                          "kind": "customer_deposit",
                          "chain": "trc20",
                          "asset": "USDT",
                          "amount": "100",
                          "from": null,
                          "to": "TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE",
                          "txId": null,
                          "explorerUrl": null,
                          "state": "pending",
                          "at": null
                        }
                      ],
                      "refundAddress": "TXyzUserRefundAddress…",
                      "refundAddressRequired": false,
                      "refundAddressAtVenue": false,
                      "payoutParty": "float-deposit-031::12207d5f1c3b9a0e4f6d8c2b1a0e9f8d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3a2f",
                      "payoutFromThisOrderOnly": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "`invalid_request` (bad field), `venue_refused` (the partner rejected the order — its message is in `error`), `idempotency_key_invalid` or `idempotency_requires_key`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "dest": {
                    "summary": "Bad destination",
                    "value": {
                      "error": "That does not look like a Canton party ID. It should look like name::1220abcd… — the venue delivers your CC there, and it cannot be changed once the order is open.",
                      "code": "invalid_request"
                    }
                  },
                  "idem": {
                    "summary": "Idempotency-Key without an API key",
                    "value": {
                      "error": "Idempotency-Key needs a partner API key (Authorization: Bearer fl_live_…): keys are remembered per partner.",
                      "code": "idempotency_requires_key"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/InvalidKey"
          },
          "409": {
            "description": "`conflict` (no route at this size, an asset Float does not list — \"Unknown asset. Use a symbol from GET /v1/assets.\" — or the named `venue` is not quoting it), `venue_unavailable` (the winning partner would not open the order — body has `venue`; nothing was opened), `idempotency_key_reused` or `idempotency_in_progress` (`Retry-After: 2`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "venueUnavailable": {
                    "summary": "Partner would not open the order",
                    "value": {
                      "error": "The Change can't open this swap right now. Pick another route — nothing has been sent.",
                      "code": "venue_unavailable",
                      "venue": "thechange"
                    }
                  },
                  "tooSmall": {
                    "summary": "Too small",
                    "value": {
                      "error": "That amount is below every venue's minimum. The lowest is 477.01 CC.",
                      "code": "conflict"
                    }
                  }
                }
              }
            }
          },
          "422": {
            "description": "`unprocessable`: the Canton party (`destination` on `in`, `refundParty` on `out`) has no transfer preapproval, so it cannot receive CC. The user enables receiving in their wallet, then retry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "502": {
            "$ref": "#/components/responses/UpstreamError"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      }
    },
    "/order/{claimToken}": {
      "get": {
        "tags": [
          "Exchange"
        ],
        "operationId": "getOrder",
        "summary": "Get an order (exchange status)",
        "description": "The order, refreshed from its partner on each read, so `state` can move forward between reads. Use `order.state_changed` webhooks to learn when to read, or poll every 10–30 seconds while the user is watching (reads have their own, generous limit), and stop at a final state. An unknown token is `404 not_found`.",
        "parameters": [
          {
            "$ref": "#/components/parameters/ClaimToken"
          }
        ],
        "responses": {
          "200": {
            "description": "The order.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Order"
                },
                "examples": {
                  "out": {
                    "summary": "CC → BTC",
                    "value": {
                      "claimToken": "3k0x9q…(64 characters)",
                      "direction": "out",
                      "from": "CC",
                      "to": "BTC",
                      "amountIn": "1000",
                      "expectedOut": "0.00124738",
                      "venue": "exolix",
                      "depositAddress": "float-deposit-012::12207d5f1c3b9a0e4f6d8c2b1a0e9f8d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3a2f",
                      "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::12207d5f1c3b9a0e4f6d8c2b1a0e9f8d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3a2f",
                          "txId": null,
                          "explorerUrl": null,
                          "state": "pending",
                          "at": null
                        }
                      ],
                      "refundParty": "alice::12207d5f1c3b9a0e4f6d8c2b1a0e9f8d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3a2f",
                      "destinationExtraId": null,
                      "depositParty": "float-deposit-012::12207d5f1c3b9a0e4f6d8c2b1a0e9f8d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3a2f",
                      "depositValidForThisOrderOnly": true,
                      "depositAddressNote": "Valid for this order only"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/InvalidKey"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "502": {
            "$ref": "#/components/responses/UpstreamError"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      }
    },
    "/order/{claimToken}/refund-address": {
      "post": {
        "tags": [
          "Exchange"
        ],
        "operationId": "setRefundAddress",
        "summary": "Add or change the user's refund address (X → CC) or refund party (CC → X)",
        "description": "**X → CC**: `address` is the user's address on the source chain. On an order in `awaiting_refund_address` this **opens** the partner order on a fresh quote from the same partner and chain (`opened: true`); the order then has its deposit address. If the fresh quote pays more than 0.5% less than the figure the user saw, the answer is `409 requote_needed` with `requote.freshOut` — re-send with `acceptOut: freshOut` to accept. On an order that is already open the address is stored for support.\n\n**CC → X**: `address` is the user's own Canton party for refunds (`name::1220…`). Allowed until a refund of the order has started; moves nothing.\n\nFinished or expired orders are `409 conflict`. Counts against the per-key **order** limit.",
        "parameters": [
          {
            "$ref": "#/components/parameters/ClaimToken"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RefundAddressRequest"
              },
              "examples": {
                "in": {
                  "summary": "X → CC",
                  "value": {
                    "address": "TXyzUserRefundAddress…"
                  }
                },
                "accept": {
                  "summary": "Accept a re-quote",
                  "value": {
                    "address": "TXyzUserRefundAddress…",
                    "acceptOut": "947.10"
                  }
                },
                "out": {
                  "summary": "CC → X refund party",
                  "value": {
                    "address": "alice::12207d5f1c3b9a0e4f6d8c2b1a0e9f8d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3a2f"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Saved (and, on a waiting X → CC order, opened).",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RefundAddressResult"
                }
              }
            }
          },
          "400": {
            "description": "`invalid_request`: no address, or one that does not suit the chain / is not a usable Canton party.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/InvalidKey"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "`requote_needed` (body has `requote`), `conflict` (finished, expired, or a refund already started), or `venue_unavailable`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "requote": {
                    "summary": "Rate moved",
                    "value": {
                      "error": "The rate changed since this order was created. Confirm the new figure to open the order, or leave it — nothing has been opened.",
                      "code": "requote_needed",
                      "requote": {
                        "venue": "rocketx",
                        "previousOut": "950.000000",
                        "freshOut": "941.20"
                      }
                    }
                  }
                }
              }
            }
          },
          "422": {
            "description": "`unprocessable`: the refund party has no transfer preapproval.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      }
    },
    "/r/{code}": {
      "get": {
        "tags": [
          "Referrals"
        ],
        "operationId": "resolveReferral",
        "summary": "Resolve a referral code",
        "description": "Counts one click on the code and returns it with the partner's app name. The embed widget calls this for you.",
        "parameters": [
          {
            "name": "code",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "acme"
          }
        ],
        "responses": {
          "200": {
            "description": "The code and its partner.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Referral"
                },
                "example": {
                  "ref": "acme",
                  "partner": "Acme Wallet"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/InvalidKey"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      }
    },
    "/webhooks": {
      "get": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "listWebhooks",
        "summary": "List your webhook endpoints",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "description": "Your endpoints (never their secrets) and the event types Float sends.",
        "responses": {
          "200": {
            "description": "Endpoints.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "endpoints",
                    "eventTypes"
                  ],
                  "properties": {
                    "endpoints": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/WebhookEndpoint"
                      }
                    },
                    "eventTypes": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "example": [
                        "order.state_changed",
                        "payout.updated"
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized` (no `fl_` key sent) or `invalid_key` (unknown, revoked, or not allowed from this IP).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookError"
                },
                "example": {
                  "error": "Send your partner API key: Authorization: Bearer fl_live_…",
                  "code": "unauthorized"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "description": "`unavailable`: the partner programme is not open.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookError"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "createWebhook",
        "summary": "Register a webhook endpoint",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "description": "Registers an `https://` URL. **The signing `secret` is in this response and no other** — store it with your other credentials (e.g. `FLOAT_WEBHOOK_SECRET`). URL rules: `https://` only; no credentials in it; no private, loopback, link-local or reserved IPs; 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.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "url"
                ],
                "properties": {
                  "url": {
                    "type": "string",
                    "format": "uri",
                    "example": "https://example.com/hooks/float"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Registered. The secret is shown only here.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookSecretResponse"
                },
                "example": {
                  "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. …"
                }
              }
            }
          },
          "400": {
            "description": "`invalid_url`: the URL breaks a rule above.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookError"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized` (no `fl_` key sent) or `invalid_key` (unknown, revoked, or not allowed from this IP).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookError"
                },
                "example": {
                  "error": "Send your partner API key: Authorization: Bearer fl_live_…",
                  "code": "unauthorized"
                }
              }
            }
          },
          "409": {
            "description": "`too_many_endpoints` (5 already) or `duplicate_url`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "description": "`unavailable`: the partner programme is not open.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookError"
                }
              }
            }
          }
        }
      }
    },
    "/webhooks/{id}": {
      "get": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "getWebhook",
        "summary": "Get one endpoint",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "whe_3f9c0a…"
          }
        ],
        "responses": {
          "200": {
            "description": "The endpoint.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "endpoint"
                  ],
                  "properties": {
                    "endpoint": {
                      "$ref": "#/components/schemas/WebhookEndpoint"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized` (no `fl_` key sent) or `invalid_key` (unknown, revoked, or not allowed from this IP).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookError"
                },
                "example": {
                  "error": "Send your partner API key: Authorization: Bearer fl_live_…",
                  "code": "unauthorized"
                }
              }
            }
          },
          "404": {
            "description": "`not_found`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "description": "`unavailable`: the partner programme is not open.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookError"
                }
              }
            }
          }
        }
      },
      "patch": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "updateWebhook",
        "summary": "Change the URL, pause or resume",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "whe_3f9c0a…"
          }
        ],
        "description": "`paused: true` pauses (nothing new is delivered), `paused: false` resumes (events already queued keep their 24 hours and go out). Resume an auto-paused endpoint after fixing the receiver.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "url": {
                    "type": "string",
                    "format": "uri"
                  },
                  "paused": {
                    "type": "boolean"
                  }
                }
              },
              "example": {
                "paused": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The endpoint.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "endpoint"
                  ],
                  "properties": {
                    "endpoint": {
                      "$ref": "#/components/schemas/WebhookEndpoint"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "`invalid_url` or `invalid_request`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookError"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized` (no `fl_` key sent) or `invalid_key` (unknown, revoked, or not allowed from this IP).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookError"
                },
                "example": {
                  "error": "Send your partner API key: Authorization: Bearer fl_live_…",
                  "code": "unauthorized"
                }
              }
            }
          },
          "404": {
            "description": "`not_found`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookError"
                }
              }
            }
          },
          "409": {
            "description": "`duplicate_url`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "description": "`unavailable`: the partner programme is not open.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookError"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "deleteWebhook",
        "summary": "Delete an endpoint",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "whe_3f9c0a…"
          }
        ],
        "description": "Its undelivered events are dropped.",
        "responses": {
          "200": {
            "description": "Deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "deleted",
                    "id"
                  ],
                  "properties": {
                    "deleted": {
                      "type": "boolean",
                      "const": true
                    },
                    "id": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized` (no `fl_` key sent) or `invalid_key` (unknown, revoked, or not allowed from this IP).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookError"
                },
                "example": {
                  "error": "Send your partner API key: Authorization: Bearer fl_live_…",
                  "code": "unauthorized"
                }
              }
            }
          },
          "404": {
            "description": "`not_found`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "description": "`unavailable`: the partner programme is not open.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookError"
                }
              }
            }
          }
        }
      }
    },
    "/webhooks/{id}/rotate-secret": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "rotateWebhookSecret",
        "summary": "Rotate the signing secret",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "whe_3f9c0a…"
          }
        ],
        "description": "Returns a new secret, once. For the next 24 hours every delivery is signed with both (`v1=<new>,v1=<old>`); `previousSecretValidUntil` says when the overlap ends.",
        "responses": {
          "200": {
            "description": "The new secret.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookSecretResponse"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized` (no `fl_` key sent) or `invalid_key` (unknown, revoked, or not allowed from this IP).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookError"
                },
                "example": {
                  "error": "Send your partner API key: Authorization: Bearer fl_live_…",
                  "code": "unauthorized"
                }
              }
            }
          },
          "404": {
            "description": "`not_found`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "description": "`unavailable`: the partner programme is not open.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookError"
                }
              }
            }
          }
        }
      }
    },
    "/webhooks/{id}/test": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "testWebhook",
        "summary": "Send a test event now",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "whe_3f9c0a…"
          }
        ],
        "description": "Sends one `webhook.test` immediately and answers with what your server said. Works on paused endpoints, never retried, never counts toward auto-pause. Limited to 6 a minute.",
        "responses": {
          "200": {
            "description": "The result.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookTestResult"
                },
                "example": {
                  "eventId": "evt_…",
                  "delivered": true,
                  "statusCode": 200,
                  "error": null,
                  "durationMs": 182
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized` (no `fl_` key sent) or `invalid_key` (unknown, revoked, or not allowed from this IP).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookError"
                },
                "example": {
                  "error": "Send your partner API key: Authorization: Bearer fl_live_…",
                  "code": "unauthorized"
                }
              }
            }
          },
          "404": {
            "description": "`not_found`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "description": "`unavailable`: the partner programme is not open.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookError"
                }
              }
            }
          }
        }
      }
    },
    "/webhooks/{id}/deliveries": {
      "get": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "listWebhookDeliveries",
        "summary": "The last 50 delivery attempts",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "whe_3f9c0a…"
          }
        ],
        "responses": {
          "200": {
            "description": "Newest first.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "attempts"
                  ],
                  "properties": {
                    "attempts": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/WebhookDeliveryAttempt"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized` (no `fl_` key sent) or `invalid_key` (unknown, revoked, or not allowed from this IP).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookError"
                },
                "example": {
                  "error": "Send your partner API key: Authorization: Bearer fl_live_…",
                  "code": "unauthorized"
                }
              }
            }
          },
          "404": {
            "description": "`not_found`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "description": "`unavailable`: the partner programme is not open.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookError"
                }
              }
            }
          }
        }
      }
    },
    "/openapi.json": {
      "get": {
        "tags": [
          "Meta"
        ],
        "operationId": "getOpenApi",
        "summary": "This OpenAPI document",
        "description": "No key needed and not rate-limited. A copy is published at https://www.myfloat.cc/openapi.json.",
        "security": [
          {}
        ],
        "responses": {
          "200": {
            "description": "OpenAPI 3.1 document.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    }
  },
  "webhooks": {
    "order.state_changed": {
      "post": {
        "operationId": "onOrderStateChangedEvent",
        "summary": "An order attributed to you (your key or referral link) changed state",
        "parameters": [
          {
            "name": "Float-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "t=1792238671,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd",
            "description": "`t=<unix seconds>,v1=<hex>`: HMAC-SHA256, keyed with the endpoint's whole `whsec_…` secret, over `<t>.<raw body>`. Two `v1` values for 24 h after a rotation — accept if either matches. Compare in constant time; reject `t` more than 5 minutes from your clock."
          },
          {
            "name": "Float-Event-Id",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The event's `id`; identical on every retry. De-duplicate on it."
          },
          {
            "name": "Float-Event-Type",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Float-Delivery-Attempt",
            "in": "header",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "1 on the first try, 2 on the first retry, …"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OrderStateChangedEvent"
              },
              "example": {
                "id": "evt_7c1e5b0a9f2d4c3b8a1e6f0d",
                "type": "order.state_changed",
                "created_at": "2026-10-17T12:04:31.512Z",
                "data": {
                  "previous_state": "awaiting_deposit",
                  "order": {
                    "claimToken": "7p2m4d…(64 characters)",
                    "direction": "in",
                    "from": "USDT",
                    "to": "CC",
                    "amountIn": "100",
                    "expectedOut": "953.672096",
                    "venue": "thechange",
                    "depositAddress": "TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE",
                    "depositExtraId": null,
                    "deliversTo": "alice::12207d5f1c3b9a0e4f6d8c2b1a0e9f8d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3a2f",
                    "status": "pending",
                    "state": "confirming",
                    "network": "TRON",
                    "venueNetwork": "TRX",
                    "custodialState": "awaiting_venue",
                    "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": null,
                    "transactions": [
                      {
                        "kind": "customer_deposit",
                        "chain": "trc20",
                        "asset": "USDT",
                        "amount": "100",
                        "from": null,
                        "to": "TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE",
                        "txId": null,
                        "explorerUrl": null,
                        "state": "pending",
                        "at": null
                      }
                    ],
                    "refundAddress": "TXyzUserRefundAddress…",
                    "refundAddressRequired": false,
                    "refundAddressAtVenue": false,
                    "payoutParty": "float-deposit-031::12207d5f1c3b9a0e4f6d8c2b1a0e9f8d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3a2f",
                    "payoutFromThisOrderOnly": true
                  }
                }
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Received. Answer within 10 seconds; do the work afterwards. Any other status, a timeout or a redirect (3xx is not followed) is retried with backoff (~30 s, 1, 2, 4, 8, 16, 32 min, 1 h, 2 h, then every 4 h, ±10% jitter) for 24 hours from the event. An endpoint that fails 10 times in a row over at least 12 hours is paused and the account is emailed."
          }
        }
      }
    },
    "payout.updated": {
      "post": {
        "operationId": "onPayoutUpdatedEvent",
        "summary": "One of your affiliate payouts was created or changed status",
        "parameters": [
          {
            "name": "Float-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "t=1792238671,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd",
            "description": "`t=<unix seconds>,v1=<hex>`: HMAC-SHA256, keyed with the endpoint's whole `whsec_…` secret, over `<t>.<raw body>`. Two `v1` values for 24 h after a rotation — accept if either matches. Compare in constant time; reject `t` more than 5 minutes from your clock."
          },
          {
            "name": "Float-Event-Id",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The event's `id`; identical on every retry. De-duplicate on it."
          },
          {
            "name": "Float-Event-Type",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Float-Delivery-Attempt",
            "in": "header",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "1 on the first try, 2 on the first retry, …"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PayoutUpdatedEvent"
              },
              "example": {
                "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"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Received. Answer within 10 seconds; do the work afterwards. Any other status, a timeout or a redirect (3xx is not followed) is retried with backoff (~30 s, 1, 2, 4, 8, 16, 32 min, 1 h, 2 h, then every 4 h, ±10% jitter) for 24 hours from the event. An endpoint that fails 10 times in a row over at least 12 hours is paused and the account is emailed."
          }
        }
      }
    },
    "webhook.test": {
      "post": {
        "operationId": "onWebhookTestEvent",
        "summary": "A test event (Send test event, or POST /webhooks/{id}/test)",
        "parameters": [
          {
            "name": "Float-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "t=1792238671,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd",
            "description": "`t=<unix seconds>,v1=<hex>`: HMAC-SHA256, keyed with the endpoint's whole `whsec_…` secret, over `<t>.<raw body>`. Two `v1` values for 24 h after a rotation — accept if either matches. Compare in constant time; reject `t` more than 5 minutes from your clock."
          },
          {
            "name": "Float-Event-Id",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The event's `id`; identical on every retry. De-duplicate on it."
          },
          {
            "name": "Float-Event-Type",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Float-Delivery-Attempt",
            "in": "header",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "1 on the first try, 2 on the first retry, …"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookTestEvent"
              },
              "example": {
                "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. …"
                }
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Received. Answer within 10 seconds; do the work afterwards. Any other status, a timeout or a redirect (3xx is not followed) is retried with backoff (~30 s, 1, 2, 4, 8, 16, 32 min, 1 h, 2 h, then every 4 h, ±10% jitter) for 24 hours from the event. An endpoint that fails 10 times in a row over at least 12 hours is paused and the account is emailed."
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "fl_live_…",
        "description": "Optional on every endpoint. Create keys at https://www.myfloat.cc/partners → API for developers; the secret is shown once. **Server-side only** — never in a browser, an app bundle or a URL. A key attributes orders to you (0.2% of the CC side), gives you per-key rate limits and enables Idempotency-Key. A key that is present but unknown, revoked or used from a non-allowed IP is `401 invalid_key`."
      }
    },
    "parameters": {
      "ClaimToken": {
        "name": "claimToken",
        "in": "path",
        "required": true,
        "description": "The order's handle from `POST /order` — a bearer capability: whoever holds it can read the order.",
        "schema": {
          "type": "string",
          "minLength": 1
        }
      },
      "IdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": false,
        "description": "1–255 visible ASCII characters (a UUID per intended order). Needs an API key. Remembered per partner for 24 hours. Same key + same body → the same order (`201`, `Idempotent-Replayed: true`); same key + different body → `409 idempotency_key_reused`; while the first is still opening → `409 idempotency_in_progress` with `Retry-After`. A request that fails frees its key.",
        "schema": {
          "type": "string",
          "pattern": "^[\\x21-\\x7e]{1,255}$"
        },
        "example": "6f1c0d7e-4b8a-4f0e-9a51-2a8b3c4d5e6f"
      }
    },
    "headers": {
      "X-RateLimit-Limit": {
        "description": "Requests allowed in this window. With a key: 60 (quote), 20 (order) or 300 (read) per minute. Without a key: 12 per minute per IP, approximate (per edge instance).",
        "schema": {
          "type": "integer"
        }
      },
      "X-RateLimit-Remaining": {
        "description": "Requests left in this window.",
        "schema": {
          "type": "integer"
        }
      },
      "X-RateLimit-Reset": {
        "description": "Unix seconds when the window ends (keyed requests, and any 429).",
        "schema": {
          "type": "integer"
        }
      },
      "Retry-After": {
        "description": "Seconds to wait before retrying.",
        "schema": {
          "type": "integer"
        }
      }
    },
    "responses": {
      "InvalidKey": {
        "description": "`invalid_key`: the API key is unknown, revoked, or used from an IP address it does not allow.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "That API key was not accepted: it is unknown, revoked, or used from an IP address the key does not allow.",
              "code": "invalid_key"
            }
          }
        }
      },
      "RateLimited": {
        "description": "`rate_limited`: wait `Retry-After` seconds and retry the identical request.",
        "headers": {
          "Retry-After": {
            "$ref": "#/components/headers/Retry-After"
          },
          "X-RateLimit-Limit": {
            "$ref": "#/components/headers/X-RateLimit-Limit"
          },
          "X-RateLimit-Remaining": {
            "$ref": "#/components/headers/X-RateLimit-Remaining"
          },
          "X-RateLimit-Reset": {
            "$ref": "#/components/headers/X-RateLimit-Reset"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "Too many requests. Slow down.",
              "code": "rate_limited"
            }
          }
        }
      },
      "Unavailable": {
        "description": "`unavailable`: Float 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 opened or moved; retry later.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "Deposits are paused right now. Nothing has been opened and nothing has moved — please try again later.",
              "code": "unavailable"
            }
          }
        }
      },
      "UpstreamError": {
        "description": "`upstream_error`: a partner did not answer, or the order could not be recorded. Re-quote and retry; with an Idempotency-Key, retry the same request.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "Could not get a price right now.",
              "code": "upstream_error"
            }
          }
        }
      },
      "NotFound": {
        "description": "`not_found`: unknown claim token, referral code or path.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "No order found for that link.",
              "code": "not_found"
            }
          }
        }
      }
    },
    "schemas": {
      "Decimal": {
        "type": "string",
        "pattern": "^-?[0-9]+(\\.[0-9]+)?$",
        "description": "A decimal number as a string. Treat it as a decimal, never a binary float: venue figures can carry artifacts such as `953.672096269999997276`. Round only for display (see the reference, \"Amounts\").",
        "example": "953.672096"
      },
      "NetworkCode": {
        "type": "string",
        "description": "Float's code for the chain of the non-CC asset: `BITCOIN`, `ETHEREUM`, `TRON`, `BSC`, `SOLANA`, `POLYGON`, `ARBITRUM`, `OPTIMISM`, `BASE`, `AVALANCHE`, `TON`, `XRP`, `LITECOIN`, `DOGECOIN`, … (~140 chains). In requests any common spelling is accepted (`ERC20`, `TRC20`, `BEP20`, `SOL`, …).",
        "example": "TRON"
      },
      "Direction": {
        "type": "string",
        "enum": [
          "in",
          "out"
        ],
        "default": "in",
        "description": "`in` = asset → CC. `out` = CC → asset."
      },
      "RateType": {
        "type": "string",
        "enum": [
          "float",
          "fixed"
        ],
        "default": "fixed",
        "description": "`float` (recommended) or `fixed` (the default; anything else is `fixed`). Fixed priced about 1.7% worse than float in a live comparison on 17 Oct 2026, and fewer partners offer it."
      },
      "NetworkChoice": {
        "type": "string",
        "enum": [
          "requested",
          "float_default",
          "venue_default"
        ],
        "description": "`requested`: you sent `network` (or a chain-suffixed ticker such as `USDC-SOL`). `float_default`: you did not, and Float applied its pinned chain for the asset (USDT → TRON, USDC → ETHEREUM, …) to every partner. `venue_default`: the asset has no pinned chain and each partner used its own default — they can differ; send `network` if the chain matters."
      },
      "OrderState": {
        "type": "string",
        "enum": [
          "awaiting_refund_address",
          "preparing_deposit_address",
          "awaiting_deposit",
          "confirming",
          "exchanging",
          "sending",
          "completed",
          "refunded",
          "failed",
          "expired"
        ],
        "description": "The one field to drive your UI from. Final: `completed`, `refunded`, `expired`, and `failed` (which moves on to `refunded` once Float has returned a deposit it holds).\n- `awaiting_refund_address` — X → CC: the route needs the user's source-chain refund address before it opens. No deposit address yet; call `/refund-address`.\n- `preparing_deposit_address` — CC → X: Float is creating this order's deposit address; `depositAddress` is \"\". Keep polling.\n- `awaiting_deposit` — waiting for the user's payment.\n- `confirming` — the payment was seen and is being confirmed / forwarded.\n- `exchanging` — the partner is swapping.\n- `sending` — the payout to the user is on its way.\n- `completed` — the user has been paid.\n- `refunded` — the payment went back to the user.\n- `failed` — the swap will not complete.\n- `expired` — the deposit window closed with nothing received."
      },
      "Error": {
        "type": "object",
        "required": [
          "error",
          "code"
        ],
        "properties": {
          "error": {
            "type": "string",
            "description": "A sentence for a person. May be reworded — do not parse it. Display it as text (`textContent`), never as HTML."
          },
          "code": {
            "type": "string",
            "enum": [
              "invalid_request",
              "invalid_key",
              "unauthorized",
              "not_found",
              "conflict",
              "unprocessable",
              "rate_limited",
              "idempotency_requires_key",
              "idempotency_key_invalid",
              "idempotency_key_reused",
              "idempotency_in_progress",
              "venue_refused",
              "venue_unavailable",
              "requote_needed",
              "upstream_error",
              "unavailable"
            ],
            "description": "Stable. Branch on this. `unauthorized` is reserved (not returned by /v1 today)."
          },
          "venue": {
            "type": "string",
            "description": "`venue_unavailable` only: the partner that would not open the order.",
            "example": "thechange"
          },
          "requote": {
            "type": "object",
            "description": "`requote_needed` only.",
            "required": [
              "venue",
              "previousOut",
              "freshOut"
            ],
            "properties": {
              "venue": {
                "type": "string"
              },
              "previousOut": {
                "$ref": "#/components/schemas/Decimal"
              },
              "freshOut": {
                "$ref": "#/components/schemas/Decimal"
              }
            }
          }
        }
      },
      "Asset": {
        "type": "object",
        "required": [
          "symbol",
          "venues",
          "icon",
          "networkIcon",
          "networkName"
        ],
        "properties": {
          "symbol": {
            "type": "string",
            "description": "Ticker to send as `from` / `to`. Some are chain-suffixed (`USDT-APT`); a suffixed ticker names its own chain.",
            "example": "USDT"
          },
          "venues": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Partner ids that list it."
          },
          "name": {
            "type": "string",
            "example": "Tether"
          },
          "network": {
            "$ref": "#/components/schemas/NetworkCode"
          },
          "networkName": {
            "type": [
              "string",
              "null"
            ],
            "description": "Display name of `network`.",
            "example": "Tron"
          },
          "venueNetwork": {
            "type": "string",
            "description": "A partner's own spelling of the chain, when it differs. Informational."
          },
          "icon": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri",
            "description": "Float-hosted 64×64 WebP token image (absolute URL). Falls back to the base token's image, then the chain logo; null → draw a lettered tile.",
            "example": "https://www.myfloat.cc/icons/tokens/usdt.webp"
          },
          "networkIcon": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri",
            "description": "Float-hosted 64×64 chain logo (PNG or WebP), or null.",
            "example": "https://www.myfloat.cc/icons/chains/tron.png"
          },
          "destinationExtraIdNeeded": {
            "type": "boolean",
            "const": true,
            "description": "Present (true) only on rows whose payouts take a destination tag / memo (XRP, XLM, HBAR, ATOM, EOS, TON tokens, …). Absent elsewhere."
          },
          "destinationExtraIdName": {
            "type": "string",
            "description": "Present with `destinationExtraIdNeeded`: \"Destination tag\" or \"Memo\"."
          }
        }
      },
      "AssetCompact": {
        "type": "object",
        "required": [
          "symbol",
          "icon",
          "networkIcon",
          "networkName"
        ],
        "properties": {
          "symbol": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "network": {
            "$ref": "#/components/schemas/NetworkCode"
          },
          "networkName": {
            "type": [
              "string",
              "null"
            ]
          },
          "icon": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri"
          },
          "networkIcon": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri"
          },
          "destinationExtraIdNeeded": {
            "type": "boolean",
            "const": true,
            "description": "Present (true) only on rows whose payouts take a destination tag / memo (XRP, XLM, HBAR, ATOM, EOS, TON tokens, …). Absent elsewhere."
          },
          "destinationExtraIdName": {
            "type": "string",
            "description": "Present with `destinationExtraIdNeeded`: \"Destination tag\" or \"Memo\"."
          }
        }
      },
      "AssetPage": {
        "type": "object",
        "required": [
          "landingAsset",
          "total",
          "offset",
          "assets"
        ],
        "properties": {
          "landingAsset": {
            "type": "string",
            "const": "CC"
          },
          "total": {
            "type": "integer",
            "description": "Matching rows across all pages."
          },
          "offset": {
            "type": "integer"
          },
          "network": {
            "type": [
              "string",
              "null"
            ],
            "description": "Present when filtering by `network`: the code it was read as (null for `none`)."
          },
          "assets": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Asset"
            }
          }
        }
      },
      "AssetCatalog": {
        "type": "object",
        "required": [
          "landingAsset",
          "total",
          "assets"
        ],
        "properties": {
          "landingAsset": {
            "type": "string",
            "const": "CC"
          },
          "total": {
            "type": "integer"
          },
          "assets": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AssetCompact"
            }
          }
        }
      },
      "QuoteRequest": {
        "type": "object",
        "required": [
          "amount"
        ],
        "properties": {
          "direction": {
            "$ref": "#/components/schemas/Direction"
          },
          "from": {
            "type": "string",
            "description": "Required when `direction` is `in`: the asset the user sends.",
            "example": "USDT"
          },
          "to": {
            "type": "string",
            "description": "Required when `direction` is `out`: the asset the user receives.",
            "example": "BTC"
          },
          "amount": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Decimal"
              },
              {
                "type": "number",
                "exclusiveMinimum": 0
              }
            ],
            "description": "> 0. `in`: amount of `from`. `out`: amount of CC. Send a string."
          },
          "rateType": {
            "$ref": "#/components/schemas/RateType"
          },
          "network": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/NetworkCode"
              },
              {
                "type": "null"
              }
            ],
            "description": "Chain of the non-CC asset. Absent: Float's pinned chain for the asset, else each partner's own default (see `networkChoice`). `null` or `\"\"` is the same as leaving it out."
          },
          "destinationExtraId": {
            "oneOf": [
              {
                "type": "string",
                "pattern": "^[\\x21-\\x7e]{1,64}$"
              },
              {
                "type": "integer",
                "minimum": 0,
                "maximum": 4294967295
              }
            ],
            "description": "`out` only (`400` on `in`): the destination tag / memo for the payout address — required by an exchange's shared XRP, XLM, TON, ATOM, EOS, HBAR… deposit address. 1–64 visible ASCII characters, validated per chain: XRP — digits 0–4294967295 (an integer is accepted); Stellar — text up to 28 characters or a numeric ID up to 18446744073709551615; TON, Cosmos chains, EOS, Hedera, BNB Beacon — text up to 64 characters; refused on chains whose addresses take none (Bitcoin, EVM chains, Tron, Solana, …). Partners that cannot carry it are left out (listed in `unavailable`); naming one in `venue` is `409 venue_unavailable`; if none can, `409 conflict`. Part of the Idempotency-Key identity.",
            "example": "104729"
          },
          "timezone": {
            "type": "string",
            "description": "Optional: the end user's IANA time zone, passed to partners whose risk checks ask for it.",
            "example": "Europe/Berlin"
          },
          "tzOffset": {
            "type": "integer",
            "description": "Optional: the end user's `Date.getTimezoneOffset()` in minutes."
          }
        }
      },
      "QuoteAlternative": {
        "type": "object",
        "required": [
          "venue",
          "amountOut"
        ],
        "properties": {
          "venue": {
            "type": "string"
          },
          "amountOut": {
            "$ref": "#/components/schemas/Decimal"
          },
          "network": {
            "type": [
              "string",
              "null"
            ]
          },
          "venueNetwork": {
            "type": [
              "string",
              "null"
            ]
          },
          "note": {
            "type": [
              "string",
              "null"
            ],
            "description": "Why this route ranks below the winner, for people: \"0.37% less than The Change\" (the winner by its display name), \"same price, slower to settle (…)\", or \"same price\". Wording may change — branch on `deltaPct`, not on this text.",
            "example": "0.37% less than The Change"
          },
          "deltaPct": {
            "type": [
              "number",
              "null"
            ],
            "description": "How much less this route delivers than the winner, in percent (0.37 = 0.37%); 0 on a tie. Null when either amount is not a number.",
            "example": 0.37
          },
          "totalFeeBps": {
            "type": [
              "integer",
              "null"
            ]
          },
          "needsRefundAddress": {
            "type": "boolean"
          },
          "destinationExtraIdNeeded": {
            "type": "boolean",
            "description": "CC → X: payouts on this chain commonly need a destination tag / memo (an exchange's address). Show the field and a warning."
          },
          "destinationExtraIdName": {
            "type": [
              "string",
              "null"
            ],
            "description": "CC → X: what to call it (\"Destination tag\" for XRP, else \"Memo\"); null when this route cannot pass one.",
            "example": "Destination tag"
          }
        }
      },
      "Quote": {
        "type": "object",
        "required": [
          "direction",
          "from",
          "to",
          "amountIn",
          "amountOut",
          "venue",
          "network",
          "networkChoice",
          "needsRefundAddress",
          "alternatives",
          "unavailable",
          "rateType",
          "minIn",
          "maxIn",
          "indicative"
        ],
        "properties": {
          "direction": {
            "$ref": "#/components/schemas/Direction"
          },
          "from": {
            "type": "string"
          },
          "to": {
            "type": "string"
          },
          "amountIn": {
            "$ref": "#/components/schemas/Decimal"
          },
          "amountOut": {
            "$ref": "#/components/schemas/Decimal",
            "description": "What the user receives, after every fee. A decimal number as a string. Treat it as a decimal, never a binary float: venue figures can carry artifacts such as `953.672096269999997276`. Round only for display (see the reference, \"Amounts\")."
          },
          "venueOut": {
            "$ref": "#/components/schemas/Decimal"
          },
          "markupBps": {
            "type": "integer",
            "description": "Float's fee, already inside `amountOut`."
          },
          "venueBaseFeeBps": {
            "type": [
              "integer",
              "null"
            ],
            "description": "The winning partner's own published base fee, in basis points — null when the partner publishes none (most do not). `totalFeeBps` is the figure to show."
          },
          "totalFeeBps": {
            "type": [
              "integer",
              "null"
            ],
            "description": "All-in cost against market, already inside `amountOut`."
          },
          "venue": {
            "type": "string",
            "description": "The winning partner's id.",
            "example": "thechange"
          },
          "network": {
            "type": [
              "string",
              "null"
            ],
            "description": "Float's code for the chain this price is on."
          },
          "venueNetwork": {
            "type": [
              "string",
              "null"
            ]
          },
          "networkChoice": {
            "$ref": "#/components/schemas/NetworkChoice"
          },
          "settleSeconds": {
            "type": [
              "integer",
              "null"
            ]
          },
          "warning": {
            "type": [
              "string",
              "null"
            ],
            "description": "Show it: the best price is notably worse than market."
          },
          "destinationExtraIdNeeded": {
            "type": "boolean",
            "description": "CC → X: payouts on this chain commonly need a destination tag / memo (an exchange's address). Show the field and a warning."
          },
          "destinationExtraIdName": {
            "type": [
              "string",
              "null"
            ],
            "description": "CC → X: what to call it (\"Destination tag\" for XRP, else \"Memo\"); null when this route cannot pass one.",
            "example": "Destination tag"
          },
          "needsRefundAddress": {
            "type": "boolean",
            "description": "X → CC: this partner opens the order only once it has the user's source-chain refund address. Send `refundAddress` with the order (or add it later)."
          },
          "alternatives": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/QuoteAlternative"
            },
            "description": "The other partners, best first."
          },
          "venueSpread": {
            "type": [
              "number",
              "null"
            ]
          },
          "unavailable": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "venue",
                "reason"
              ],
              "properties": {
                "venue": {
                  "type": "string"
                },
                "reason": {
                  "type": "string"
                },
                "minIn": {
                  "$ref": "#/components/schemas/Decimal"
                }
              }
            },
            "description": "Partners that did not quote, with the reason."
          },
          "suggestedMin": {
            "type": [
              "string",
              "null"
            ]
          },
          "rateType": {
            "$ref": "#/components/schemas/RateType"
          },
          "flatFee": {
            "type": [
              "string",
              "null"
            ],
            "description": "A flat fee the partner reports (display only; already inside `amountOut`)."
          },
          "flatFeeAsset": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "in",
              "out",
              null
            ]
          },
          "minIn": {
            "type": [
              "string",
              "null"
            ],
            "description": "Winning route's minimum, in the input asset. Round **up** for display."
          },
          "maxIn": {
            "type": [
              "string",
              "null"
            ],
            "description": "Winning route's maximum, in the input asset. Round **down** for display."
          },
          "indicative": {
            "type": "boolean",
            "const": true
          }
        }
      },
      "OrderRequest": {
        "type": "object",
        "required": [
          "amount",
          "destination"
        ],
        "description": "Required fields by direction: `in` needs `from` and `destination` (a Canton party); `out` needs `to` and `destination` (an address on the asset's chain), plus `destinationExtraId` when that address needs a tag or memo.",
        "properties": {
          "direction": {
            "$ref": "#/components/schemas/Direction"
          },
          "from": {
            "type": "string",
            "description": "Required on `in`."
          },
          "to": {
            "type": "string",
            "description": "Required on `out`."
          },
          "amount": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Decimal"
              },
              {
                "type": "number",
                "exclusiveMinimum": 0
              }
            ],
            "description": "`in`: amount of `from`. `out`: amount of CC the user will send."
          },
          "rateType": {
            "$ref": "#/components/schemas/RateType"
          },
          "network": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/NetworkCode"
              },
              {
                "type": "null"
              }
            ],
            "description": "Chain of the non-CC asset. Omitted, `null` or `\"\"` all mean the same: the asset's default chain (Float's pinned chain for the asset, else each partner's own default — see `networkChoice`)."
          },
          "destination": {
            "type": "string",
            "description": "Where the user is paid. `in`: their Canton party ID (`name::1220…`). `out`: an address on the asset's chain (the partner validates it). Cannot be changed later.",
            "example": "alice::12207d5f1c3b9a0e4f6d8c2b1a0e9f8d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3a2f"
          },
          "venue": {
            "type": "string",
            "description": "Execute at this partner (from the quote). Refused with `409 conflict` if it is not quoting this pair at this size — never swapped for another."
          },
          "ref": {
            "type": "string",
            "description": "A referral code; used for attribution only when no valid API key is sent."
          },
          "refundAddress": {
            "type": "string",
            "description": "`in` only: the user's address on the source chain, for a failed swap's refund. Needed when the quote says `needsRefundAddress: true`."
          },
          "destinationExtraId": {
            "oneOf": [
              {
                "type": "string",
                "pattern": "^[\\x21-\\x7e]{1,64}$"
              },
              {
                "type": "integer",
                "minimum": 0,
                "maximum": 4294967295
              }
            ],
            "description": "`out` only (`400` on `in`): the destination tag / memo for the payout address — required by an exchange's shared XRP, XLM, TON, ATOM, EOS, HBAR… deposit address. 1–64 visible ASCII characters, validated per chain: XRP — digits 0–4294967295 (an integer is accepted); Stellar — text up to 28 characters or a numeric ID up to 18446744073709551615; TON, Cosmos chains, EOS, Hedera, BNB Beacon — text up to 64 characters; refused on chains whose addresses take none (Bitcoin, EVM chains, Tron, Solana, …). Partners that cannot carry it are left out (listed in `unavailable`); naming one in `venue` is `409 venue_unavailable`; if none can, `409 conflict`. Part of the Idempotency-Key identity.",
            "example": "104729"
          },
          "refundParty": {
            "type": "string",
            "pattern": "^[^:\\s]+::[0-9a-fA-F]{16,}$",
            "description": "`out` only: the user's own Canton party. Every automatic refund of the order's CC (short, excess, late, failed) goes there. Ask for it whenever the user may pay from an exchange. Must be able to receive CC (transfer preapproval) or the order is `422`."
          },
          "timezone": {
            "type": "string"
          },
          "tzOffset": {
            "type": "integer"
          }
        }
      },
      "Transaction": {
        "type": "object",
        "required": [
          "kind",
          "chain",
          "asset",
          "amount",
          "from",
          "to",
          "txId",
          "explorerUrl",
          "state",
          "at"
        ],
        "properties": {
          "kind": {
            "type": "string",
            "enum": [
              "customer_deposit",
              "venue_payment",
              "venue_payout",
              "customer_payout",
              "refund"
            ]
          },
          "chain": {
            "type": [
              "string",
              "null"
            ],
            "description": "`canton`, or the chain the partner settles on."
          },
          "asset": {
            "type": "string"
          },
          "amount": {
            "type": [
              "string",
              "null"
            ]
          },
          "from": {
            "type": [
              "string",
              "null"
            ]
          },
          "to": {
            "type": [
              "string",
              "null"
            ]
          },
          "txId": {
            "type": [
              "string",
              "null"
            ]
          },
          "explorerUrl": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri",
            "description": "Link to the transfer, when known (Canton: ccview.io)."
          },
          "state": {
            "type": "string",
            "enum": [
              "pending",
              "confirmed",
              "failed",
              "cancelled"
            ]
          },
          "at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "Order": {
        "type": "object",
        "required": [
          "claimToken",
          "direction",
          "from",
          "to",
          "amountIn",
          "expectedOut",
          "venue",
          "depositAddress",
          "depositExtraId",
          "deliversTo",
          "status",
          "state",
          "network",
          "venueNetwork",
          "custodialState",
          "createdAt",
          "deadline",
          "depositDeadline",
          "depositGraceUntil"
        ],
        "properties": {
          "claimToken": {
            "type": "string",
            "description": "The order's handle. Store it server-side; anyone holding it can read the order."
          },
          "direction": {
            "$ref": "#/components/schemas/Direction"
          },
          "from": {
            "type": "string"
          },
          "to": {
            "type": "string"
          },
          "amountIn": {
            "$ref": "#/components/schemas/Decimal",
            "description": "What the user sends — show it exactly, never rounded."
          },
          "expectedOut": {
            "$ref": "#/components/schemas/Decimal",
            "description": "What the user should receive."
          },
          "venue": {
            "type": "string"
          },
          "depositAddress": {
            "type": "string",
            "description": "Where the user sends. CC → X: this order's own Float deposit address (a Canton party). X → CC: the partner's address on the source chain. `\"\"` while `state` is `preparing_deposit_address` or `awaiting_refund_address`."
          },
          "depositExtraId": {
            "type": [
              "string",
              "null"
            ],
            "description": "X → CC only: when not null, the memo / tag the user must include on the source chain. Always null on CC → X — paying CC in never needs a memo."
          },
          "destinationExtraId": {
            "type": [
              "string",
              "null"
            ],
            "description": "CC → X only (absent on X → CC): the destination tag / memo the partner was given for the payout address, or null. Cannot be changed after the order opens."
          },
          "deliversTo": {
            "type": "string",
            "description": "Where the user is paid (the request's `destination`)."
          },
          "status": {
            "type": "string",
            "description": "Internal; kept for compatibility. Do not branch on it."
          },
          "state": {
            "$ref": "#/components/schemas/OrderState"
          },
          "network": {
            "type": [
              "string",
              "null"
            ]
          },
          "venueNetwork": {
            "type": [
              "string",
              "null"
            ]
          },
          "custodialState": {
            "type": [
              "string",
              "null"
            ],
            "description": "Internal; do not branch on it."
          },
          "receivedCc": {
            "type": [
              "string",
              "null"
            ],
            "description": "CC Float received for the order."
          },
          "receivedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "settledCc": {
            "type": [
              "string",
              "null"
            ],
            "description": "X → CC: CC Float paid the user."
          },
          "settledTxId": {
            "type": [
              "string",
              "null"
            ],
            "description": "Canton update id of the payout to the user."
          },
          "venueTxId": {
            "type": [
              "string",
              "null"
            ],
            "description": "Canton update id of Float's payment to the partner."
          },
          "refundTxId": {
            "type": [
              "string",
              "null"
            ],
            "description": "Canton update id of a refund."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "deadline": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Unix ms of `depositDeadline` (compatibility)."
          },
          "depositDeadline": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Send by this time. Show a countdown from this field. CC → X: Float's 30-minute window (or the partner's earlier deadline). X → CC: the partner's deadline (or 30 minutes)."
          },
          "depositGraceUntil": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "CC → X only: a payment already in flight is still accepted until this time (10 minutes after `depositDeadline`). Null when the user pays the partner."
          },
          "transactions": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Transaction"
            },
            "description": "Every on-chain transfer behind the order, oldest first."
          },
          "refundAddress": {
            "type": [
              "string",
              "null"
            ],
            "description": "X → CC: the user's refund address on file."
          },
          "refundAddressRequired": {
            "type": "boolean",
            "description": "X → CC: the order is waiting for a refund address."
          },
          "refundAddressAtVenue": {
            "type": "boolean",
            "description": "X → CC: the partner itself holds the refund address."
          },
          "refundParty": {
            "type": [
              "string",
              "null"
            ],
            "description": "CC → X: the user's Canton refund party on file."
          },
          "depositParty": {
            "type": [
              "string",
              "null"
            ],
            "description": "CC → X: this order's deposit address (equals `depositAddress` once ready)."
          },
          "depositValidForThisOrderOnly": {
            "type": "boolean",
            "description": "CC → X: the deposit address serves this order only — the user must not reuse it."
          },
          "depositAddressNote": {
            "type": "string",
            "description": "Show next to the address.",
            "example": "Valid for this order only"
          },
          "depositPreparing": {
            "type": "boolean",
            "description": "CC → X: the deposit address is still being created."
          },
          "payoutParty": {
            "type": [
              "string",
              "null"
            ],
            "description": "X → CC: the Float wallet the partner delivers the CC to before Float pays the user. Informational — the user never sends here."
          },
          "payoutFromThisOrderOnly": {
            "type": "boolean"
          }
        }
      },
      "RefundAddressRequest": {
        "type": "object",
        "required": [
          "address"
        ],
        "properties": {
          "address": {
            "type": "string",
            "description": "X → CC: the user's source-chain address. CC → X: the user's Canton party (`name::1220…`)."
          },
          "acceptOut": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Decimal"
              },
              {
                "type": "number"
              }
            ],
            "description": "X → CC: accept a re-quote — send `requote.freshOut` from a `409 requote_needed`."
          }
        }
      },
      "RefundAddressResult": {
        "type": "object",
        "required": [
          "opened",
          "venueHasAddress",
          "message",
          "order"
        ],
        "properties": {
          "opened": {
            "type": "boolean",
            "description": "True when this call opened a waiting X → CC order."
          },
          "venueHasAddress": {
            "type": "boolean"
          },
          "message": {
            "type": "string"
          },
          "order": {
            "$ref": "#/components/schemas/Order"
          }
        }
      },
      "Referral": {
        "type": "object",
        "required": [
          "ref",
          "partner"
        ],
        "properties": {
          "ref": {
            "type": "string"
          },
          "partner": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "WebhookEndpoint": {
        "type": "object",
        "required": [
          "id",
          "url",
          "status",
          "secretHint",
          "events",
          "createdAt"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "paused"
            ]
          },
          "pausedReason": {
            "type": [
              "string",
              "null"
            ]
          },
          "pausedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "secretHint": {
            "type": "string",
            "description": "The secret's last characters, to tell secrets apart. Never the secret."
          },
          "previousSecretValidUntil": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "After a rotation: until when the old secret also signs."
          },
          "consecutiveFailures": {
            "type": "integer"
          },
          "failingSince": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "lastSuccessAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Last successful delivery of a real event. Test events (`POST /webhooks/{id}/test`) never change this, `lastFailureAt`, `consecutiveFailures`, `failingSince` or auto-pause."
          },
          "lastFailureAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "createdVia": {
            "type": "string",
            "description": "`dashboard`, or `api_key:<key id>` — the id (a UUID) of the API key that registered it, as listed in the dashboard. Never the key itself.",
            "example": "api_key:3f2b9c1e-7a4d-4e8b-9c0f-5d6e7a8b9c0d"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "description": "Last change to the endpoint itself (URL, pause, secret)."
          },
          "events": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "The events this endpoint receives — every subscribable type (there is no per-endpoint filter). `webhook.test` is not listed: it is sent only by `POST /webhooks/{id}/test`, and always delivered there, paused or not."
          }
        }
      },
      "WebhookSecretResponse": {
        "type": "object",
        "required": [
          "endpoint",
          "secret",
          "notice"
        ],
        "properties": {
          "endpoint": {
            "$ref": "#/components/schemas/WebhookEndpoint"
          },
          "secret": {
            "type": "string",
            "description": "`whsec_…` — shown only in this response.",
            "example": "whsec_hY3…Q2aZ"
          },
          "notice": {
            "type": "string"
          }
        }
      },
      "WebhookTestResult": {
        "type": "object",
        "required": [
          "eventId",
          "delivered",
          "statusCode",
          "error",
          "durationMs"
        ],
        "properties": {
          "eventId": {
            "type": "string"
          },
          "delivered": {
            "type": "boolean"
          },
          "statusCode": {
            "type": [
              "integer",
              "null"
            ]
          },
          "error": {
            "type": [
              "string",
              "null"
            ]
          },
          "durationMs": {
            "type": "integer"
          }
        }
      },
      "WebhookDeliveryAttempt": {
        "type": "object",
        "properties": {
          "eventId": {
            "type": "string"
          },
          "eventType": {
            "type": "string"
          },
          "attempt": {
            "type": "integer"
          },
          "delivered": {
            "type": "boolean"
          },
          "statusCode": {
            "type": [
              "integer",
              "null"
            ]
          },
          "error": {
            "type": [
              "string",
              "null"
            ],
            "description": "Float's description (`HTTP 503`, `timeout — no answer within 10 s`, `HTTP 302 — redirects are not followed`, …), never your server's body."
          },
          "durationMs": {
            "type": "integer"
          },
          "at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "WebhookError": {
        "type": "object",
        "required": [
          "error",
          "code"
        ],
        "properties": {
          "error": {
            "type": "string"
          },
          "code": {
            "type": "string",
            "enum": [
              "unauthorized",
              "invalid_key",
              "forbidden",
              "invalid_url",
              "invalid_request",
              "not_found",
              "too_many_endpoints",
              "duplicate_url",
              "rate_limited",
              "unavailable",
              "internal"
            ]
          }
        }
      },
      "OrderStateChangedEvent": {
        "type": "object",
        "required": [
          "id",
          "type",
          "created_at",
          "data"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "`evt_…` — unique per event, identical on every retry."
          },
          "type": {
            "type": "string",
            "const": "order.state_changed"
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the change was seen (not when this POST was made)."
          },
          "data": {
            "type": "object",
            "required": [
              "previous_state",
              "order"
            ],
            "properties": {
              "previous_state": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/OrderState"
                  },
                  {
                    "type": "null"
                  }
                ],
                "description": "What Float last told you; null on an order's first event."
              },
              "order": {
                "$ref": "#/components/schemas/Order",
                "description": "Exactly what `GET /order/{claimToken}` returns at that moment. Act on `order.state`; you may not see every state (one event can jump `awaiting_deposit → completed`), and state never goes backwards."
              }
            }
          }
        }
      },
      "PayoutUpdatedEvent": {
        "type": "object",
        "required": [
          "id",
          "type",
          "created_at",
          "data"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "`evt_…` — unique per event, identical on every retry."
          },
          "type": {
            "type": "string",
            "const": "payout.updated"
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the change was seen (not when this POST was made)."
          },
          "data": {
            "type": "object",
            "required": [
              "previous_status",
              "payout"
            ],
            "properties": {
              "previous_status": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "payout": {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string"
                  },
                  "amountCc": {
                    "type": "string"
                  },
                  "destination": {
                    "type": "string"
                  },
                  "status": {
                    "type": "string",
                    "enum": [
                      "requested",
                      "sent",
                      "pending_funds",
                      "failed",
                      "unknown"
                    ]
                  },
                  "failureReason": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "createdAt": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "updatedAt": {
                    "type": "string",
                    "format": "date-time"
                  }
                }
              }
            }
          }
        }
      },
      "WebhookTestEvent": {
        "type": "object",
        "required": [
          "id",
          "type",
          "created_at",
          "data"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "`evt_…` — unique per event, identical on every retry."
          },
          "type": {
            "type": "string",
            "const": "webhook.test"
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the change was seen (not when this POST was made)."
          },
          "data": {
            "type": "object",
            "properties": {
              "endpoint_id": {
                "type": "string"
              },
              "message": {
                "type": "string"
              }
            }
          }
        }
      }
    }
  }
}
