Skip to main content

TL;DR

Jupiter’s Prediction API lets you add prediction market trading to your Solana app. Users can trade on real-world event outcomes (sports, crypto, politics) with liquidity aggregated from Polymarket and Kalshi. The API handles order matching, position tracking, and settlement. Base URL: https://api.jup.ag/prediction/v1
Video walkthrough + source code: Watch the video walkthrough and grab the demo app source code on GitHub.

Prerequisites

  1. Get an API key at portal.jup.ag
  2. All requests require the x-api-key header
  3. Users need JupUSD (or USDC) to trade
BETAThe Prediction Market API is currently in beta and subject to breaking changes. If you have feedback, reach out in Discord.

Quick start

# Get available prediction events
curl -X GET "https://api.jup.ag/prediction/v1/events?category=crypto" \
  -H "x-api-key: YOUR_API_KEY"

When you need this

You’re building an app where users can:
  • Trade on event outcomes. “Will BTC hit $100k by end of 2026?”
  • Bet on sports, politics, or crypto. Binary YES/NO markets.
  • Build a prediction market frontend. Custom UI for trading.
  • Add speculation features. Let users put money on their predictions.
  • Track trading performance. Leaderboards, P&L history, profiles.
Common searches that lead here:
  • “prediction market api solana”
  • “binary options solana”
  • “bet on events solana api”
  • “polymarket on solana”

Why Jupiter

Building prediction markets from scratch requires:
  • Liquidity sourcing and market making
  • Order matching infrastructure
  • Settlement and payout systems
  • Real-world event data feeds
Jupiter handles all of this:
  • Aggregates liquidity from Polymarket and Kalshi.
  • Keeper network matches and fills orders.
  • On-chain settlement with guaranteed payouts.
  • No payout fees. Winners receive the full $1 per contract.
You get prediction market functionality without building the infrastructure.

How prediction markets work

Binary outcomes: Every market is YES or NO. “Will X happen?” → YES or NO. Contract pricing: Prices range from 0.01to0.01 to 0.99. Price = implied probability.
  • 70¢ YES price = market thinks 70% chance of YES
  • If you buy YES at 70¢ and YES wins, you profit 30¢ per contract
  • Losing contracts expire worthless
Settlement: When the event resolves, winning contracts pay out $1 each. No fees on payouts.

API reference

Base URL: https://api.jup.ag/prediction/v1
EndpointDescription
GET /eventsList prediction events with filters
GET /events/search?query={term}Search events by keyword
GET /events/{eventId}Get event details
GET /markets/{marketId}Get market pricing and status
POST /ordersCreate order (returns unsigned tx)
GET /orders?ownerPubkey={pubkey}Get user’s orders
GET /orders/status/{orderPubkey}Get order fill status
GET /positions?ownerPubkey={pubkey}Get user’s positions
DELETE /positions/{positionPubkey}Close a position (sell all contracts)
DELETE /positionsClose all positions
POST /positions/{positionPubkey}/claimClaim winnings (returns unsigned tx)

Code examples

Browse available events

curl -X GET "https://api.jup.ag/prediction/v1/events?category=crypto&includeMarkets=true" \
  -H "x-api-key: YOUR_API_KEY"
Filter options:
  • category: all, crypto, sports, politics, esports, culture, economics, tech
  • filter: new (last 24h), live (in progress), trending
  • provider: polymarket (default), kalshi

Search for specific events

const response = await fetch(
  'https://api.jup.ag/prediction/v1/events/search?query=bitcoin&limit=10',
  {
    headers: { 'x-api-key': 'YOUR_API_KEY' }
  }
);
const { data: events } = await response.json();

Create a buy order

curl -X POST "https://api.jup.ag/prediction/v1/orders" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "ownerPubkey": "USER_WALLET_PUBKEY",
    "marketId": "MARKET_ID",
    "isYes": true,
    "isBuy": true,
    "depositAmount": "2000000",
    "depositMint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"
  }'
Amount format: All USD values use native token units where 1,000,000 = $1.00.
  • depositAmount: '2000000' = $2.00
  • depositMint accepts USDC (EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v) or JupUSD (JuprjznTrTSp2UFa3ZBUFgwdAmtZCq4MQCwysN55USD)

Get user positions

