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

# How to Build a Prediction Market App on Solana

> Build prediction market features into your Solana app using Jupiter's Prediction API.

## 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`

<Tip>
  **Video walkthrough + source code:** Watch the [video walkthrough](https://x.com/JupDevRel/status/2031926314811486657) and grab the [demo app source code](https://github.com/jup-ag/api-examples/tree/main/apps/prediction-API-video-demo) on GitHub.
</Tip>

***

## Prerequisites

1. Get an API key at [Portal](https://developers.jup.ag/portal)
2. All requests require the `x-api-key` header
3. Users need JupUSD (or USDC) to trade

<Note>
  **BETA**

  The Prediction Market API is currently in beta and subject to breaking changes. If you have feedback, reach out in [Discord](https://discord.gg/jup).
</Note>

***

## Quick start

```bash theme={null}
# 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.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`

| Endpoint                                 | Description                           |
| ---------------------------------------- | ------------------------------------- |
| `GET /events`                            | List 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 /orders`                           | Create 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 /positions`                      | Close all positions                   |
| `POST /positions/{positionPubkey}/claim` | Claim winnings (returns unsigned tx)  |

***

## Code examples

### Browse available events

<CodeGroup>
  ```bash curl theme={null}
  curl -X GET "https://api.jup.ag/prediction/v1/events?category=crypto&includeMarkets=true" \
    -H "x-api-key: YOUR_API_KEY"
  ```

  ```javascript JavaScript theme={null}
  const response = await fetch(
    'https://api.jup.ag/prediction/v1/events?category=crypto&includeMarkets=true',
    {
      headers: { 'x-api-key': 'YOUR_API_KEY' }
    }
  );
  const { data: events } = await response.json();

  // Each event contains markets with pricing
  events.forEach(event => {
    console.log(`${event.metadata.title}`);
    event.markets?.forEach(market => {
      console.log(`  ${market.metadata.title}: YES ${market.pricing.buyYesPriceUsd}¢`);
    });
  });
  ```
</CodeGroup>

**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

```javascript theme={null}
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

<CodeGroup>
  ```bash curl theme={null}
  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"
    }'
  ```

  ```javascript JavaScript theme={null}
  const response = await fetch('https://api.jup.ag/prediction/v1/orders', {
    method: 'POST',
    headers: {
      'x-api-key': 'YOUR_API_KEY',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      ownerPubkey: userWallet.publicKey.toString(),
      marketId: 'MARKET_ID',
      isYes: true,              // YES side (false for NO)
      isBuy: true,              // Buy order (false to sell)
      depositAmount: '2000000', // $2 in native token units (1,000,000 = $1)
      depositMint: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v' // USDC
    })
  });

  const orderResponse = await response.json();

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

  const signature = await connection.sendRawTransaction(transaction.serialize(), {
    skipPreflight: true,
  });
  ```
</CodeGroup>

**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

```javascript theme={null}
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

```javascript theme={null}
// 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

```typescript theme={null}
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

```typescript theme={null}
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

```typescript theme={null}
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

```typescript theme={null}
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

```typescript theme={null}
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

<AccordionGroup>
  <Accordion title="What tokens can users deposit?">
    JupUSD or USDC. Specify via `depositMint` parameter (defaults to USDC).
  </Accordion>

  <Accordion title="How do I know if a market is tradeable?">
    Check `market.status === 'open'`. A market with status `open` is actively trading.
  </Accordion>

  <Accordion title="What happens if my order doesn't fill?">
    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`.
  </Accordion>

  <Accordion title="How do I calculate potential profit?">
    ```javascript theme={null}
    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
    ```
  </Accordion>

  <Accordion title="How do I handle the micro USD format?">
    ```javascript theme={null}
    // 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();
    ```
  </Accordion>

  <Accordion title="Which data provider should I use?">
    Polymarket (default) has more markets. Kalshi is US-regulated. Use the `provider` query param to switch.
  </Accordion>
</AccordionGroup>

***

## Next steps

* **[Prediction API Reference](/api-reference/prediction).** Full endpoint documentation.
* **[About Prediction Markets](/prediction/index).** Architecture and concepts.
* **[Portal](https://developers.jup.ag/portal).** Get your API key.
* **[Jupiter Dev Notifications](https://t.me/jup_dev).** API updates and announcements.
