API Reference v1

Build on Unhedged.

A REST API for prediction markets and Continuous Trading Markets (CTM). Public endpoints for market data; authenticated endpoints for placing bets, submitting predictions, and managing your balance.

Authentication

Pass your API key as a Bearer token in the Authorization header. Public endpoints (most market reads) work without a key.

Base URL
https://api.unhedged.gg
Key Prefix
ak_*
Authorization: Bearer ak_your_key_here

Quickstart

Place your first bet in three steps.

  1. 1 Create an API key

    Sign in and visit Account → API Keys. Default scopes cover reading and betting; opt-in for balance:withdraw only if you need it.

  2. 2 Find an active market
    curl https://api.unhedged.gg/api/v1/feed?status=ACTIVE&limit=5
  3. 3 Place a bet
    curl -X POST https://api.unhedged.gg/api/v1/bets \
      -H "Authorization: Bearer ak_your_key_here" \
      -H "Content-Type: application/json" \
      -d '{"marketId":"clx...","outcomeIndex":0,"amount":50}'

Scopes

Each API key has a fixed set of scopes. Endpoints that require a scope are marked below the endpoint title. Withdrawals are gated behind an explicit opt-in scope.

market:read Read market and CTM round data. Most market endpoints allow unauthenticated reads.
balance:read Read available, locked, deposited, withdrawn, and transaction amounts.
balance:withdraw Initiate withdrawals from available balance. Disabled by default.
bet:place Place bets on markets and submit CTM predictions.
bet:read Read your bets, predictions, and positions.
portfolio:read Read portfolio positions, equity snapshots, and history records.
user:read Read your own profile via GET /api/v1/users/me (API key only).
order:place Place orders on ORDER_BOOK markets.
order:cancel Cancel your open matching-engine orders.
order:read Read your matching-engine order history.

Rate Limits

Limits are applied per IP. Endpoint-specific limits are listed on each endpoint card. When exceeded you'll receive 429 Too Many Requests — back off and retry.

Global default 600 req/min
Bets / authenticated reads 60 req/min
Bets per market per IP 15 req/min
CTM predict 80 req/min
Order book (place / cancel / read) 120 req/min · 60 per market
Price history 300 req/min
Withdrawals 5 req/min
API key creation 10 req/hour

Errors

Errors return a JSON body of the shape { "error": "ErrorType", "message": "..." }. Some 4xx responses include an additional code field for machine-readable handling.

400 Bad request — invalid params, malformed body, or insufficient balance
401 Unauthorized — missing or invalid API key
403 Forbidden — key lacks required scope, account suspended, or email not verified
404 Not found — market, round, or bet doesn't exist
409 Conflict — idempotency key reused with different payload
429 Rate limited — slow down. Backoff and retry after a few seconds
503 Service unavailable — betting/CTM temporarily disabled by admin

Markets

GET /api/v1/feed PUBLIC
List Feed (Markets + Groups) Sorted stream of standalone markets and Kalshi-style market groups from the public markets page source. Each item has type: "market" or "binary_group". Grouped child markets appear inside their group, not as separate feed items. No API key required.
Rate limit: 600 req/min
Query Parameters
status string — ACTIVE | ENDED | RESOLVED | VOIDED
category string — Filter by category (max 50 chars)
tag string — Comma-separated tag filter (max 500 chars)
search string — Search query (2–200 chars)
sort string — ending_soonest | ending_latest | newest | biggest_pool | most_bets (default ending_soonest)
limit number — Results per page (1–100, default 20)
offset number — Pagination offset (default 0)
Example
curl https://api.unhedged.gg/api/v1/feed
Response
{
  "items": [
    {
      "type": "market",
      "market": {
        "id": "clx...",
        "slug": "will-btc-hit-100k-by-june",
        "question": "Will BTC hit $100k by June?",
        "status": "ACTIVE",
        "category": "Crypto",
        "totalPool": "1500.0000000000",
        "endTime": "2026-06-01T00:00:00.000Z",
        ...
      }
    },
    {
      "type": "binary_group",
      "group": {
        "id": "clg...",
        "slug": "who-wins-the-2028-election",
        "title": "Who will win the 2028 election?",
        "category": "Politics",
        "marketCount": 5,
        "earliestStartTime": "2028-11-07T00:00:00.000Z",
        "earliestEndTime": "2028-11-07T00:00:00.000Z",
        "aggregateVolume": "12500.0000000000",
        "markets": [
          { "id": "clm...", "groupLabel": "Candidate A", "status": "ACTIVE", "outcomes": [ ... ], "orderBookStats": { ... }, ... }
        ]
      }
    }
  ],
  "total": 87,
  "marketTotal": 62,
  "groupTotal": 25,
  "activeCount": 40,
  "endedCount": 12,
  "resolvedCount": 35
}
GET /api/v1/markets/:id PUBLIC
Get Market Details Returns one market with outcomes, pool sizes, odds, and status. No API key required.
Rate limit: 600 req/min
Example
curl https://api.unhedged.gg/api/v1/markets/<id>
Response
{
  "market": {
    "id": "clx...",
    "question": "Will BTC hit $100k by June?",
    "description": "Resolves Yes if...",
    "status": "ACTIVE",
    "category": "Crypto",
    "outcomes": [
      { "index": 0, "label": "Yes" },
      { "index": 1, "label": "No" }
    ],
    "totalPool": "1500.0000000000",
    "endTime": "2026-06-01T00:00:00.000Z",
    "createdAt": "2026-01-15T...",
    ...
  }
}
GET /api/v1/balance AUTH
Get Balance Returns available, locked, deposited, withdrawn, and total CC amounts.
Scope: balance:read Rate limit: 60 req/min
Example
curl https://api.unhedged.gg/api/v1/balance \
  -H "Authorization: Bearer ak_your_key_here"