const response = await fetch(
  `https://api.jup.ag/prediction/v1/positions?ownerPubkey=${userPubkey}`,
  {
    headers: { 'x-api-key': 'YOUR_API_KEY' }
  }
);
const { data: positions } = await response.json();

positions.forEach(pos => {
  const side = pos.isYes ? 'YES' : 'NO';
  const pnl = pos.pnlUsd ? (parseInt(pos.pnlUsd) / 1_000_000).toFixed(2) : 'N/A';
  console.log(`${pos.marketMetadata.title}: ${pos.contracts} ${side} contracts, P&L: $${pnl}`);
});

Claim winnings from a settled position

// Check if position is claimable
if (position.claimable) {
  const response = await fetch(
    `https://api.jup.ag/prediction/v1/positions/${position.pubkey}/claim`,
    {
      method: 'POST',
      headers: {
        'x-api-key': 'YOUR_API_KEY',
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        ownerPubkey: userPubkey
      })
    }
  );

  const claimResponse = await response.json();

  // Deserialize, sign, and send
  const transaction = VersionedTransaction.deserialize(
    Buffer.from(claimResponse.transaction, 'base64')
  );
  transaction.sign([userWallet]);

  const signature = await connection.sendRawTransaction(transaction.serialize(), {
    skipPreflight: true,
  });
}

Response format

Event

interface Event {
  eventId: string;
  isActive: boolean;                       // Whether the event is listed
  isLive: boolean;                         // Whether the event is currently live
  category: 'crypto' | 'sports' | 'politics' | 'esports' | 'culture' | 'economics' | 'tech';
  subcategory: string;                     // Subcategory within the category
  tags: string[];                          // Tags associated with the event
  metadata: {
    eventId: string;
    title: string;
    subtitle: string;
    slug: string;                          // URL-friendly identifier
    series: string;                        // Groups related events (e.g., "NFL Week 1")
    closeTime: string;                     // ISO 8601 formatted close time
    imageUrl: string;
    isLive: boolean;
  };
  markets: Market[];
  volumeUsd: string;                       // Total volume (micro USD)
  closeCondition: string;
  beginAt: string | null;                  // When the event begins
  rulesPdf: string;                        // URL to full rules document
}

Market

interface Market {
  marketId: string;
  status: 'open' | 'closed' | 'cancelled';
  result: null | 'yes' | 'no';
  openTime: number;                          // Unix timestamp
  closeTime: number;                         // Unix timestamp
  resolveAt: number | null;                  // Unix timestamp when market resolved
  marketResultPubkey: string | null;         // Market result account public key
  imageUrl: string;                          // Market image URL
  metadata: {
    title: string;
    status: string;
    openTime: number;
    closeTime: number;
    isTeamMarket: boolean;                   // Whether this is a team market
    rulesPrimary: string;
    rulesSecondary: string;
  };
  pricing: {
    buyYesPriceUsd: number | null;           // Price to buy YES (micro USD)
    buyNoPriceUsd: number | null;            // Price to buy NO (micro USD)
    sellYesPriceUsd: number | null;          // Price to sell YES (micro USD)
    sellNoPriceUsd: number | null;           // Price to sell NO (micro USD)
    volume: number;                          // Trading volume for this market
  };
}

Position

interface Position {
  pubkey: string;                            // Position account
  ownerPubkey: string;                       // User's wallet
  marketId: string;
  isYes: boolean;                            // YES or NO side
  contracts: string;                         // Contracts held (u64 as string)
  avgPriceUsd: string;                       // Average entry price (micro USD)
  totalCostUsd: string;                      // Cost basis (micro USD)
  sizeUsd: string;                           // Alias of totalCostUsd (micro USD)
  valueUsd: string | null;                   // Mark-to-market value (null if market closed)
  markPriceUsd: string | null;               // Current mark price (null if market closed)
  sellPriceUsd: string | null;               // Best exit price (null if unavailable)
  pnlUsd: string | null;                     // Unrealized P&L (micro USD)
  pnlUsdPercent: number | null;
  pnlUsdAfterFees: string | null;            // P&L after fees (micro USD)
  pnlUsdAfterFeesPercent: number | null;
  realizedPnlUsd: number;                    // Realized P&L (micro USD)
  feesPaidUsd: string;                       // Total fees paid (micro USD)
  payoutUsd: string;                         // Payout if position wins (contracts x $1)
  claimable: boolean;                        // Can claim payout?
  claimed: boolean;                          // Has payout been claimed?
  claimedUsd: string;                        // Amount claimed (micro USD)
  openOrders: number;                        // Number of open orders
  openedAt: number;                          // Unix timestamp when position opened
  updatedAt: number;                         // Unix timestamp of last update
  claimableAt: number | null;                // When position becomes claimable
  settlementDate: number | null;             // When market settles
  eventMetadata: EventMetadata;
  marketMetadata: MarketMetadata;
}

