Skip to main content
Creating an order in V2 is a two-step process: craft and sign a deposit transaction, then submit it alongside your order parameters.
For a working reference of the full order creation flow, see how jup.ag handles it. The frontend implements the same API with validation, error handling, and UX patterns you can use as a guide.

Prerequisites

Signing deposit transactions requires @solana/web3.js:
npm install @solana/web3.js

End-to-End Flow

1. Get vault          →  GET /v2/vault (or /v2/vault/register on first use)
2. Craft deposit      →  POST /v2/deposit/craft
3. Sign deposit tx    →  (client-side)
4. Create order       →  POST /v2/orders/price (with signed tx)

Step 1: Get Your Vault

Retrieve your vault, or register one if this is your first time. The vault is a Privy-managed custodial account that holds deposits for your orders.
let vaultResponse = await fetch('https://api.jup.ag/trigger/v2/vault', {
  headers: {
    'x-api-key': 'your-api-key',
    'Authorization': `Bearer ${token}`,
  },
});

// First time user — register a new vault
if (!vaultResponse.ok) {
  vaultResponse = await fetch('https://api.jup.ag/trigger/v2/vault/register', {
    headers: {
      'x-api-key': 'your-api-key',
      'Authorization': `Bearer ${token}`,
    },
  });
}

const vault = await vaultResponse.json();
// { userPubkey: "...", vaultPubkey: "...", privyVaultId: "..." }

Step 2: Craft a Deposit Transaction

The deposit endpoint builds an unsigned transaction that transfers tokens from your wallet to your vault.
const depositResponse = await fetch('https://api.jup.ag/trigger/v2/deposit/craft', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'x-api-key': 'your-api-key',
    'Authorization': `Bearer ${token}`,
  },
  body: JSON.stringify({
    inputMint: 'So11111111111111111111111111111111111111112',  // SOL
    outputMint: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',  // USDC
    userAddress: walletAddress,
    amount: '1000000000',  // 1 SOL in lamports
  }),
});

const deposit = await depositResponse.json();
Response:
{
  "transaction": "Base64EncodedUnsignedTransaction...",
  "requestId": "01234567-89ab-cdef-0123-456789abcdef",
  "receiverAddress": "VaultPublicKey...",
  "mint": "So11111111111111111111111111111111111111112",
  "amount": "1000000000",
  "tokenDecimals": 9
}
The vault address is automatically resolved from your JWT. You do not specify it in the request.

Step 3: Sign the Deposit Transaction

import { VersionedTransaction } from '@solana/web3.js';

const transaction = VersionedTransaction.deserialize(
  Buffer.from(deposit.transaction, 'base64')
);
const signedTransaction = await wallet.signTransaction(transaction);
const depositSignedTx = Buffer.from(signedTransaction.serialize()).toString('base64');

Step 4: Create the Order

Submit the signed deposit alongside your order parameters. The order type determines which fields are required.

Single Order (Limit)

A single order triggers when the price of triggerMint crosses above or below the target price.
const orderResponse = await fetch('https://api.jup.ag/trigger/v2/orders/price', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'x-api-key': 'your-api-key',
    'Authorization': `Bearer ${token}`,
  },
  body: JSON.stringify({
    orderType: 'single',
    depositRequestId: deposit.requestId,
    depositSignedTx: depositSignedTx,
    userPubkey: walletAddress,
    inputMint: 'So11111111111111111111111111111111111111112',
    inputAmount: '1000000000',
    outputMint: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
    triggerMint: 'So11111111111111111111111111111111111111112',
    triggerCondition: 'above',  // 'above' or 'below'
    triggerPriceUsd: 200.00,
    slippageBps: 100,  // 1%
    expiresAt: Date.now() + 7 * 24 * 60 * 60 * 1000,  // 7 days
  }),
});

const order = await orderResponse.json();
// { id: "order-uuid", txSignature: "..." }

OCO Order (One-Cancels-Other)

An OCO order creates a take-profit and stop-loss pair sharing one deposit. When one side fills, the other cancels automatically.
const orderResponse = await fetch('https://api.jup.ag/trigger/v2/orders/price', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'x-api-key': 'your-api-key',
    'Authorization': `Bearer ${token}`,
  },
  body: JSON.stringify({
    orderType: 'oco',
    depositRequestId: deposit.requestId,
    depositSignedTx: depositSignedTx,
    userPubkey: walletAddress,
    inputMint: 'So11111111111111111111111111111111111111112',
    inputAmount: '1000000000',
    outputMint: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
    triggerMint: 'So11111111111111111111111111111111111111112',
    tpPriceUsd: 250.00,    // Take profit price
    slPriceUsd: 150.00,    // Stop loss price
    tpSlippageBps: 100,
    slSlippageBps: 100,
    expiresAt: Date.now() + 7 * 24 * 60 * 60 * 1000,
  }),
});
The take-profit price must be greater than the stop-loss price.

