Skip to content
UNHEDGED / DOCS

Search documentation

Browse docs or start typing to find a topic

↑ ↓ to navigate Enter to open Esc to close

Documentation

Back to app
API Reference v1

Build on Unhedged.

A REST API for prediction markets. Public endpoints for market data; authenticated endpoints for trading shares and managing your balance. Trading balances, prices, fees, and payouts are denominated in USD.

Building with an AI agent? Connect through the MCP server.

Make your first request

Fetch live market data from your terminal. Public market reads work without an account or API key.

  1. 1

    Fetch an active market

    Terminal
    curl 'https://api.unhedged.gg/api/v1/feed?status=ACTIVE&limit=1'

    The feed returns standalone markets and market groups. An empty items array means no markets match your filters.

  2. 2

    Read the response

    Example response · selected fields
    {
      "items": [
        {
          "type": "market",
          "market": {
            "id": "example_market_id",
            "question": "Will this event happen?",
            "status": "ACTIVE",
            "marketType": "ORDER_BOOK",
            "outcomes": [
              { "index": 0, "label": "Yes" },
              { "index": 1, "label": "No" }

    Use the returned market.id when requesting market details. For an item with type: "binary_group", choose a child market from group.markets.

Authentication

Create a key under Account → API Keys and select the scopes your integration needs. Withdrawals require an opt-in scope.

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

TypeScript SDK

@unhedged/typescript-sdk is a TypeScript client for market data, account reads, order-book trading, and WebSocket feeds.

Requires Node.js 20+ and ESM.

Install
npm install @unhedged/typescript-sdk

Save as markets.mjs and run node markets.mjs. No API key is needed; TypeScript uses the same imports.

markets.mjs
import { UnhedgedClient } from '@unhedged/typescript-sdk';

const client = new UnhedgedClient();
const { markets } = await client.markets.list({
  status: ['ACTIVE'], includeGrouped: true, limit: 5,
});
console.log(markets);

Pass apiKey to UnhedgedClient for protected calls. The API defaults to https://api.unhedged.gg; a custom baseUrl must omit /api/v1. Create a key under Account → API Keys with the required scopes.

Full SDK documentation on npm, including authentication, order handling, streaming, and runnable examples.

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 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.
portfolio:read
Read portfolio positions, equity snapshots, and history records.
user:read
Read your own profile via GET /api/v1/users/me.
user:write
Update only your trading preferences via PATCH /api/v1/users/me/preferences. Selected by default in Account → API Keys. Include it explicitly when creating keys through the API. Does not grant general profile editing.
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

Endpoint cards show default limits. Order-book limits apply per API key or, for session requests, per user. Quotes and reads share a budget; placement and replacement share another. Cancellations have a separate budget. Configured limits and API-key profiles can override these defaults. When a limit is exceeded you'll receive 429 Too Many Requests — back off and retry.

Global default
600 req/min
Authenticated reads
60 req/min
Order placement / replacement
120 req/min · 60 per market
Order cancellation (separate budget)
120 req/min · 60 per market
Order quotes / reads
240 req/min
Withdrawals
5 req/min
API key creation
10 req/hour

Errors

Error bodies use { "error": "ErrorType", "message": "..." }. Some 4xx responses also include a machine-readable code.

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 — requested resource 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 — feed or portfolio calculation capacity is full, or a feature is temporarily disabled. Capacity responses include a Retry-After header.

Portfolio capacity and retries

Full equity and equity history share a calculation-capacity limit; equity summary is independent. At capacity, reads return 503 immediately. Per-user rate limits return 429.

Portfolio overload response
HTTP/1.1 503 Service Unavailable
Retry-After: 1
Cache-Control: no-store
Content-Type: application/json

{
  "statusCode": 503,
  "error": "Service Unavailable",
  "message": "Portfolio calculations are busy. Please retry shortly."
}
  • Wait at least the Retry-After HTTP header's delay in seconds before retrying the same GET.
  • For repeated overload, use exponential backoff with jitter and a bounded retry count. Do not retry the same read in parallel.
  • Retries count toward rate limits. Retry-After does not reserve capacity or guarantee success.
  • Show failed financial reads as unavailable, not zero or stale values.

Markets

GET /api/v1/feed Public

List Feed (Markets + Groups)

Sorted feed of standalone markets and market groups, with item type "market" or "binary_group". Grouped markets appear only inside their group. For category=esports, gameCounts contains per-title totals across all filtered pages. Anonymous responses may reuse a snapshot for one second. At capacity, returns 503 with Retry-After: 1.

Rate limit: 120 req/min per IP

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)
Request 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",
GET /api/v1/markets/:id Public

Get Market Details

Returns one market with outcomes, status, and trading details.

Rate limit: 600 req/min
Request example
curl https://api.unhedged.gg/api/v1/markets/REPLACE_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" }
GET /api/v1/balance Requires API key

Get Balance

Returns available, locked, deposited, withdrawn, and total USD amounts, read from the database on every request (Cache-Control: no-store). withdrawalFee and bettingFee are also in USD. Held Canton Coin is reported separately in ccAvailable and ccLocked (CC units); ccPriceUsd is the USD price of one CC.

Scope: balance:read Rate limit: 60 req/min
Request 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",
    "ccAvailable": "25.0000000000",

Users

PATCH /api/v1/users/me/preferences Requires API key

Update Trading Preferences

Updates account-wide trading preferences, also available in Account Settings. Send at least one of autoBurn, marketCashHeadroomEnabled or marketCashHeadroomUsd. Omitted fields stay unchanged; the response contains only the fields sent. Accepts login sessions and API keys with user:write. All accounts can send { "autoBurn": false } alone. Only MARKET_MAKER accounts can send either headroom field; other roles receive 403 even when marketCashHeadroomEnabled is false. The example is for a market-maker account. Saving preferences does not move cash or redeem shares; final market settlement is unchanged. PATCH /users/:userId remains unavailable to API keys.

Scope: user:write Rate limit: 60 req/min

Request body

autoBurn boolean
Optional
Defaults to true. Redeems complete available sets for $1 each, excluding shares locked in orders. Set false to keep shares until sold or settled. Takes effect on a later eligible fill or ask unlock and does not reverse earlier burns.
marketCashHeadroomEnabled boolean
Optional
Market makers only. Defaults to true for market makers. Enables a best-effort spare cash target per active market, set by marketCashHeadroomUsd. The engine shares free cash across active markets. Already-funded requests do not refill it. Applies to cash accounts; does not reserve credit grants.
marketCashHeadroomUsd string
Optional
Market makers only. Positive USD decimal string below 10000000000 with at most 2 decimal places. Defaults to "50.00". Returned with exactly 2 decimal places. Can be saved while marketCashHeadroomEnabled is false. Unused cash remains available for other orders and withdrawals.
Request example
curl -X PATCH https://api.unhedged.gg/api/v1/users/me/preferences \
  -H "Authorization: Bearer ak_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{ "autoBurn": false, "marketCashHeadroomEnabled": true, "marketCashHeadroomUsd": "50.00" }'
Response
{ "autoBurn": false, "marketCashHeadroomEnabled": true, "marketCashHeadroomUsd": "50.00" }
GET /api/v1/users/me Requires API key

Get Current User

Returns your profile and preferences. Accepts login sessions and API keys with user:read. autoBurn defaults to true. marketCashHeadroomEnabled defaults to true for market makers and is always false for other roles. marketCashHeadroomUsd defaults to "50.00" and is returned with exactly 2 decimal places. Change autoBurn in Account Settings or PATCH /api/v1/users/me/preferences. Only market makers can change the headroom fields.

Scope: user:read Rate limit: 60 req/min
Request 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,

Portfolio

GET /api/v1/portfolio/me Requires API key

Get Portfolio

Returns summary statistics, a preview of up to 50 active/ended positions, and the 10 most recent payouts. The example shows selected order-book summary fields. positionsTotal is the complete active/ended row count represented by /portfolio/me/positions; use that paginated endpoint for positions beyond the preview.

Scope: portfolio:read Rate limit: 1,000,000 req/min
Request example
curl https://api.unhedged.gg/api/v1/portfolio/me \
  -H "Authorization: Bearer ak_your_key_here"
Response
{
  "portfolio": {
    "summary": {
      "pendingOrderBookPositions": 3,
      "pendingOrderBookCostBasis": "124.5000000000",
      ...
    },
    "positions": [...],
    "positionsTotal": 61,
    "recentPayouts": [...]
GET /api/v1/portfolio/me/positions Requires API key

Get Active Positions

Returns unresolved positions. Use marketType=ORDER_BOOK for paginated share inventory across markets, avoiding per-market /portfolio/me/shares requests.

Scope: portfolio:read Rate limit: 1,000,000 req/min

Query parameters

marketId string
Optional filter to a single market.
marketType string
Use ORDER_BOOK to return order-book positions.
limit number
Results per page (default 50, 1–500).
offset number
Pagination offset (default 0).
Request example
curl https://api.unhedged.gg/api/v1/portfolio/me/positions \
  -H "Authorization: Bearer ak_your_key_here"

Response fields

ORDER_BOOK fields
Order-book positions include quantity, availableQuantity, lockedQuantity, averagePrice, hasBids, feesPaid, openOrderCount, and canSell. markPrice is included when hasBids is true.
markPrice, currentValue, unrealized P/L
markPrice is the best executable sell bid, including cross-outcome liquidity. Value and P/L estimates exclude exit fees and do not guarantee depth for the full position.
hasBids
When false, markPrice, currentValue, estimatedPayout, unrealizedPnl, and unrealizedPnlPercent are omitted. Also false for unresolved markets that are ENDED or at/after scheduledResolutionTime ?? endTime, even if resting orders remain.
canSell
True when the market is ACTIVE, before its cutoff, and shares are available to sell. A limit sell can still be placed without a bid.
Response
{
  "positions": [
    {
      "marketId": "clx...",
      "marketType": "ORDER_BOOK",
      "outcomeIndex": 0,
      "outcomeName": "YES",
      "totalBetAmount": "64.5000000000",
      "betCount": 2,
      "currentValue": "71.2000000000",
GET /api/v1/portfolio/me/shares Requires API key

Get Order-Book Shares

Returns every ACTIVE and ENDED ORDER_BOOK share row, including zero balances. Use marketId after a fill to refresh one market (uncached; VOIDED excluded). Resolved history uses cursor pagination; follow nextCursor until null. Collections are cached for 10 seconds.

Scope: portfolio:read Rate limit: 60 req/min (120 req/min with ?marketId=)

Query parameters

marketId string
Exact market (uncached). Cannot be combined with status, limit, or cursor.
status string
RESOLVED for history. Omit for current inventory.
limit number
History page size, 1–100. Default 100. Requires status=RESOLVED.
cursor string
Previous page nextCursor. Omit on page 1. Requires status=RESOLVED.
Request 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,
      "costAmount": "9.6750000000",
      "updatedAt": "2026-04-12T15:34:01.000Z",
GET /api/v1/portfolio/me/equity-summary Requires API key

Get Equity Summary

Returns cash, locked balances, and the marked value of open ORDER_BOOK positions from one fresh database snapshot per request (Cache-Control: no-store). Use /api/v1/portfolio/me/equity for day and seven-day P/L.

Scope: portfolio:read Rate limit: 120 req/min per user
Request example
curl https://api.unhedged.gg/api/v1/portfolio/me/equity-summary \
  -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",
    "updatedAt": "2026-04-12T15:35:00.000Z",
    "asOf": "2026-04-12T15:35:00.000Z"
GET /api/v1/portfolio/me/equity Requires API key

Get Equity Snapshot

Returns available cash, locked balances, open ORDER_BOOK mark-to-market value, and rolling P/L from one fresh database snapshot per request (Cache-Control: no-store). Use /api/v1/portfolio/me/equity-summary for totals without rolling P/L. Returns 503 with Retry-After: 1 at capacity.

Scope: portfolio:read Rate limit: 30 req/min per user

Query parameters

fresh string
Optional compatibility parameter. All reads are uncached.
Request example
curl https://api.unhedged.gg/api/v1/portfolio/me/equity \
  -H "Authorization: Bearer ak_your_key_here"
Portfolio retry guidance
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,
GET /api/v1/portfolio/me/equity-history Requires API key

Get Equity History

Returns funding-neutral P/L chart points for the past day or week from one fresh database snapshot per request (Cache-Control: no-store). Each equity value is cumulative P/L within that window. Returns 503 with Retry-After: 1 at capacity.

Scope: portfolio:read Rate limit: 30 req/min per user

Query parameters

range string
1D or 1W. Default 1D.
Request example
curl https://api.unhedged.gg/api/v1/portfolio/me/equity-history \
  -H "Authorization: Bearer ak_your_key_here"
Portfolio retry guidance
Response
{
  "range": "1D",
  "points": [
    { "t": "2026-04-11T15:35:00.000Z", "equity": "0.0000000000" },
    { "t": "2026-04-11T16:35:00.000Z", "equity": "2.5000000000" }
  ]
}
GET /api/v1/portfolio/me/history Requires API key

Get Position History

Returns resolved and voided market positions from newest to oldest by market.resolutionTime. The default page size is 50 and the maximum is 500. ORDER_BOOK pagination is by market, so one market can return multiple outcome rows. When payoutPending is true, winning shares await a payout credit: payout reports only the credit recorded so far, and final P/L must wait. Pending payouts and exact-market reads bypass the stored history cache. Use status=RESOLVED|VOIDED and startTime/endTime to filter a resolution window, for example /api/v1/portfolio/me/history?marketType=ORDER_BOOK&status=RESOLVED&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
Use ORDER_BOOK to return order-book positions.
status string
RESOLVED | VOIDED
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 (default 50, 1–500).
offset number
Pagination offset (default 0).
Request example
curl https://api.unhedged.gg/api/v1/portfolio/me/history \
  -H "Authorization: Bearer ak_your_key_here"
Response
{
  "positions": [
    {
      "marketId": "clx...",
      "marketType": "ORDER_BOOK",
      "outcomeIndex": 1,
      "outcomeName": "NO",
      "betAmount": "50.0000000000",
      "payout": "92.0000000000",
      "payoutPending": false,

Order Book Markets

Order-book basics

Trade binary or multi-outcome markets on a central limit order book. Prices are quoted in USD per share, between 0 and 1. A price of 0.59 USD represents a 59% implied probability.

Crypto order books use a 20 ms maker-protection window for every account, including market makers, before incoming taking-capable orders or replacements can execute. Typed validation runs during this window. Read maker_protection_ms from the gateway's list_markets or new_market metadata for the configured window. A batch waits as one instruction if any leg can take. Matching checks current prices and funds after the wait; the API returns the final result.

Crypto markets with priceMethod: "twap-60s-v1" settle from two 60-second time-weighted average prices. Each reference averages 60 one-second closing prices in the minute before its boundary; the boundary itself is excluded. BTC, ETH, and SOL use Binance one-second candles. CC uses Bybit trades sampled once per second. Up wins when the final ending TWAP is greater than the final starting TWAP; equal or lower resolves Down. Range markets have fixed dollar bands and are listed after the starting TWAP is final.

Up/Down markets are listed five minutes before the start with a pending starting price. References can be provisional after their boundary and target finalization 10 seconds later. Missing source data can delay finalization. The market response's priceReferences contains each reference's status, exact price, source, time window, and samples. Use windowStartTime for the nominal market start. Markets without this price method retain their one-minute candle open settlement rule.

Other accounts' post-only quotes and cancels can run during the window. Your later mutations in the same market, including cancels, follow your pending orders. New stop-loss and take-profit registrations wait; already-armed conditions keep their existing activation rule. The window is a minimum age, not a maximum response time.

Find a market

Choose a market from the feed with marketType set to ORDER_BOOK and status set to ACTIVE.

Choose a side

bid buys an outcome; ask sells it. In binary markets, selling YES at p is economically equivalent to buying NO at 1 − p.

Prices & quantities

REST prices & budgets
Prices, trigger prices, and budgets are denominated in USD and use strings with exactly 2 decimal places, e.g. "0.59" means 0.59 USD.
Share quantities
Strings with 0–2 decimals; minimum "0.01". Positive integers mean whole contracts: 100 = "100.00". Fractions require strings; responses use decimal strings.
Price bounds
Limit and trigger prices: (0, 1), endpoints excluded. Optional Market price bounds: [0, 1], endpoints included.
Invalid decimals
Errors return code, field, and received.

Fees

Schedule
Set engineMarketType on creation: default (default) or sports_vip. The API returns feeSchedule tiers from market_fee_type. platformFeeRate and per-market maker/taker fields do not apply.
Per fill
Orders expose cum_fees and last_trade_fee.
Fee reserve
locked_fees uses the snapshotted maker_fee_rate, order price, and remaining quantity with the risk-shaped curve.

Settlement & balance lock

  1. Place: maximum cost moves from balance.available to balance.lockedEngine, visible immediately in GET /balance.
  2. Fill: the matched portion is debited from lockedEngine; a position contract is created on the Canton ledger.
  3. Cancel: unfilled funds return to available.

Matching is real-time off-chain; settlement is atomic on-chain in batches.

Client order IDs are not retry receipts

After a timeout, the outcome is unknown. Check every page of open orders (follow nextCursor) and trade history. If still unresolved, stop. Reusing the same client ID or choosing a new one can duplicate a fill.

Client order IDs
Optional clientOrderId on place and clientOrderIds on cancel identify open orders; order objects use client_order_id. An alias is unique among open orders for one balance and market, across outcomes and API keys. It can be reused after close. Trade history has numeric order IDs only.
Idempotency-Key cache
  • The same key can replay a completed success or committed-batch HTTP 400 for up to 60 seconds on POST, PUT, and PATCH. Pre-execution validation failures and DELETE cancellations are not cached.
  • After a committed-batch HTTP 400, inspect every results entry: successful legs may have filled or remain open.
  • Keys are not reserved in flight: simultaneous requests can both execute. A fresh key is a new request; an unresolved timeout remains ambiguous.
Market Gateway WebSocket

Stream public ORDER_BOOK market data and request execution quotes over WebSocket. Place and cancel orders through authenticated REST /api/v1/orders.

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

JSON-RPC over WebSocket text frames.

Request
id, method, optional params.
Response
Same id, plus result or error.
Reconnect after shutdown

Close code 1012: market gateway stopped; reconnect required.

  1. Reconnect.
  2. Request a fresh snapshot.
  3. Resume sequenced deltas.
Connection Example
const ws = new WebSocket("wss://market-gateway.unhedged.gg/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, including maker_protection_ms: the minimum age for incoming taking-capable instructions. Zero disables the window.
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 Historical public trade ticks for one market; at most 500 trade IDs.
quote_execution Estimates immediate fill size, notional, fees, and worst_execution_price from the mirrored L2 book. For Market orders, derive a price bound from best ask (buy) or best bid (sell); use it for both quote and placement. Zero tolerance forbids worse prices. Reuse quoteQuantity with budgetIncludesFees to cap live cost, including automatically selected fees.
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

Market added to the gateway catalog.

finish_market

Market removed from 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

Public trade tick; direct and cross-outcome fills share 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.00"
  }
}

Quantity format

Requests
quantity is in contracts: decimal string, at most 2 decimals, minimum "0.01". JSON integers mean whole contracts: 10 = "10.00".
Responses
Book levels, trade ticks, and quotes return quantities as 2-decimal strings.

Quote limitations

Estimate
quote_execution estimates immediate fills using aggregated L2 depth.
Excluded
Your balance and self-trade prevention.
Freshness
The gateway mirror can lag while processing market-data messages.
User Fill Stream

Receive private order events over /ws. Authenticate with an API key; use REST to place, cancel, and list orders.

WebSocket URL
wss://api.unhedged.gg/ws
Scope
order:read

Fee signs: Top-level fee is negative when paid; cashflow = payment + fee, as in GET /api/v1/trades. Nested order.cum_fees stays engine-positive.

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

Each match emits one event per leg: 2 for regular trades, N for cross trades. All share trade_id, unique within the market; key on market_id + trade_id + order_id. Top-level fee is signed (negative when paid); cashflow = payment + fee, as in GET /api/v1/trades.

share_update

Authoritative post-change snapshot for one market/outcome after ask lock/unlock, trade settlement, or auto-redeem. available, locked_me, and total = available + locked_me are contract quantities as 2-decimal strings.

burned

Complete-set redemption. quantity is a 2-decimal contract string; payment = cashflow = $1 per 1.00 set ("0.25" redeemed = $0.25).

conditional_order

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

cancelled_order

Final order snapshot for a cancelled resting order.

amended_order

Order snapshot after a same-price size decrease or an accepted higher revision with unchanged price and size. The order keeps its id and queue priority; refresh open orders and balances.

Trade Event Shape
{
  "type": "trade",
  "room": "me:clx...",
  "timestamp": 1770000000000,
  "data": {
    "exec_id": "42",
    "trade_id": "184221",
    "qty": "25.00",
    "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.00",
      "cum_quantity": "25.00",
      "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.00",
    "cause_type": "trade",
    "reference_id": "clx...:184221",
    "payment": "1.00",
    "cashflow": "1.00",
    "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.00",
    "locked_me": "3.00",
    "total": "15.00",
    "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

No replay cursor. After connecting or reconnecting:

  1. Backfill from your last processed fill. GET /api/v1/trades?limit=500&startTime=<last fill time> Use ISO 8601 or epoch milliseconds; divide transact_ts microseconds by 1000. This avoids paging the full history.
  2. Deduplicate fills. Key on market_id + trade_id + order_id.
  3. Refresh positions. Apply live share_update snapshots to your positions map. After reconnecting, recover missed snapshots with: GET /api/v1/portfolio/me/shares?marketId=clx...
POST /api/v1/orders/quote Requires API key

Quote Order

Estimates immediate fills and fees from the displayed book using your account's fee rates. Places no order and reserves no funds or liquidity. Does not check funds, share ownership, or self-trade restrictions; execution can change with the book. postOnly applies only to placement.

Scope: order:read Rate limit: 240 req/min shared with order reads (default)

Request body

marketId string
Required
Market ID. Must be type ORDER_BOOK and open for trading.
outcome string
Required
Outcome label or engine ID. Ambiguous label/engine ID collisions return 400.
side string
Required
"bid" (buy outcome) or "ask" (sell outcome).
type string
Required
Limit | Market | StopLoss | TakeProfit. StopLoss/TakeProfit quotes estimate Market execution against the book at quote time, not at the future trigger.
price string
Optional
Exactly 2 decimals. Required for Limit in (0,1); optional for Market in [0,1] as a maximum buy price or minimum sell price. Omit for StopLoss/TakeProfit.
triggerPrice string
Optional
Required for StopLoss/TakeProfit: exactly 2 decimals in (0,1). Validates the proposed order; the quote does not wait for the trigger.
quantity string | integer
Optional
Human shares: a string with 0–2 decimals (minimum 0.01), or a positive whole-contract integer. Supply exactly one of quantity or quoteQuantity.
quoteQuantity string
Optional
USD budget greater than zero with exactly 2 decimals. Notional-only by default. Supply exactly one of quantity or quoteQuantity.
budgetIncludesFees boolean
Optional
Defaults to false. For Market/Limit bids with quoteQuantity, include fees in the estimated budget. Place with the same quoteQuantity and budgetIncludesFees to enforce the cap using actual fees and any resting maker-fee reserve.
Request example
curl -X POST https://api.unhedged.gg/api/v1/orders/quote \
  -H "Authorization: Bearer ak_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
  "marketId": "clx...",
  "outcome": "YES",
  "side": "bid",
  "type": "Market",
  "price": "0.99",
  "quantity": "0.01"
}'

Response fields

quantity, worstExecutionPrice
Returned with 2 decimals; preserve this precision in calculations. worstExecutionPrice can be null when unavailable or inapplicable.
notional, takerFee, makerFeeLock
USD values with 10 decimals; takerFee is signed. Can be null when unavailable or inapplicable. Preserve this precision in calculations.
makerFeeRate, takerFeeRate, feeTier
Identify the rates used for the estimate.
Response
{
  "quote": {
    "quantity": "0.01",
    "notional": "0.0050000000",
    "takerFee": "0.0001250000",
    "makerFeeLock": null,
    "worstExecutionPrice": "0.50",
    "makerFeeRate": "0.02",
    "takerFeeRate": "0.05",
    "feeTier": 0
POST /api/v1/orders Requires API key

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
Required
Market ID. Must be type ORDER_BOOK and status ACTIVE.
outcome string
Required
Outcome label or engine ID. Ambiguous label/engine ID collisions return 400.
side string
Required
"bid" (buy outcome) or "ask" (sell outcome).
type string
Required
Limit | Market | StopLoss | TakeProfit
price string
Optional
Price decimal string with exactly 2 decimals. Required for Limit orders in (0,1). Optional for Market orders in [0,1]: maximum buy price or minimum sell price per fill; any unfilled remainder is cancelled. Omitted for Stop/TP.
triggerPrice string
Optional
Trigger price decimal string in (0,1) with exactly 2 decimals. Required for StopLoss/TakeProfit.
quantity string | integer
Optional
Size in human shares: string with 0–2 decimals (e.g. "10", "1.2", "1.25"; min 0.01) or a legacy positive integer in whole contracts (10 = 10.00). Mutually exclusive with quoteQuantity.
quoteQuantity string
Optional
Cost cap decimal string in USD (> 0) with exactly 2 decimals. Notional-only by default; set budgetIncludesFees for an all-in Market/Limit buy budget. Mutually exclusive with quantity.
budgetIncludesFees boolean
Optional
Defaults to false. For Market/Limit bids with quoteQuantity, includes actual execution fees and any resting maker-fee reserve in the USD cap. Fee rates are selected automatically; do not pass a rate.
clientOrderId string
Optional
Client id for later cancel/replace (1–64 chars, [A-Za-z0-9._:-], case preserved); returned as client_order_id. Unique among open orders for this balance in this market (all outcomes/API keys). Reusable after the order closes — not a placement retry receipt.
postOnly boolean
Optional
Defaults to false. Limit only: reject with 400 if postOnly is true and the order would immediately take direct or cross liquidity. Setting true on Market / StopLoss / TakeProfit is rejected. Supported on placement and replacement; quotes estimate execution without postOnly.
Request 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.00",
  "clientOrderId": "bot-42",
  "postOnly": true
}'
Response
{
  "order": {
    "id": "213",
    "market_id": "clx...",
    "outcome": "YES",
    "side": "bid",
    "type": "Limit",
    "price": "0.62",
    "quantity": "100.00",
    "client_order_id": "bot-42",
POST /api/v1/orders/batch Requires API key

Batch Place Orders

Places 1–32 orders on one ORDER_BOOK market. Legs may fill; Market type is rejected before any leg runs. Returns 201 when all legs succeed, or 400 with results in request order when a committed batch contains failures. Successful legs remain committed. Each failed leg has a string err; a committed zero-fill InternalCancel also includes its order. Inspect every result before retrying any leg. Counts as one write against the place/replace rate budget. The example shows a mixed HTTP 400 response with selected order fields.

Scope: order:place Rate limit: 120 req/min · 60 per market

Request body

marketId string
Required
Shared market ID. Must be type ORDER_BOOK and status ACTIVE.
orders array
Required
1–32 place legs. Same fields as Place Order except marketId, but type is Limit | StopLoss | TakeProfit only (Market not allowed). Optional postOnly on Limit legs. Legs may fill against the book and against earlier legs in the batch.
Request example
curl -X POST https://api.unhedged.gg/api/v1/orders/batch \
  -H "Authorization: Bearer ak_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
  "marketId": "clx...",
  "orders": [
    { "outcome": "YES", "side": "bid", "type": "Limit", "price": "0.40", "quantity": "5.00" },
    { "outcome": "YES", "side": "bid", "type": "Limit", "price": "0.45", "quantity": "5.00", "postOnly": true }
  ]
}'
Response
{
  "error": "One or more orders in the batch failed",
  "results": [
    {
      "order": {
        "id": "213",
        "status": "Placed",
        "price": "0.40",
        "quantity": "5.00",
        "cum_quantity": "0.00"
DELETE /api/v1/orders Requires API key

Cancel Orders

Cancels open orders in one market. Targets are the union of orderIds and clientOrderIds. Cancel-all (every open order for this balance on the market) runs only when both lists are empty. Numeric ids and clientOrderIds are best-effort (continue after soft failures such as unresolved client ids; { dropped, err } when some succeed).

Scope: order:cancel Rate limit: 120 req/min · 60 per market

Request body

marketId string
Required
Market ID
orderIds string[]
Required
Numeric order IDs (as strings). Pass [] when cancelling only by clientOrderIds, or with empty/omitted clientOrderIds for cancel-all.
clientOrderIds string[]
Optional
Client order IDs (same charset/length as clientOrderId). Union with orderIds. Both empty = cancel-all.
Request 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"],
  "clientOrderIds": ["bot-42"]
}'
Response
{
  "result": {
    "dropped": ["213", "214"]
  }
}

// client-only cancel:
// { "marketId": "clx...", "orderIds": [], "clientOrderIds": ["bot-42"] }

// cancel-all:
POST /api/v1/orders/replace Requires API key

Replace Order

Replace or amend an open Limit order. quantity is the total fill limit (filled + desired remaining). Same-price size decrease keeps id/FIFO priority (status "amended") or cancels when nothing remains ("cancelled"). Price change or size increase cancel+places a new Limit sized to total − fills. Send postOnly: true on maker-only full replaces and on size decreases (would-take is skipped on decreases; postOnly still avoids maker-protection delay). Requires order:place and order:cancel.

Scope: order:place, order:cancel Rate limit: 120 req/min · 60 per market

Request body

marketId string
Required
Market ID (must be ORDER_BOOK and active).
orderId string
Optional
Numeric string ID of the open Limit order to replace. Exactly one of orderId / clientOrderId.
clientOrderId string
Optional
Client id of the open Limit order to replace. Exactly one of orderId / clientOrderId.
replacementClientOrderId string
Optional
Optional client id for the replacement/amend. Omit to inherit. Must not collide on an actual amend.
price string
Required
New limit price decimal string in (0,1) with exactly 2 decimals.
quantity string | integer
Required
Total fill limit (filled + desired remaining) in human shares: 0–2 decimal string or whole-contract integer. quoteQuantity is not supported.
revision integer
Optional
Optional positive safe integer (1–9007199254740991) for this open order lineage. Must exceed its last accepted value; otherwise returns "stale order revision" without changing the order. Follows replacements and alias renames and survives engine restarts. Omit for legacy behavior, preserving the stored revision. Cancellation or full fill ends the lineage; use a fresh clientOrderId for a new order.
postOnly boolean
Optional
Defaults to false on each request; not inherited from the source order. Send true for maker-only full replaces and for size decreases (avoids maker-protection delay; would-take is skipped on decreases). Would-take on a full replace keeps the source.
Request 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...",
  "clientOrderId": "bot-42",
  "price": "0.64",
  "quantity": "50.00",
  "revision": 12,
  "postOnly": true
}'
Response
{
  "result": {
    "cancelledOrderId": "213",
    "replacementOrderId": "214",
    "cancelledOrder": { "id": "213", "status": "Cancelled", ... },
    "replacementOrder": {
      "id": "214",
      "status": "Placed",
      "price": "0.64",
      "quantity": "50.00",
POST /api/v1/orders/replace/batch Requires API key

Batch Replace Orders

Replaces 1–32 open Limit orders on one market. Each leg can supply its own revision using the single-replace rules; revisions are scoped to each open order lineage, not the batch. Replacements may fill; self-trade follows the market setting. Returns 200 when all legs succeed, or 400 with results in request order when a committed batch contains failures. A pre-trade rejection returns only a string err and keeps its source. A committed zero-fill InternalCancel includes err, cancellation/replacement IDs, and the replacement order; its source has been cancelled. Successful legs remain committed. Inspect every result before retrying any leg. Counts as one write against the place/replace rate budget. The example shows a mixed HTTP 400 response with selected order fields; source order 3 remains open.

Scope: order:place, order:cancel Rate limit: 120 req/min · 60 per market

Request body

marketId string
Required
Shared market ID (ORDER_BOOK, active).
replaces array
Required
1–32 legs: exactly one of orderId / clientOrderId, price, quantity (total fill limit), optional replacementClientOrderId / postOnly / revision. Each revision is a positive safe integer (1–9007199254740991) for that open order lineage. A stale leg returns err: stale order revision; other legs can succeed. Send postOnly: true on maker-only full replaces and size decreases.
Request example
curl -X POST https://api.unhedged.gg/api/v1/orders/replace/batch \
  -H "Authorization: Bearer ak_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
  "marketId": "clx...",
  "replaces": [
    { "orderId": "1", "price": "0.52", "quantity": "10.00", "revision": 12 },
    { "orderId": "2", "price": "0.57", "quantity": "10.00", "revision": 3 },
    { "orderId": "3", "price": "0.62", "quantity": "10.00", "postOnly": true }
  ]
}'
Response
{
  "error": "One or more orders in the batch failed",
  "results": [
    {
      "cancelledOrderId": "1",
      "replacementOrderId": "4",
      "replacementOrder": {
        "id": "4",
        "status": "Placed",
        "price": "0.52",
GET /api/v1/orders Requires API key

List Open Orders

Returns open orders (including pending StopLoss/TakeProfit) from newest to oldest. Omit marketId to cover the account; follow every nextCursor until null. Keep marketId, outcome, and limit fixed while paging. Each row may include client_order_id.

Scope: order:read Rate limit: 240 req/min shared with order reads (default)

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.
Request 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.00",
GET /api/v1/orders/aliases Requires API key

Recover Client Order IDs

Lists retained mappings for accepted orders tagged with a client ID, whether open or closed, on your account. Results are available when the matching engine is disabled. Omit marketId to page across all markets; follow nextCursor until it is null. Client IDs can be reused after closure, so multiple closed orders may share one. Match a mapping to trade history by market_id and order_id. Results report retention_days (default 30). This is identity history, not an order-status lookup or replay receipt. Absence does not prove that a request failed or was never executed.

Scope: order:read Rate limit: 240 req/min shared with order reads (default)

Query parameters

marketId string
Optional market filter. Omit to enumerate all markets for your account.
limit number
Page size, 1-500. Default 100.
cursor string
Previous page nextCursor. Omit on page 1.
Request example
curl https://api.unhedged.gg/api/v1/orders/aliases \
  -H "Authorization: Bearer ak_your_key_here"
Response
{
  "aliases": [
    {
      "market_id": "clx...",
      "order_id": "213",
      "client_order_id": "quote-17",
      "accepted_at": "2026-09-01T12:00:00.123Z"
    }
  ],
  "nextCursor": "eyJhY2NlcHRlZEF0IjoiMTc4ODI2NDAwMDEyMyIsIm1hcmtldElkIjoiY2x4Li4uIiwib3JkZXJJZCI6IjIxMyJ9",
GET /api/v1/trades Requires API key

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. One row is one order leg, not one match: a regular trade writes 2 legs (aggressive + passive) and a cross trade writes N (1 aggressive + N-1 passives, N = outcomes in the market), all sharing one trade_id, and trade_id restarts at 1 in every market — dedup on fill_id (burns: burn_uid), never on trade_id or burn_id, and count matches as distinct market_id + trade_id. Rows expose numeric order IDs only — no client_order_id mapping; absence from open orders does not prove a timed-out placement never executed.

Scope: order:read Rate limit: 240 req/min shared with order reads (default)

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.
Request example
curl https://api.unhedged.gg/api/v1/trades \
  -H "Authorization: Bearer ak_your_key_here"
Response
{
  "trades": [
    {
      "fill_id": "clx...:184221:213",
      "market_id": "clx...",
      "trade_id": "184221",
      "order_id": "213",
      "exec_id": "42",
      "outcome": "YES",
      "side": "bid",

Types & statuses

Values returned on order objects and accepted by POST /api/v1/orders.

Order types
Limit May match once eligible to execute; an eligible unfilled remainder rests at the limit price. Requires price as a decimal string in (0,1) with exactly 2 decimals. Set postOnly to true to reject an order that would immediately take liquidity.
Market Consumes available liquidity once eligible to execute. Optional price in [0,1] sets the maximum buy price or minimum sell price per fill. Any unfilled remainder is cancelled and never rests.
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.
Row identity Dedup on fill_id (trades) and burn_uid (burns). trade_id and burn_id restart at 1 in every market, and one match writes one row per leg — 2 for a regular trade, N for a cross trade (N = outcomes) — so those keys repeat by design.
Engine RPC reference

Engine order WebSocket RPC names are listed for debugging direct ME connections — production bots should use REST.

new_order Engine order RPC behind POST /api/v1/orders. Wire params use snake_case fields such as market_id, trigger_price, quote_quantity, and optional client_order_id. post_only: true requires Limit and rejects immediate direct or cross execution.
batch_new_order Behind POST /api/v1/orders/batch. Shared market_id/user plus 1–32 place legs (Limit/StopLoss/TakeProfit only; Market rejected). Legs may fill. A committed batch can include failed legs; REST returns HTTP 400 with results in request order. Successful legs remain committed. One write rate unit per HTTP request.
cancel_order Engine order RPC behind DELETE /api/v1/orders. Cancels by numeric order_ids and/or client_order_ids (union). Both empty = cancel-all for that user in the market.
replace_order Behind POST /api/v1/orders/replace. Replace or amend one open Limit (market_id, exactly one of order_id / client_order_id, price, quantity; optional replacement_client_order_id, post_only, and revision). Outcome, side, and type are copied from the source order. revision is a positive safe integer (1–9007199254740991) for the open order lineage; equal or lower values return stale order revision before mutation. Accepted values survive engine restarts and follow replacements and alias renames. Omitting revision keeps legacy behavior and preserves its stored value. Cancellation or full fill ends the lineage; use a fresh client_order_id for a new order. post_only defaults to false on each request; a would-take rejection keeps the source unchanged.
batch_replace_order Behind POST /api/v1/orders/replace/batch. Shared market_id/user plus 1–32 replace legs. Each leg can include revision under the replace_order rules; it is not a batch-wide counter. A committed batch can include failed legs; REST returns HTTP 400 with results in request order. Stale-revision or post-only would-take rejection keeps that source; other legs continue. One write rate unit per HTTP request.
query_orders Engine order RPC used to list a user's open orders for one market.

Combos

How combos work

Combos bundle 2–32 outcomes from ACTIVE ORDER_BOOK markets and trade through requests for quotes (RFQs). Select one outcome per market and at most one market per group. Check the enabled flag from GET /api/v1/combos/config; every participating category must also allow combos and satisfy its size limits.

Request, accept, confirm

Request a BUY or SELL quote, accept an OPEN quote, then wait for maker confirmation. ACCEPTED is not a trade. Requests last up to 60 seconds, quotes 30 seconds, and confirmation 3 seconds, all capped by the earliest leg cutoff. Fills cover the full quantity. Only OPEN requests can be cancelled. Poll the request’s quotes endpoint until it returns a fill or a terminal EXPIRED/CANCELLED request; an empty quotes array alone does not establish the outcome.

Funds and settlement

BUY requests and acceptance do not lock cash. SELL requests reserve available gross long shares, even when net shares are zero. Minting locks price × quantity from the buyer and (1 − price) × quantity from the seller: $1 per share. Existing long claims can also be sold. Once every leg resolves or is voided, the long payout is quantity × product of leg values; shorts receive the remaining collateral. A winning leg is 1, a losing leg 0. Voided legs use their payout vector, otherwise the frozen YES mark (NO uses 1 − mark; fallback 0.5), or 1/N for categorical outcomes.

Authentication and retries

Use the scopes listed on each endpoint. Email accounts must be verified to request, post, accept, or confirm quotes. Save request and quote IDs. Optional Idempotency-Key replays a completed successful identical request for 60 seconds; reusing it with a different JSON body returns 409. It does not serialize concurrent/in-flight calls. After an ambiguous response, reconcile state before retrying.

Maker notifications

Connect to wss://api.unhedged.gg/ws before quoting and send {"type":"auth","token":"ak_..."}, with order:read. Authentication joins your private room automatically. MARKET_MAKER or SUPER_ADMIN accounts can subscribe with {"type":"subscribe","room":"combo-quotes"}. This feed sends combo_quote_request and combo_quote_request_closed, initially replaying at most 100 OPEN requests. Private combo_quote, combo_quote_accepted, and combo_fill events carry updates in data. Send periodic ping messages. Private events and ACCEPTED quotes are not replayed after reconnect. Requesters reconcile through REST; makers cannot recover a missed confirmation window. Use the request’s fill to identify execution: combo_fill includes fillId, but no requestId or quoteId.

GET /api/v1/combos/config Public

Combo availability

Global availability only. Category policies and individual leg eligibility still apply.

Rate limit: 600 req/min per IP by default (configurable)
Request example
curl https://api.unhedged.gg/api/v1/combos/config

Errors: 429 global rate limit.

Retries and idempotency: Safe to repeat; no mutation.

Response
{
  "enabled": true
}
POST /api/v1/combos Requires API key

Get or create a combo

Returns 201 for a new canonical definition or 200 for an existing one. Limited to 500 new combos per user per UTC day. Existing definitions do not count toward the quota and may be returned without revalidating eligibility; quote requests recheck it.

Scope: order:place Rate limit: 600 req/min per IP by default (configurable)

Request body

legs { marketId: string; outcomeIndex: integer }[]
Required
2–32 distinct markets; outcomeIndex is non-negative and identifies the stored outcome index.
Request example
curl -X POST https://api.unhedged.gg/api/v1/combos \
  -H "Authorization: Bearer ak_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
  "legs": [
    {
      "marketId": "market_a",
      "outcomeIndex": 0
    },
    {
      "marketId": "market_b",
      "outcomeIndex": 1
    }
  ]
}'

Errors: 401 invalid credentials; 403 missing scope or suspended account. 400 invalid/ineligible legs or incompatible size bounds; 403 disabled combos/category; 429 daily creation quota or rate limit.

Retries and idempotency: Same canonical legs return the existing combo.

Response
{
  "combo": {
    "id": "combo_1",
    "slug": "example-combo",
    "question": "Event A: YES + Event B: DOWN",
    "status": "ACTIVE",
    "legs": [
      {
        "marketId": "market_a",
        "outcomeIndex": 0,
GET /api/v1/market-groups/:idOrSlug/combos Public

List group combos

Combos containing at least one child of the group, newest first. Use group ID or slug; returns all statuses unless filtered.

Rate limit: 600 req/min per IP by default (configurable)

Query parameters

limit integer
1–100, default 50
offset integer
Non-negative, default 0
status string
ACTIVE | SETTLED | CANCELLED
Request example
curl https://api.unhedged.gg/api/v1/market-groups/REPLACE_idOrSlug/combos

Errors: 400 invalid pagination/status; 404 group not found; 429 rate limit.

Retries and idempotency: Safe to repeat. Pages are not an atomic snapshot.

Response
{
  "combos": [
    {
      "id": "combo_1",
      "slug": "example-combo",
      "question": "Event A: YES + Event B: DOWN",
      "status": "ACTIVE",
      "legs": [
        {
          "marketId": "market_a",
GET /api/v1/combos/:id Public

Read a combo

Returns the definition and ordered legs, including selected market metadata. A definition alone does not guarantee that new trades are allowed.

Rate limit: 600 req/min per IP by default (configurable)
Request example
curl https://api.unhedged.gg/api/v1/combos/REPLACE_id

Errors: 404 combo not found; 429 rate limit.

Retries and idempotency: Safe to repeat.

Response
{
  "combo": {
    "id": "combo_1",
    "slug": "example-combo",
    "question": "Event A: YES + Event B: DOWN",
    "status": "ACTIVE",
    "legs": [
      {
        "marketId": "market_a",
        "outcomeIndex": 0,
POST /api/v1/combos/:id/quote-requests Requires API key

Request a BUY or SELL quote

Requester opens an RFQ, returning 201. SELL requires unreserved gross long inventory and reserves it while OPEN/ACCEPTED. BUY does not lock cash. At most 60s, capped at the earliest leg cutoff. No maker role required.

Scope: order:place Rate limit: 600 req/min per IP by default (configurable)

Request body

side string
Required
BUY | SELL
quantity decimal string | number
Required
Positive, at most 1,000,000, at most 10 decimal places; decimal strings recommended.
Request example
curl -X POST https://api.unhedged.gg/api/v1/combos/REPLACE_id/quote-requests \
  -H "Authorization: Bearer ak_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
  "side": "BUY",
  "quantity": "10"
}'

Errors: 400 invalid/expired trade or insufficient cash/inventory; 403 disabled combo/category or unverified email; 404 missing ID.

Retries and idempotency: Save request.id. Retrying can create another RFQ; there is no requester request-list endpoint. Recover a completed call with its original Idempotency-Key.

Response
{
  "request": {
    "id": "request_1",
    "comboId": "combo_1",
    "requesterUserId": "user_1",
    "side": "BUY",
    "quantity": "10",
    "status": "OPEN",
    "expiresAt": "2026-11-01T12:01:00.000Z"
  }
GET /api/v1/combos/quote-requests/:id/quotes Requires API key

Read quotes and execution outcome

Requester only. Returns OPEN/unexpired and ACCEPTED quotes, current request, and fill or null. Expired windows are swept before reading. Filled quotes leave the quotes array; inspect fill to establish execution. Cache-Control: no-store.

Scope: order:read Rate limit: 600 req/min per IP by default (configurable)
Request example
curl https://api.unhedged.gg/api/v1/combos/quote-requests/REPLACE_id/quotes \
  -H "Authorization: Bearer ak_your_key_here"

Errors: 401 invalid credentials; 403 missing scope or suspended account. 403 if not the requester; 404 request not found; 429 rate limit.

Retries and idempotency: Safe to repeat. Poll through ACCEPTED until a fill or a terminal EXPIRED/CANCELLED request. An empty quotes array alone does not mean execution failed.

Response
{
  "quotes": [
    {
      "id": "quote_1",
      "requestId": "request_1",
      "makerUserId": "maker_1",
      "price": "0.35",
      "quantity": "10",
      "status": "OPEN",
      "expiresAt": "2026-11-01T12:00:30.000Z"
POST /api/v1/combos/quote-requests/:id/cancel Requires API key

Cancel an open request

Requester only. Cancels an OPEN request and its OPEN quotes; releases sell-inventory reservations. Allowed while combos are disabled. ACCEPTED requests cannot be cancelled.

Scope: order:cancel Rate limit: 600 req/min per IP by default (configurable)
Request example
curl -X POST https://api.unhedged.gg/api/v1/combos/quote-requests/REPLACE_id/cancel \
  -H "Authorization: Bearer ak_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{}'

Errors: 401 invalid credentials; 403 missing scope or suspended account. 403 wrong requester; 404 missing request; 400 request not OPEN; 429 rate limit.

Retries and idempotency: Read request state after uncertainty. Repeated cancellation without a cached replay returns 400.

Response
{
  "request": {
    "id": "request_1",
    "comboId": "combo_1",
    "requesterUserId": "user_1",
    "side": "BUY",
    "quantity": "10",
    "status": "CANCELLED",
    "expiresAt": "2026-11-01T12:01:00.000Z"
  }
POST /api/v1/combos/quote-requests/:id/quotes Requires API key

Post or replace a maker quote

Requires MARKET_MAKER or SUPER_ADMIN. Returns 201. Cannot quote your own RFQ. A new quote replaces this maker’s previous OPEN quotes on the request. Full quantity only, no partial fills. Quote TTL at most 30s, capped by request expiry and leg cutoff.

Scope: order:place Rate limit: 600 req/min per IP by default (configurable)

Request body

price decimal string | number
Required
Strictly between 0 and 1, at most 10 decimal places.
quantity decimal string | number
Optional
Defaults to request quantity; must equal it. Same precision and size bounds as the RFQ.
Request example
curl -X POST https://api.unhedged.gg/api/v1/combos/quote-requests/REPLACE_id/quotes \
  -H "Authorization: Bearer ak_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
  "price": "0.35",
  "quantity": "10"
}'

Errors: 400 invalid/expired trade or insufficient cash/inventory; 403 disabled combo/category or unverified email; 404 missing ID. 403 wrong role; 400 self-quote.

Retries and idempotency: Each call replaces this maker’s OPEN quote. Reconcile before reposting.

Response
{
  "quote": {
    "id": "quote_1",
    "requestId": "request_1",
    "makerUserId": "maker_1",
    "price": "0.35",
    "quantity": "10",
    "status": "OPEN",
    "expiresAt": "2026-11-01T12:00:30.000Z"
  }
POST /api/v1/combos/quotes/:id/accept Requires API key

Accept a quote

Requester only. Returns 200 and an ACCEPTED quote, not a fill. Starts a maker confirmation window of at most 3s; competing OPEN quotes are cancelled. Cash capacity is checked, but cash is not locked until confirm. Use order:read to reconcile via the request endpoint.

Scope: order:place Rate limit: 600 req/min per IP by default (configurable)
Request example
curl -X POST https://api.unhedged.gg/api/v1/combos/quotes/REPLACE_id/accept \
  -H "Authorization: Bearer ak_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{}'

Errors: 400 invalid/expired trade or insufficient cash/inventory; 403 disabled combo/category or unverified email; 404 missing ID. 403 wrong requester.

Retries and idempotency: Repeating acceptance does not extend its window. After expiry, create a new RFQ; after a fill, this returns the existing quote.

Response
{
  "quote": {
    "id": "quote_1",
    "requestId": "request_1",
    "makerUserId": "maker_1",
    "price": "0.35",
    "quantity": "10",
    "status": "ACCEPTED",
    "expiresAt": "2026-11-01T12:00:13.000Z",
    "request": {
POST /api/v1/combos/quotes/:id/confirm Requires API key

Maker confirms and executes

Requires the quoting MARKET_MAKER or SUPER_ADMIN. Atomic full-quantity fill, returning 201 with fill. Must arrive while the ACCEPTED request and quote remain unexpired. Balances, inventory, and eligibility are rechecked; a previous acceptance does not guarantee execution.

Scope: order:place Rate limit: 600 req/min per IP by default (configurable)
Request example
curl -X POST https://api.unhedged.gg/api/v1/combos/quotes/REPLACE_id/confirm \
  -H "Authorization: Bearer ak_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{}'

Errors: 400 invalid/expired trade or insufficient cash/inventory; 403 disabled combo/category or unverified email; 404 missing ID. 403 wrong maker/role.

Retries and idempotency: Repeating a completed confirmation returns the existing fill, including after settlement.

Response
{
  "fill": {
    "id": "fill_1",
    "requestId": "request_1",
    "quoteId": "quote_1",
    "comboId": "combo_1",
    "buyerId": "user_1",
    "sellerId": "maker_1",
    "price": "0.35",
    "quantity": "10",
GET /api/v1/portfolio/me/combos Requires API key

Read funded combo positions

Session or API key. Returns all active funded claims, including zero-net positions that hold both long and short shares. No pagination. Settled claims disappear. Gross longs, minus outstanding SELL reservations, determine sellable inventory. Cache-Control: no-store.

Scope: portfolio:read Rate limit: Portfolio read profile (default 1,000,000 req/min), plus credential IP backstop 600 req/min
Request example
curl https://api.unhedged.gg/api/v1/portfolio/me/combos \
  -H "Authorization: Bearer ak_your_key_here"

Errors: 401 invalid credentials; 403 missing scope or suspended account. 429 rate limit.

Retries and idempotency: Safe to repeat; use the request fill, not position deltas alone, to reconcile execution.

Response
{
  "positions": [
    {
      "comboId": "combo_1",
      "question": "Event A: YES + Event B: DOWN",
      "status": "ACTIVE",
      "shares": "10",
      "longShares": "10",
      "shortShares": "0",
      "costBasis": "3.5",