Mystic Router
Mystic Router is a leading DEX aggregator that finds the best prices across 100+ liquidity sources on multiple chains.
Base URL
https://router.mysticfinance.xyz
Interactive API docs
Machine-readable spec (for AI agents / codegen)
Health
Runnable demos (frontend)
demos/frontend (React + Vite)
Runnable demos (backend)
demos/backend (Node.js)
Support
Contact the Mystic team at [email protected] for an API key, and partner onboarding.
Contents
Why Mystic
Authentication
Quickstart
API reference
Supported chains & coverage
Fees
Errors
Why Mystic
Access the best swap rates, deep liquidity and reliable execution across 6+ chains and 100+ liquidity sources with a single API.
Core capabilities
Two-layer aggregation. A first-party routing engine that prices pools directly from on-chain dexes, plus a meta-aggregator over every major external aggregator. Both are quoted in parallel on every request, so you get the best price possible in one single API.
Smart order routing. Multi-hop and split routes across pools, ranked by net output rather than by whichever venue answered first, giving unified access to 100+ liquidity sources
Coverage where others are thin. Mystic aggregates on the major EVM chains, plus chains the big aggregators serve poorly (Flare, Plume, Citrea), giving a unified interface for 12+ chains
Built-in monetisation. Route swaps under your API key and earn a share of every fee. See Fees.
Authentication
The API is open: quoting, building and tracking work with no credentials. An API key buys throughput and fee attribution.
Using a key
Send it as the x-api-key header (Authorization: Bearer <key> is also accepted) on your quote and build calls:
With a valid key → your revenue share is applied and attributed to you, and you get the higher rate limit. See Fees.
With an invalid key →
401 Invalid API key. (Omitting the key entirely is fine; sending a bad one is not.)Without a key → anonymous: the standard 0.15% fee, no attribution, rate limit of 20 requests per second.
You can also earn fees without a key at all, by passing a referrer address on the swap. See Referral fees.
Keeping your key safe
Your API key is a secret tied to your revenue share. Keep it server-side and proxy browser traffic through your own backend. CORS is open, so a key shipped to the frontend can be read and used by anyone. Keys can be rotated by the operator at any time, and partner accounts support an origin allowlist for browser-facing setups.
Quickstart
Swap tokens in 6 steps:
Get token info
Get price quote
Build transaction from quote
Set a token allowance
Send transaction
Get transaction status (optional)
Steps 2 and 3 can be collapsed into a single call if you'd rather not hold a quote id, see step 3.
Two conventions before you start:
Amounts are integers in the token's smallest unit. Every amount in every request and response is a decimal string in wei, never a float and never a human-readable number.
1 USDC(6 decimals) is"1000000";1 WETH(18 decimals) is"1000000000000000000". Step 1 gets you thedecimalsto build it with.
Native assets use the sentinel address
0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeEassellTokenorbuyToken. No wrapping on your side, and no approval needed when selling native.
All examples use JavaScript with axios for HTTP and ethers.js for chain interactions.
1. Get token info
List the supported tokens for a chain (use this to populate a token picker):
Example response (GET /v1/tokens?chainId=14):
When a user pastes an unknown asset address, resolve its on-chain metadata (this also adds it to the registry):
Example response (GET /v1/tokens/resolve?chainId=14&address=0x1D80…783d):
resolveTokenreturns metadata only, it does not tell you whether the token is tradeable. To check if there's a route for this pair, run the quote in Step 2: a result means tradeable; a404 INSUFFICIENT_LIQUIDITYmeans no available route. There is no separate pool-check endpoint, the quote is the check.
2. Get price quote
Fan out across every DEX + aggregator and return routes ranked best-first (quotes[0] is the best):
Example response (selling 10 WFLR for USDC.e on chain 14). The winning route is flattened onto the root, and quotes holds the full ranked list, best first:
outAmount is the expected output (64199 = 0.064199 USDC.e, since USDC.e has 6 decimals); minOutAmount is the worst case after slippage. Both are reported before Mystic's fee, see Fees for the net-output formula.
To execute, keep the root quoteSetId and quoteId and pass both to Step 3. Read quotes[] only when you want to show alternatives or let the user pick a venue; set bestOnly: true to drop it from the response entirely.
Native asset in/out: use 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE as the token. Optional fields: recipient (send output elsewhere), deadlineSeconds, minOutput, bestOnly, referrer/referrerFee, includeDexes/excludeDexes, partnerId, mevProtect. See the API reference for all of them.
3. Build transaction from quote
Turn the chosen quote into an unsigned transaction (and learn what approval it needs):
Example response (data truncated for readability):
txRequest is what you send from the wallet (Step 5). approval tells you which token/spender to approve in Step 4, it's null when no approval is needed. If feeMode is augustus, the fee handling is already baked into txRequest.data; you don't need to add anything.
Approve the
approval.spender, nottxRequest.to. They are usually different contracts. Approving the wrong address is the single most common integration bug.
One-call swap. If you don't want to hold a quoteSetId/quoteId between calls, send the swap parameters straight to build. It quotes, picks the winner and returns its transaction in one round trip:
The trade-off is that you never see the ranked alternatives, and the price is fixed at build time rather than shown to the user first. Use the two-step flow when a human is confirming the trade.
4. Set a token allowance
If approval is returned and the current allowance is insufficient, approve the spender (skip for native sells, or use permit2 if present):
5. Send transaction
tx.wait() resolves once the transaction is mined. The receipt you get back from ethers looks like:
status: 1means success,status: 0means the transaction reverted.hashis what you register in Step 6.
Gas. txRequest carries no gas field on purpose. Let your wallet or provider run eth_estimateGas on it. The estimatedGas on a quote is a ranking input, not a gas limit; if you set a limit from it, add a buffer of 1.25×–2.5×, or the transaction may run out of gas on a route whose real cost differs from the estimate. To price the fee yourself, or to show the user a speed choice, read GET /v1/gas-price.
6. Get transaction status (optional)
Read a swap's status back from the API. Nothing needs to be registered first: a Mystic swap carries a correlation id in its calldata, so the API recognises the hash and attaches it to the quote that produced it.
Example response (GET /v1/tx/0x9c1f…4e7a):
status is SUCCESS or FAILED once the receipt is on-chain. A hash the API hasn't indexed yet comes back as { hash, status: "UNKNOWN", adopting: true }; poll again shortly. Referral and partner fees are booked from the on-chain event, not from this call, so a spoofed or mismatched hash can't record a fee.
Putting it all together
API reference
All business routes are versioned under /v1. Request and response bodies are JSON.
POST /v1/swap/quote
Fans out across every routing source available for the chain and returns them ranked.
Request
chainId
integer
✅
Target chain. See Supported chains & coverage.
sellToken
string
✅
ERC-20 address, or 0xEeee…EEeE for native.
buyToken
string
✅
ERC-20 address, or 0xEeee…EEeE for native.
sellAmount
string
✅
Integer string in the smallest unit. Must be > 0.
taker
string
✅
Wallet that signs and sends the swap.
slippageBps
integer
—
Basis points; 50 = 0.5%. Range 0–5000. Default 50.
recipient
string
—
Where the bought token is delivered. Defaults to taker. See the note below this table.
deadlineSeconds
integer
—
Execution deadline encoded into the route. Range 60–86400.
minOutput
string
—
Hard floor on the bought amount, in base units. Routes whose guaranteed minimum can't meet it are dropped instead of quoted. Overrides the slippage-derived minimum.
includeDexes
string[]
—
Restrict the fan-out to these venues, by the id values from GET /v1/dexes.
excludeDexes
string[]
—
Quote every venue except these.
includeAdapters
string[]
—
Same idea at adapter granularity, using the adapterId values quotes report (e.g. uniswap-v3, 1inch). Merged with the dex filters.
excludeAdapters
string[]
—
Quote everything except these adapters.
referrer
string
—
Any EOA you control. Identifies you as the fee payee with no API key and no registration. Pair with referrerFee.
referrerFee
number
—
Total fee to charge, as a percent (1 = 1%), range 0.01–5. Supersedes whatever fee your API key would have applied. See Referral fees.
bestOnly
boolean
—
Return only the flattened winner and omit the quotes array. The top-level quoteId still builds it.
mevProtect
boolean
—
Ask for MEV-aware routing. The response's mevAdvice reports whether a private RPC is available for the chain.
useSmartAccount
boolean
—
Caller settles through a smart account, which lets the first-party engine offer atomic cross-pool split routes.
partnerId
string
—
Usually set implicitly via your API key. Passing it explicitly can only request the same or a lower fee.
partnerFeeBpsOverride
integer
—
Request a lower fee than the default fee. Rejected with FEE_VIOLATION if above the platform cap.
Response
The winning route is flattened onto the root of the response, so you can read the numbers without walking into quotes[0]. Both views describe the same route.
quoteSetId
string
qs_…, identifies this fan-out. Pass to build. Retrievable for ~2 minutes.
quoteId
string
The winning route's id. Pass to build.
adapterId
string
Source that produced the winner.
chainId
integer
Echo of the request.
inToken / outToken
object
{ address, symbol, name, decimals } for each side.
inAmount
string
Input amount in base units.
outAmount
string
Expected output in base units, gross of the Mystic fee.
minOutAmount
string
Worst-case output after slippage, gross of the fee.
estimatedGas
number
Gas estimate for the winning route.
price_impact
string
Price impact as a percentage string, e.g. "0.12%".
from
string
The taker you sent.
to
string
Contract the swap will call.
value
string
Native value the transaction carries, in wei.
data
string
Calldata, when the winning source produced it at quote time. Call build for the authoritative transaction.
partner.partnerId
string
protocol when anonymous, referrer:0x… when using a referrer, otherwise your partner id.
partner.feeBps
integer
Total fee in bps charged on this swap, in the bought token.
partner.recipient
string
On-chain fee collection address.
dexFilter.notHonored
string[]
Present only when a includeDexes/excludeDexes id could not be applied, either because it's unknown or because filtering it would have caught sibling venues on the same adapter. Filters are never applied silently.
quotes[]
array
Ranked routes, best first. Omitted when bestOnly is set.
Quote object (each entry of quotes[])
quoteId
string
<adapterId>::<quoteSetId>. Pass to build.
adapterId
string
Identifier of the source that produced the route. Also what includeAdapters / excludeAdapters take.
rank
integer
1 = best.
tier
string
tier-1 marks a route from Mystic's own pathfinder (mystic-tier1); every other source, including the direct-DEX adapters, reports tier-2.
venueName
string
Real DEX brand for display, e.g. SparkDEX V3.1, Rooster Finance.
routeSummary
string
Human-readable path description.
fillType
string
single or split (route divided across pools). Populated by the pathfinder.
route[]
array
Machine-readable breakdown: { protocol, dexId, tokens[], portionBps, pool }. Populated by the pathfinder.
sellAmount
string
Echo of the requested input amount.
buyAmount
string
Expected output, gross of the Mystic fee.
minBuyAmount
string
Worst-case output after slippageBps, gross of the fee.
priceImpactBps
integer
Estimated price impact in bps. Evaluating and surfacing this is the integrator's responsibility; Mystic does not block high-impact trades.
estimatedGas
string
Gas estimate used for ranking, not a gas limit. See step 5.
estimatedGasUsd
number
Gas cost in USD, when pricing is available.
estimatedAmountOutUsd
number
Output value in USD, when pricing is available.
partnerFeeBps
integer
Fee applied to this route.
partnerFeeApplied
string
native (the venue skims it) or pending (applied at build time).
validUntil
integer
Epoch ms. Building after this returns 410 QUOTE_EXPIRED.
approvalTarget
string
Spender this route would need. The authoritative value comes from build.
permit2
object | null
Permit2 typed data, when the route supports approval-free spending.
warnings[]
string[]
Route-specific advisories, when present.
score
number
Internal ranking score. Informational.
raw
object
Opaque adapter payload. Internal, do not depend on its shape.
Returns 404 INSUFFICIENT_LIQUIDITY when no source can fill the trade.
Quote lifetime. A quote set stays retrievable for about two minutes, and each quote carries its own validUntil (epoch ms). Nothing is reserved or locked by quoting, so quote as often as you need. Build before validUntil, or you'll get 410 QUOTE_EXPIRED and have to re-quote.
Recipient. Set recipient ≠ taker to deliver the bought token to a different address. Only sources that can honor a distinct recipient are offered for such a request, so funds never land on the taker by accident. If that filter leaves nothing routable you'll get 404 INSUFFICIENT_LIQUIDITY; retry without recipient and transfer separately.
POST /v1/swap/build
Two ways to call it. Either pass the ids from a quote, or pass the swap parameters and let build quote and pick the winner itself.
Request, from a quote
quoteSetId
string
✅
From the quote response.
quoteId
string
✅
The chosen route.
userAddress
string
✅
Wallet that will send the transaction.
recipient
string
—
Overrides the quote's recipient. Defaults to the quote's recipient, then userAddress.
referrer
string
—
Accepted, but the value used is the one from the original quote's request, so a build can never change the payee the swap was quoted with.
referrerFee
number
—
Accepted, but the value used is the one from the original quote's request, so a build can never raise the fee the user was quoted.
Request, without a quote
Omit both quoteSetId and quoteId and send the swap parameters instead. build runs the fan-out and builds the best route in the same call.
userAddress
string
✅
Wallet that will send the transaction, and the taker for the internal quote.
chainId
integer
✅
Target chain.
sellToken
string
✅
ERC-20 address, or 0xEeee…EEeE for native.
buyToken
string
✅
ERC-20 address, or 0xEeee…EEeE for native.
sellAmount
string
✅
Integer string in the smallest unit.
slippageBps
integer
—
Basis points; 50 = 0.5%. Range 0–5000. Default 50.
minOutput
string
—
Hard floor on the bought amount, in base units.
includeDexes
string[]
—
Restrict the fan-out to these venues, by the id values from GET /v1/dexes.
excludeDexes
string[]
—
Quote every venue except these.
includeAdapters
string[]
—
Restrict the fan-out to these adapters.
excludeAdapters
string[]
—
Quote everything except these adapters.
referrer
string
—
Any EOA you control. Identifies you as the fee payee. Pair with referrerFee.
referrerFee
number
—
Total fee to charge, as a percent (1 = 1%), range 0.01–5.
recipient
string
—
Where the bought token is delivered. Defaults to userAddress.
Missing parameters return 404 naming which ones are absent.
Response
quoteSetId
string
Echo.
adapterId
string
Source that built the transaction.
txRequest
object
{ chainId, to, data, value, from }. Send this. value is a decimal string in wei.
approval
object | null
{ token, spender, amount }. null when no ERC-20 approval is needed (native sells, or a Permit2 flow).
permit2
object | null
Typed data to sign instead of approving, when the route supports it.
feeMode
string
How the fee is collected: augustus (atomic, inside txRequest.data), native (the venue's own referral mechanism), bundle (smart-account bundle), none. In every case there is nothing extra for you to do.
partner
object
{ partnerId, feeBps, protocolBps, partnerBps, partnerRecipient }, the fee split for this swap.
simulation
object
Advisory pre-flight result. { ok: true } by default.
directTxRequest
object
The un-wrapped venue transaction, before Mystic's fee wrapper. Informational: send txRequest, not this.
Returns 404 for an unknown quoteSetId/quoteId, 410 QUOTE_EXPIRED for a stale quote.
GET /v1/swap/quote/:quoteSetId
Re-read a quote set you already fetched (debugging, or picking a different route later without re-quoting). Returns the stored per-route documents. Individual quotes still expire on their own validUntil.
GET /v1/tokens?chainId=
Registry list for a chain: [{ chainId, address, symbol, decimals, name, coingeckoId?, tags[] }]. tags drives fee tiering (stable, correlated, common, exotic) and is useful for grouping in a picker.
GET /v1/tokens/resolve
chainId
✅
Chain to read from.
address
✅
Token address, or the native sentinel.
Reads symbol/decimals/name on-chain and adds the token to the registry. A non-ERC-20 address resolves to UNKNOWN/Unknown with decimals: 18 rather than erroring, so treat "unknown symbol" as a signal to warn the user, and rely on the quote to decide tradeability.
GET /v1/chains
Live chain support with the contracts Mystic uses:
GET /v1/gas-price
Current gas price in three speed tiers. You don't need this to swap, txRequest is signable as returned, but it's useful for showing a fee estimate or setting your own gas fields.
chainId
—
One chain. Omit it to get every supported chain as an array; a chain whose RPC is unreachable is left out rather than failing the request.
chainId
integer
Chain the reading is for.
isEip1559
boolean
Which fields to use. true: send maxFeePerGas + maxPriorityFeePerGas. false: send legacyGasPrice as gasPrice.
baseFeePerGas
string | null
Base fee of the latest block. null on legacy chains.
standard / fast / instant
object
The three tiers.
<tier>.legacyGasPrice
string
Type-0 gas price.
<tier>.maxPriorityFeePerGas
string
Tip. "0" on legacy chains.
<tier>.maxFeePerGas
string
Priority fee plus headroom over the current base fee.
<tier>.waitTimeEstimate
integer
Rough seconds to inclusion. Indicative only, the mempool isn't sampled.
All values are wei strings, the same units txRequest uses, so nothing needs converting before you sign. Readings are cached for a few seconds per chain, so polling is cheap.
GET /v1/tx/:hash
Status of a swap transaction.
chainId
integer
Chain the transaction was sent on.
hash
string
Transaction hash.
status
string
SUCCESS or FAILED once the receipt is on-chain.
blockNumber
integer
Block it landed in.
gasUsed
string
Gas consumed.
effectiveGasPrice
string
Gas price actually paid.
from
string
Sender.
to
string
Contract called.
quoteSetId / quoteId
string
The quote this transaction executed, once matched.
receipt
object
Full receipt.
A hash that hasn't been indexed yet returns { hash, status: "UNKNOWN", adopting: true }. The matching runs in the background, so poll again shortly rather than treating it as an error.
GET /v1/dexes
Every venue quotable on a chain, which is what includeDexes / excludeDexes filter on. Pass ?chainId= for one chain, or omit it for all of them.
id
string
Stable id to use in includeDexes / excludeDexes.
name
string
Display name.
chainId
integer
Chain the venue is on.
type
string
dex for a liquidity venue Mystic routes through itself, aggregator for a third-party router it asks for a quote.
protocol
string
Pool family, e.g. uniswap-v3, algebra, curve.
router
string
Router contract, for first-party venues.
adapter
string
Adapter that serves it, which is the granularity filtering operates at.
POST /v1/swap/decode
Explain a transaction's calldata before or after you send it.
data
string
✅
Transaction input data, 0x-prefixed.
chainId
integer
—
The decoder is chain-agnostic; accepted to keep request shapes uniform.
kind is augustus-simple-swap for a fee-wrapped Mystic swap, or call for a plain adapter call. An unrecognised selector comes back with function: null rather than failing, so a partially-recognised route still tells you what it can.
Service endpoints
GET /health
Liveness and dependency check.
GET /docs
Swagger UI (interactive, always current).
GET /llm.txt
Compact machine-readable API contract for AI agents and codegen.
GET /integration.md
This guide, served by the API.
GET /metrics
Operational metrics snapshot.
Supported chains & coverage
Mystic aggregates 100+ liquidity sources across 12 chains. GET /v1/chains returns each chain's metadata plus the Multicall3, Permit2 and WETH addresses Mystic uses there.
Ethereum
1
ETH
etherscan.io
Uniswap v3 & forks, Curve, CoW Protocol · 0x, 1inch, ParaSwap, KyberSwap, OpenOcean, Odos, OKX, Unizen, Enso, LI.FI, Beam, Nordstern
Base
8453
ETH
basescan.org
Uniswap v3 & forks (BaseSwap V3), Algebra pools, CoW Protocol · 0x, 1inch, ParaSwap, KyberSwap, OpenOcean, Odos, OKX, Unizen, Enso, LI.FI, fly.trade
Arbitrum One
42161
ETH
arbiscan.io
Uniswap v3 & forks, Camelot V3, Curve, CoW Protocol · 0x, 1inch, ParaSwap, KyberSwap, OpenOcean, Odos, OKX, Unizen, Enso, LI.FI, fly.trade
Flare
14
FLR
flarescan.com
SparkDEX V2 / V3.1 / V4, BlazeSwap, Enosys · KyberSwap, OpenOcean, LI.FI
Plume
98866
PLUME
explorer.plume.org
Rooster Finance, Uniswap v3 forks, Curve, Nest RWA mints · OpenOcean
Citrea
4114
cBTC
explorer.citrea.xyz
Satsuma, Uniswap v3 forks · Fibrous, LI.FI
Fees
Trading fee
Mystic charges a flat 15 bps (0.15%) on every swap, on every chain, taken in the bought token and collected on-chain into Mystic's fee contract at swap time.
Fees are priced by token category. Today all four categories carry the same rate:
Stable
0.15%
USDC ↔ USDT
Correlated
0.15%
USDC ↔ ETH; ETH ↔ stETH
Common
0.15%
Top 200 tokens by market cap (excluding stable/correlated)
Exotic
0.15%
All other token combinations
These rates can change. The tiers may be differentiated in future (cheaper stables, higher exotics). Always read
partner.feeBpsfrom the quote response and apply it dynamically rather than hardcoding15.
Referral fees
The fastest way to monetise, with no API key and no onboarding: pass an address you control as referrer, and the total fee you want charged as referrerFee.
referrerFeeis a percent and must be between0and5. Anything higher returns400 FEE_VIOLATION.It replaces the standard fee rather than adding to it. It is the whole fee charged on that swap, and it supersedes whatever an API key would have applied.
You keep 85% of what's collected and Mystic keeps the rest.
The address is the identity. Nothing to register, and it keys the payout ledger the same way a partner does, so accruals settle through the same path.
Referral inputs are read from the quote when you build from a
quoteId, so a build can never change the payee or raise the fee the user was quoted.
Fee Collection is not immediate and is done in batches, it accumulates trades rbought by referrer or partner and disburses fees twice every hour.
Partner fees
Partners earn a cut of the fee on the swaps they route. The default arrangement is revenue share at 75/25: the user pays the standard 0.15% and you receive your share of it (11.25 bps), while Mystic keeps the other half. Attaching your key never makes a quote worse for your user.
You can choose either model when your account is provisioned:
Revenue share (default)
Unchanged, 0.15%
Your configured percentage of the fee. Default 75%, so 0.1125%
Surcharge
0.15% + your bps
Your full bps, on top of Mystic's cut
On the surcharge model your bps is capped by 100 bps (1%); requesting more returns 400 FEE_VIOLATION. partnerFeeBpsOverride can only ever request less than your configured default, for a promotional pair or a fee-free campaign.
How you get paid
The total fee is collected on-chain into Mystic's fee contract at swap time, one collection whichever model you're on.
Your share is booked to an off-chain ledger once the swap confirms on-chain. Mystic's indexer reads the correlation id embedded in the swap event and matches it to the quote that produced it, so attribution needs no call from you and a spoofed hash books nothing.
Balances are settled to your payout wallet on the operator's settlement cycle.
You can start routing before you have a payout wallet. Fees accrue and are held until you set one, then become payable at the next settlement.
Errors
Domain errors return a machine-readable body:
Branch on code, not on the message text.
HTTP
code
Meaning
What to do
400
—
Request validation failed (bad address, unknown field, out-of-range slippageBps).
Fix the request.
400
UNSUPPORTED_CHAIN
chainId isn't served.
Check GET /v1/chains.
400
UNSUPPORTED_TOKEN
Token rejected, or sellAmount ≤ 0.
Fix the token or amount.
400
FEE_VIOLATION
Requested fee exceeds your cap, or unknown/inactive partner.
Lower partnerFeeBpsOverride, or check your account.
401
—
Invalid API key.
Check the key; omit it to fall back to anonymous.
404
INSUFFICIENT_LIQUIDITY
No route, including the case where no source can honor your recipient.
Try a different size or pair, or drop recipient.
404
—
Unknown quoteSetId, quoteId or adapter.
Re-quote.
410
QUOTE_EXPIRED
The quote passed its validUntil.
Re-quote and rebuild.
429
—
Rate limited.
Back off using Retry-After.
502
ADAPTER_*
An upstream venue failed.
Retry; the fan-out normally routes around this on its own.
500
—
Server error.
Retry with backoff; contact the operator if it persists.
Request bodies are strictly validated: an unrecognised field returns 400 rather than being ignored, so send only the documented parameters.
Last updated