Authentication
All requests require an API key:
curl -X POST https://trade-api.gateway.uniswap.org/v1/quote \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-H "accept: application/json" \
-d '{"tokenIn":"0x...","tokenOut":"0x...","amount":"1000000",...}'
Basic Quote Request
const response = await fetch('https://trade-api.gateway.uniswap.org/v1/quote', {
method: 'POST',
headers: {
'x-api-key': 'YOUR_API_KEY',
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify({
tokenIn: '0x0000000000000000000000000000000000000000', // ETH
tokenOut: '0xdAC17F958D2ee523a2206206994597C13D831ec7', // USDT
tokenInChainId: 1,
tokenOutChainId: 1,
type: 'EXACT_INPUT',
amount: '1000000000000000000', // 1 ETH in wei
swapper: '0x...', // User's wallet address
slippageTolerance: 0.5 // 0.5%
})
});
const quote = await response.json();
AI builders can consume the full Open API Specification (OAS) at https://trade-api.gateway.uniswap.org/v1/api.json.
Full code examples for completing a basic swap workflow are available in Swapping Code Examples.
Architecture
Client-Side Responsibilities
The Trading API is a quote and transaction building service augmented with useful validation checks. The API handles:
- Route Finding: Identifying the most efficient route using Uniswap AMM and UniswapX liquidity
- Transaction Creation: Generation of validated transaction calldata, ready for signature by the user’s wallet
- Balance Checks: Verify token balances before requesting quotes (checked during simulation)
- Allowance Management: Check and request token approvals (via Permit2)
- Gas Estimation: Suggested gas usage
- Transaction Monitoring: Track confirmations (see Swap Status and UniswapX Order Status)
Your application handles:
- Nonce Management: Track transaction nonces for the user’s wallet
- Transaction Broadcasting: Sign and submit transactions via your RPC provider
- Gas Payment: Actual gas usage
- Transaction Error Handling: Handle reverts
Required Infrastructure
Your integration must include:
- RPC Provider: Connection to blockchain nodes (ex. Infura, Alchemy, or self-hosted)
- Web3 Library: ethers.js, viem, or web3.js for transaction signing
- Wallet Integration: Uniswap Wallet, MetaMask, or similar for user signing
See the Build Prerequisites for more details.
Data Flow
User Request
|
Your Application
|-- (Optionally) Check balances (via your RPC)
|-- Checks wallet approval for Permit2 (Trading API)
|-- Get user signature for Permit2 approval (Wallet)
|-- Request quote (Trading API)
|-- Approve Permit2 spending (Wallet)
|-- Build transaction (Trading API)
|-- Get user signature for swap (Wallet)
|-- Manage nonce (your tracking)
+-- Broadcast transaction (via your RPC)
|
Blockchain
Available Endpoints
Core Endpoints
| Endpoint | Description |
|---|
| POST /check_approval | Check if token approval is required |
| POST /quote | Generate a quote for a token swap |
| POST /swap | Convert an AMM quote into an unsigned transaction |
| POST /order | Create a UniswapX order (gasless) |
| GET /swaps | Check status of an AMM transaction |
| GET /orders | Check status of a UniswapX order |
Special Case Endpoints
| Endpoint | Description |
|---|
| POST /swap_5792 | Generate batch transactions for EIP-5792 |
| POST /swap_7702 | Generate transaction with EIP-7702 delegation |
Routing Types
The API returns different quote types based on the optimal routing strategy. The quote type is provided in the top-level routing field of the response from the /quote endpoint:
| Type | Description |
|---|
| CLASSIC | Standard AMM swap through Uniswap pools |
| DUTCH_V2 | UniswapX Dutch auction V2 |
| DUTCH_V3 | UniswapX Dutch auction V3 |
| PRIORITY | UniswapX MEV-protected priority order |
| WRAP | ETH to WETH wrap |
| UNWRAP | WETH to ETH unwrap |
| BRIDGE | Cross-chain bridge |
After receiving a /quote response, check the routing field:
- If routing is “DUTCH_V2”, “DUTCH_V3”, or “PRIORITY” → call POST /order
- If routing is “CLASSIC”, “WRAP”, “UNWRAP”, or “BRIDGE” → call POST /swap
Schema Reference
TransactionRequest
The TransactionRequest object returned by /check_approval and /swap contains all fields needed to broadcast a transaction:
interface TransactionRequest {
to: string; // Contract address to call
from: string; // User's wallet address
data: string; // Encoded function call (hex string)
value: string; // Native token amount (wei)
chainId: number;
gasLimit?: string;
maxFeePerGas?: string;
maxPriorityFeePerGas?: string;
gasPrice?: string; // Legacy gas price
}
Critical Field: data
The data field contains the encoded contract call and must always be present in swap transactions.
Validation Requirements
- Never Empty: The
data field must be a non-empty hex string (not "" or "0x").
- Never Modify: The API endpoints return pre-validated and correct data. Modifying its value may cause funds to be lost or on-chain transaction reverts.
- Always Validate: Check
data exists before broadcasting.
PermitSingleData
For Permit2-based approvals:
interface PermitSingleData {
domain: TypedDataDomain;
values: PermitSingle;
types: Record<string, TypedDataField[]>;
}
interface PermitSingle {
details: {
token: string;
amount: string;
expiration: string;
nonce: string;
};
spender: string;
sigDeadline: string;
}
Permit2 Flow
Permit2 is a token approval system which uses an off-chain (gasless) EIP-712 signed message to allow an on-chain contract to spend tokens from a wallet within pre-defined bounds. The API will return a permitData in the /quote response when one must be signed in order to perform the swap. The API will return "permitData": null in the /quote response when no permit needs to be signed.
Implementation Steps
Get Quote with Permit Data
const quoteResponse = await fetch('/quote', {
method: 'POST',
headers: {
'x-api-key': API_KEY,
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify({
tokenIn: '0x...',
tokenOut: '0x...',
amount: '1000000',
type: 'EXACT_INPUT',
swapper: '0x...',
tokenInChainId: 1,
tokenOutChainId: 1,
permitAmount: 'FULL', // or 'EXACT'
slippageTolerance: 0.5
})
});
const {quote, permitData, routing} = await quoteResponse.json();
Sign the Permit
let signature: string | undefined;
if (permitData) {
signature = await wallet._signTypedData(
permitData.domain,
permitData.types,
permitData.values
);
}
Submit to /swap
// CORRECT - Both fields provided
const swapRequest = {
quote: quote,
signature: signature,
permitData: permitData
};
// CORRECT - No permit (both fields omitted), ONLY when no permitData is returned in the /quote response
const swapRequest = {
quote: quote
// signature and permitData omitted entirely
};
// WRONG - Missing permitData
const swapRequest = {
quote: quote,
signature: signature // Will fail validation
};
// WRONG - Missing signature
const swapRequest = {
quote: quote,
permitData: permitData // Will fail validation
};
Broadcast Transaction
const {swap} = await swapResponse.json();
validateTransaction(swap);
const signedTx = await wallet.signTransaction(swap);
const txReceipt = await provider.sendTransaction(signedTx);
Error Handling
HTTP Status Codes
| Code | Meaning |
|---|
| 200 | Request succeeded |
| 400 | Invalid request (validation error) |
| 401 | Invalid API key |
| 429 | Rate limit exceeded |
| 500 | API error (retry with backoff) |
| 503 | Temporary unavailability (retry) |
interface ErrorResponse {
error: string;
message: string;
details?: Record<string, any>;
}
Common Errors
No Quotes Available
Cause: No route found for the requested swap
Solutions:
- Verify token addresses are correct and on the specified chain
- Check chains are available for selected protocol (note limited chain support for UniswapX)
- Try different protocols or routing preferences
- Reduce trade size
Validation Error
Cause: Invalid request parameters
Solutions:
- Check all required fields are present
- Verify addresses are valid checksummed addresses
- Ensure amount is in correct units (wei, not ether)
- Validate chain IDs are supported
Authentication Error
Cause: Invalid or missing authentication header
Solutions:
- Include the authentication header with a valid API key
- Check that API key was copied correctly (must match exact capitalization)
Transaction Revert Scenarios
If a transaction reverts on-chain, check the on-chain revert error. Additionally check:
- Check
data field: Verify it’s not empty
- Verify token balance: User has sufficient tokens at broadcast time
- Check allowance: Token approval is sufficient
- Check slippage: Price moved beyond slippage tolerance
- Check deadline: Quote expired before broadcast
- Nonce collision: Another transaction used the same nonce
Recommended Client-Side Checks:
async function validateBeforeBroadcast(
tx: TransactionRequest,
provider: Provider,
token: string,
amount: string
): Promise<void> {
// 1. Validate transaction structure
if (!tx.data || tx.data === '' || tx.data === '0x') {
throw new Error('Invalid transaction: empty data field');
}
// 2. Check native balance
const balance = await provider.getBalance(tx.from);
if (balance.lt(tx.value)) {
throw new Error('Insufficient native token balance');
}
// 3. Check ERC-20 balance (if applicable)
if (token !== NATIVE_TOKEN_ADDRESS) {
const tokenContract = new Contract(token, ERC20_ABI, provider);
const tokenBalance = await tokenContract.balanceOf(tx.from);
if (tokenBalance.lt(amount)) {
throw new Error('Insufficient token balance');
}
}
// 4. Simulate transaction (optional but recommended)
try {
await provider.call(tx);
} catch (error) {
throw new Error(`Transaction simulation failed: ${error.message}`);
}
}
Best Practices
Quote Freshness
Quotes are time-sensitive due to price volatility:
- Refresh quotes if more than 30 seconds old before broadcasting
- Use
deadline parameter to prevent execution of stale quotes
- Monitor price impact and warn users of significant changes
const QUOTE_EXPIRY_MS = 30000; // 30 seconds
const quoteTimestamp = Date.now();
// ... user reviews and signs ...
if (Date.now() - quoteTimestamp > QUOTE_EXPIRY_MS) {
quote = await fetchQuote(params); // Fetch fresh quote
}
Transaction Validation
Always validate transaction payloads before broadcasting:
function validateSwapTransaction(tx: TransactionRequest): void {
if (!tx.data || tx.data === '' || tx.data === '0x') {
throw new Error('Transaction data is empty');
}
if (!tx.to || !isAddress(tx.to)) {
throw new Error('Invalid recipient address');
}
if (!tx.from || !isAddress(tx.from)) {
throw new Error('Invalid sender address');
}
if (tx.maxFeePerGas && tx.gasPrice) {
throw new Error('Cannot set both maxFeePerGas and gasPrice');
}
if (tx.value && BigNumber.from(tx.value).lt(0)) {
throw new Error('Invalid transaction value');
}
}
Gas Management
The API provides gas estimates, but clients should:
- Update gas prices: Use
refreshGasPrice: true for fresh estimates or check your own RPC
- Handle gas spikes: Warn users when gas is unusually high
- EIP-1559 vs Legacy: Use EIP-1559 on supported chains
Slippage Configuration
Balance protection vs execution success:
| Setting | Slippage | Use Case |
|---|
| Conservative | 0.05-0.5% | Stable pairs, low volatility |
| Moderate | 0.5-1% | Most swaps |
| Aggressive | 1-5% | Large trades, volatile markets, low liquidity |
Error Recovery
If a quote fails, read the error message to address the specific error cause. If the quote fails due to no quotes available, implement retry logic with exponential backoff:
async function fetchQuoteWithRetry(
params: QuoteRequest,
maxRetries = 3
): Promise<QuoteResponse> {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
const response = await fetch('/quote', {
method: 'POST',
headers: {
'x-api-key': API_KEY,
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify(params)
});
if (!response.ok) {
if (response.status === 429) {
await sleep(Math.pow(2, attempt) * 1000);
continue;
}
throw new Error(`API error: ${response.status}`);
}
return await response.json();
} catch (error) {
if (attempt === maxRetries - 1) throw error;
await sleep(Math.pow(2, attempt) * 1000);
}
}
throw new Error('Max retries exceeded');
}
Monitoring & Logging
We highly recommend that you track key metrics for production reliability:
// Example metrics to track
interface SwapMetrics {
quoteLatency: number;
swapLatency: number;
revertRate: number;
}
function logSwapAttempt(
params: QuoteRequest,
quoteResponse: QuoteResponse,
txHash?: string,
error?: Error
): void {
const metrics = {
timestamp: Date.now(),
chainId: params.tokenInChainId,
tokenIn: params.tokenIn,
tokenOut: params.tokenOut,
amount: params.amount,
routing: quoteResponse.routing,
txHash,
success: !!txHash && !error,
error: error?.message
};
analytics.track('swap_attempt', metrics);
}
Troubleshooting
Quote Issues
Problem: No quotes returned
- Verify token addresses are valid for the specified chains
- Confirm liquidity exists for the trading pair
- Ensure the amount is within reasonable bounds
- Verify chain IDs are supported
- Check that specified protocols (if any) are available on the target chain
Problem: Quote price seems incorrect
- Verify the amount is in the correct units (wei, not ether)
- Confirm token decimals match the on-chain contract
- Review slippage tolerance for appropriateness
- Evaluate price impact relative to trade size
Transaction Issues
Problem: Transaction reverts on-chain
- Verify the
data field is not empty (see Transaction Validation)
- Confirm the token balance is sufficient at broadcast time
- Ensure token approval is in place (if not using Permit2)
- Check that the quote has not expired
- Review slippage tolerance and adjust (tighten or loosen) as needed
- Verify gas limit is sufficient
- Confirm nonce has no collisions with pending transactions
Problem: Transaction takes too long to confirm
- Use
refreshGasPrice: true to get competitive gas pricing or check your own RPC
- Check network congestion on the target chain
- Verify the transaction nonce is not blocked by a pending transaction
API Issues
Problem: 429 Rate Limit Exceeded
Solution: Implement exponential backoff and request caching:
const cache = new Map<string, {data: any, timestamp: number}>();
async function fetchWithCache(
url: string,
params: any,
cacheDuration = 30000
): Promise<any> {
const cacheKey = `${url}:${JSON.stringify(params)}`;
const cached = cache.get(cacheKey);
if (cached && Date.now() - cached.timestamp < cacheDuration) {
return cached.data;
}
const data = await fetchWithRetry(url, params);
cache.set(cacheKey, {data, timestamp: Date.now()});
return data;
}
Problem: Inconsistent responses
- Review slippage tolerance and adjust (tighten or loosen) as needed
- Confirm liquidity exists for the trading pair
Integration Issues
Problem: Permit2 validation errors
Solution: Check that permit signature is built correctly.
let signature
if (permitData) {
signature = await signer._signTypedData(permitData.domain, permitData.types, permitData.values)
}
EIP-712The API does not return every field that some libraries expect to generate an EIP-712 signature. You may need to add EIP712Domain or other fields in the function call to the signature library you are using.
Limitations
- UniswapX v2: Mainnet, Arbitrum, Base
- UniswapX v3: Arbitrum only
- Mainnet UniswapX minimum: 300 USDC equivalent
- L2 UniswapX minimum: 1000 USDC equivalent
- Native token swaps: UniswapX for native token input only when
x-erc20eth-enabled header is set to true
Support
For support, reach out via the help link in the Uniswap Developer Portal.
When reporting issues, include:
- Request ID from API response
- Full request/response payloads (sanitize sensitive data)
- Chain ID and transaction hash (if applicable)
- Timestamp of the request