Jupiter Swap API with Python: A Trader's Guide
Learn how to use Jupiter Swap API with Python to execute Solana DEX trades programmatically, from setup to live swaps with real code examples.
Learn how to use Jupiter Swap API with Python to execute Solana DEX trades programmatically, from setup to live swaps with real code examples.
Jupiter is the leading DEX aggregator on Solana, routing swaps across dozens of liquidity pools to get you the best possible price. If you've ever executed a trade on Phantom wallet or Solflare and wondered how it finds that optimal route — that's Jupiter under the hood. Now, with the Jupiter Swap API, you can plug directly into that same routing engine using Python, bypassing any UI and automating your trades with precision.
This is meaningfully different from trading on centralized exchanges like Binance or Bybit. There's no order book, no account registration, no API key to generate from a dashboard. You interact directly with the Solana blockchain through a publicly available REST API. Your wallet signs the transaction locally, and Jupiter routes it to the best available liquidity — all in one flow.
Jupiter's API is a two-step process: first you fetch a quote, then you build and submit a transaction. The quote endpoint scans all available Solana DEXes — Orca, Raydium, Meteora, Lifinity and others — and returns the optimal swap route. The second endpoint takes that route and returns a serialized transaction ready for your wallet to sign.
Unlike platforms like Coinbase or OKX where the exchange holds custody and executes on your behalf, everything here is non-custodial. Your private key never leaves your machine. The API just provides the routing logic and transaction structure — you do the signing.
You'll need a few libraries: requests for HTTP calls, solders or solana-py for transaction signing and RPC submission, and base64 for deserializing the transaction. Keep your private key in an environment variable — never hardcode it.
pip install requests solders base58 python-dotenv
import os
import requests
import base64
from dotenv import load_dotenv
from solders.keypair import Keypair
from solders.transaction import VersionedTransaction
from solana.rpc.api import Client
load_dotenv()
# Load wallet from env — never hardcode private keys
RAW_KEY = os.environ["SOLANA_PRIVATE_KEY"] # base58 encoded private key
keypair = Keypair.from_base58_string(RAW_KEY)
# Solana RPC endpoint (use a reliable one like Helius or QuickNode in production)
RPC_URL = os.environ.get("SOLANA_RPC_URL", "https://api.mainnet-beta.solana.com")
client = Client(RPC_URL)
JUPITER_QUOTE_URL = "https://quote-api.jup.ag/v6/quote"
JUPITER_SWAP_URL = "https://quote-api.jup.ag/v6/swap"
print(f"Wallet loaded: {keypair.pubkey()}")
Use a dedicated trading wallet with only the funds you need for swaps. Never use your main wallet's private key in a script. Keep the key in .env and add .env to .gitignore before committing anything.
The quote endpoint needs three required parameters: inputMint (the token you're selling), outputMint (the token you're buying), and amount (in the token's smallest unit — lamports for SOL, which has 9 decimals, or base units for USDC, which has 6). Slippage tolerance is optional but you should always set it.
def get_quote(input_mint: str, output_mint: str, amount: int, slippage_bps: int = 50) -> dict:
"""
Fetch best swap route from Jupiter.
slippage_bps: 50 = 0.5%, 100 = 1%
"""
params = {
"inputMint": input_mint,
"outputMint": output_mint,
"amount": amount,
"slippageBps": slippage_bps,
"onlyDirectRoutes": False, # allow multi-hop for better price
"asLegacyTransaction": False
}
resp = requests.get(JUPITER_QUOTE_URL, params=params, timeout=10)
resp.raise_for_status()
quote = resp.json()
in_amount = int(quote["inAmount"]) / 1e6 # USDC has 6 decimals
out_amount = int(quote["outAmount"]) / 1e9 # SOL has 9 decimals
price_impact = float(quote["priceImpactPct"])
print(f"Swap: {in_amount:.2f} USDC → {out_amount:.6f} SOL")
print(f"Price impact: {price_impact:.4f}%")
print(f"Route: {' → '.join([r['swapInfo']['label'] for r in quote['routePlan']])}")
return quote
# Example: swap 10 USDC for SOL
USDC_MINT = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"
SOL_MINT = "So11111111111111111111111111111111111111112"
quote = get_quote(
input_mint=USDC_MINT,
output_mint=SOL_MINT,
amount=10_000_000, # 10 USDC (6 decimals)
slippage_bps=50
)
Notice the price impact field — this is critical for larger trades. On a DEX like Jupiter you're trading against automated market makers, not order books like on Bybit or Gate.io. A 10 USDC swap will have near-zero impact, but 50,000 USDC can move the price meaningfully depending on pool depth. Always check this before executing.
Once you have a quote, pass it to the swap endpoint to get a serialized transaction. You deserialize it, sign it with your keypair, and send it to Solana's RPC. Jupiter returns a versioned transaction by default, which supports address lookup tables for efficiency.
def execute_swap(quote: dict, keypair: Keypair, client: Client) -> str:
"""
Build transaction from quote, sign it, and submit to Solana.
Returns transaction signature.
"""
payload = {
"quoteResponse": quote,
"userPublicKey": str(keypair.pubkey()),
"wrapAndUnwrapSol": True, # auto-wrap SOL to wSOL and back
"dynamicComputeUnitLimit": True, # let Jupiter estimate CU
"prioritizationFeeLamports": "auto" # auto priority fee
}
resp = requests.post(JUPITER_SWAP_URL, json=payload, timeout=15)
resp.raise_for_status()
swap_data = resp.json()
# Deserialize the transaction
raw_tx = base64.b64decode(swap_data["swapTransaction"])
tx = VersionedTransaction.from_bytes(raw_tx)
# Sign with our wallet
tx.sign([keypair])
# Submit to Solana
raw_signed = bytes(tx)
result = client.send_raw_transaction(
raw_signed,
opts={"skip_preflight": False, "preflight_commitment": "confirmed"}
)
sig = str(result.value)
print(f"Transaction submitted: https://solscan.io/tx/{sig}")
return sig
def wait_for_confirmation(client: Client, signature: str, max_retries: int = 30) -> bool:
"""Poll for transaction confirmation."""
import time
for attempt in range(max_retries):
status = client.get_signature_statuses([signature])
info = status.value[0]
if info is not None:
if info.err:
print(f"Transaction failed: {info.err}")
return False
if info.confirmation_status in ("confirmed", "finalized"):
print(f"Confirmed after {attempt + 1} polls")
return True
time.sleep(1)
print("Timed out waiting for confirmation")
return False
# Execute the swap we quoted above
sig = execute_swap(quote, keypair, client)
confirmed = wait_for_confirmation(client, sig)
Set prioritizationFeeLamports to 'auto' during high network congestion — Solana can get crowded and under-priced transactions get dropped. In calm markets, a fixed 5000 lamports is usually fine and cheaper.
Raw API calls will fail eventually — network timeouts, stale quotes, slippage exceeded, insufficient SOL for fees. A production bot needs to handle all of these gracefully. Here's a pattern that wraps the full flow with retry logic and meaningful error messages.
import time
import logging
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
log = logging.getLogger("jup_bot")
def safe_swap(input_mint: str, output_mint: str, amount: int,
slippage_bps: int = 50, max_retries: int = 3) -> str | None:
"""Full swap flow with retry and error handling."""
for attempt in range(1, max_retries + 1):
try:
log.info(f"Attempt {attempt}: fetching quote...")
quote = get_quote(input_mint, output_mint, amount, slippage_bps)
# Reject if price impact is too high
impact = float(quote["priceImpactPct"])
if impact > 1.0:
log.warning(f"Price impact {impact:.2f}% exceeds threshold — aborting")
return None
log.info("Executing swap...")
sig = execute_swap(quote, keypair, client)
confirmed = wait_for_confirmation(client, sig)
if confirmed:
log.info(f"Swap completed: {sig}")
return sig
else:
log.warning("Transaction not confirmed, retrying...")
except requests.exceptions.HTTPError as e:
log.error(f"HTTP error: {e.response.status_code} — {e.response.text[:200]}")
except requests.exceptions.Timeout:
log.error("Request timed out")
except Exception as e:
log.error(f"Unexpected error: {e}")
if attempt < max_retries:
time.sleep(2 ** attempt) # exponential backoff
log.error("All retry attempts failed")
return None
# Example usage with guard
if __name__ == "__main__":
result = safe_swap(
input_mint=USDC_MINT,
output_mint=SOL_MINT,
amount=10_000_000,
slippage_bps=50
)
if result:
print(f"Success: {result}")
else:
print("Swap failed — check logs")
This same pattern can be extended into a signal-driven bot. Platforms like VoiceOfChain emit real-time trading signals for Solana tokens — when a signal fires, your bot fetches a fresh quote and executes the swap automatically. The key is that quotes are only valid for about 30 seconds, so never cache them; always fetch a new one right before you intend to submit.
Trading on Jupiter is fundamentally different from operating on centralized exchanges like KuCoin or Bitget. There's no cancel button once a transaction lands on-chain. Slippage settings are your only protection against bad fills. Here's what matters before you run real money through a bot:
| Factor | Centralized (Binance, Bybit) | Jupiter DEX |
|---|---|---|
| Custody | Exchange holds funds | Your wallet, non-custodial |
| Order types | Limit, market, stop-loss | Market only (slippage = your limit) |
| Cancellation | Cancel anytime before fill | Irreversible once submitted |
| Latency | Milliseconds via WebSocket | Solana block time ~400ms |
| Fees | Maker/taker 0.1% | DEX fee + priority fee + routing |
| Liquidity | Deep order books | AMM pools — depth varies by token |
The Jupiter Swap API with Python gives you direct, non-custodial access to Solana's best DEX liquidity in under 100 lines of code. The flow is clean: fetch a quote, check price impact and slippage, build the transaction, sign locally, submit. No exchange accounts, no KYC, no withdrawal limits like you'd face on OKX or Coinbase.
The real power comes when you chain this with a signal source. VoiceOfChain provides real-time on-chain trading signals that can trigger your swap logic automatically — catching momentum moves before they're reflected on centralized exchanges. Your bot becomes a pipeline: signal fires → quote fetched → slippage checked → transaction submitted, all within seconds of the on-chain event.
Start small, verify every step with print statements and Solscan transaction links, and scale only after you've confirmed the full flow works reliably. The code above is production-ready as a foundation — extend it with your own position sizing, signal filters, and risk rules.