Response
{
  "balance": {
    "available": "500.0000000000",
    "lockedWithdraws": "0.0000000000",
    "lockedBets": "150.0000000000",
    "lockedEngine": "0.0000000000",
    "total": "650.0000000000",
    "totalDeposited": "1000.0000000000",
    "totalWithdrawn": "200.0000000000"
  },
  "withdrawalFee": "1.0000000000",
  "bettingFee": "0.0000000000",
  "ccPriceUsd": 0.015
}
POST /api/v1/bets AUTH
Place a Bet Creates a bet on one market outcome and deducts the amount from available balance.
Scope: bet:place Rate limit: 60 req/min globally · 15 req/min per market per IP
Request Body
marketId string * — Market ID to bet on
outcomeIndex number * — Index of the outcome (0-based)
amount number * — Amount in CC (min 0.0001)
idempotencyKey string — Optional dedup key to prevent double bets
Example
curl -X POST https://api.unhedged.gg/api/v1/bets \
  -H "Authorization: Bearer ak_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
  "marketId": "clx...",
  "outcomeIndex": 0,
  "amount": 50
}'
Response
{
  "bet": {
    "id": "clx...",
    "marketId": "clx...",
    "outcomeIndex": 0,
    "amount": "50.0000000000",
    "status": "CONFIRMED",
    "timeWeight": "1.0000"
  },
  "balanceAfter": "450.0000000000",
  "fee": "0.0000000000"
}
GET /api/v1/bets AUTH
List Your Bets Returns your bets, optionally filtered by status or marketId.
Scope: bet:read Rate limit: 60 req/min
Query Parameters
status string — PENDING | CONFIRMED | WON | LOST | REFUNDED
marketId string — Filter by market
limit number — Results per page (1–100, default 20)
offset number — Pagination offset
Example
curl https://api.unhedged.gg/api/v1/bets \
  -H "Authorization: Bearer ak_your_key_here"
Response
{
  "bets": [
    {
      "id": "clx...",
      "marketId": "clx...",
      "market": {
        "question": "Will BTC hit $100k?",
        "status": "ACTIVE",
        "outcomes": [...]
      },
      "outcomeIndex": 0,
      "amount": "50.0000000000",
      "status": "CONFIRMED",
      "createdAt": "2026-02-20T..."
    }
  ],
  "total": 5
}

Users

GET /api/v1/users/me AUTH
Get Current User Returns the API key owner profile (id, partyId, username, email, preferences). API keys only — JWT and browser session tokens receive 403.
Scope: user:read Rate limit: 60 req/min
Example
curl https://api.unhedged.gg/api/v1/users/me \
  -H "Authorization: Bearer ak_your_key_here"
Response
{
  "id": "clx1abc2d0000abcdefghij",
  "partyId": "namespace::fingerprint",
  "username": "trader_jane",
  "email": "[email protected]",
  "authProvider": "email",
  "avatarUrl": "https://cdn.unhedged.gg/avatars/clx....webp",
  "role": "USER",
  "showInLeaderboard": true,
  "autoBurn": true,
  "emailVerified": true,
  "onboardingComplete": true,
  "referralCode": "ABCD2345",
  "createdAt": "2026-01-15T12:00:00.000Z"
}

Portfolio

GET /api/v1/portfolio/me AUTH
Get Portfolio Returns your summary counts, active positions, and recent payouts.
Scope: portfolio:read Rate limit: 1,000,000 req/min
Example
curl https://api.unhedged.gg/api/v1/portfolio/me \
  -H "Authorization: Bearer ak_your_key_here"
Response
{
  "portfolio": {
    "summary": {
      "totalBets": 12,
      "totalWagered": "850.0000000000",
      "totalWins": 7,
      "totalLosses": 5,
      "pendingBets": 2,
      "pendingOrderBookPositions": 3,
      "pendingOrderBookCostBasis": "124.5000000000",
      "totalPayouts": "1200.0000000000",
      "totalProfit": "350.0000000000",
      "winRate": 58.33,
      "roi": 41.18
    },
    "positions": [...],
    "recentPayouts": [...]
  }
}
GET /api/v1/portfolio/me/positions AUTH
Get Active Positions Returns unresolved market positions, including ORDER_BOOK positions. ORDER_BOOK positions carry full share-inventory mechanics — quantity, availableQuantity, lockedQuantity, averagePrice, markPrice, feesPaid, openOrderCount, and canSell — so you can read share counts here in one paginated call (use marketType=ORDER_BOOK) instead of polling /portfolio/me/shares per market. These fields are omitted for PARIMUTUEL positions. canSell reflects whether the market is open for trading (ACTIVE and before its cutoff) with shares available to sell.
Scope: portfolio:read Rate limit: 1,000,000 req/min
Query Parameters
marketId string — Optional filter to a single market.
marketType string — PARIMUTUEL | ORDER_BOOK
limit number — Results per page (1–500).
offset number — Pagination offset (default 0).
Example
curl https://api.unhedged.gg/api/v1/portfolio/me/positions \
  -H "Authorization: Bearer ak_your_key_here"
