> ## Documentation Index
> Fetch the complete documentation index at: https://developers.jup.ag/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# How to Swap Tokens on Solana

> Add token swaps to your Solana app with Jupiter Ultra. Two API calls, no RPC needed, best execution. Powers Phantom, Backpack, and most Solana apps.

> [!WARNING]
>
> Ultra V1 is deprecated. Use [Swap API V2](https://developers.jup.ag/docs/swap/index.md) instead - it unifies Ultra and Metis into a single API with `/order` (managed execution) and `/build` (custom transactions). See the [Migration Guide](https://developers.jup.ag/docs/swap/migration/ultra-to-order.md).

## TL;DR

Swap any token on Solana in two API calls. Get a quote and unsigned transaction, sign it, submit it. No RPC node, no routing logic, no slippage math. Jupiter handles routing across multiple DEXs and 20+ market makers, MEV protection, and transaction landing.

**Base URL:** `https://api.jup.ag/ultra/v1`

***

## Prerequisites

1. Get an API key at [Portal](https://developers.jup.ag/portal) (free)
2. All requests need the `x-api-key` header
3. Install `@solana/web3.js` for transaction signing

***

## Quick start

```bash theme={null}
curl -X GET "https://api.jup.ag/ultra/v1/order?inputMint=So11111111111111111111111111111111111111112&outputMint=EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v&amount=100000000&taker=YOUR_WALLET_ADDRESS" \
  -H "x-api-key: YOUR_API_KEY"
```

You get back a `transaction` (base64 unsigned) and a `requestId`. Sign the transaction, then hit execute:

```bash theme={null}
curl -X POST "https://api.jup.ag/ultra/v1/execute" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "signedTransaction": "BASE64_SIGNED_TX",
    "requestId": "REQUEST_ID_FROM_ORDER"
  }'
```

Done. Your swap lands in 50-400ms via [Jupiter Beam](https://developers.jup.ag/blog/ultra-v3#sub-second-transaction-landing).

***

## API reference

**Base URL:** `https://api.jup.ag/ultra/v1`

| Endpoint | Description |
| - | - |
| `GET /order` | Get a swap quote and unsigned transaction |
| `POST /execute` | Submit signed transaction for execution |
| `GET /holdings/{address}` | Get wallet token balances |
| `GET /shield?mints={mints}` | Get token security warnings |
| `GET /search?query={query}` | Search tokens by name, symbol, or mint |

Full schema at [API Reference](https://developers.jup.ag/docs/api-reference/ultra).

***

## The swap flow

Three steps: get order, sign, execute.

### Step 1: Get an order

`GET /order` takes the token pair, amount, and the taker's wallet. It returns a quote with an unsigned transaction ready to sign.

```bash curl theme={null}
curl -X GET "https://api.jup.ag/ultra/v1/order?inputMint=So11111111111111111111111111111111111111112&outputMint=EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v&amount=100000000&taker=YOUR_WALLET_ADDRESS" \
  -H "x-api-key: YOUR_API_KEY"
```

```javascript JavaScript theme={null}
const orderResponse = await (
  await fetch(
    'https://api.jup.ag/ultra/v1/order?' +
      'inputMint=So11111111111111111111111111111111111111112' +
      '&outputMint=EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v' +
      '&amount=100000000' +
      '&taker=YOUR_WALLET_ADDRESS',
    {
      headers: { 'x-api-key': 'YOUR_API_KEY' },
    }
  )
).json();

console.log(`Swap ${orderResponse.inAmount} → ${orderResponse.outAmount}`);
console.log(`Router: ${orderResponse.router}, Gasless: ${orderResponse.gasless}`);
```

```python Python theme={null}
import requests

response = requests.get(
    'https://api.jup.ag/ultra/v1/order',
    params={
        'inputMint': 'So11111111111111111111111111111111111111112',
        'outputMint': 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
        'amount': '100000000',
        'taker': 'YOUR_WALLET_ADDRESS',
    },
    headers={'x-api-key': 'YOUR_API_KEY'},
)
response.raise_for_status()
order = response.json()
print(f"Swap {order['inAmount']} → {order['outAmount']}")
```

**Parameters:**

| Parameter | Required | Description |
| - | - | - |
| `inputMint` | Yes | Input token mint address |
| `outputMint` | Yes | Output token mint address |
| `amount` | Yes | Amount in native token units (before decimals) |
| `taker` | No | User's wallet address. Required to get a signable transaction. Omit to get a quote only (useful for showing price before wallet connect). |
| `referralAccount` | No | Referral account for [integrator fees](https://developers.jup.ag/docs/ultra/add-fees-to-ultra) |
| `referralFee` | No | Referral fee in basis points (50-255) |
| `excludeRouters` | No | [Manual mode](https://developers.jup.ag/docs/ultra/gasless) only. Comma-separated routers to exclude: `metis`, `jupiterz`, `dflow`, `okx` |
| `excludeDexes` | No | [Manual mode](https://developers.jup.ag/docs/ultra/gasless) only. Comma-separated DEXs to exclude (applies to Metis router only). [Full list of DEXs](https://developers.jup.ag/docs/api-reference/swap/v1/program-id-to-label). |

**What you get back:**

| Field | What it means |
| - | - |
| `transaction` | Base64 unsigned transaction. Sign this. `null` if you didn't pass `taker`. |
| `requestId` | Pass this to `/execute`. It's how Ultra tracks your order. |
| `outAmount` | Expected output in native token units |
| `router` | Which router won the auction: `metis`, `jupiterz`, `dflow`, `okx` |
| `gasless` | `true` if this swap is gasless |
| `slippageBps` | Slippage tolerance set by RTSE (you don't need to configure this) |
| `feeBps` | Total fee in basis points |
| `feeMint` | Which token the fee is taken in |
| `priceImpact` | Price impact as a percentage |
| `errorCode` | Present when simulation fails (1 = insufficient funds, 2 = need SOL for gas, 3 = below gasless minimum) |

### Step 2: Sign the transaction

Deserialise the base64 transaction, sign it, serialise it back:

```javascript theme={null}
import { VersionedTransaction, Keypair } from '@solana/web3.js';
import bs58 from 'bs58';

// Load wallet (use env vars in production, not hardcoded keys)
const wallet = Keypair.fromSecretKey(bs58.decode(process.env.PRIVATE_KEY));

// Deserialise and sign
const transaction = VersionedTransaction.deserialize(
  Buffer.from(orderResponse.transaction, 'base64')
);
transaction.sign([wallet]);

const signedTransaction = Buffer.from(transaction.serialize()).toString('base64');
```

Don't modify the transaction. Ultra transactions are meant to be signed and submitted as-is.

### Step 3: Execute

Post the signed transaction and `requestId` to `/execute`. Jupiter broadcasts it via Beam and polls for the result. You get back the final status.

```bash curl theme={null}
curl -X POST "https://api.jup.ag/ultra/v1/execute" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "signedTransaction": "BASE64_SIGNED_TX",
    "requestId": "REQUEST_ID_FROM_ORDER"
  }'
```

```javascript JavaScript theme={null}
const executeResponse = await (
  await fetch('https://api.jup.ag/ultra/v1/execute', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'x-api-key': 'YOUR_API_KEY',
    },
    body: JSON.stringify({
      signedTransaction,
      requestId: orderResponse.requestId,
    }),
  })
).json();

if (executeResponse.status === 'Success') {
  console.log(`Swapped! https://solscan.io/tx/${executeResponse.signature}`);
  console.log(`Got ${executeResponse.outputAmountResult} tokens`);
} else {
  console.error(`Failed (code ${executeResponse.code}): ${executeResponse.error}`);
}
```

```python Python theme={null}
import requests

execute_response = requests.post(
    'https://api.jup.ag/ultra/v1/execute',
    headers={
        'x-api-key': 'YOUR_API_KEY',
        'Content-Type': 'application/json',
    },
    json={
        'signedTransaction': signed_transaction,
        'requestId': order['requestId'],
    },
).json()

if execute_response.get('status') == 'Success':
    print(f"Swapped! https://solscan.io/tx/{execute_response['signature']}")
else:
    print(f"Failed: {execute_response.get('error')}")
```

> [!TIP]
>
> **Connection dropped?** Re-submit the same `signedTransaction` and `requestId` to `/execute` for up to 2 minutes. The transaction won't double-execute because it has the same signature. This is your built-in polling mechanism.

***

## Full working example

End-to-end: swap 1 SOL to USDC, from scratch.

```javascript expandable theme={null}
import { VersionedTransaction, Keypair } from '@solana/web3.js';
import bs58 from 'bs58';

const API_KEY = 'YOUR_API_KEY';
const wallet = Keypair.fromSecretKey(bs58.decode(process.env.PRIVATE_KEY));

// 1. Get order
const orderResponse = await (
  await fetch(
    'https://api.jup.ag/ultra/v1/order?' +
      'inputMint=So11111111111111111111111111111111111111112' +
      '&outputMint=EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v' +
      '&amount=1000000000' +
      `&taker=${wallet.publicKey.toBase58()}`,
    { headers: { 'x-api-key': API_KEY } }
  )
).json();

if (!orderResponse.transaction) {
  throw new Error(orderResponse.errorMessage || orderResponse.error || 'No transaction returned');
}

console.log(`Quote: ${orderResponse.inAmount} → ${orderResponse.outAmount}`);
console.log(`Router: ${orderResponse.router} | Gasless: ${orderResponse.gasless} | Fee: ${orderResponse.feeBps} bps`);

// 2. Sign
const transaction = VersionedTransaction.deserialize(
  Buffer.from(orderResponse.transaction, 'base64')
);
transaction.sign([wallet]);
const signedTransaction = Buffer.from(transaction.serialize()).toString('base64');

// 3. Execute
const executeResponse = await (
  await fetch('https://api.jup.ag/ultra/v1/execute', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'x-api-key': API_KEY,
    },
    body: JSON.stringify({
      signedTransaction,
      requestId: orderResponse.requestId,
    }),
  })
).json();

if (executeResponse.status === 'Success') {
  console.log(`Done: https://solscan.io/tx/${executeResponse.signature}`);
  console.log(`In: ${executeResponse.inputAmountResult} | Out: ${executeResponse.outputAmountResult}`);
} else {
  console.error(`Failed (${executeResponse.code}): ${executeResponse.error}`);
}
```

That's the entire integration.

***

## Response format

### Order response

```typescript expandable theme={null}
interface OrderResponse {
  mode: string;
  inputMint: string;
  outputMint: string;
  inAmount: string;                // Input amount in native units
  outAmount: string;               // Expected output in native units
  otherAmountThreshold: string;    // Minimum output after slippage
  swapMode: string;                // "ExactIn"
  slippageBps: number;             // Set automatically by RTSE
  routePlan: RoutePlan[];
  router: 'metis' | 'jupiterz' | 'dflow' | 'okx';
  transaction: string | null;      // Base64 unsigned tx (null if no taker)
  requestId: string;               // Pass to /execute
  gasless: boolean;
  feeBps: number;                  // Total fee in bps
  feeMint: string;                 // Token mint fees are collected in
  inUsdValue: number;
  outUsdValue: number;
  priceImpact: number;
  signatureFeeLamports: number;
  prioritizationFeeLamports: number;
  rentFeeLamports: number;
  totalTime: number;               // API response time in ms
  // JupiterZ only
  quoteId?: string;
  maker?: string;
  expireAt?: string;
  // Present when simulation fails but quote exists
  errorCode?: number;
  errorMessage?: string;
}

interface RoutePlan {
  swapInfo: {
    ammKey: string;
    label: string;       // e.g. "MeteoraDLMM", "JupiterZ", "Raydium"
    inputMint: string;
    outputMint: string;
    inAmount: string;
    outAmount: string;
  };
  percent: number;       // Route split percentage
  bps: number;           // Route split in basis points
}
```

### Execute response

```typescript expandable theme={null}
interface ExecuteResponse {
  status: 'Success' | 'Failed';
  signature: string;
  slot: string;
  code: number;                    // 0 = success, negative = error
  error?: string;
  inputAmountResult?: string;      // Actual input consumed
  outputAmountResult?: string;     // Actual output received
  swapEvents?: SwapEvent[];        // Breakdown of individual swaps in the route
}

interface SwapEvent {
  inputMint: string;
  inputAmount: string;
  outputMint: string;
  outputAmount: string;
}
```

***

## Error handling

### Order errors

The order can return a quote but fail simulation. When that happens, you'll see `errorCode` and `errorMessage` alongside the quote data, but `transaction` will be empty.

| `errorCode` | What happened | What to show the user |
| - | - | - |
| 1 | Insufficient funds | "Not enough tokens to swap" |
| 2 | Not enough SOL for gas | "Top up X SOL for gas fees" |
| 3 | Below gasless minimum | "Minimum \~\$10 swap for gasless" |

Show the quote even when there's an error. Users want to see the price. Just disable the swap button and explain why.

### Execute errors

| Code | What happened | What to do |
| - | - | - |
| 0 | Success | Show the Solscan link |
| -1 | Order not found or expired | Get a new order |
| -2 | Invalid signed transaction | Check your signing logic |
| -3 | Invalid message bytes | Don't modify the transaction |
| -4 | Missing request ID | Include `requestId` from the order response |
| -5 | Missing signed transaction | Include `signedTransaction` in the request body |
| -1000 | Failed to land on network | Retry with a fresh order |
| -1005 | Transaction expired | Get a new order and try again |
| -1006 | Timed out | Get a new order and try again |
| 6001 | Slippage exceeded | Market moved. Retry with a new order. |

For the full list of error codes including aggregator and RFQ-specific codes, see [Response docs](https://developers.jup.ag/docs/ultra/response).

***

## Gasless swaps

Ultra handles gasless automatically. You don't configure anything.

**Ultra Gasless Support (Metis).** When the taker has less than 0.01 SOL and the trade is at least \~\$10, Jupiter covers everything: network fee, priority fee, token account rent, and other accounts rent (e.g. DEX-specific accounts). The cost gets deducted from the swap output (you'll see it in `feeBps`).

**JupiterZ (RFQ).** When a market maker provides the route, the market maker pays network and priority fees. If the taker's output token account does not exist, Jupiter's gas wallet pays the rent to create it.

Check `gasless: true` in the order response to know if it kicked in. For the full breakdown of scenarios, see [Gasless Support](https://developers.jup.ag/docs/ultra/gasless).

***

## Integrator fees

You can earn fees on swaps through your integration. Ultra handles collection.

1. Create a referral account using the [@jup-ag/referral-sdk](https://www.npmjs.com/package/@jup-ag/referral-sdk) or the [Referral Dashboard](https://referral.jup.ag/)
2. Create referral token accounts for the mints you expect to collect fees in (at minimum: SOL, USDC, USDT)
3. Pass `referralAccount` and `referralFee` to `/order`

```javascript theme={null}
const orderResponse = await (
  await fetch(
    'https://api.jup.ag/ultra/v1/order?' +
      'inputMint=So11111111111111111111111111111111111111112' +
      '&outputMint=EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v' +
      '&amount=100000000' +
      '&taker=YOUR_WALLET_ADDRESS' +
      '&referralAccount=YOUR_REFERRAL_ACCOUNT' +
      '&referralFee=50',  // 50 bps = 0.5%
    { headers: { 'x-api-key': 'YOUR_API_KEY' } }
  )
).json();

// Verify your fee was applied
console.log(`Fee: ${orderResponse.feeBps} bps in ${orderResponse.feeMint}`);
```

Fees range from 50 to 255 bps. Jupiter takes 20% of your integrator fee (no additional Ultra base fee on top). Ultra decides which token to collect fees in based on a [priority list](https://developers.jup.ag/docs/ultra/fees#priority-list). If the referral token account for that mint isn't initialised, the swap still goes through, but without your fee. So always check that `feeBps` matches what you set.

Full setup walkthrough: [Add Integrator Fees](https://developers.jup.ag/docs/ultra/add-fees-to-ultra).

***

## Fees

Ultra charges 0-10 bps on most swaps, 50 bps for new tokens. Lower than the industry average by 8-10x.

| Token pair | Fee |
| - | - |
| SOL/Stable → JUP/JLP/jupSOL | 0 bps |
| Stable ↔ Stable, LST ↔ LST | 0 bps |
| SOL ↔ Stable | 2 bps |
| LST ↔ Stable | 5 bps |
| Everything else | 10 bps |
| New tokens (\< 24h old) | 50 bps |

Full breakdown: [Fees](https://developers.jup.ag/docs/ultra/fees).

***

## Common questions

> **Do I need an RPC if I integrate Ultra?**
>
> Nope. Ultra runs on Jupiter's own validator stake and dedicated infra so you don't have to. You just sign the transaction.

> **Can I modify the transaction before signing?**
>
> No. Ultra transactions must be submitted as-is. This is what lets Jupiter provide end-to-end observability, customer support, and continuous optimisation across your swaps.

> **How fast is it?**
>
> `/order` averages \~300ms (P50). Transaction landing via Jupiter Beam is 50-400ms. End-to-end including polling, 95% of swaps complete under 2 seconds.

> **What's the rate limit?**
>
> Dynamic. Base is 50 requests per 10-second sliding window, and it scales up with your swap volume. \$1M in daily volume gets you \~165 req/10s. No paid tiers. Just get a free API key at [Portal](https://developers.jup.ag/portal). Details: [Rate Limits](https://developers.jup.ag/docs/ultra/rate-limit).

> **Can I get a quote without connecting a wallet?**
>
> Yes. Omit the `taker` parameter. You'll get the full quote (pricing, route plan, fees) but no `transaction` field. Good for showing price previews before wallet connect.

> **My connection dropped during /execute. Did my swap go through?**
>
> Re-submit the same `signedTransaction` and `requestId` to `/execute`. You can do this for up to 2 minutes. The transaction has the same signature so it can't double-execute. This is your built-in retry and polling mechanism.

***

## Next steps

* **[Ultra API Reference](https://developers.jup.ag/docs/api-reference/ultra).** Full endpoint schema and playground.
* **[Gasless Support](https://developers.jup.ag/docs/ultra/gasless).** Deep dive into gasless mechanisms.
* **[Add Integrator Fees](https://developers.jup.ag/docs/ultra/add-fees-to-ultra).** Set up referral accounts and earn on swaps.
* **[Embed Swap Widget](https://developers.jup.ag/docs/guides/how-to-embed-a-swap-widget.md).** Want a drop-in UI instead of an API? Use Plugin.
* **[Ultra V3 Blog](https://developers.jup.ag/blog/ultra-v3).** What makes Ultra the best execution engine on Solana.
* **[Portal](https://developers.jup.ag/portal).** Get your API key.

Built something? Tag [@JupDevRel](https://x.com/JupDevRel) or [@0xanmol](https://x.com/0xanmol).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.