OTOCO Order (One-Triggers-One-Cancels-Other)

An OTOCO order has a parent trigger that executes first. Once filled, it automatically activates a TP/SL pair (OCO) on the output tokens.
const orderResponse = await fetch('https://api.jup.ag/trigger/v2/orders/price', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'x-api-key': 'your-api-key',
    'Authorization': `Bearer ${token}`,
  },
  body: JSON.stringify({
    orderType: 'otoco',
    depositRequestId: deposit.requestId,
    depositSignedTx: depositSignedTx,
    userPubkey: walletAddress,
    inputMint: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
    inputAmount: '200000000',  // 200 USDC
    outputMint: 'So11111111111111111111111111111111111111112',
    triggerMint: 'So11111111111111111111111111111111111111112',
    triggerCondition: 'below',
    triggerPriceUsd: 180.00,   // Entry: buy SOL when it drops to \$180
    tpPriceUsd: 220.00,       // Take profit at \$220
    slPriceUsd: 160.00,       // Stop loss at \$160
    slippageBps: 100,         // Parent order slippage
    tpSlippageBps: 100,
    slSlippageBps: 100,
    expiresAt: Date.now() + 30 * 24 * 60 * 60 * 1000,  // 30 days
  }),
});

Order Parameters Reference

Common Fields (All Order Types)

ParameterTypeRequiredDescription
orderTypestringYessingle, oco, or otoco
depositRequestIdstringYesFrom the deposit craft response
depositSignedTxstringYesBase64-encoded signed deposit transaction
userPubkeystringYesYour wallet public key
inputMintstringYesMint address of the token to sell
inputAmountstringYesAmount in smallest unit (lamports, etc.)
outputMintstringYesMint address of the token to buy
triggerMintstringYesMint address to monitor for price trigger
expiresAtnumberYesExpiration timestamp in milliseconds

Single Order Fields

ParameterTypeRequiredDescription
triggerConditionstringYesabove or below
triggerPriceUsdnumberYesUSD price that triggers execution
slippageBpsnumberNoSlippage tolerance in basis points (0-10000)

OCO Order Fields

ParameterTypeRequiredDescription
tpPriceUsdnumberYesTake-profit trigger price (USD)
slPriceUsdnumberYesStop-loss trigger price (USD)
tpSlippageBpsnumberNoTake-profit slippage (basis points)
slSlippageBpsnumberNoStop-loss slippage (basis points)

OTOCO Order Fields

ParameterTypeRequiredDescription
triggerConditionstringYesParent trigger: above or below
triggerPriceUsdnumberYesParent trigger price (USD)
tpPriceUsdnumberYesTake-profit price after parent fills
slPriceUsdnumberYesStop-loss price after parent fills
slippageBpsnumberNoParent order slippage
tpSlippageBpsnumberNoTake-profit slippage
slSlippageBpsnumberNoStop-loss slippage

Default Slippage

If slippage is not specified, defaults vary by order type and trigger condition:
Order sideDefault slippage
Take-profit / buy below (buying into strength)Auto slippage via RTSE (Real-Time Slippage Estimator)
Stop-loss / buy above (selling into weakness)20% (2000 bps)
Stop-loss orders use a higher default because execution certainty is more important than price precision when cutting losses.

Validation Rules

  • Minimum order size: 10 USD
  • Input and output mints must be different
  • Slippage: 0-10000 basis points
  • expiresAt is required and must be a future timestamp (milliseconds)
  • OCO/OTOCO: take-profit price must be greater than stop-loss price
Unlike V1 where orders could exist indefinitely, V2 requires an expiration on every order. Adding a TTL allows the system to prune orders that are unlikely to fill, improving execution quality for active orders. For long-lived orders, use a far-future timestamp (e.g. 30 days).

Response

{
  "id": "order-uuid",
  "txSignature": "5eykt4UsFv8P8NJdTREpY1vzqKqZKvdpKuc147dw2N9d..."
}
The txSignature confirms the deposit transaction landed on-chain. The order is now active and will execute when price conditions are met.