Response
{
  "positions": [
    {
      "marketId": "clx...",
      "marketType": "ORDER_BOOK",
      "outcomeIndex": 0,
      "outcomeName": "YES",
      "totalBetAmount": "64.5000000000",
      "betCount": 2,
      "currentValue": "71.2000000000",
      "unrealizedPnl": "6.7000000000",
      "unrealizedPnlPercent": 10.39,
      "quantity": 100,
      "availableQuantity": 97,
      "lockedQuantity": 3,
      "averagePrice": "0.6450000000",
      "markPrice": "0.7120000000",
      "feesPaid": "0.5000000000",
      "openOrderCount": 1,
      "canSell": true,
      "createdAt": "2026-04-12T15:33:21.000Z",
      "market": {
        "question": "Will BTC be up at 12:00 UTC?",
        "status": "ACTIVE",
        "outcomes": [
          { "index": 0, "label": "YES", "engineId": "out_0" },
          { "index": 1, "label": "NO", "engineId": "out_1" }
        ]
      }
    }
  ],
  "total": 1
}
GET /api/v1/portfolio/me/shares AUTH
Get Order-Book Shares Returns ORDER_BOOK share inventory. Pass marketId after a fill to refresh one order ticket.
Scope: portfolio:read Rate limit: 1,000,000 req/min
Query Parameters
marketId string — Optional market filter.
Example
curl https://api.unhedged.gg/api/v1/portfolio/me/shares \
  -H "Authorization: Bearer ak_your_key_here"
Response
{
  "shares": [
    {
      "marketId": "clx...",
      "outcome": "YES",
      "available": 12,
      "locked": 3,
      "total": 15,
      "updatedAt": "2026-04-12T15:34:01.000Z",
      "market": {
        "question": "Will BTC be up at 12:00 UTC?",
        "status": "ACTIVE",
        "outcomes": [
          { "index": 0, "label": "YES", "engineId": "out_0" },
          { "index": 1, "label": "NO", "engineId": "out_1" }
        ]
      }
    }
  ]
}
GET /api/v1/portfolio/me/equity AUTH
Get Equity Snapshot Returns available cash, locked balances, open ORDER_BOOK mark-to-market value, and rolling P/L.
Scope: portfolio:read Rate limit: 1,000,000 req/min
Example
curl https://api.unhedged.gg/api/v1/portfolio/me/equity \
  -H "Authorization: Bearer ak_your_key_here"
Response
{
  "equity": {
    "total": "1320.4500000000",
    "available": "1100.0000000000",
    "lockedWithdraws": "0.0000000000",
    "lockedBets": "95.9500000000",
    "lockedEngine": "60.0000000000",
    "unrealizedClob": "64.5000000000",
    "dayChange": "18.2500000000",
    "dayChangePercent": 1.40,
    "sevenDayChange": "84.0000000000",
    "sevenDayChangePercent": 6.80,
    "dayPnl": "18.2500000000",
    "sevenDayPnl": "84.0000000000",
    "updatedAt": "2026-04-12T15:35:00.000Z",
    "asOf": "2026-04-12T15:35:00.000Z"
  }
}
GET /api/v1/portfolio/me/history AUTH
Get Position History Returns resolved and voided market positions from newest to oldest by market.resolutionTime. Use startTime/endTime to filter a resolution window, for example /api/v1/portfolio/me/history?startTime=2026-04-01T00:00:00.000Z&endTime=2026-04-30T23:59:59.999Z.
Scope: portfolio:read Rate limit: 1,000,000 req/min
Query Parameters
marketId string — Optional filter to a single market.
marketType string — PARIMUTUEL | ORDER_BOOK
startTime string — Optional inclusive lower bound on market.resolutionTime. ISO 8601 date-time.
endTime string — Optional inclusive upper bound on market.resolutionTime. ISO 8601 date-time.
limit number — Results per page (1–500).
offset number — Pagination offset (default 0).
Example
curl https://api.unhedged.gg/api/v1/portfolio/me/history \
  -H "Authorization: Bearer ak_your_key_here"
Response
{
  "positions": [
    {
      "marketId": "clx...",
      "marketType": "PARIMUTUEL",
      "outcomeIndex": 1,
      "outcomeName": "NO",
      "totalBetAmount": "50.0000000000",
      "actualPayout": "92.0000000000",
      "isWinner": true,
      "createdAt": "2026-04-10T12:00:00.000Z",
      "market": {
        "question": "Will BTC close above $100k?",
        "status": "RESOLVED",
        "winningOutcome": 1,
        "resolutionTime": "2026-04-12T15:35:00.000Z"
      }
    }
  ],
  "total": 1
}

CTM – Continuous Trading Markets

