# Soheil Router API > Soheil Router (https://soheil.fi/developers/) is the swap API of Soheil.fi: one REST call returns the best price for a token swap on an EVM network and the one transaction that makes it, through our own contract (SoheilRouter: source verified on the explorers, not upgradeable, holds no funds between trades). The router takes 0.1% (10 bps) per swap; the integrator sets their own fee on top (up to 0.9%), paid to their wallet in the same transaction. Free API key from the developer dashboard. Last updated: 2026-11-06. Live facts (networks, contracts, fees) come from the API itself: GET https://api.soheil.fi/v1/chains and GET https://api.soheil.fi/v1/info. # Introduction Source: https://soheil.fi/developers/docs/ Soheil Router is a swap API for wallets, apps and bots: one request, one transaction, and a 0.1% fee, where other swap APIs take 0.15% to 0.25%. ## What it is For every request, Soheil Router finds the best price it can reach and returns **one transaction** to our own contract, `SoheilRouter`. That transaction takes the fees, runs the swap and sends the output to the user. If anything fails, or the output is below the minimum, it reverts as a whole and the user keeps their tokens. ## Why Soheil Router - **0.1% fee.** Other swap APIs take 0.15% to 0.25% ([compare](https://soheil.fi/developers/docs/fees/#compare)). More of every swap stays with your user and with you. - **You earn on every swap.** Add your own fee, 0 to 0.9%. It is paid to your wallet inside the same transaction: no invoice, nothing to claim. - **Non-custodial.** Funds go from the user's wallet through the contract to the recipient in one transaction. The contract holds nothing between trades and cannot be upgraded. - **One integration.** The same request and answer on every network, from one endpoint. > **Today**Swaps within one EVM network. Bridges come next, through the same contract and the same API. ## Base URL and formats ```bash https://api.soheil.fi/v1 ``` - Requests and answers are JSON. - Amounts are strings in the token's smallest unit (100 USDC is `"100000000"`). - The network's own coin (ETH, BNB, POL, AVAX) is `0x0000000000000000000000000000000000000000`. ## Where next [**Quick start**Your first swap in three calls.](https://soheil.fi/developers/docs/quick-start/)[**Get an API key**How keys work and how to ask for one.](https://soheil.fi/developers/docs/keys/)[**Fees and earnings**What the user pays and what you earn.](https://soheil.fi/developers/docs/fees/)[**API reference**Every field of GET /v1/quote.](https://soheil.fi/developers/docs/quote/) --- # Quick start Source: https://soheil.fi/developers/docs/quick-start/ From an API key to a confirmed swap: quote, approve, send. ## Steps 1. **Get an API key.** See [API keys](https://soheil.fi/developers/docs/keys/). 2. **Ask for a quote** with [`GET /v1/quote`](https://soheil.fi/developers/docs/quote/). 3. **Approve** exactly `fromAmount` to `approvalAddress`, when it is set. 4. **Send** `transactionRequest` as it is, from the user's wallet. 5. **Follow it** with [`GET /v1/status`](https://soheil.fi/developers/docs/status/), or just read the receipt. ## The code JavaScript (viem): ```js // 1. Quote (on your server: keep the key there) const params = new URLSearchParams({ fromChainId: "8453", toChainId: "8453", fromToken: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", // USDC on Base toToken: "0x0000000000000000000000000000000000000000", // ETH fromAmount: "100000000", // 100 USDC (6 decimals) fromAddress: user, slippageBps: "50", }); const q = await fetch(`https://api.soheil.fi/v1/quote?${params}`, { headers: { "x-api-key": process.env.SOHEIL_ROUTER_KEY } }) .then((r) => r.json()); if (q.error) throw new Error(q.error.message); // 2. Approve exactly fromAmount to our contract (skipped for the coin) if (q.approvalAddress) { await wallet.writeContract({ address: q.fromToken, abi: erc20Abi, functionName: "approve", args: [q.approvalAddress, BigInt(q.fromAmount)] }); } // 3. Send the transaction as it is const hash = await wallet.sendTransaction({ to: q.transactionRequest.to, data: q.transactionRequest.data, value: BigInt(q.transactionRequest.value), }); ``` curl: ```bash curl "https://api.soheil.fi/v1/quote?fromChainId=8453&toChainId=8453\ &fromToken=0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913\ &toToken=0x0000000000000000000000000000000000000000\ &fromAmount=100000000&fromAddress=0xYourUser&slippageBps=50" \ -H "x-api-key: sk_xxxxxxxx_…" ``` > **Keep the key on your server**The API allows calls from browsers, but a key in a public page can be read by anyone. Call us from your backend. --- # API keys Source: https://soheil.fi/developers/docs/keys/ One key per app: it carries your fee address, your fee and your limits. ## Getting a key 1. Create a free account in the [developer dashboard](https://soheil.fi/developers/dashboard/) (email and password, confirmed with a code). 2. In **API keys**, make a key: its name, the address that receives your fee, and your fee (0 to 90 bps). 3. Copy the key: it is shown only once. While keys are approved by hand, it works once we approve it (usually within a day). You get a key like `sk__`. ## Using it Send it in the `x-api-key` header on every call that needs one. ```bash -H "x-api-key: sk_xxxxxxxx_…" ``` > We keep only a hash of the secret, so a lost key is replaced, not recovered. Keep it on your server. ## Changing a key Each key carries its fee address, its default fee, its fee ceiling and its limits. Change the name, fee address and fee in the dashboard, switch a key off or delete it there at once if it leaks; write to developers \[at\] soheil.fi for higher limits. > Turn on two-factor sign-in in the dashboard's Settings: your account controls where your fees go. --- # Build with an AI assistant Source: https://soheil.fi/developers/docs/ai/ Give Claude, ChatGPT, Gemini, Cursor or Copilot these docs, and it can write the whole integration for you. ## Give it the docs Point your assistant to one of these, or paste it in: | File | What it is | | --- | --- | | [`soheil.fi/developers/llms-full.txt`](https://soheil.fi/developers/llms-full.txt) | Every docs page in one Markdown file. | | [`soheil.fi/developers/llms.txt`](https://soheil.fi/developers/llms.txt) | A short index, with a link to each page in Markdown. | | [`api.soheil.fi/v1/openapi.json`](https://api.soheil.fi/v1/openapi.json) | The whole API as OpenAPI 3.1, for generated clients and typed code. | Every page here also has **Copy page as Markdown** under its title. ## Connect it over MCP The API is also an MCP server, so an assistant or agent can call it as tools: `get_networks`, `get_api_info`, `get_quote` and `get_trade_status`. It speaks Streamable HTTP at `https://api.soheil.fi/v1/mcp`. The networks and info work without a key; a quote and a status take your API key as `Authorization: Bearer` (or `x-api-key`), counted like any other call. Claude Code: ```bash claude mcp add --transport http soheil-router https://api.soheil.fi/v1/mcp \ --header "Authorization: Bearer $SOHEIL_ROUTER_KEY" ``` Cursor: ```json { "mcpServers": { "soheil-router": { "url": "https://api.soheil.fi/v1/mcp", "headers": { "Authorization": "Bearer sk_your_key" } } } } ``` > The assistant gets the transaction to sign, never a key to a wallet: the user still approves and signs every trade in their own wallet. ## Prompts that work Swap in a web app: ```text Using the Soheil Router API (docs: https://soheil.fi/developers/llms-full.txt), add token swaps to my Next.js app on Base. Call /v1/quote from a server route with my key from the SOHEIL_ROUTER_KEY env var (never in the browser), charge integratorBps 30, ask the user's wallet (viem) to approve exactly fromAmount when approvalAddress is set, send transactionRequest unchanged, then poll /v1/status. ``` Telegram bot: ```text Read https://soheil.fi/developers/llms-full.txt. Write a Node.js Telegram bot command /quote that calls the Soheil Router API on Base and replies with toAmount, toAmountMin and both fees in readable units. Read the key from an env var. ``` Python script: ```text Using the OpenAPI at https://api.soheil.fi/v1/openapi.json, write a Python function quote(chain_id, from_token, to_token, amount, wallet) that returns the parsed quote, raises on the {"error": {"code", "message"}} shape, and treats unknown status values as PENDING. ``` ## Check what it wrote - The API key stays on your server; a key in browser code can be used by anyone. - Approvals are for exactly `fromAmount`, never unlimited. - `transactionRequest` is sent unchanged, and amounts stay strings or BigInt, never JavaScript numbers. - Errors are read from `error.code`, and unknown codes are handled by their HTTP status. > Quotes are free and count only against your key's limits, so let the assistant test against the live API with a small amount. --- # Networks Source: https://soheil.fi/developers/docs/networks/ Where the contract is live today, read live from GET /v1/chains. ## Live networks The contract has the same address on every network except Base. Click an address to see the verified source on the network's explorer. The live list: `GET https://api.soheil.fi/v1/chains` (no key; every network, our contract there and the contracts a trade may call). > More EVM networks are added over time. `GET /v1/chains` is always the current list. --- # Fees and earnings Source: https://soheil.fi/developers/docs/fees/ The router takes 0.1%. You set your own fee on top, up to 0.9%, paid to you in the same transaction. ## How fees work Two fees come off the amount sold, in the token sold, before the swap: the router's **10 bps (0.1%)**, and **yours**, paid to your key's fee address. Your fee is your key's default, or `integratorBps` on the request (0 up to your key's ceiling, 90 bps at most). Together they never exceed 100 bps: the contract refuses more. ## Example Selling 1,000 USDC with your fee at 20 bps: | | Amount | Goes to | | --- | --- | --- | | Router fee (0.1%) | 1 USDC | the router | | Your fee (0.2%) | 2 USDC | your wallet, in the same transaction | | Swapped | 997 USDC | the user's output | The quote shows all of this in `fees`, so you can show your users exactly what they pay. [`GET /v1/me`](https://soheil.fi/developers/docs/me/) adds up what your trades earned. ## Compared with other APIs The fee each takes for itself on a swap: | API | Its fee | Note | | --- | --- | --- | | **Soheil Router** | **0.1%** | on each trade; your own fee is separate and all yours | | Other swap APIs | 0.15% to 0.25% | what other aggregator APIs take for themselves, as their own docs publish it | > On a $10,000 swap, the router takes $10 where other swap APIs take $15 to $25. The difference stays with your user, or becomes room for your own fee. ## Guarantees - The contract checks the router's fee against what the quote said (`maxSoheilBps`), so it can never charge more than the user was shown. - Coin sent along with a token trade pays the same rates. - Both fees are paid inside the trade. There is no invoice, no payout and nothing to claim. --- # Conventions Source: https://soheil.fi/developers/docs/conventions/ How every endpoint writes chains, addresses, amounts and times, and what v1 promises not to change. ## Values | Value | Written as | | --- | --- | | Chains | `fromChainId`, `toChainId`, `chainId`: the EVM chain ID, a number | | Addresses | 0x…, lower case in answers; any case accepted in requests | | The coin | `0x0000000000000000000000000000000000000000` (`0xeeee…eeee` is accepted too) | | Amounts | decimal strings in the token's smallest unit, never numbers (they overflow JavaScript) | | The transaction's `value` | hex (0x…), as wallets take it | | Ids and hashes | 0x + 64 hex | | Times | unix seconds | | Names ending in `Bps` | basis points (1 bps = 0.01%); names ending in Fee are amounts | | Unknown | `null`, never a placeholder | ## What stays the same - Within v1, fields and endpoints are never renamed or removed. New fields and new endpoints may appear: ignore what you do not know. - Values like `type`, `status`, `substatus` and error codes may gain new members (bridges will add some). Treat an unknown one by its meaning: an unknown `status` as `PENDING`, an unknown error code by its HTTP status. - Shapes already carry both networks (`fromChainId`, `toChainId`, `sending`, `receiving`), so bridges come without a v2. ## One quote, one transaction Each quote has its own `id`, and its transaction is meant to be sent once. The contract does not refuse an id it has seen: sending the same transaction twice makes two trades, if the user's balance and approval allow it. Approving exactly `fromAmount` (never more) keeps a resent transaction from going through. --- # Get a quote Source: https://soheil.fi/developers/docs/quote/ The best price for a swap, with the transaction that makes it. Also POST /v1/quote with the same fields as JSON. `GET /v1/quote` (API key) ## Request ```bash curl "https://api.soheil.fi/v1/quote?fromChainId=8453&toChainId=8453\ &fromToken=0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913\ &toToken=0x0000000000000000000000000000000000000000\ &fromAmount=100000000&fromAddress=0xYourUser&slippageBps=50" \ -H "x-api-key: sk_xxxxxxxx_…" ``` ## Fields - `fromChainId` (integer, required): Chain ID where the user sends from. - `toChainId` (integer, required): Chain ID where the recipient receives: the same as `fromChainId` until bridges come. - `fromToken` (address, required): Token sold (the coin is `0x000…0`). - `toToken` (address, required): Token bought. - `fromAmount` (string, required): Amount sold, in the token's smallest unit, above 0. - `fromAddress` (address, required): The wallet that signs and pays. - `toAddress` (address): Who receives the output, on `toChainId`; `fromAddress` by default. Never a token or our contract. - `slippageBps` (integer): 1 to 5000; 50 (0.5%) by default. - `integratorBps` (integer): Your fee for this request, 0 up to your key's ceiling; your key's default when left out. ## Response ```json { "id": "0x9f2c…", // the trade's request id (bytes32 in the call and in the Traded event) "type": "swap", // "swap" on one network; "bridge" when bridges come "fromChainId": 8453, "toChainId": 8453, "fromToken": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "toToken": "0x0000000000000000000000000000000000000000", "fromAmount": "100000000", "toAmount": "41287340000000000", // expected output, both fees already taken from the input "toAmountMin": "41080903300000000", // the least the recipient gets: the transaction reverts below it "fees": { "token": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "totalBps": 30, "routerBps": 10, "integratorBps": 20, "routerFee": "100000", "integratorFee": "200000" }, "approvalAddress": "0x…", // our contract: approve exactly fromAmount first (null for the coin) "transactionRequest": { "to": "0x…", "data": "0x4c5d1e10…", "value": "0x0", "chainId": 8453 }, "gasEstimate": "270000", "validUntil": 1793710800, // unix seconds: send before it, or quote again "estimatedDurationSeconds": 0 // from mined to received: 0 for a swap (the same transaction) } ``` ## Reading the answer - `toAmount` is what the user should receive, with both fees already taken. - `toAmountMin` is the least they will receive, enforced by the contract. - `fees` are taken from `fromAmount`, in `fees.token`. - `gasEstimate` includes the contract's own work. - `validUntil` (about 20 minutes ahead) is how long the price is fresh, not an on-chain deadline: send before it, or quote again. Sent later, the trade may revert (the user keeps their tokens) or fill at a worse price, never below `toAmountMin`. --- # Sending the transaction Source: https://soheil.fi/developers/docs/execute/ What the user signs after a quote, and what happens if the price moves. ## Approval When `approvalAddress` is set, the user approves exactly `fromAmount` of `fromToken` to it (our contract) first. > Ask for exact approvals, never unlimited ones. Tokens like USDT that refuse a new approval over an old one need an approval of 0 first. ## Selling the coin When `fromToken` is the coin, there is no approval; `transactionRequest.value` carries the amount. ## Sending Send `to`, `data` and `value` unchanged. Let the wallet estimate gas, or use `gasEstimate` with some margin. ## Reverts A trade that would give less than `toAmountMin`, or whose swap fails, reverts as a whole: the user keeps the tokens and pays only gas. Quote again and retry. --- # Trade status Source: https://soheil.fi/developers/docs/status/ Whether a trade went through, and what the contract recorded. `GET /v1/status?chainId=&txHash=` (API key) ## Request ```bash curl "https://api.soheil.fi/v1/status?chainId=8453&txHash=0x…" -H "x-api-key: sk_xxxxxxxx_…" ``` ## States | status | substatus | Meaning | | --- | --- | --- | | `PENDING` | `NOT_MINED` | Sent, not mined yet. Ask again in a few seconds. | | `DONE` | `COMPLETED` | Mined; `trade` has what the contract recorded. | | `FAILED` | `REVERTED` | Sent to our contract and reverted; the user kept their tokens. | | `NOT_OURS` | `null` | Mined, but not a trade through our contract. | | `NOT_FOUND` | `null` | The network does not know this transaction (yet). Check the hash and chainId. | ## Response ```json { "status": "DONE", "substatus": "COMPLETED", "sending": { "chainId": 8453, "txHash": "0x…", "blockNumber": 22018445, "timestamp": 1793710210 }, "receiving": { "chainId": 8453, "txHash": "0x…", "blockNumber": 22018445, "timestamp": 1793710210 }, "trade": { "id": "0x9f2c…", "sender": "0xyouruser…", "recipient": "0xyouruser…", "fromToken": "0x8335…2913", "fromAmount": "100000000", "toToken": "0x0000…0000", "toAmount": "41290000000000000", "routerFee": "100000", "integratorFee": "200000", "integratorAddress": "0xyourfeeaddress…" } } ``` ## Notes - `chainId` is the network the transaction was sent on. `sending` is that transaction; `receiving` is the one that paid the recipient: the same for a swap, a second one on `toChainId` for a bridge. - New values may appear in `status` and `substatus` (bridges will add some): treat an unknown `status` as `PENDING`. - A transaction holding more than one trade (a contract calling ours twice) shows the first. - Every trade is also read from the chain on its own, so your earnings count whether or not you ask. --- # Your key and earnings Source: https://soheil.fi/developers/docs/me/ Your key's settings, this month's requests, and what your trades earned per token. `GET /v1/me` (API key) ## Response ```json { "prefix": "abcdef12", "name": "My app", "integratorAddress": "0x…", "integratorBps": 20, "maxIntegratorBps": 90, "perMinute": 60, "monthlyQuota": 50000, "requestsThisMonth": 1234, "routerBps": 10, "earned": { "trades": 87, "byToken": [ { "chainId": 8453, "token": "0x8335…2913", "symbol": "USDC", "decimals": 6, "volume": "52000000000", "integratorFee": "104000000" } ] } } ``` `earned.byToken` lists every token your trades sold, on every network, in one answer. `symbol` and `decimals` are `null` when the network could not be asked just then. --- # Public endpoints Source: https://soheil.fi/developers/docs/public/ Endpoints that need no key. ## Endpoints `GET /v1/health` (no key) `{ "status": "ok", "networks": 7 }` when the API can serve; `503` with `"down"` when it cannot. For uptime checks. `GET /v1/info` (no key) The API's version, the router's fee, the highest fee you can set, and how keys are given. `GET /v1/chains` (no key) The networks, our contract on each, its explorer, and the contracts a trade may call there. `GET /v1/openapi.json` (no key) This API as an OpenAPI 3 description, for generating clients. --- # Errors Source: https://soheil.fi/developers/docs/errors/ Every error is JSON with a code and a message you can show your users. ## Shape ```json { "error": { "code": "NO_ROUTE", "message": "No route for this pair and amount right now." } } ``` ## Codes New codes may be added: handle an unknown one by its HTTP status. | Code | HTTP | Meaning | | --- | --- | --- | | `INVALID` | 400 | A field is missing or wrong; the message says which. | | `UNAUTHORIZED` | 401 | No key, or a wrong one. | | `KEY_DISABLED` | 403 | Your key is switched off. | | `NOT_FOUND` | 404 | No such endpoint. | | `METHOD_NOT_ALLOWED` | 405 | The endpoint does not take this method. | | `UNSUPPORTED_CHAIN` | 422 | The router is not on this network, or cannot reach `toChainId` from it yet. | | `NO_ROUTE` | 422 | No route for this pair and amount right now. | | `RATE_LIMITED` | 429 | Over your key's requests a minute; wait `Retry-After` seconds. | | `QUOTA_EXCEEDED` | 429 | Your key's monthly requests are used up; `Retry-After` says when they start again. | | `INTERNAL` | 500 | Something went wrong on our side; try again. | | `UPSTREAM` | 502 | A network we read from did not answer; try again. | | `PAUSED` | 503 | The contract on this network is paused. | | `NOT_READY` | 503 | The contract on this network is not ready for trades yet. | --- # Rate limits Source: https://soheil.fi/developers/docs/limits/ What a new key starts with, and what happens over the limit. ## Limits | Limit | New key | | --- | --- | | Requests a minute | 60 | | Requests a month | 50,000 | Every call your key makes counts, except the ones refused for the key or its limits. | Header | On every answer | | --- | --- | | `X-RateLimit-Limit` | requests a minute for your key | | `X-RateLimit-Remaining` | what is left this minute | | `X-RateLimit-Reset` | seconds until the minute starts again | Over the minute limit the answer is `429 RATE_LIMITED`; over the month, `429 QUOTA_EXCEEDED` (it starts again on the 1st, UTC). Both say in `Retry-After` how many seconds to wait. > Need more? Write to developers \[at\] soheil.fi with your expected volume. --- # Contracts and events Source: https://soheil.fi/developers/docs/contracts/ Every trade is one call to SoheilRouter, verified on the explorers and on Sourcify. Addresses are on [Networks](https://soheil.fi/developers/docs/networks/). ## The Traded event ```solidity event Traded( bytes32 indexed requestId, address indexed sender, address tokenIn, uint256 amountIn, address tokenOut, uint256 amountOut, uint256 soheilFee, uint256 integratorFee, address integrator, address recipient ); ``` `requestId` is the quote's `id`; `soheilFee` (the router's fee) and `integratorFee` are the two fees in `tokenIn`. You can index these events yourself; `GET /v1/status` and `GET /v1/me` read the same data. ## Contracts a trade may call For wallets and security policies that allowlist contracts. The router chooses among them per quote; a new one is callable only 2 days after the owner announces it on-chain. The live list: `GET https://api.soheil.fi/v1/chains` (no key; every network, our contract there and the contracts a trade may call). --- # Security Source: https://soheil.fi/developers/docs/security/ What the contract can and cannot do, and how changes are announced. ## Design - The contract holds no funds between trades, has no upgrade path and no `delegatecall`. - It approves only the trade's own tokens, for the exact amount of each call, and resets the approval right after. - It refuses any call that would use or hand out its own token rights (`transferFrom`, `approve` and similar), whatever contract it calls. - The owner can never move users' funds or charge more than a quote showed. ## Changes A new contract to call, or a new fee wallet, takes effect only **2 days** after the owner announces it on-chain, so a stolen owner key cannot use one at once. The owner can pause trading, or remove a contract, at once. ## Reporting > Reviewed internally, with tests for every finding; no third-party audit yet. Report a vulnerability to security \[at\] soheil.fi. --- # FAQ Source: https://soheil.fi/developers/docs/faq/ Short answers to what integrators ask first. ## Which tokens can be traded? Any ERC-20 token the router finds liquidity for, and the network's coin. Tokens that take a fee on every transfer may fail: the quote expects the full amount to arrive. ## Can I charge a different fee per user? Yes: send `integratorBps` with each quote, from 0 up to your key's ceiling. ## Where does my fee arrive, and when? At your key's fee address, in the token sold, in the same transaction as the trade. ## What happens if the price moves? The trade reverts below `toAmountMin`, and the user keeps their tokens. Quote again; a higher `slippageBps` helps on volatile tokens. ## Is there a testnet? Not yet. Test on mainnet with small amounts; quotes are free and count only against your key's limits. ## Bridges, Solana and other networks? Bridges come next, through the same contract and API. More EVM networks are added over time. `GET /v1/chains` is always the current list. --- # Support Source: https://soheil.fi/developers/docs/support/ Where to write, and what to include. ## Contact | For | Write to | | --- | --- | | Keys, limits, integration questions | developers \[at\] soheil.fi | | Security reports | security \[at\] soheil.fi |