# FSwap > FSwap (https://fswap.io) is a cryptocurrency exchange aggregator. It quotes 1,500+ coins across many networks, routes each swap to the best available liquidity provider, and gives the user a one-time deposit address. Partners can integrate FSwap as an exchange provider in a wallet, bot, website or aggregator through a public JSON HTTP API, and earn a commission on every swap made with their API key. This file tells an LLM or a developer everything needed to add FSwap as a swap provider. It is self-contained; the links at the end point to the human docs and the machine-readable OpenAPI spec. - API base URL: `https://api.fswap.io` - OpenAPI 3.0 spec (JSON): https://api.fswap.io/api/swagger/json - Swagger UI: https://api.fswap.io/api/swagger/docs/ - Human docs: https://docs.fswap.io/docs/ - All partner endpoints are `POST` with a JSON body (`Content-Type: application/json`), except the XML rates feed (`GET`). - Successful responses are HTTP 200 with the raw JSON object (no envelope). - CORS is open, so the API can be called from a browser, but keep the API key server-side. ## Integration checklist 1. Get an API key (see "Authentication"). 2. `POST /api/pairs/get-list` – load and cache the list of coins. 3. `POST /api/pairs/rate/get-one` – get a quote; keep the returned `rateCalcId`. 4. Optionally `POST /api/pairs/address-validate` – pre-check the user's payout address. 5. `POST /api/claims/create` – create the exchange order with the `rateCalcId` and payout address; get `claimId`. 6. `POST /api/claims/get-one` – show the user `payinAddress` (+ `payinExtraId`), `amountExpectedFrom` and `expiredAt`, then poll until a final status. Send the same `x-user-api-key` header on every call (quote and order creation must be made with the same key, see below). ## Authentication Header: `x-user-api-key: ` - There is no `Authorization` header, no `x-api-key`, and no request signing/HMAC. - The key is optional on public endpoints (`pairs/*`, `claims/*`, rates XML). Without it the request is anonymous and uses default pricing, and you earn nothing. - An invalid or deactivated key is silently ignored on public endpoints: the request still succeeds as anonymous. It does NOT return 401. If commissions are not being credited, check the key. - The key is required on `/api/profile/*` (401 `requireAuthMW.API_KEY_INCORRECT` if wrong). - A valid key applies that key's fixed commission percentage and credits your share to your FSwap account balance. The percentage cannot be changed through the API; contact FSwap support. - `claims/create` must use the same identity as the `rateCalcId` quote. Quoting with a key and creating without it (or with another key) fails with `error.claims.no_user_api_key_id` / `error.claims.incorrect_user_api_key_id`. How to get a key: - Self-service: register at https://fswap.io, open the API keys section of the account, create a key. - Or ask FSwap support with your email; they create the account and send the key. Earnings from API keys and referral links accumulate in one account balance, visible in the personal account, and can be withdrawn (in the currency/network you choose) after reaching the minimum withdrawal amount. Referral codes: `refCode` (letters and digits only) is an alternative to an API key for link-based partners. It gives the end user a discount and the code owner a reward. When a valid API key is sent, `refCode` is ignored. An unknown `refCode` is silently ignored. ## Core concepts - Coin key: `TICKER-NETWORK`, uppercase, e.g. `BTC-BTC`, `ETH-ERC20`, `USDT-TRC20`, `USDT-BEP20`, `USDT-SOL`, `USDT-TON`, `XMR-XMR`. Always use keys exactly as returned by `pairs/get-list`; do not build them yourself. - Rate types: - `MARKET` (floating): the final amount follows the market until the deposit is exchanged. Send `amountFrom`. - `FIXED`: the quoted amount is locked. Send either `amountFrom` or `amountTo`. - Amounts are decimal strings (e.g. `"0.015"`, `"500"`), never JSON numbers. Send exactly one of `amountFrom` / `amountTo`. - `rateCalcId`: the quote identifier. It is required to create an order, it is single-use, and it expires: MARKET after 2 hours, FIXED after 5 minutes. After expiry (or after one use, even a failed one) request a new quote. - `claimId`: the order ID (short alphanumeric string). Anyone who has it can read the order status, so treat it like an order link, not a public identifier. - Extra ID (memo / destination tag): some coins (XRP, XLM, TON, EOS, etc.) need one. If a coin's `extraIdName` is not null, collect it from the user: `withdrawAddressExtraId` for the destination coin, `refundAddressExtraId` for the source coin. The API does not force it, but missing memos can lose funds. The deposit memo to show the user is `payinExtraId`. ## POST /api/pairs/get-list List of coins available for exchange. The response is large (~1,700 coins, ~1 MB); cache it and refresh every few minutes rather than on each user action. Request body (all optional): ```json { "refCode": "abc123", "isNoKyc": false } ``` - `isNoKyc: true` restricts to routes whose providers do not require KYC. Response: ```json { "from": ["BTC-BTC", "USDT-TRC20", "..."], "to": ["BTC-BTC", "USDT-TRC20", "..."], "mapTickerToCoin": { "XMR-XMR": { "key": "XMR-XMR", "ticker": "XMR", "network": "XMR", "fullName": "monero", "extraIdName": null, "image": "https://s3-api.fswap.io/fswap-instrument-img/XMR-XMR.png", "isNetworkCanValidate": true, "unsupportedCurrencies": ["XMR-XMR"], "isFixedActive": true, "isNoKycActive": true, "payInConfirmations": 10, "providerSecretList": ["alpha", "beta", "gamma"] } } } ``` - `from` / `to`: keys that can be sold / bought. Only offer keys that also exist in `mapTickerToCoin`. - `extraIdName`: `"Memo/Tag"` when the coin needs an extra ID, otherwise `null`. - `isNetworkCanValidate`: FSwap can validate addresses for this network. - `unsupportedCurrencies`: keys that cannot be paired with this coin (at least the coin itself). - `isFixedActive`: FIXED quotes are available for this coin. If false, use MARKET. - `isNoKycActive`: available when `isNoKyc: true`. - `payInConfirmations`: blockchain confirmations required for the deposit (may be null). - `providerSecretList`: opaque codes of the underlying liquidity sources. Informational only; ignore for integration. ## POST /api/pairs/rate/get-one Get a quote. Request body: ```json { "rateType": "MARKET", "keyFrom": "USDT-TRC20", "keyTo": "BTC-BTC", "amountFrom": "500" } ``` Fields: - `rateType` (required): `"MARKET"` or `"FIXED"`. - `keyFrom`, `keyTo` (required): coin keys; must differ. - `amountFrom` (string): amount the user sends. Use with MARKET or FIXED. - `amountTo` (string): amount the user wants to receive. Use with FIXED only. - `refCode` (optional), `isNoKyc` (optional boolean). - Do not send `isTest` (see "Testing"). Response: ```json { "status": "OK", "rateCalcId": "zjNmygCXb0Ud1lzc8ezBh1jDGu2s...", "keyFrom": "USDT-TRC20", "keyTo": "BTC-BTC", "rateType": "MARKET", "amountFrom": "500", "amountTo": "0.00644259", "rate": "0.00001290814", "rateReverse": "77470.49536184144268", "limitFrom": { "min": "6.532260852", "max": "18156219.546157" }, "limitTo": { "min": "0.000082404", "max": "583.54199437" }, "isRefundAddressRequire": false, "payInConfirmations": 1 } ``` - `status`: - `OK` – quote is valid; `rateCalcId` can be used to create an order. - `LOWER_THAN_MIN_FROM` / `GREATER_THAN_MAX_FROM` – `amountFrom` is outside `limitFrom`. - `LOWER_THAN_MIN_TO` / `GREATER_THAN_MAX_TO` – `amountTo` is outside `limitTo`. - `RATE_NOT_AVAILABLE` – no provider can quote this pair/amount right now. - Non-OK statuses still return HTTP 200 and a `rateCalcId`, but that id cannot be used; show the limits to the user and re-quote. - `rate`: units of `keyTo` per 1 `keyFrom`. `rateReverse`: units of `keyFrom` per 1 `keyTo`. - `amountTo` / `amountFrom`: estimated (MARKET) or locked (FIXED) counterpart amount, fees included. - `rateType`: the type actually quoted. It can differ from the requested one; trust the response. - `isRefundAddressRequire`: if true, `refundAddress` is mandatory in `claims/create`. - `limitFrom` / `limitTo`: min/max amounts. `"0"` means no limit on that side. ## POST /api/pairs/address-validate Optional pre-check of an address before creating an order. Request: `{ "network": "TRC20", "address": "TYwfZF7UfYA6uxUftbDnGkWMSFesKxXCzc" }` (`network` is the coin's `network` field). Response: `{ "isValid": true }` It returns `true` when the network is not supported by the validator, so `true` means "not known to be invalid". ## POST /api/claims/create Create the exchange order. Request body: ```json { "rateCalcId": "zjNmygCXb0Ud1lzc8ezBh1jDGu2s...", "withdrawAddress": "bc1q...", "withdrawAddressExtraId": "optional memo/tag of the payout address", "refundAddress": "TYwfZF7UfYA6uxUftbDnGkWMSFesKxXCzc", "refundAddressExtraId": "optional memo/tag of the refund address", "clientEmail": "user@example.com", "utmData": "partner-campaign-42" } ``` Fields: - `rateCalcId` (required): from an `OK` quote made with the same API key. - `withdrawAddress` (required): user's address on the `keyTo` network, where FSwap sends the exchanged coins. - `withdrawAddressExtraId`: memo/tag for `withdrawAddress` when `keyTo` has `extraIdName`. - `refundAddress`: user's address on the `keyFrom` network for refunds. Required when the quote had `isRefundAddressRequire: true`; strongly recommended always. - `refundAddressExtraId`: memo/tag for `refundAddress`. - `clientEmail` (optional, valid email): FSwap emails order status updates to it. - `utmData` (optional string): your own tracking label, returned in `/api/profile/claims/get-list`. - `isTg` (optional boolean): marks Telegram-originated orders; leave unset unless you are a Telegram bot. - Addresses are trimmed and must match `^[A-Za-z0-9#\-_=$]+$`; they are also validated for the network where supported. Response: ```json { "claimId": "a1B2c3D4e5F6" } ``` Then immediately call `claims/get-one` to get the deposit address. ## POST /api/claims/get-one Order details and status. Use it to show deposit instructions and to poll progress. Request: `{ "claimId": "a1B2c3D4e5F6" }` Response: ```json { "id": "a1B2c3D4e5F6", "createdAt": "2026-09-12T19:34:54.251Z", "expiredAt": "2026-09-12T19:49:54.251Z", "type": "FIXED", "status": "WAIT_DEPOSIT", "currencyFrom": "USDT", "networkFrom": "TRC20", "currencyFromImage": "https://...", "currencyTo": "ETH", "networkTo": "ERC20", "currencyToImage": "https://...", "payInConfirmations": 5, "refCodeValue": null, "refCodeId": null, "payinAddress": "TUm1RiULX3teGspf1BbGohfu8aMQtzTRmN", "payinExtraId": null, "payinHash": null, "payoutAddress": "0x78c6995bdbd500b445a90efa7105f4918531cb68", "payoutExtraId": null, "payoutHash": null, "refundAddress": "TYwfZF7UfYA6uxUftbDnGkWMSFesKxXCzc", "refundExtraId": null, "refundHash": null, "explorerUrlFrom": null, "explorerUrlTo": null, "explorerUrlRefund": null, "amountExpectedFrom": "10000", "amountExpectedTo": "5", "amountFrom": null, "amountTo": null } ``` - Deposit instructions for the user: send exactly `amountExpectedFrom` of `currencyFrom` on `networkFrom` to `payinAddress`, including `payinExtraId` as memo if it is not null, before `expiredAt`. - `amountExpectedTo`: estimated (MARKET) or fixed (FIXED) payout. - `amountFrom` / `amountTo`: actual amounts once known; `null` (or `"0"`) before that. - `payinHash`, `payoutHash`, `refundHash`: transaction hashes once they exist; `explorerUrl*` are ready-made block explorer links (may be null). - `expiredAt`: deposit deadline. Typically 2 hours for MARKET and 15 minutes for FIXED, but always use the field. ## Order statuses `status` values: - `NEW` – order created, deposit address being prepared. - `WAIT_DEPOSIT` – waiting for the user's deposit. - `CONFIRM` – deposit seen, waiting for blockchain confirmations. - `EXCHANGE` – exchange in progress. - `SEND` – exchange done, payout being sent to `withdrawAddress`. - `HOLD` – paused by the liquidity provider (for example for a compliance check). Not final; the user should contact FSwap support with the order ID. - `FINISH` – completed successfully (final). - `EXPIRE` – no deposit received before `expiredAt` (final). - `REFUND` – funds returned to `refundAddress` (final). - `FAIL` – exchange error; user should contact support (final). Final statuses: `FINISH`, `EXPIRE`, `REFUND`, `FAIL`. Typical path: `NEW → WAIT_DEPOSIT → CONFIRM → EXCHANGE → SEND → FINISH`. Transitions are not guaranteed to be strictly linear: steps can be skipped, and `FAIL`, `REFUND` or `HOLD` can happen at any stage. There are no webhooks or callbacks. Poll `claims/get-one` (e.g. every 30–60 seconds while the user is waiting, less often afterwards) and stop at a final status. ## POST /api/claims/client-email-add Attach a notification email to an existing order after creation. Request: `{ "claimId": "a1B2c3D4e5F6", "clientEmail": "user@example.com" }` → `{ "message": "Ok" }` Works only while the order is `NEW` or `WAIT_DEPOSIT` and has no email yet. ## POST /api/claims/is-exist Request: `{ "claimId": "a1B2c3D4e5F6" }` → `{ "message": "Ok" }` if the order exists; HTTP 400 `error.claims.not_found` otherwise. ## POST /api/profile/claims/get-list (requires API key) Orders placed by the account that owns the API key, newest first. Use it for reconciliation and reporting. Headers: `x-user-api-key: ` Request (all optional): `{ "page": 1, "perPage": 100 }` – `page` starts at 1; `perPage` default 25, max 200. Response: `{ "count": 12, "dataList": [ ...orders ] }`. Each order has all `claims/get-one` fields plus: `isTg`, `utmData`, `amountExpectedFromUsdt`, `amountExpectedToUsdt`, `amountFromUsdt`, `amountToUsdt`, `userApiKeyId`, `userApiKeyAccumulationUsdt` (your commission on this order, USDT), `refCodeAccumulationUsdt` (referral reward, USDT). ## GET /api/rates-xml/get-full (rates feed for monitors) Exchange-rate feed in the BestChange XML format, for rate monitors and aggregators that consume XML. - `GET https://api.fswap.io/api/rates-xml/get-full` – optional header `x-user-api-key`, optional query `?refCode=...`. - Returns `Content-Type: application/xml` (raw XML, even though Swagger shows a JSON wrapper). - `/api/pairs/xml` is a deprecated alias. - Currency codes in the XML are BestChange-style (e.g. `USDTTRC20`, `BTC`), not FSwap `TICKER-NETWORK` keys. ```xml USDTTRC20BTC 10.0000129 5000000 718000000 0 ``` A custom markup for the XML feed can be set per API key or referral code by FSwap support. ## Errors Error response (HTTP 400 unless stated): ```json { "traceId": "H6UOPyhlP_XqE2E1bxQHTPKow4U-XlouABFRl52z", "message": "error.pairs.amount_both_exist" } ``` Validation errors add the failing fields: ```json { "traceId": "...", "message": "api.bodyValidator.ERR", "validateErrList": [ { "code": "invalid_enum_value", "path": ["rateType"], "message": "Invalid enum value. Expected 'MARKET' | 'FIXED', received 'X'" } ] } ``` - `message` is a stable machine code; map it to your own user-facing text. Codes use both `error.` and `errors.` prefixes; match the whole string. - Include `traceId` when contacting FSwap support. - HTTP 401: missing/invalid credentials on `/api/profile/*`. HTTP 404: unknown route. HTTP 500: unexpected error, including malformed JSON or a non-numeric amount string. Common codes: - `api.bodyValidator.ERR`, `api.headersValidator.ERR`, `api.queryValidator.ERR` – request schema error, see `validateErrList`. - `error.pairs.invalid_pair` – pair not available (or `keyFrom` equals `keyTo`). - `errors.pairs.any_provider_no_active` – no provider currently serves this pair. - `error.pairs.amount_both_exist` / `error.pairs.amount_both_no_exist` – send exactly one of `amountFrom` / `amountTo`. - `error.claims.rate_calc_not_found` – `rateCalcId` expired, already used, or came from a non-OK quote. Re-quote. - `error.claims.no_refund_address` – `refundAddress` is required for this quote. - `errors.claims.no_amount_from_market` – MARKET order needs a quote made with `amountFrom`. - `errors.claims.amount_to_not_support` / `errors.claims.incorrect_rate_type` – `amountTo` is not supported for this route or rate type; quote with `amountFrom` or use FIXED. - `errors.claims.incorrect_address` – address contains invalid characters or was rejected by the provider. - `errors.claims.withdraw_address_incorrect` / `errors.claims.refund_address_incorrect` – address invalid for its network. - `error.claims.no_user_api_key_id` / `error.claims.incorrect_user_api_key_id` – order created with a different API key than the quote. - `errors.claims.create_no_active` / `errors.claims.provider_no_active` – order creation temporarily disabled; retry later with a new quote. - `errors.claims.external_service_error` – provider error; re-quote and retry. - `error.claims.not_found` – unknown `claimId`. Some provider errors are passed through as plain text in `message`. ## Testing - There is no sandbox. Orders created via `claims/create` are real: nothing moves until the user deposits, but do not create orders you do not intend to fund. Do not rely on `isTest` as a sandbox flag; leave it unset. - Quotes (`pairs/get-list`, `pairs/rate/get-one`) and address validation are free and safe to call. - To build and test status screens without funds, call `claims/get-one` with a fixture ID of the form `{status}_{type}` or `{status}_{type}_extra`: - `status`: `new`, `wait`, `confirm`, `exchange`, `send`, `finish`, `fail`, `refund`, `hold`, `expire` - `type`: `market`, `fixed` - `_extra` adds memo fields (`payinExtraId`, `payoutExtraId`). - Examples: `wait_market`, `finish_fixed`, `refund_market_extra`. ## Example: full swap with curl ```bash KEY="your-api-key" API="https://api.fswap.io" # 1. Coins curl -s -X POST "$API/api/pairs/get-list" \ -H "content-type: application/json" -H "x-user-api-key: $KEY" -d '{}' # 2. Quote 500 USDT (TRC20) -> BTC, floating rate curl -s -X POST "$API/api/pairs/rate/get-one" \ -H "content-type: application/json" -H "x-user-api-key: $KEY" \ -d '{"rateType":"MARKET","keyFrom":"USDT-TRC20","keyTo":"BTC-BTC","amountFrom":"500"}' # -> check status == "OK", take rateCalcId, check isRefundAddressRequire # 3. Create order (within 2 h for MARKET, 5 min for FIXED) curl -s -X POST "$API/api/claims/create" \ -H "content-type: application/json" -H "x-user-api-key: $KEY" \ -d '{"rateCalcId":"","withdrawAddress":"","refundAddress":""}' # -> {"claimId":"..."} # 4. Deposit instructions + polling curl -s -X POST "$API/api/claims/get-one" \ -H "content-type: application/json" -H "x-user-api-key: $KEY" \ -d '{"claimId":""}' ``` ## TypeScript types ```ts type RateType = 'MARKET' | 'FIXED'; type RateStatus = | 'OK' | 'LOWER_THAN_MIN_FROM' | 'GREATER_THAN_MAX_FROM' | 'LOWER_THAN_MIN_TO' | 'GREATER_THAN_MAX_TO' | 'RATE_NOT_AVAILABLE'; type ClaimStatus = | 'NEW' | 'WAIT_DEPOSIT' | 'CONFIRM' | 'EXCHANGE' | 'SEND' | 'FINISH' | 'FAIL' | 'REFUND' | 'HOLD' | 'EXPIRE'; const FINAL_STATUSES: ClaimStatus[] = ['FINISH', 'FAIL', 'REFUND', 'EXPIRE']; interface Coin { key: string; ticker: string; network: string; fullName: string; extraIdName: string | null; image: string | null; isNetworkCanValidate: boolean; unsupportedCurrencies: string[]; isFixedActive: boolean; isNoKycActive: boolean; payInConfirmations: number | null; providerSecretList: string[]; } interface PairsListResponse { from: string[]; to: string[]; mapTickerToCoin: Record; } interface RateRequest { rateType: RateType; keyFrom: string; keyTo: string; amountFrom?: string; amountTo?: string; // exactly one refCode?: string; isNoKyc?: boolean; } interface Limit { min: string; max: string; } interface RateResponse { status: RateStatus; rateCalcId: string; keyFrom: string; keyTo: string; rateType: RateType; amountFrom: string; amountTo: string; rate: string; rateReverse: string; limitFrom: Limit; limitTo: Limit; isRefundAddressRequire: boolean; payInConfirmations: number | null; } interface CreateClaimRequest { rateCalcId: string; withdrawAddress: string; withdrawAddressExtraId?: string; refundAddress?: string; refundAddressExtraId?: string; clientEmail?: string; utmData?: string; isTg?: boolean; } interface CreateClaimResponse { claimId: string; } interface Claim { id: string; createdAt: string; expiredAt: string; type: RateType; status: ClaimStatus; currencyFrom: string; networkFrom: string; currencyFromImage: string | null; currencyTo: string; networkTo: string; currencyToImage: string | null; payInConfirmations: number | null; refCodeValue: string | null; refCodeId: string | null; payinAddress: string; payinExtraId: string | null; payinHash: string | null; payoutAddress: string; payoutExtraId: string | null; payoutHash: string | null; refundAddress: string | null; refundExtraId: string | null; refundHash: string | null; explorerUrlFrom: string | null; explorerUrlTo: string | null; explorerUrlRefund: string | null; amountExpectedFrom: string; amountExpectedTo: string; amountFrom: string | null; amountTo: string | null; } interface ApiError { traceId: string; message: string; validateErrList?: { code: string; path: (string | number)[]; message: string }[]; } ``` ## Implementation notes for provider adapters - Map your generic swap interface to FSwap as: `getCurrencies → pairs/get-list`, `getEstimate/getRate → pairs/rate/get-one`, `getMinMax → limitFrom/limitTo from the quote`, `createTransaction → claims/create + claims/get-one`, `getTransactionStatus → claims/get-one`. - Map statuses to a typical aggregator model: `NEW, WAIT_DEPOSIT → waiting`, `CONFIRM → confirming`, `EXCHANGE → exchanging`, `SEND → sending`, `FINISH → finished`, `FAIL → failed`, `REFUND → refunded`, `HOLD → hold/verifying`, `EXPIRE → expired`. - Keep all amounts as strings or a decimal library; never convert to floating point. - For FIXED quotes the `rateCalcId` lives 5 minutes: create the order right after the user confirms, and re-quote if the user waits. - Quote and create with the same API key. - Always collect a refund address; require it when `isRefundAddressRequire` is true. - Require a memo/tag input when the relevant coin's `extraIdName` is not null. - Do not send unknown fields; they are ignored. ## Docs - [General operating rules](https://docs.fswap.io/docs/EN/General_operating_rules_of_the_system): API keys, fees, referral links, balance and withdrawals. - [API exchange workflow](https://docs.fswap.io/docs/): step-by-step order flow and `rateCalcId` lifetime. - [Transaction status flow](https://docs.fswap.io/docs/EN/Transaction_Status_Flow): status list and diagram. - [API reference](https://docs.fswap.io/docs/EN/API_Reference): endpoint examples. - [XML rates](https://docs.fswap.io/docs/EN/XML_rates): BestChange XML feed and custom percentage. ## Optional - [OpenAPI spec (JSON)](https://api.fswap.io/api/swagger/json): full machine-readable spec, including account endpoints (API key and referral code management, withdrawals) that are not needed for a swap integration. - [Swagger UI](https://api.fswap.io/api/swagger/docs/) - [FSwap website](https://fswap.io)