GET /api/v1/ctm/rounds PUBLIC
List CTM Rounds Returns CTM rounds filtered by status, asset, or round type. No API key required.
Rate limit: 600 req/min
Query Parameters
status string — OPEN | HEATMAP | LOCKED | RESOLVED | VOIDED (comma-separated)
asset string — Filter by asset (e.g. BTC, ETH)
roundType string — FLASH | BLITZ | SPRINT | DAILY | WEEKLY | EVENT
limit number — Results per page (1–100, default 20)
offset number — Pagination offset (default 0)
sort string — asc | desc
Example
curl https://api.unhedged.gg/api/v1/ctm/rounds
Response
{
  "rounds": [
    {
      "id": "clx...",
      "asset": "BTC",
      "roundType": "BLITZ",
      "status": "OPEN",
      "question": "Where will BTC be in 5 min?",
      "entryFeeBlind": "10.0000000000",
      "totalPool": "500.0000000000",
      "predictionCount": 12,
      "openStartsAt": "2026-04-05T12:00:00.000Z",
      "heatmapStartsAt": "2026-04-05T12:03:00.000Z",
      "lockedStartsAt": "2026-04-05T12:04:00.000Z",
      "resolvesAt": "2026-04-05T12:05:00.000Z",
      ...
    }
  ],
  "total": 58
}
GET /api/v1/ctm/rounds/:id PUBLIC
Get Round Details Returns one CTM round with phase times, fees, pool size, and settlement fields. No API key required.
Rate limit: 600 req/min
Example
curl https://api.unhedged.gg/api/v1/ctm/rounds/<id>
Response
{
  "round": {
    "id": "clx...",
    "asset": "BTC",
    "roundType": "BLITZ",
    "status": "OPEN",
    "question": "Where will BTC be in 5 min?",
    "description": "...",
    "entryFeeBlind": "10.0000000000",
    "heatmapFeeMultiplier": "2.0000000000",
    "platformFeeRate": "0.0500000000",
    "totalPool": "500.0000000000",
    "predictionCount": 12,
    "openStartsAt": "2026-04-05T12:00:00.000Z",
    "heatmapStartsAt": "2026-04-05T12:03:00.000Z",
    "lockedStartsAt": "2026-04-05T12:04:00.000Z",
    "resolvesAt": "2026-04-05T12:05:00.000Z",
    "settlementPrice": null,
    "resolvedAt": null,
    ...
  }
}
GET /api/v1/ctm/rounds/:id/heatmap PUBLIC
Get Heatmap Returns prediction counts by price bucket for one round. No API key required.
Rate limit: 600 req/min
Query Parameters
bucketCount number — Number of buckets (5–50, default 20)
Example
curl https://api.unhedged.gg/api/v1/ctm/rounds/<id>/heatmap
Response
{
  "heatmap": [
    { "min": 83000, "max": 83500, "count": 3 },
    { "min": 83500, "max": 84000, "count": 7 },
    ...
  ]
}
GET /api/v1/ctm/rounds/:id/results PUBLIC
Get Round Results Returns settlement price, payout totals, and top predictions for one resolved round. No API key required.
Rate limit: 600 req/min
Example
curl https://api.unhedged.gg/api/v1/ctm/rounds/<id>/results
Response
{
  "round": { ... },
  "result": {
    "settlementPrice": "84250.5000000000",
    "totalPool": "2000.0000000000",
    "platformFee": "100.0000000000",
    "payoutPool": "1900.0000000000",
    "totalPredictions": 45,
    "uniquePlayers": 32,
    "resolvedAt": "2026-04-05T12:05:00.000Z"
  },
  "topPredictions": [...]
}
GET /api/v1/ctm/rounds/:id/price-history PUBLIC
Get Price History Returns a round asset's price history, downsampled to at most 600 points. No API key required.
Rate limit: 300 req/min
Example
curl https://api.unhedged.gg/api/v1/ctm/rounds/<id>/price-history
Response
{
  "prices": [
    { "price": 84100.5, "bid": 84099.0, "ask": 84102.0, "timestamp": 1712318400000 },
    { "price": 84110.2, "bid": 84109.0, "ask": 84111.5, "timestamp": 1712318410000 },
    ...
  ],
  "asset": "BTC"
}
POST /api/v1/ctm/rounds/:id/predict AUTH
Place a Prediction Creates a price prediction for an open CTM round and deducts the entry fee from available balance.
Scope: bet:place Rate limit: 80 req/min
Request Body
predictedPrice number * — Your predicted settlement price (min 0)
idempotencyKey string — Optional dedup key to prevent double entries
Example
curl -X POST https://api.unhedged.gg/api/v1/ctm/rounds/<id>/predict \
  -H "Authorization: Bearer ak_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
  "predictedPrice": 84250.50
}'
Response
{
  "prediction": {
    "id": "clx...",
    "roundId": "clx...",
    "phase": "BLIND",
    "predictedPrice": "84250.5000000000",
    "amount": "10.0000000000",
    "status": "CONFIRMED"
  },
  "balanceAfter": "490.0000000000",
  "fee": "10.0000000000",
  "phase": "BLIND"
}
GET /api/v1/ctm/my-predictions AUTH
List Your Predictions Returns your CTM predictions, optionally filtered by roundId.
Scope: bet:read Rate limit: 60 req/min
Query Parameters
roundId string — Filter by specific round
Example
curl https://api.unhedged.gg/api/v1/ctm/my-predictions \
  -H "Authorization: Bearer ak_your_key_here"
