The Problem: Every Crypto Exchange Wants My Passport
I needed to swap some ETH for BTC last month. Nothing fancy — just a quick cross-chain swap. I went to three different exchanges and every single one wanted a selfie, my passport, and a utility bill. For a $200 swap.
That's when I started looking into SimpleSwap's API. No KYC for swaps under their threshold, clean REST API, and they support a ton of coin pairs. I built a small Python tool around it and figured I'd share what I learned.
If you're tired of the KYC circus, there's a solid list of no-KYC exchanges that breaks down which ones actually work without ID verification.
SimpleSwap API: The Basics
SimpleSwap has a straightforward REST API. No OAuth dance, no complex auth — just an API key and standard JSON requests.
Here's how to create a swap with curl first:
# Create a swap: ETH -> BTC
curl -X POST https://api.simpleswap.io/v1/create_exchange \
-H "Content-Type: application/json" \
-d '{
"api_key": "YOUR_API_KEY",
"currency_from": "eth",
"currency_to": "btc",
"amount": "0.05",
"address_to": "YOUR_BTC_ADDRESS"
}'
The response gives you a deposit address and an exchange ID. You send your ETH to the deposit address, they send BTC to your address. That's it.
Building the Python Client
I wrapped this into a proper Python class with error handling because raw API calls kept biting me when networks got flaky:
import requests
import time
import os
from typing import Optional, Dict
class SimpleSwapClient:
BASE_URL = "https://api.simpleswap.io/v1"
def __init__(self, api_key: Optional[str] = None):
self.api_key = api_key or os.environ.get("SIMPLESWAP_API_KEY")
if not self.api_key:
raise ValueError("Set SIMPLESWAP_API_KEY env var or pass api_key")
def get_currencies(self, fixed: bool = False) -> list:
"""Get available currencies. Set fixed=True for fixed-rate pairs."""
endpoint = "/get_currencies" if not fixed else "/get_pairs"
resp = requests.get(
f"{self.BASE_URL}{endpoint}",
params={"api_key": self.api_key, "fixed": fixed}
)
resp.raise_for_status()
return resp.json()
def get_estimate(self, currency_from: str, currency_to: str,
amount: float) -> Dict:
"""Estimate how much you'll receive."""
resp = requests.get(
f"{self.BASE_URL}/get_estimate",
params={
"api_key": self.api_key,
"currency_from": currency_from,
"currency_to": currency_to,
"amount": str(amount),
}
)
resp.raise_for_status()
return resp.json()
def create_swap(self, currency_from: str, currency_to: str,
amount: float, address_to: str,
extra_id: Optional[str] = None) -> Dict:
"""Create a swap. Returns deposit address and swap ID."""
payload = {
"api_key": self.api_key,
"currency_from": currency_from,
"currency_to": currency_to,
"amount": str(amount),
"address_to": address_to,
}
if extra_id:
payload["extra_id"] = extra_id
resp = requests.post(
f"{self.BASE_URL}/create_exchange",
json=payload,
timeout=30
)
resp.raise_for_status()
return resp.json()
def get_status(self, swap_id: str) -> Dict:
"""Check swap status. Poll this until it's done."""
resp = requests.get(
f"{self.BASE_URL}/get_status",
params={"api_key": self.api_key, "id": swap_id}
)
resp.raise_for_status()
return resp.json()
def wait_for_swap(self, swap_id: str, timeout: int = 1800,
poll_interval: int = 30) -> Dict:
"""Block until swap completes or times out."""
start = time.time()
while time.time() - start < timeout:
status = self.get_status(swap_id)
if status.get("status") in ("finished", "failed", "expired"):
return status
print(f" Status: {status.get('status', 'unknown')}... waiting {poll_interval}s")
time.sleep(poll_interval)
raise TimeoutError(f"Swap {swap_id} didn't complete in {timeout}s")
Actually Using It
Here's the full flow I used to swap ETH to BTC:
client = SimpleSwapClient() # reads from env
# First, check the rate
estimate = client.get_estimate("eth", "btc", 0.05)
print(f"0.05 ETH -> ~{estimate['result']} BTC")
# Create the swap
swap = client.create_swap(
currency_from="eth",
currency_to="btc",
amount=0.05,
address_to="bc1q..." # your BTC address
)
print(f"Send ETH to: {swap['address_from']}")
print(f"Swap ID: {swap['id']}")
# Wait for completion (this blocks)
result = client.wait_for_swap(swap["id"])
print(f"Done! TX: {result.get('tx_from')} -> {result.get('tx_to')}")
Error Handling Gotchas
A few things that broke for me:
Rate limits. If you poll too fast, you get 429s. The 30-second poll interval in my code exists because I originally had 5 seconds and got throttled.
Invalid pairs. Not every coin pair exists. Always check with get_currencies() first. I spent 20 minutes debugging a "pair not found" error on a coin that was listed but had no BTC pair.
Address validation. SimpleSwap doesn't validate your receiving address format. If you put an ETH address for a BTC swap, your crypto is gone. Double-check.
Minimum amounts. Each pair has a minimum swap amount. The get_estimate endpoint returns an error if you're below it, but the error message isn't always clear.
Handling Network Failures
The API occasionally times out or returns 5xx errors. I added retries:
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
def create_retry_session(retries=3, backoff_factor=1.0):
session = requests.Session()
retry = Retry(
total=retries,
backoff_factor=backoff_factor,
status_forcelist=[500, 502, 503, 504, 429],
)
adapter = HTTPAdapter(max_retries=retry)
session.mount("https://", adapter)
session.mount("http://", adapter)
return session
Use this session in the client instead of raw requests and your scripts won't die on transient failures.
Fixed vs Float Rates
SimpleSwap offers two modes: floating rates and fixed rates. Floating means you get whatever the market price is at execution time. Fixed locks in a rate but usually has a slightly worse spread.
For automated scripts, I prefer fixed rates. You know exactly what you're getting. The API endpoint is the same — just add "type": "fixed" to the create payload.
Wrapping Up
SimpleSwap's API is honestly one of the cleaner crypto APIs I've worked with. No auth complexity, reasonable rate limits, and it just works. If you want to see how it compares to other options, check out SimpleSwap and similar services for a deeper comparison.
The full code from this article is about 80 lines of Python. You could have a working crypto swap script in under 30 minutes. Way better than spending an hour uploading passport photos.
Originally published at https://no-kyc-exchanges.vercel.app
Top comments (0)