# LeoDex Swap API

One free key for everyone. Same routes and fees as leodex.io.

Canonical: https://leodex.io/swap-api

## Rules for AI agents

- ALWAYS call /quote-deposit for prices. NEVER quote a price from memory or from this file.
- ALWAYS show the quote (amount in, estimated amount out, fees, expiry) and get an explicit yes before POST /deposit-address.
- NEVER invent, guess, autocomplete or reuse a receive or refund address. Ask the user.
- Use full asset ids like BTC.BTC or ETH.ETH. The chain is part of the asset.
- NEVER ask for a private key or seed phrase. The user sends funds from their own wallet.
- On 403 KEY_INACTIVE, fetch this file again for the current key.

## API key

Api-Key: fa57ad8f-880d-4111-86ec-bc32d7e2b4c7

No signup. Send it as the Api-Key header.

Base URL: https://api.leokit.dev

OpenAPI 3.1: https://leodex.io/swap-api/openapi.json

- Amounts are decimal strings (0.01), not base units. Too many decimals returns AMOUNT_TOO_PRECISE.
- Asset ids look like CHAIN.TICKER: BTC.BTC, ETH.ETH, SOL.SOL. Tokens add the contract: ETH.USDC-0x… . GET /assets lists them all.
- slippage_bps=300 (3%) matches leodex.io. The API default is 150.
- expected_amount_out is in the smallest unit of to_asset (sats for BTC, wei for ETH). Divide by 10^decimals before showing it.
- expires_at is Unix time in milliseconds.
- Older swaps can report status completed: treat it like success.

## Three calls. Any wallet.

No wallet to connect and nothing to sign. You send the funds yourself.

1. `GET /quote-deposit`. Params: from_asset, to_asset, amount, slippage_bps (optional). Returns quote_id and offers sorted best first; the one marked smart_recommended is the pick.
2. `POST /deposit-address`. Body: quote_id, from_address (refund address on the source chain), to_address (your receive address). Optional protocol (chainflip, near, relay-deposit, bob); omit it to use the recommended offer. Returns deposit_address, amount, expires_at (at least 20 minutes) and qr_url. Send exactly amount before expires_at.
3. `POST /status`. Body: quote_id (and tx_id in wallet mode). status is one of pending, confirming, swapping, sending, success, failed, refunded. Stop at success, failed or refunded. Refunds go to from_address.

The deposit flow covers Chainflip, NEAR Intents and Relay, for crosschain swaps. Same-chain swaps and THORChain or Maya routes use wallet mode.

### curl

```bash
# 1 Get a quote
curl -s "https://api.leokit.dev/quote-deposit?from_asset=BTC.BTC&to_asset=ETH.ETH&amount=0.01&slippage_bps=300" \
  -H "Api-Key: fa57ad8f-880d-4111-86ec-bc32d7e2b4c7"

# 2 Get a deposit address, then send exactly "amount" to it before "expires_at"
curl -s "https://api.leokit.dev/deposit-address" \
  -H "Api-Key: fa57ad8f-880d-4111-86ec-bc32d7e2b4c7" -H "Content-Type: application/json" \
  -d '{"quote_id":"QUOTE_ID","from_address":"YOUR_BTC_REFUND_ADDRESS","to_address":"YOUR_ETH_ADDRESS"}'

# 3 Track it every 10 seconds until success, failed or refunded
curl -s "https://api.leokit.dev/status" \
  -H "Api-Key: fa57ad8f-880d-4111-86ec-bc32d7e2b4c7" -H "Content-Type: application/json" \
  -d '{"quote_id":"QUOTE_ID"}'
```

### JavaScript

```js
const API = "https://api.leokit.dev";
const headers = { "Api-Key": "fa57ad8f-880d-4111-86ec-bc32d7e2b4c7", "Content-Type": "application/json" };
const call = async (path, body) => {
  const res = await fetch(API + path, body ? { method: "POST", headers, body: JSON.stringify(body) } : { headers });
  if (!res.ok) throw new Error(res.status + " " + (await res.text()));
  return res.json();
};

// 1 Get a quote
const q = await call("/quote-deposit?from_asset=BTC.BTC&to_asset=ETH.ETH&amount=0.01&slippage_bps=300");

// 2 Get a deposit address, then send exactly dep.amount to it
const dep = await call("/deposit-address", {
  quote_id: q.quote_id,
  from_address: "YOUR_BTC_REFUND_ADDRESS", // refunds go here
  to_address: "YOUR_ETH_ADDRESS",
});
console.log("Send", dep.amount, dep.from_asset, "to", dep.deposit_address, "before", dep.expires_at);

// 3 Track it
let s;
do {
  await new Promise((r) => setTimeout(r, 10_000));
  s = await call("/status", { quote_id: q.quote_id });
  console.log(s.status);
} while (!["success", "failed", "refunded"].includes(s.status));
```