Response
{
  "predictions": [
    {
      "id": "clx...",
      "roundId": "clx...",
      "phase": "BLIND",
      "predictedPrice": "84250.5000000000",
      "amount": "10.0000000000",
      "status": "CONFIRMED",
      "distance": "50.5000000000",
      "rank": 3,
      "payoutAmount": "25.0000000000",
      "submittedAt": "2026-04-05T12:01:00.000Z",
      "round": {
        "question": "Where will BTC be in 5 min?",
        "asset": "BTC",
        "status": "RESOLVED"
      }
    }
  ]
}
GET /api/v1/ctm/my-stats AUTH
Get Your CTM Stats Returns your CTM win rate, streaks, prediction count, and payout total.
Scope: bet:read Rate limit: 60 req/min
Example
curl https://api.unhedged.gg/api/v1/ctm/my-stats \
  -H "Authorization: Bearer ak_your_key_here"
Response
{
  "stats": {
    "totalRounds": 85,
    "totalPredictions": 102,
    "totalWins": 28,
    "totalPayouts": "5600.0000000000",
    "bestRank": 1,
    "currentStreak": 3,
    "bestStreak": 7,
    "avgDistance": "125.3400000000"
  }
}

Order Book Markets

ORDER_BOOK markets use a central limit order book instead of pool betting. You quote prices for binary outcomes (e.g. YES / NO) on a continuous market between 0 and 1, where the price represents the implied probability.

Market discovery

Markets in /api/v1/feed carry a marketType field: ORDER_BOOK markets use the endpoints below. PARIMUTUEL are pool markets — use the /api/v1/bets endpoints.

Sides

bid = buy this outcome. ask = sell this outcome. Selling YES at p is economically equivalent to buying NO at 1 − p.

Prices

REST order decimals use strings with exactly 2 decimal places, e.g. "0.59". Contract counts use numbers, e.g. 100. Prices are strict (0, 1). Invalid decimals return code, field, and received.

Fees

ORDER_BOOK markets do not use platformFeeRate or per-market maker/taker fields. Set engineMarketType on create (default default; also sports_vip). The API returns feeSchedule (tiers from market_fee_type). Per fill, orders expose cum_fees and last_trade_fee. locked_fees is derived from the snapshotted maker_fee_rate, order price, and remaining quantity using the risk-shaped curve.

Settlement & balance lock

When you place an order, the maximum cost is moved from balance.available to balance.lockedEngine — visible immediately on GET /balance. On a fill, the matched portion is debited from lockedEngine and a position contract is created on the Canton ledger. On cancel, any unfilled portion returns to available. Trade matching is real-time off-chain; settlement is atomic on-chain in batches.

No server-side idempotency

POST /orders does not deduplicate retries. If a request times out, query GET /orders before resubmitting. Maintain a client-side dedup key per logical order.

Market Gateway WebSocket

Order-book market data is served by the Rust market gateway, a separate WebSocket process from the REST API. Use REST /api/v1/orders for authenticated order placement and cancellation. Use the gateway for public ORDER_BOOK discovery, snapshots, live book updates, trade ticks, and execution quotes.

WebSocket URL
wss://unhedged.gg/market-gateway/ws
Protocol

JSON-RPC over standard WebSocket text frames. Requests include id, method, and optional params. Responses echo id with either result or error.

State Source

The gateway mirrors the matching engine's market-data stream in memory. It does not query PostgreSQL for live catalog or book state.

WebSocket Close

On gateway shutdown, clients receive a WebSocket close event with code 1012 and reason market gateway stopped; reconnect required. Reconnect, request a fresh snapshot, then resume sequenced deltas.

Connection Example
const ws = new WebSocket("wss://unhedged.gg/market-gateway/ws");

ws.onopen = () => {
  ws.send(JSON.stringify({
    id: 1,
    method: "subscribe",
    params: {
      market_id: "clx...",
      best_bid_ask: true,
      snapshot_update: true,
      trade: true
    }
  }));
};

ws.onmessage = (event) => {
  const message = JSON.parse(event.data);
  if (message.id) {
    // JSON-RPC response
    console.log(message.result ?? message.error);
    return;
  }

  // Unsolicited gateway event: best_bid_ask, snapshot_update, trade, etc.
  console.log(message);
};
Gateway Methods
list_markets List active matching-engine markets from the gateway catalog.
snapshot Return the gateway order-book mirror for one market.
subscribe Subscribe to best bid/ask, book diff, and trade streams for a market.
unsubscribe Remove one or more active subscriptions for a market.
query_trade Fetch historical public trade ticks for a market, capped at 500 trade IDs.
quote_execution Estimate immediate fill size and notional against the mirrored L2 book.
Gateway Errors
-32601 Method not found.
-32602 Invalid params.
-32004 Market or outcome does not exist in the active gateway catalog.
-32005 Gateway message rate limit exceeded.
Gateway Events
new_market

Lifecycle event when a market is added to the gateway catalog.

finish_market

Lifecycle event when a market leaves the gateway catalog.

best_bid_ask

Top-of-book update for one outcome.

snapshot_update

