REST API
Endpoints, request parameters, the quote response, error codes, rate limits and how to send the returned transaction from your own code.
The Tirio API is a public, read-only JSON API. It returns quotes together with a ready-to-send Router.swap transaction; your own wallet or backend signs and sends it. Replace $TIRIO_API below with your Tirio API base URL.
Conventions
- Chains are addressed by slug in the path:
bsc(BNB Chain),robinhood(Robinhood Chain, once live). - Addresses are hex; responses use EIP-55 checksums.
- The native coin (BNB, ETH) is the zero address
0x0000000000000000000000000000000000000000. - Amounts are decimal integer strings in the token's raw units (wei-style), never floats.
- Every error is a JSON body
{ "error": "<code>", "message": "<text>" }. - Rate limit: 10 requests per second per client IP with a burst of 30. Exceeding it returns 429 with a
Retry-Afterheader (seconds)./healthand/readyare not limited. - The API answers browser requests directly (CORS enabled,
Retry-Afterexposed).
Errors
error | HTTP status | When |
|---|---|---|
invalidRequest | 400 | A parameter is missing or malformed (bad address, non-positive amountIn, slippage out of range, q longer than 64 characters). |
unsupportedToken | 400 | The token cannot be traded through Tirio; message says why. |
notFound | 404 | Unknown chain slug. |
noRoute | 404 | No route for this pair and amount, or every route failed in simulation. |
unavailable | 503 | Quotes are paused: the Router is not deployed on that chain, pools are still loading, or the pool state is behind the chain. Retry later. |
rateLimited | 429 | Too many requests; wait for Retry-After. |
GET /health
Overall status of every chain the API serves.
{
"status": "ok",
"chains": {
"bsc": {
"ready": true,
"reason": null,
"pools": 0,
"tokens": 0,
"streamLagMs": null,
"engineLagMs": null,
"engineLagBlocks": null,
"feeBps": 1
}
}
}status is "ok" or "degraded"; the response is 503 while degraded. Per chain: ready and, when not ready, reason; pools and tokens tracked; streamLagMs, engineLagMs and engineLagBlocks (how far the pool state trails the chain, null when unknown); feeBps as read from the deployed Router. The values above are placeholders showing the shape.
GET /ready
204 No Content when the instance can serve quotes on every chain it is configured for, otherwise 503 with { "error": "unavailable", "message": "<chain>: <reason>" }. Meant for load balancers and health checks.
GET /{chain}/tokens?q=
The chain's listed tokens, native coin first, as an array of { address, symbol, name, decimals }. q (at most 64 characters) filters by name or symbol. If q is an address, that token is read on chain (symbol, name, decimals) and returned first even when it is not listed. decimals is null when it could not be read; the Tirio app skips such tokens.
curl "$TIRIO_API/bsc/tokens?q=usdt"GET /{chain}/quote
Quotes a swap and returns the transaction that executes it.
Query parameters
| Parameter | Required | Meaning |
|---|---|---|
tokenIn | yes | Token you sell (zero address for the native coin). |
tokenOut | yes | Token you receive (zero address for the native coin). |
amountIn | yes | Amount sold, raw integer string, greater than zero. |
slippageBps | no | Maximum slippage in basis points, 0 to 5000. Default 50 (0.5 %). |
recipient | yes | Address that receives the output. It is written into the calldata. |
deadline | no | Unix timestamp in seconds after which the Router rejects the transaction. Default: 120 seconds from now. |
maxHops | no | Maximum hops per path, 1 to 6. Default 4. Wrapping or unwrapping the native coin counts as a hop. |
maxPaths | no | Maximum candidate paths the order may be split across, 1 to 10. Default 8. |
Example
curl "$TIRIO_API/bsc/quote?tokenIn=0x0000000000000000000000000000000000000000&tokenOut=0x55d398326f99059fF775485246999027B3197955&amountIn=1000000000000000000&slippageBps=50&recipient=<your address>"This asks for 1 BNB (10^18 wei) into USDT with 0.5 % slippage.
Response
{
"tokenIn": "address",
"tokenOut": "address",
"amountIn": "string",
"amountOut": "string",
"minOut": "string",
"feeBps": 1,
"gasEstimate": 0,
"gasPrice": "string",
"deadline": 0,
"priceImpactBps": 0,
"amountInUsd": "string or null",
"amountOutUsd": "string or null",
"splitGainPpm": 0,
"quoteExact": true,
"simulated": true,
"taxIn": 0,
"taxOut": 0,
"taxInKnown": true,
"taxOutKnown": true,
"route": {
"paths": [
{
"sharePpm": 1000000,
"amountIn": "string",
"amountOut": "string",
"hops": [
{
"dex": "pancakeswap_v3",
"pool": "address",
"feePpm": 2500,
"tokenIn": "address",
"tokenOut": "address",
"amountIn": "string",
"amountOut": "string",
"source": "local",
"exact": true
}
]
}
]
},
"tx": { "to": "address", "data": "0x…", "value": "string", "gas": 0 }
}The values above show types, not real numbers.
| Field | Meaning |
|---|---|
tokenIn, tokenOut, amountIn | Echo of the request. |
amountOut | Expected output. When simulated is true this is the simulated output. |
minOut | amountOut × (10000 − slippageBps) / 10000, at least 1. Encoded in the calldata; the Router reverts if the recipient receives less. |
feeBps | Tirio fee applied, read from the Router. Taken from amountIn. |
gasEstimate | Gas the route is expected to use (the simulated figure when simulated is true). |
gasPrice | Current gas price in wei, as a string. |
deadline | Unix timestamp in seconds encoded in the calldata. |
priceImpactBps | How far the order moves the pool prices against the spot rate, in basis points; null when unknown. |
amountInUsd, amountOutUsd | USD values of input and output as decimal strings; null when the token cannot be priced. |
splitGainPpm | How much more the chosen route yields than the best single path for the whole amount, in parts per million of that single-path output; 0 when splitting gained nothing or no single path exists. |
quoteExact | true when every hop was quoted exactly; false when some hops were interpolated between exact samples. |
simulated | true when the full transaction was simulated on chain and its output and gas replaced the estimates. |
taxIn, taxOut | Detected transfer tax on the input (sell) and output (buy) token in parts per million; already applied to the quote. |
taxInKnown, taxOutKnown | false when the tax could not be measured; the actual output may then be lower. |
route.paths[] | One entry per path. sharePpm is the path's share of the input in parts per million; amountIn and amountOut are the path's amounts. |
route.paths[].hops[] | dex is the adapter name (for example pancakeswap_v3, uniswap_v2, wrapped_native for a wrap or unwrap), pool the pool address, feePpm the pool fee in parts per million, tokenIn, tokenOut, amountIn, amountOut the hop's tokens and amounts, source either local (quoted from the pool's current state) or call (quoted on chain), exact whether the hop was quoted exactly. |
tx | The transaction to send: to is always the Router 0x0000000000F315f7C21DcdF4885FB01064f58da5, data the ABI-encoded swap(SwapParams, bytes) or swapForwardingNativeOut call, value the native amount to attach (equal to amountIn for a native input, 0 otherwise), gas the gas limit to use. |
Sending the transaction
Quotes expire; request a fresh one right before sending rather than reusing an old response. Before signing, check that tx.to is the Router address and that the calldata's recipient, minOut and deadline match what you expect. This is what the Tirio app does on every quote.
For an ERC-20 input, the sender must first approve the Router for at least amountIn. A native input needs no approval; the amount travels in tx.value.
With viem in a browser:
import { createWalletClient, custom, erc20Abi } from "viem";
import { bsc } from "viem/chains";
const API = "https://<your Tirio API base URL>";
const ROUTER = "0x0000000000F315f7C21DcdF4885FB01064f58da5";
const NATIVE = "0x0000000000000000000000000000000000000000";
const client = createWalletClient({ chain: bsc, transport: custom(window.ethereum) });
const [account] = await client.getAddresses();
const params = new URLSearchParams({
tokenIn: "0x55d398326f99059fF775485246999027B3197955",
tokenOut: NATIVE,
amountIn: "1000000000000000000",
slippageBps: "50",
recipient: account,
});
const response = await fetch(`${API}/bsc/quote?${params}`);
if (!response.ok) throw new Error((await response.json()).message);
const quote = await response.json();
if (quote.tx.to.toLowerCase() !== ROUTER.toLowerCase()) throw new Error("unexpected target");
if (quote.tokenIn !== NATIVE) {
const approvalHash = await client.writeContract({
account,
address: quote.tokenIn,
abi: erc20Abi,
functionName: "approve",
args: [ROUTER, BigInt(quote.amountIn)],
});
await waitForApproval(approvalHash);
}
const hash = await client.sendTransaction({
account,
to: quote.tx.to,
data: quote.tx.data,
value: BigInt(quote.tx.value),
gas: BigInt(quote.tx.gas),
});waitForApproval stands for waiting until the approval's receipt is mined (for example waitForTransactionReceipt on a viem public client); the swap must not be sent before that. The swap's receipt status tells you whether it succeeded; a reverted swap moves no tokens and costs only gas.