GeminiGemini
Demo environmentGet API key
  • Overview
  • Crypto Trading
  • Prediction Markets
  • Perpetuals
  • Stocks
  • API Reference
  • SDKs & Tools
Changelog
Gemini logoGemini logo

© 2026 Gemini Space Station, Inc.

Overview
Core Concepts
    Events, contracts, and outcomesCombo contracts
Market Data
    OverviewFetching Markets
    Ticker formats
      OverviewCryptoSportsSports Market TaxonomyCommoditiesWeather
Trading
    QuickstartOrder lifecycle and settlement
    Full REST reference
      Combo RFQ
      Events
      Volume
      Terms
      Order Management
      Positions
      Rewards
    WebSocket APIs
      OverviewAuthenticationMessage format
      Streams
        Book tickerL2 partial depthL2 differential depthTrade streamOrder eventsBalance updatesPosition updatesContract statusPrediction
      Utility
      Subscriptions
      L2 Data
      Order management
      Combo RFQ
        OverviewWebSocket streamsQuote methodsMaker integrationExamples
Market Makers
    Taker Rewards ProgramMaker Rebate ProgramLiquidity Rewards ProgramMarket Maker Program
Resources
    SchemasDemo environmentRate limitsErrors
Prediction Markets

Prediction Markets

Prediction Markets on Gemini

Fetch live markets, stream prices, and place orders on event-based contracts via REST and WebSocket.

Get API key
Start here

Make a Public REST Request

No API key required. Fetch live markets and copy an instrumentSymbol before writing a single line of trading code.

GETv1/prediction-markets/events
curl --request GET \
  --url 'https://api.gemini.com/v1/prediction-markets/events?status=active&limit=3'

Copy an instrumentSymbol from an active, open contract. That value is what you pass to WebSocket streams and order requests. All prices and quantities are decimal strings.

Events, Contracts, and Outcomes

An event is the broader question. A contract is one tradable proposition expressed in YES space. Each contract supports YES and NO outcomes.

Buy YES if you think the contract proposition will resolve YES, or NO if you think it will resolve NO. The winning outcome pays $1.00 and the losing outcome pays $0.00. Price reflects the market's implied probability.

Example
Event:       "Where will BTC finish on Dec 31?"
Instrument:  BTC above $100,000 (YES-space proposition)
YES ask:     $0.42
NO ask:      $0.60

Know Which Identifier to Use

API responses include several ticker fields. Use instrumentSymbol verbatim, exactly as returned, for all WebSocket and order requests.

IdentifierUse
Event tickerIdentifies the event, such as BTC05M2606011000. Use it to fetch event details.
Contract tickerIdentifies one YES-space proposition inside an event, such as UP.
instrumentSymbolThe full tradable symbol, such as GEMI-BTC05M2606011000-UP. Use this exact value for WebSocket streams and orders.

Place a Maker-Only Limit Order

Use WebSocket for active trading. Authentication happens during the WebSocket handshake, so create an account-scoped key with time-based nonces enabled before connecting.

Before your first live order, create an API key, read the current terms, and check GET /v1/prediction-markets/terms/status. If the latest terms are not accepted, call POST /v1/prediction-markets/terms/accept. BTC 5-minute contracts expire every 5 minutes, so fetch a current instrumentSymbol from GET /v1/prediction-markets/events before sending this request. Buying 1 contract of the YES outcome at $0.48 costs $0.48; that outcome pays $1.00 if the proposition resolves YES and $0.00 if it does not.

order.place
{
  "id": "1",
  "method": "order.place",
  "params": {
    "symbol": "GEMI-BTC05M2606011000-UP",
    "side": "BUY",
    "type": "LIMIT",
    "timeInForce": "MOC",
    "price": "0.48",
    "quantity": "1",
    "eventOutcome": "YES",
    "clientOrderId": "btc-5m-quote-001"
  }
}
Testing in the demo environment

Gemini has a full demo environment, also called the sandbox, at api.sandbox.gemini.com. It requires a separate account. The WebSocket endpoint is wss://api.sandbox.gemini.com. Confirm that the product and operation are supported in the demo environment before testing.

Running multiple bots or strategies?Subaccounts give each bot isolated balances, rate limits, and blast radius — and they work with prediction markets.

WebSocket Trading

Stream prices, place orders, and receive fills over a single persistent WebSocket connection. Authenticated streams require upgrade headers. Browsers cannot set these, so use a backend or local client.

1

Set up credentials

Create an account-scoped API key and export it as environment variables. WebSocket authentication requires account-scoped keys with time-based nonces enabled. Keys without these settings will be rejected at connection time.

Terminal
export GEMINI_API_KEY="account-xxxxxxxxxxxxxxxx"
export GEMINI_API_SECRET="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
2

Fetch active market and stream prices