Incremental L2 book diff for one or more price levels.

trade

Unified public trade tick. Direct and cross-outcome fills use the same msg value.

Gateway trade_id is a JSON number on the wire. REST and private /ws order/trade IDs are strings.

Snapshot Request
{
  "id": 2,
  "method": "snapshot",
  "params": { "market_id": "clx..." }
}
Quote Request
{
  "id": 3,
  "method": "quote_execution",
  "params": {
    "market_id": "clx...",
    "outcome": "out_0",
    "side": "bid",
    "type": "Limit",
    "price": 0.50,
    "quantity": 10
  }
}
Quote Limitations

quote_execution is an immediate-fill estimate from the gateway mirror. It uses aggregated L2 depth, does not model your balance or self-trade prevention, and can lag while the gateway processes market-data messages. Place and cancel orders through the authenticated REST endpoints.

User Fill Stream

Bots use /ws for private order events. Authenticate with an API key that has order:read. Place, cancel, and list orders through REST. GET /api/v1/trades uses the same signed top-level fee / cashflow convention; nested order.cum_fees stays engine-positive.

WebSocket URL
wss://api.unhedged.gg/ws
Scope
order:read
Auth Message
{
  "type": "auth",
  "token": "Bearer ak_your_key_here"
}
Auth Responses
// success
{ "type": "authenticated", "userId": "clx...", "role": "USER" }

// missing order:read
{
  "type": "error",
  "error": "forbidden",
  "code": "MISSING_SCOPE",
  "scope": "order:read"
}
trade

Your executed order leg. Top-level fee is signed (negative when paid); cashflow equals payment plus fee. Same convention on GET /api/v1/trades.

share_update

Live share position snapshot for one market/outcome after ask lock/unlock, trade settlement, or auto-redeem. available, locked_me, and total (available + locked_me) are post-change contract counts; treat as authoritative for that leg.

burned

Your complete-set burn/redeem event. payment and cashflow are $1 per burned set.

conditional_order

Final order snapshot for a triggered stop-loss or take-profit step.

cancelled_order

Final order snapshot for a cancelled resting order.

Trade Event Shape
{
  "type": "trade",
  "room": "me:clx...",
  "timestamp": 1770000000000,
  "data": {
    "exec_id": "42",
    "trade_id": "184221",
    "qty": 25,
    "transact_ts": 1770000000000000,
    "market_id": "clx...",
    "trade_type": "direct",
    "role": "taker",
    "is_maker": false,
    "price": "0.62",
    "fee": "-0.25",
    "payment": "-15.5",
    "cashflow": "-15.75",
    "order": {
      "id": "123",
      "market_id": "clx...",
      "outcome": "out_0",
      "outcome_label": "YES",
      "side": "bid",
      "type": "Limit",
      "user": "clx_balance...",
      "price": "0.62",
      "quantity": 100,
      "cum_quantity": 25,
      "cum_quote_quantity": "15.5",
      "cum_fees": "0.25",
      "last_trade_fee": "0.25",
      "last_exec_price": "0.62",
      "status": "Placed"
    }
  }
}
Burn Event Shape
{
  "type": "burned",
  "room": "me:clx...",
  "timestamp": 1770000000000,
  "data": {
    "msg": "burned",
    "burn_id": "1",
    "market_id": "clx...",
    "balance_id": "clx_balance...",
    "quantity": 1,
    "cause_type": "trade",
    "reference_id": "clx...:184221",
    "payment": "1",
    "cashflow": "1",
    "transact_ts": 1770000000000000
  }
}
Share Update Event Shape
{
  "type": "share_update",
  "room": "me:clx...",
  "timestamp": 1770000000000,
  "data": {
    "msg": "share_update",
    "user": "clx_balance...",
    "market_id": "clx...",
    "outcome": "out_0",
    "outcome_label": "YES",
    "available": 12,
    "locked_me": 3,
    "total": 15,
    "cause_type": "trade",
    "reference_id": "clx...:184221",
    "transact_ts": 1770000000000000
  }
}
Bot Connection Example
const apiKey = process.env.UNHEDGED_API_KEY;
const marketId = "clx...";
const ws = new WebSocket("wss://api.unhedged.gg/ws");

ws.onopen = () => {
  ws.send(JSON.stringify({
    type: "auth",
    token: `Bearer ${apiKey}`
  }));
};

ws.onmessage = async (event) => {
  const message = JSON.parse(event.data);
  if (message.type === "authenticated") {
    await backfillFills(marketId);
    return;
  }
  if (message.type === "trade") {
    handleFill(message.data);
  }
  if (message.type === "share_update") {
    applyShareSnapshot(message.data);
  }
  if (message.type === "burned") {
    handleBurn(message.data);
  }
};
Reconnect Recovery

The stream has no replay cursor. After connect or reconnect, call GET /api/v1/trades?limit=500&startTime=<last fill time> (ISO 8601 or epoch milliseconds — divide transact_ts micros by 1000) so the backfill stops at your last processed fill instead of paging the full history, and dedupe fills by market_id, trade_id, and order_id. Apply live share_update snapshots to your positions map; on reconnect also refresh with GET /api/v1/portfolio/me/shares?marketId=clx... in case any snapshots were missed.

