GeminiGemini
SandboxGet API key
  • Crypto Trading
  • Prediction Markets
Changelog
Gemini Crypto Exchange LogoGemini Crypto Exchange Logo

© 2026 Gemini Space Station, Inc.

Getting Started
Market Makers
    Maker Rebate ProgramLiquidity Rewards Program
WebSocket
    IntroductionAuthenticationMessage Format
    Streams
      Book TickerL2 Partial DepthL2 Differential DepthTrade StreamOrder EventsBalance UpdatesPosition UpdatesContract Status
    Playground
      OverviewconninfopingtimeSUBSCRIBEUNSUBSCRIBELIST_SUBSCRIPTIONSdepthorder.placeorder.cancelorder.cancel_allorder.cancel_session
REST APIs
    Combos
    Events
    Terms
    Order Management
    Positions
    Rewards
Combo Contracts
    Overview
Tickers
    OverviewCryptoSportsCommoditiesWeather
Schemas
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 question. A contract is one tradable outcome inside that event. Each contract has a YES side and a NO side.

Buy YES if you think the event happens, NO if you don't. Right pays $1.00, wrong pays $0.00. The price is what the market thinks the odds are.

Example
Event:     "Will BTC finish above $100,000 on Dec 31?"
Contract:  BTC above $100,000
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 outcome 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 and 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 YES contract at $0.48 costs $0.48; the contract pays $1.00 if YES wins, $0.00 if it doesn't.

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 Sandbox

Gemini has a full sandbox environment at api.sandbox.gemini.com. It requires a separate account. The WebSocket endpoint is wss://ws.sandbox.gemini.com. Auth, endpoints, and message format are identical to production.

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 rejects the order rather than crossing the spread. If it returns MakerOrCancelWouldTake, the price moved before the order landed.

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 rejects if we'd 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 orders@account for any open 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
Last modified on May 27, 2026