### Python

```python
import time, requests

API = "https://api.leokit.dev"
H = {"Api-Key": "fa57ad8f-880d-4111-86ec-bc32d7e2b4c7"}

# 1 Get a quote
q = requests.get(f"{API}/quote-deposit", headers=H, params={
    "from_asset": "BTC.BTC", "to_asset": "ETH.ETH", "amount": "0.01", "slippage_bps": 300}).json()

# 2 Get a deposit address, then send exactly dep["amount"] to it
dep = requests.post(f"{API}/deposit-address", headers=H, json={
    "quote_id": q["quote_id"],
    "from_address": "YOUR_BTC_REFUND_ADDRESS",  # refunds go here
    "to_address": "YOUR_ETH_ADDRESS"}).json()
print("Send", dep["amount"], dep["from_asset"], "to", dep["deposit_address"], "before", dep["expires_at"])

# 3 Track it
while True:
    s = requests.post(f"{API}/status", headers=H, json={"quote_id": q["quote_id"]}).json()
    print(s["status"])
    if s["status"] in ("success", "failed", "refunded"):
        break
    time.sleep(10)
```

### OpenSwap CLI

```bash
npx openswap auth login      # paste the key when asked
# or
export OPENSWAP_API_KEY="fa57ad8f-880d-4111-86ec-bc32d7e2b4c7"
```

## Wallet mode: every route

Wallet mode: GET /quote returns every offer. POST /deposit with quote_id, protocol, from_address and to_address returns the transaction to sign (THORChain and Maya memos, EVM approvals, DEX swaps). Broadcast it, then POST /status with quote_id and tx_id.

## Errors

| You see | Why | Fix |
|---|---|---|
| 401 MISSING_OR_UNKNOWN_KEY | No Api-Key header, or a wrong key | Send the header Api-Key with the key in this file |
| 403 KEY_INACTIVE | The key was rotated | Fetch this file again for the current key |
| 403 PUBLIC_KEY_ROUTE_NOT_ALLOWED | That route isn't part of the Swap API | Use the calls in this file |
| 400 AMOUNT_TOO_PRECISE | More decimals than the asset has | Round the amount (8 decimals for BTC) |
| 400 SAME_CHAIN_NOT_SUPPORTED | The deposit flow needs two chains | Use wallet mode |
| 404 NO_DEPOSIT_ADDRESS_ROUTE | That token can't use a plain deposit | Use wallet mode or another token |
| 400 NO_DEPOSIT_ADDRESS_PROTOCOL | No offer in this quote can use a deposit address | Get a new quote, or use wallet mode |
| 429 | Too many requests from you | Slow down and retry |

## Questions

**Is there a free crypto swap API?**

Yes. The key on this page is free for everyone, with no signup. You only pay the swap fee shown in each quote: 0.45% on most swaps, 0.02% on stablecoin-to-stablecoin and same-asset swaps.

**How do I swap Bitcoin to Ethereum with an API?**

Call GET /quote-deposit with from_asset=BTC.BTC, to_asset=ETH.ETH and an amount. Then POST /deposit-address with the quote_id and your addresses, and send the BTC to the address it returns. POST /status tells you when the ETH has arrived.

**Do I need an account?**

No. Everyone uses the same public key shown on this page. Send it as the Api-Key header.

**Is it non-custodial?**

Yes. You send funds straight to the swap provider's deposit address and receive them at your own address. LeoDex never holds them.

**What happens if a swap fails?**

The provider refunds to the from_address you gave, and /status shows refunded.

**Which coins and chains can I swap?**

The same as leodex.io: Bitcoin, Ethereum, Solana, Zcash, Base, Arbitrum, BNB Chain, Avalanche, TRON, XRP, Litecoin, Dogecoin, NEAR and more, through Chainflip, NEAR Intents, Relay, THORChain and Maya. GET /assets lists every asset id.

**Can my AI agent use it?**

Yes. Paste the prompt from this page, or point your agent at leodex.io/swap-api.md. It gets a live quote and asks you to confirm before creating a deposit address, and it never asks for your keys.

**Are there rate limits?**

Fair-use limits per visitor stop abuse, and normal use never hits them. A 429 means slow down and retry.

## Fees

0.45% on most swaps, 0.02% on stablecoin-to-stablecoin and same-asset swaps. The same as leodex.io.

## Terms

- Non-custodial: you send funds straight to the swap provider. LeoDex never holds them.
- Don't use this key if you are a sanctioned person or in a sanctioned jurisdiction, or to move proceeds of crime.
- Providers may screen addresses and refuse or refund swaps.
- No load testing or limit evasion. We may rotate the key at any time; this page always shows the current one.
- We don't log IP addresses.