POST /api/v1/orders AUTH
Place Order Creates a Limit, Market, StopLoss, or TakeProfit order on an ORDER_BOOK market. On acceptance, cost moves from available balance to lockedEngine.
Scope: order:place Rate limit: 120 req/min · 60 per market
Request Body
marketId string * — Market ID. Must be type ORDER_BOOK and status ACTIVE.
outcome string * — Outcome label or engine ID. Ambiguous label/engine ID collisions return 400.
side string * — "bid" (buy outcome) or "ask" (sell outcome).
type string * — Limit | Market | StopLoss | TakeProfit
price string — Limit price decimal string in (0,1) with exactly 2 decimals. Required for Limit orders, omitted for Market/Stop/TP.
triggerPrice string — Trigger price decimal string in (0,1) with exactly 2 decimals. Required for StopLoss/TakeProfit.
quantity number — Number of contracts as a positive integer. Mutually exclusive with quoteQuantity.
quoteQuantity string — Cost cap decimal string in CC (> 0) with exactly 2 decimals. Mutually exclusive with quantity.
Example
curl -X POST https://api.unhedged.gg/api/v1/orders \
  -H "Authorization: Bearer ak_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
  "marketId": "clx...",
  "outcome": "YES",
  "side": "bid",
  "type": "Limit",
  "price": "0.62",
  "quantity": 100
}'
Response
{
  "order": {
    "id": "213",
    "market_id": "clx...",
    "outcome": "YES",
    "side": "bid",
    "type": "Limit",
    "price": "0.62",
    "quantity": 100,
    "exec_id": "42",
    "cum_quantity": 0,
    "cum_quote_quantity": "0",
    "cum_fees": "0",
    "last_exec_price": null,
    "status": "Placed"
  }
}
DELETE /api/v1/orders AUTH
Cancel Orders Cancels one or more open orders in a single market. Pass an empty orderIds array to cancel every open order in that market.
Scope: order:cancel Rate limit: 120 req/min · 60 per market
Request Body
marketId string * — Market ID
orderIds string[] * — IDs to cancel. Empty array = cancel all in this market.
Example
curl -X DELETE https://api.unhedged.gg/api/v1/orders \
  -H "Authorization: Bearer ak_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
  "marketId": "clx...",
  "orderIds": ["213", "214"]
}'
Response
{
  "result": {
    "dropped": ["213", "214"]
  }
}
POST /api/v1/orders/replace AUTH
Replace Order Cancels one open Limit order and places one replacement Limit order in the same engine transaction. If price and quantity match the open order, the engine returns status "unchanged" and keeps the order id and priority. Requires scopes order:place and order:cancel.
Scope: order:place, order:cancel Rate limit: 120 req/min · 60 per market
Request Body
marketId string * — Market ID (must be ORDER_BOOK and active).
orderId string * — Numeric string ID of the open Limit order to replace.
price string * — New limit price decimal string in (0,1) with exactly 2 decimals.
quantity number * — New size in contracts as a positive integer. quoteQuantity is not supported here.
Example
curl -X POST https://api.unhedged.gg/api/v1/orders/replace \
  -H "Authorization: Bearer ak_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
  "marketId": "clx...",
  "orderId": "213",
  "price": "0.64",
  "quantity": 50
}'
Response
{
  "result": {
    "cancelledOrderId": "213",
    "replacementOrderId": "214",
    "cancelledOrder": { "id": "213", "status": "Cancelled", ... },
    "replacementOrder": { "id": "214", "status": "Placed", "price": "0.64", "quantity": 50, ... }
  }
}

// unchanged no-op:
{
  "result": {
    "status": "unchanged",
    "orderId": "213"
  }
}
GET /api/v1/orders AUTH
List Open Orders Returns open orders from newest to oldest. Omit cursor on page 1; pass nextCursor as cursor until nextCursor is null. Keep marketId, outcome, and limit fixed while paging.
Scope: order:read Rate limit: 120 req/min
Query Parameters
marketId string — Optional market filter.
outcome string — Optional label or engine ID filter. Requires marketId. Ambiguous label/engine ID collisions return 400.
limit number — Page size, 1-500. Default 100.
cursor string — Previous page nextCursor. Omit on page 1.
Example
curl https://api.unhedged.gg/api/v1/orders \
  -H "Authorization: Bearer ak_your_key_here"
Response
{
  "orders": [
    {
      "id": "213",
      "market_id": "clx...",
      "outcome": "YES",
      "side": "bid",
      "type": "Limit",
      "price": "0.62",
      "quantity": 100,
      "exec_id": "42",
      "cum_quantity": 25,
      "cum_fees": "0.5000000000",
      "status": "Placed",
      "created_at": "2026-04-12T15:33:21.000Z"
    }
  ],
  "nextCursor": "eyJjcmVhdGVkQXQiOiIxNzc2MDA4MDAxMDAwIiwibWFya2V0SWQiOiJjbHguLi4iLCJvcmRlcklkIjoiMjEzIn0"
}

// next request:
// GET /api/v1/orders?marketId=clx...&limit=100&cursor=eyJjcmVhdGVkQXQiOiIxNzc2MDA4MDAxMDAwIiwibWFya2V0SWQiOiJjbHguLi4iLCJvcmRlcklkIjoiMjEzIn0