BTC 5-minute contracts expire every 5 minutes, so fetch the current symbol from REST before connecting. The code below resolves the active BTC05M contract, authenticates, and subscribes to {symbol}@bookTicker.

Python
# pip install websockets
import asyncio, websockets, hmac, hashlib, base64, json, os, time, urllib.request, urllib.parse
from decimal import Decimal

API_KEY = os.environ["GEMINI_API_KEY"]
API_SECRET = os.environ["GEMINI_API_SECRET"]

def get_active_btc_5min_symbol() -> str:
    url = ("https://api.gemini.com/v1/prediction-markets/events?"
           + urllib.parse.urlencode({"status": "active", "category": "crypto", "limit": "50"}))
    with urllib.request.urlopen(url) as r:
        data = json.loads(r.read())
    for event in data.get("data", []):
        for contract in event.get("contracts", []):
            sym = contract.get("instrumentSymbol", "")
            if ("BTC05M" in sym
                    and contract.get("status") == "active"
                    and contract.get("marketState") == "open"):
                return sym
    raise RuntimeError("No active BTC 5-minute contract found")

SYMBOL = get_active_btc_5min_symbol()

def auth_headers():
    nonce = str(int(time.time() * 1000))
    payload = base64.b64encode(nonce.encode())
    signature = hmac.new(API_SECRET.encode(), payload, hashlib.sha384).hexdigest()
    return {
        "X-GEMINI-APIKEY": API_KEY,
        "X-GEMINI-NONCE": nonce,
        "X-GEMINI-PAYLOAD": payload.decode(),
        "X-GEMINI-SIGNATURE": signature,
    }

async def stream_prices():
    async with websockets.connect("wss://ws.gemini.com", additional_headers=auth_headers()) as ws:
        await ws.send(json.dumps({
            "id": "1",
            "method": "SUBSCRIBE",
            "params": [f"{SYMBOL}@bookTicker"],
        }))

        async for msg in ws:
            data = json.loads(msg)
            if "b" in data and "a" in data:
                print(f"{data['s']}  bid: ${data['b']} ({data['B']} contracts)  ask: ${data['a']} ({data['A']} contracts)")

asyncio.run(stream_prices())
3

Subscribe and Place an Order

Accept Prediction Markets terms via REST before the first live order, then subscribe to orders@account and positions@account alongside the price stream. MOC accepts the order request, then cancels it if it would take liquidity. If it returns MakerOrCancelWouldTake, the price moved before the order could rest.

Python
async def trade():
    async with websockets.connect("wss://ws.gemini.com", additional_headers=auth_headers()) as ws:
        await ws.send(json.dumps({
            "id": "1",
            "method": "SUBSCRIBE",
            "params": [
                f"{SYMBOL}@bookTicker",  # live prices
                "orders@account",         # your order updates
                "positions@account",      # your position updates
                "contractStatus",         # contract lifecycle events
            ],
        }))

        # Only place once; MOC is canceled if it would cross the spread.
        async for msg in ws:
            data = json.loads(msg)
            if "b" in data and "a" in data and data.get("s") == SYMBOL:
                best_bid = Decimal(data["b"])
                best_ask = Decimal(data["a"])
                price = min(best_bid, best_ask - Decimal("0.01"))
                if price <= Decimal("0"):
                    continue
                break

        await ws.send(json.dumps({
            "id": "2",
            "method": "order.place",
            "params": {
                "symbol": SYMBOL,
                "side": "BUY",
                "type": "LIMIT",
                "timeInForce": "MOC",
                "price": str(price),
                "quantity": "1",
                "eventOutcome": "YES",
                "clientOrderId": "btc-5m-quote-001",
            },
        }))
4

Handle Order Updates and Cancel

Cancel resting quotes before intentionally closing the connection with order.cancel_session. On unexpected disconnect, reconnect and query the REST active-orders and order-history endpoints for any open or accepted quotes before placing new ones.

Python
        # Watch order updates
        # X = status, S = side, O = outcome, p = price, q = quantity
        async for msg in ws:
            data = json.loads(msg)
            if data.get("X") in ("NEW", "OPEN", "FILLED", "PARTIALLY_FILLED", "CANCELED"):
                print(f"Order {data['X']}: side={data.get('S')} outcome={data.get('O')} price=${data.get('p')} qty={data.get('q')}")

            # In production, cancel only when your quoting logic decides the price is stale.
            if data.get("X") == "OPEN":
                await ws.send(json.dumps({
                    "id": "3",
                    "method": "order.cancel",
                    "params": { "orderId": data.get("i") },
                }))

            if data.get("X") == "CANCELED":
                break
5
Output
Order NEW: side=BUY outcome=YES price=$0.48 qty=1
Order OPEN: side=BUY outcome=YES price=$0.48 qty=1
Order CANCELED: side=BUY outcome=YES price=$0.48 qty=1