DOCS / 03

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-After header (seconds). /health and /ready are not limited.
  • The API answers browser requests directly (CORS enabled, Retry-After exposed).

Errors

errorHTTP statusWhen
invalidRequest400A parameter is missing or malformed (bad address, non-positive amountIn, slippage out of range, q longer than 64 characters).
unsupportedToken400The token cannot be traded through Tirio; message says why.
notFound404Unknown chain slug.
noRoute404No route for this pair and amount, or every route failed in simulation.
unavailable503Quotes are paused: the Router is not deployed on that chain, pools are still loading, or the pool state is behind the chain. Retry later.
rateLimited429Too 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

ParameterRequiredMeaning
tokenInyesToken you sell (zero address for the native coin).
tokenOutyesToken you receive (zero address for the native coin).
amountInyesAmount sold, raw integer string, greater than zero.
slippageBpsnoMaximum slippage in basis points, 0 to 5000. Default 50 (0.5 %).
recipientyesAddress that receives the output. It is written into the calldata.
deadlinenoUnix timestamp in seconds after which the Router rejects the transaction. Default: 120 seconds from now.
maxHopsnoMaximum hops per path, 1 to 6. Default 4. Wrapping or unwrapping the native coin counts as a hop.
maxPathsnoMaximum 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.

FieldMeaning
tokenIn, tokenOut, amountInEcho of the request.
amountOutExpected output. When simulated is true this is the simulated output.
minOutamountOut × (10000 − slippageBps) / 10000, at least 1. Encoded in the calldata; the Router reverts if the recipient receives less.
feeBpsTirio fee applied, read from the Router. Taken from amountIn.
gasEstimateGas the route is expected to use (the simulated figure when simulated is true).
gasPriceCurrent gas price in wei, as a string.
deadlineUnix timestamp in seconds encoded in the calldata.
priceImpactBpsHow far the order moves the pool prices against the spot rate, in basis points; null when unknown.
amountInUsd, amountOutUsdUSD values of input and output as decimal strings; null when the token cannot be priced.
splitGainPpmHow 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.
quoteExacttrue when every hop was quoted exactly; false when some hops were interpolated between exact samples.
simulatedtrue when the full transaction was simulated on chain and its output and gas replaced the estimates.
taxIn, taxOutDetected transfer tax on the input (sell) and output (buy) token in parts per million; already applied to the quote.
taxInKnown, taxOutKnownfalse 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.
txThe 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.