Tell the router what you hold and what you want. It walks live order books from ~60 exchanges to your size, adjusts every level for that venue's fees, and returns the cheapest way to get there — one venue, several, or through a bridge asset.
Public, no account and no key: every endpoint is open, limited to 5 requests per second per IP, IPv4 clients only.
okx shows the best ask. After its taker fee it is the most expensive of the four. Comparing venues before fees picks the wrong one — this is what the router does instead.
A real request to the live router, from this page. No key, nothing to install.
The router is a plain HTTP endpoint — and it is also a class in ccxt, in every language ccxt
ships. Hand it your exchange instances once: fetchRoute then only quotes venues
you can actually trade on, and execute sends the orders there. No new dependency,
no key, no client to write — the same live books the playground above just used.
import ccxt from 'ccxt';
// Venues go on the router. Routes are filtered to them, and execute sends there.
const venues = { 'mexc': new ccxt.mexc ({ 'apiKey': '...', 'secret': '...' }) };
const router = new ccxt.OrderRouter ({ venues });
const route = await router.fetchRoute ('USDT', 'BTC', { 'amountIn': 20 });
// This places the orders. Pass dryRun: true to rehearse instead.
const report = await router.execute (route);
import ccxt
# Venues go on the router. Routes are filtered to them, and execute sends there.
venues = {'mexc': ccxt.mexc({'apiKey': '...', 'secret': '...'})}
router = ccxt.OrderRouter({'venues': venues})
route = router.fetch_route('USDT', 'BTC', {'amountIn': 20})
# This places the orders. Pass dryRun: True to rehearse instead.
report = router.execute(route)
<?php
// Venues go on the router. Routes are filtered to them, and execute sends there.
$venues = array('mexc' => new \ccxt\mexc(array('apiKey' => '...', 'secret' => '...')));
$router = new \ccxt\OrderRouter(array('venues' => $venues));
$route = $router->fetchRoute('USDT', 'BTC', array('amountIn' => 20));
// This places the orders. Pass dryRun => true to rehearse instead.
$report = $router->execute($route);
using ccxt;
// Venues go on the router. Routes are filtered to them, and execute sends there.
var venues = new Dictionary<string, Exchange>() { { "mexc", new Mexc() } };
var router = new OrderRouter(new Dictionary<string, object>() { { "venues", venues } });
var route = await router.FetchRoute("USDT", "BTC", new Dictionary<string, object>() { { "amountIn", 20 } });
// This places the orders. Pass dryRun true to rehearse instead.
var report = await router.Execute(route);
import ccxt "github.com/ccxt/ccxt/go/v4"
// Venues go on the router. Routes are filtered to them, and execute sends there.
venues := map[string]ccxt.IExchange{"mexc": ccxt.NewMexc(nil)}
router, _ := ccxt.NewOrderRouter(map[string]any{"venues": venues})
route, _ := router.FetchRoute("USDT", "BTC", map[string]any{"amountIn": 20})
// This places the orders. Pass "dryRun": true to rehearse instead.
report, _ := router.Execute(route, nil, nil)
use ccxt::{OrderRouter, RouterVenue, Value};
use ccxt::value::HashMap;
use std::collections::BTreeMap;
// Rust installs venues with set_venues: its Value type cannot carry them.
let mut router = OrderRouter::new(&Value::Map(HashMap::new()))?;
router.set_venues(venues);
let mut params = HashMap::new();
params.insert("amountIn".to_string(), Value::Float(20.0));
let route = router.fetch_route("USDT", "BTC", &Value::Map(params)).await?;
// This places the orders. Pass dryRun to rehearse instead.
let report = router.execute(&route, &BTreeMap::new(), &Value::Map(HashMap::new())).await?;
What these two calls do by default — the venue modes, the
checks execute runs before it sends anything, and the guarantees that hold whatever
you configure. Prefer HTTP? The API reference covers the same
route in one GET.
One parameter. Fewer venues means less execution risk; more venues means a better price.
One venue for the whole order. Simplest to execute, one fee, one counterparty — and the baseline everything else is measured against.
Minimum cost across the merged, fee-adjusted book with no venue limit. Because levels are fee-adjusted before merging, the greedy walk is provably cost-minimal rather than a guess.
The same, but never more venues than you want to manage. Three captures about 95% of the unconstrained gain.
Four things, each of which changes the price you actually get.
Every level is adjusted for that venue's taker fee before the books are merged. Fees differ by up to ~4× between venues and routinely invert the ranking — as above.
Best bid/ask is the price for a trade you are not making. Each book is walked to your real size, so the answer is the price you would actually pay.
No direct market? It solves the direct pair and every bridge, then takes the best. Live now,
USDC→TRY routes through ETH — ~9 bps better than the obvious USDT path.
Every hop reports what your size cost against the best price available anywhere. Positive always means worse, on both sides — so you can shrink, split, or wait.
No side parameter. USDT → BTC is a buy of BTC/USDT;
BTC → USDT is a sell of the same market. Working that out is the step people
get backwards, so the router does it.
curl -s "https://docs.ccxt.com/router/api/route?from=USDT&to=BTC&amountOut=1"
route computation, p50
exchanges streamed over WebSocket
measured gain from splitting
best measured bridge gain
It refuses to quote rather than quote badly. Books older than the freshness window are excluded, not used. An empty route with a stated reason is a deliberate answer — and a far better one than a confident price that no longer exists.
Copy the command and send it. Nothing to install, nothing to register — the endpoint is public.
Read the API docsThe router prices and routes orders. It never holds funds and never places trades — you do.