Create order response

interface CreateOrderResponse {
  transaction: string;  // Base64 encoded unsigned transaction
  txMeta: {
    blockhash: string;
    lastValidBlockHeight: number;
  };
  order: {
    orderPubkey: string;
    positionPubkey: string;
    contracts: string;
    orderCostUsd: string;
    estimatedTotalFeeUsd: string;
  };
}

History event

interface HistoryEvent {
  id: number;                                // Unique event identifier
  eventType: 'order_created' | 'order_filled' | 'order_failed'
    | 'payout_claimed' | 'position_updated' | 'position_lost';
  signature: string;                         // Solana transaction signature
  slot: string;                              // Solana slot number
  timestamp: number;                         // Unix timestamp (seconds)
  orderPubkey: string;                       // Associated order account
  positionPubkey: string;                    // Associated position account
  marketId: string;
  ownerPubkey: string;
  keeperPubkey: string;                      // Keeper that processed the tx
  externalOrderId: string;                   // Client-provided order ID
  orderId: string;                           // Venue order ID
  isBuy: boolean;                            // Buy or sell
  isYes: boolean;                            // YES or NO side
  contracts: string;                         // Contracts in the order
  filledContracts: string;                   // Contracts filled
  contractsSettled: string;                  // Contracts settled
  avgFillPriceUsd: string;                   // Average fill price (micro USD)
  maxFillPriceUsd: string;                   // Max fill price (micro USD)
  maxBuyPriceUsd: string | null;             // Buyer max price (micro USD)
  minSellPriceUsd: string | null;            // Seller min price (micro USD)
  depositAmountUsd: string;                  // Deposit amount (micro USD)
  totalCostUsd: string;                      // Total cost (micro USD)
  feeUsd: string | null;                     // Fee charged (micro USD)
  grossProceedsUsd: string;                  // Gross proceeds (micro USD)
  netProceedsUsd: string;                    // Net proceeds (micro USD)
  transferAmountToken: string | null;        // Token amount transferred
  realizedPnl: string | null;                // Realized P&L (micro USD)
  realizedPnlBeforeFees: string | null;      // Realized P&L before fees (micro USD)
  payoutAmountUsd: string;                   // Payout amount (micro USD)
  eventId: string;
  marketMetadata: MarketMetadata;
  eventMetadata: EventMetadata;
}

Order lifecycle

  1. Create order: Call POST /orders → get unsigned transaction
  2. Sign & submit: User signs, you submit to Solana
  3. Keeper fills: Jupiter’s keeper network matches the order
  4. Position updates: Position reflects new contracts
  5. Market settles: When event resolves, result is recorded
  6. Claim payout: If position won, call /claim to withdraw

Common questions

JupUSD or USDC. Specify via depositMint parameter (defaults to USDC).
Check market.status === 'open'. A market with status open is actively trading.
You can close a position using DELETE /positions/{positionPubkey}, which sells all contracts. Unfilled orders that fail are closed automatically by the keeper network. Use GET /orders/status/{orderPubkey} to check if an order is pending, filled, or failed.
const contracts = 10;
const buyPrice = 0.70;  // 70¢
const potentialProfit = contracts * (1 - buyPrice);  // $3.00 if wins
const potentialLoss = contracts * buyPrice;          // $7.00 if loses
// Convert micro USD to USD for display
const displayPrice = (microUsd) => (parseInt(microUsd) / 1_000_000).toFixed(2);

// Convert USD to micro USD for API
const toMicroUsd = (usd) => Math.round(usd * 1_000_000).toString();
Polymarket (default) has more markets. Kalshi is US-regulated. Use the provider query param to switch.

Next steps