// final page:
{
  "orders": [],
  "nextCursor": null
}
GET /api/v1/trades AUTH
Trade History Returns fills from newest to oldest. Omit cursors on page 1. Pass nextCursor as cursor and burnsNextCursor as burnsCursor. Stop each stream when its cursor is null. Use startTime/endTime to bound a time window.
Scope: order:read Rate limit: 120 req/min
Query Parameters
marketId string — Optional market filter.
limit number — Page size, 1-500. Default 100.
cursor string — Previous trades nextCursor. Omit on page 1.
burnsCursor string — Previous burnsNextCursor. Omit on page 1.
startTime string — Optional inclusive lower bound on execution time (transact_ts). ISO 8601 date-time, or Unix epoch milliseconds if all digits. Applies to trades and burns.
endTime string — Optional inclusive upper bound on execution time (transact_ts). Same formats as startTime. Must not be before startTime.
Example
curl https://api.unhedged.gg/api/v1/trades \
  -H "Authorization: Bearer ak_your_key_here"
Response
{
  "trades": [
    {
      "market_id": "clx...",
      "trade_id": "184221",
      "order_id": "213",
      "exec_id": "42",
      "outcome": "YES",
      "side": "bid",
      "trade_type": "direct",
      "role": "taker",
      "is_maker": false,
      "price": "0.62",
      "qty": 25,
      "quote_quantity": "15.5",
      "fee": "-0.25",
      "payment": "-15.5",
      "cashflow": "-15.75",
      "transact_ts": "1770000000000000000",
      "created_at": "2026-04-12T15:33:21.000Z"
    }
  ],
  "burns": [
    {
      "msg": "burned",
      "burn_id": "1",
      "market_id": "clx...",
      "balance_id": "clx_balance...",
      "quantity": 1,
      "cause_type": "trade",
      "reference_id": "clx...:184221",
      "payment": "1",
      "cashflow": "1",
      "transact_ts": "1770000000000000000",
      "created_at": "2026-04-12T15:33:22.000Z"
    }
  ],
  "nextCursor": "eyJ0cmFuc2FjdFRpbWUiOiIxNzcwMDAwMDAwMDAwMDAwMDAwIiwibWFya2V0SWQiOiJjbHguLi4iLCJ0cmFkZUlkIjoiMTg0MjIxIiwib3JkZXJJZCI6IjIxMyJ9",
  "burnsNextCursor": "eyJ0cmFuc2FjdFRpbWUiOiIxNzcwMDAwMDAwMDAwMDAwMDAwIiwibWFya2V0SWQiOiJjbHguLi4iLCJidXJuSWQiOiIxIn0"
}

// next request, continuing both streams:
// GET /api/v1/trades?marketId=clx...&limit=100&cursor=eyJ0cmFuc2FjdFRpbWUiOiIxNzcwMDAwMDAwMDAwMDAwMDAwIiwibWFya2V0SWQiOiJjbHguLi4iLCJ0cmFkZUlkIjoiMTg0MjIxIiwib3JkZXJJZCI6IjIxMyJ9&burnsCursor=eyJ0cmFuc2FjdFRpbWUiOiIxNzcwMDAwMDAwMDAwMDAwMDAwIiwibWFya2V0SWQiOiJjbHguLi4iLCJidXJuSWQiOiIxIn0

// final page:
{
  "trades": [],
  "nextCursor": null
}

Types & statuses

Values returned on order objects and accepted by POST /api/v1/orders. Engine order WebSocket RPC names are listed for debugging direct ME connections — production bots should use REST.

Order types
Limit Rests on the book at price. Requires price as a decimal string in (0,1) with exactly 2 decimals.
Market Consumes liquidity immediately. No price; bounded only by available balance / book depth.
StopLoss Conditional. Triggers a market-style execution when last trade price falls to triggerPrice.
TakeProfit Conditional. Triggers a market-style execution when last trade price rises to triggerPrice.
Order statuses
New Accepted by API; not yet on the book.
Placed Resting on the book (Limit) or armed (StopLoss/TakeProfit).
Filled Fully executed. cum_quantity == requested quantity.
Cancelled Cancelled by user (or auto-cancelled at market close).
InternalCancel Engine could not place the resting remainder (e.g. insufficient balance after partial fill). Includes a reason string.
Cursor pagination
Page 1 Omit cursor. Use limit to choose 1-500 rows; the default is 100.
Next page Pass nextCursor as cursor. Stop when nextCursor is null.
Stable filters Keep marketId, outcome, limit, startTime, and endTime fixed within one cursor chain.
Trade burns Trades and burns page independently. Pass nextCursor as cursor and burnsNextCursor as burnsCursor.
Engine order WebSocket RPC
new_order Engine order RPC behind POST /api/v1/orders. Wire params use snake_case fields such as market_id, trigger_price, and quote_quantity.
cancel_order Engine order RPC behind DELETE /api/v1/orders. Cancels one or more open order IDs in a single market.
replace_order Behind POST /api/v1/orders/replace. Atomically cancel one open Limit and submit a new Limit (market_id, order_id, price, quantity). Outcome, side, and type are copied from the source order.
query_orders Engine order RPC used to list a user's open orders for one market.