Skip to content

REST API Reference

All endpoints return JSON. Errors return {"error": "message"} with appropriate HTTP status codes.


Events

Events group one or more outcome markets under a single question.

POST /v1/events

Create a new event with named outcomes. Admin-gated in production (X-Admin-Key header).

Request:

{
  "title": "Who will win the 2024 US Election?",
  "category": {"name": "politics", "sub": "elections"},
  "outcomes": ["Trump", "Biden", "Other"],
  "expiry": 1798761600
}
  • Binary events: pass ["Yes"] for a single market with YES/NO sides.
  • Multi-outcome: pass N labels. One binary market is created per outcome.
  • neg_risk (optional, boolean): when true, enables negative-risk mode. All outcome YES prices are expected to sum to ~$1.00. Resolution is atomic — exactly one outcome wins (YES), all others become NO. Use POST /admin/resolve-event to resolve.

Response (201):

{
  "event_id": "abc123...",
  "event": {
    "event_id": "abc123...",
    "title": "Who will win the 2024 US Election?",
    "category": {"name": "politics", "sub": "elections"},
    "outcomes": [
      {
        "label": "Trump",
        "market_id": "def456...",
        "yes_price": 6200,
        "no_price": 3800,
        "yes_bid": 6100,
        "yes_ask": 6300,
        "volume": 0
      }
    ],
    "expiry": 1798761600,
    "status": "active"
  }
}

GET /v1/events

List all events with live outcome prices. Supports same query params as /v1/markets: limit, offset, category, status, search.

GET /v1/events/{event_id}

Get event details with all outcome markets and current prices.


Markets

Individual tradable markets. Each has a YES/NO order book.

GET /v1/markets

Every entry has event_id + outcomes[]. Binary events have 1 outcome, multi-outcome have N.

Query params: | Param | Type | Default | Description | |-------|------|---------|-------------| | limit | int | 50 | Max results (1-200) | | offset | int | 0 | Skip N results | | category | string | - | Filter by category name (e.g. crypto, sports) | | status | string | - | Filter by status (active, halted, resolved) | | search | string | - | Search by question text (case-insensitive) |

Examples:

GET /v1/markets?category=crypto&status=active
GET /v1/markets?search=bitcoin&limit=10
GET /v1/markets?offset=20&limit=20

Response (200):

{
  "markets": [
    {
      "event_id": "abc123...",
      "question": "Will BTC reach $200k by end of 2026?",
      "status": "active",
      "category": {"name": "crypto", "sub": "bitcoin"},
      "fee_bps": 25,
      "expiry": 1798761600,
      "outcomes": [
        {
          "label": "Yes",
          "market_id": "def456...",
          "yes_price": 7200,
          "no_price": 2800,
          "yes_bid": 7000,
          "yes_ask": 7400,
          "spread": 400,
          "volume": 15000
        }
      ]
    },
    {
      "event_id": "ghi789...",
      "question": "Who will win the 2026 FIFA World Cup?",
      "status": "active",
      "category": {"name": "sports", "sub": "soccer"},
      "fee_bps": 200,
      "expiry": 1798761600,
      "outcomes": [
        {"label": "Brazil", "market_id": "aaa...", "yes_price": 2500, "yes_bid": 2300, "yes_ask": 2700},
        {"label": "Argentina", "market_id": "bbb...", "yes_price": 2000},
        {"label": "France", "market_id": "ccc...", "yes_price": 1800},
        {"label": "Other", "market_id": "ddd...", "yes_price": 3700}
      ]
    }
  ]
}

Fields: | Field | Description | |-------|-------------| | event_id | Unique event identifier (use for URL routing) | | outcomes | Array of tradable outcomes. Each has its own market_id for order placement. | | market_id | Per-outcome order book ID. Use this when placing orders. | | yes_price | Midpoint of best YES bid/ask (basis points) | | no_price | 10000 - yes_price | | yes_bid / yes_ask | Best bid/ask for YES | | spread | yes_ask - yes_bid | | fee_bps | Protocol fee in basis points | | volume | Cumulative volume traded |

NO-side prices can be derived: NO bid = 10000 - yes_ask, NO ask = 10000 - yes_bid.

GET /v1/markets/{id}

Get single market with full price data.

GET /v1/markets/{id}/book

Order book snapshot with all four sides.

Response (200):

{
  "market_id": "abc123...",
  "yes_bids": [{"price": 6200, "size": 500, "order_count": 3}],
  "yes_asks": [{"price": 6300, "size": 200, "order_count": 1}],
  "no_bids": [{"price": 3700, "size": 200, "order_count": 1}],
  "no_asks": [{"price": 3800, "size": 500, "order_count": 3}],
  "yes_price": 6250,
  "no_price": 3750,
  "yes_bid": 6200,
  "yes_ask": 6300,
  "spread": 100,
  "volume": 150000,
  "time": 1700000000,
  "sequence": 42
}

GET /v1/markets/{id}/trades

Trade history for a market. Newest first, cursor-paginated.

Query params: | Param | Type | Default | Description | |-------|------|---------|-------------| | limit | int | 100 | Max trades (1-1000) | | before | int | - | Return trades with id < before |

Response (200):

{
  "trades": [
    {
      "id": 42,
      "market_id": "abc123...",
      "maker_order_id": 10,
      "taker_order_id": 15,
      "maker": "0x4b2a9e7c...",
      "taker": "0x7a3f1c0d...",
      "side": "buy",
      "outcome": "yes",
      "price": 6200,
      "size": 100,
      "fee": 12,
      "time": 1700000000
    }
  ],
  "next_cursor": 41
}

Pagination: Pass next_cursor as before to get the next page. When next_cursor is null, there are no more results.

GET /v1/markets/{id}/candles

OHLCV candlestick data for charting.

Query params: | Param | Type | Default | Description | |-------|------|---------|-------------| | interval | string | "1m" | 1m, 5m, 15m, 1h, 1d | | from | int | 0 | Start timestamp (Unix seconds) | | to | int | now | End timestamp (Unix seconds) |

Response (200):

{
  "candles": [
    {"t": 1700000000, "o": 6100, "h": 6500, "l": 6000, "c": 6200, "v": 5000},
    {"t": 1700000060, "o": 6200, "h": 6300, "l": 6150, "c": 6250, "v": 3000}
  ]
}

All prices in basis points. Volume is total quantity traded in the interval.


Orders

POST /v1/orders

Submit a new order. Auth: requires the session cookie from POST /v1/builders/register and the signature field below (EIP-712 Order struct). Without the cookie the gateway returns 401 unauthenticated; a signature that recovers to the wrong address returns 403 invalid_signature. See Builder Integration.

Request:

{
  "market_id": "abc123...",
  "user": "0x4b2a9e7c...",
  "side": "buy",
  "outcome": "yes",
  "price": 6500,
  "size": 100,
  "order_type": "gtc",
  "signature": "0x<65-byte EIP-712 Order signature>",
  "nonce": 1700000000123
}
Field Values Description
side buy, sell Buy = acquire contracts, Sell = exit position
outcome yes, no Which outcome to trade
price 1-9999 Price in basis points
size > 0 Number of contracts
order_type gtc, ioc, fok, post_only Time-in-force
signature hex EIP-712 typed-data signature of the order, from your EVM wallet
nonce int Monotonically increasing per user

Order types: - gtc (Good-Til-Cancelled): Rest unmatched portion on the book. - ioc (Immediate-Or-Cancel): Fill what you can, cancel the rest. - fok (Fill-Or-Kill): Fill entirely or reject the whole order. - post_only: Reject if it would match. Guarantees maker status.

Response (201):

{
  "order_id": 42,
  "market_id": "abc123...",
  "fills": [
    {
      "maker_order_id": 10,
      "taker_order_id": 42,
      "maker": "0x7a3f1c0d...",
      "taker": "0x4b2a9e7c...",
      "maker_side": "sell",
      "taker_side": "buy",
      "outcome": "yes",
      "quantity": 50,
      "price": 6200,
      "settlement_type": "mint",
      "taker_fee": 0,
      "maker_rebate": 0,
      "builder_fee": 0,
      "builder_api_key": null,
      "timestamp": 1700000000
    }
  ],
  "remaining": 50
}

Fill fields: - quantity: contracts filled (not size — the REST /trades endpoint uses size, but live order fills use quantity) - timestamp: unix seconds (not time) - settlement_type: mint (opening), burn (closing), or transfer (secondary market)

Market Buy / Sell Pattern

To place a "market" order, use ioc with an extreme price: - Market Buy: { "side": "buy", "price": 9999, "order_type": "ioc" } — will fill at best available asks - Market Sell: { "side": "sell", "price": 100, "order_type": "ioc" } — will fill at best available bids

The engine matches against the best opposite-side orders up to the size, and cancels the unfilled portion.

POST /v1/orders/cancel

Cancel a single resting order. Auth: session cookie only — no signature. There is no Cancel EIP-712 struct; the engine resolves the acting user from the cookie and owner-checks the order.

Request:

{
  "market_id": "abc123...",
  "order_id": 42,
  "user": "0x4b2a9e7c..."
}

POST /v1/orders/cancel-all

Cancel all open orders for a user. Optionally scoped to one market. Auth: session cookie only — no signature and no nonce (there is no CancelAll EIP-712 struct; the engine owner-checks each resting order).

Request:

{
  "user": "0x4b2a9e7c...",
  "market_id": "abc123..."
}

market_id is optional. If omitted, cancels across all markets.

Response (200):

{
  "cancelled": 5,
  "unlocked": 250000
}

GET /v1/orders/{market_id}/{user}

Get user's open (resting) orders in a market.


User / Balance

GET /v1/balance/{user}

Get user's fUSD balance.

Response (200):

{
  "available": 5000000,
  "locked": 1000000,
  "total": 6000000
}

All values in micro-USDG (6 decimals). locked = funds held by open orders.

GET /v1/position/{market_id}/{user}

Get user's position in a market.

Response (200):

{
  "market_id": "abc123...",
  "user": "0x4b2a9e7c...",
  "yes_contracts": 100,
  "no_contracts": 0,
  "market_status": "active",
  "outcome": null
}

GET /v1/trades/{user}

User's trade history across all markets. Cursor-paginated, newest first.

Query params: Same as market trades (limit, before).

POST /v1/deposit

Credit fUSD to a user (testnet / admin only).

{"user": "0x4b2a9e7c...", "amount": 10000000}

POST /v1/withdraw

Debit fUSD from a user.

{"user": "0x4b2a9e7c...", "amount": 5000000}

POST /v1/redeem

Redeem winning contracts after market resolution.

{"market_id": "abc123...", "user": "0x4b2a9e7c..."}

Response (200):

{
  "market_id": "abc123...",
  "user": "0x4b2a9e7c...",
  "outcome": "yes",
  "winning_contracts": 100,
  "payout": 100000000,
  "redeemed": true,
  "balance": {"available": 105000000, "locked": 0, "total": 105000000}
}

Fees

GET /v1/fees

Public fee schedule -- categories, rates, builders.

Response (200):

{
  "default_fees": { "taker_fee_bps": 25, "maker_rebate_bps": 5 },
  "category_fees": { "sports": 200, "crypto": 25 },
  "market_fee_overrides": { "<market_id>": { "taker_fee_bps": 50, "maker_rebate_bps": 0 } },
  "categories": [
    { "name": "sports", "subcategories": ["football", "basketball"] },
    { "name": "crypto", "subcategories": ["bitcoin", "ethereum"] }
  ],
  "fee_treasury_wallet": "0x2b5ad0f0...",
  "builders": {
    "<api_key>": { "name": "MyApp", "fee_bps": 100, "wallet": "0x7a3f1c0d...", "enabled": true }
  }
}

builders is a map keyed by api_key (not an array). default_fees carries both the taker fee and the maker rebate; category_fees / market_fee_overrides override the taker fee per category / per market.


Admin

All /admin/* routes require X-Admin-Key header in production.

POST /admin/resolve

Resolve a single binary market. {"market_id": "abc123...", "outcome": "yes"}

POST /admin/resolve-event

Atomically resolve all markets in a neg_risk event. Exactly one outcome wins (YES), all others become NO.

{
  "event_id": "abc123...",
  "winning_outcome": 0
}

winning_outcome is the 0-indexed position in the event's outcomes array. All child markets are resolved in a single transaction on-chain via the resolve_neg_risk_event vault instruction.

POST /admin/fees/category

Set fees for a category. {"category": "sports", "fee_bps": 200}

POST /admin/fees/market

Override fees for one market. {"market_id": "abc123...", "fee_bps": 50}

POST /admin/fees/treasury

Set fee collection wallet. {"wallet": "0x2b5ad0f0..."}

POST /admin/categories

Add/update category with subcategories. {"name": "weather", "subcategories": ["temperature","precipitation"], "fee_bps": 25}

POST /admin/builders

Register a builder. {"api_key": "bld_abc...", "name": "MyApp", "fee_bps": 100, "wallet": "0x7a3f1c0d..."}

POST /admin/builders/fee

Update builder fee. {"api_key": "bld_abc...", "fee_bps": 150}

POST /admin/rescan-events

Force a re-scan of the settled-event log from start_slot. Used after engine restarts to recover orders/positions that landed during downtime.

{"start_slot": 12345678}

Response:

{"scanned": 2048, "replayed": 17}

Rewards

Liquidity rewards use Oracle's extended quadratic formula (multi-level depth, gold-band bonus, c=2 single-sided penalty, uptime^0.8 weighting, 5-min anti-spoofing clamp). Distributed pro-rata at 00:00 UTC with a 40% per-wallet cap. Full methodology: Liquidity Provision.

GET /v1/rewards/leaderboard

Current-day scores for a market.

Query params: market_id (required, hex), day (optional YYYY-MM-DD, defaults today UTC).

Response (200):

{
  "market_id": "abc123...",
  "day": "2026-04-15",
  "entries": [
    {"wallet": "4zGaxW...", "score": 42150.3},
    {"wallet": "8vYu7k...", "score":  9880.2}
  ]
}

GET /v1/rewards/wallet/{wallet}

Cumulative unclaimed rewards in micro-USDG (across all markets).

{"wallet": "4zGaxW...", "claimable_micro_usdc": 5400000}

GET /v1/rewards/config

Per-market rewards parameters. Markets without a config earn no rewards.

{
  "configs": {
    "abc123...": {
      "max_spread_bps":     200,
      "min_size":           100,
      "daily_budget_usdc":  10000000,
      "in_game_multiplier": 1.0
    }
  }
}

POST /admin/rewards/config

Create / update a market's rewards parameters. X-Admin-Key required.

{
  "market_id":          "abc123...",
  "max_spread_bps":     200,
  "min_size":           100,
  "daily_budget_usdc":  10000000,
  "in_game_multiplier": 1.0
}

POST /admin/rewards/claim

Operator-relayed claim. Moves USDG from the fee treasury to the user via the claimRewards call on the PartiVault contract on Robinhood Chain. X-Admin-Key required.

{
  "wallet":             "<0x EVM address>",
  "amount_micro_usdc":  5000000
}

amount_micro_usdc is optional — defaults to the full claimable balance.

Response (200):

{
  "claimed_micro_usdc": 5000000,
  "remaining":          400000,
  "signature":          "0x5e8c..."
}

remaining is the wallet's claimable_micro_usdc after the decrement (atomic, clamps at zero). signature is the Robinhood Chain tx hash.

Partial-failure mode: if the tx lands but the Redis decrement fails, the response is still 200 with remaining: null and a warning field — funds are delivered on-chain but the counter needs manual reconciliation.


Deposit service

Deposit-address derivation and the signed-withdraw endpoint. See Deposits & Withdrawals for the full flow, per-environment hosts, and the PartiVault / USDG addresses.

GET /v1/deposit/robinhood-address/{address}

Returns the user's deposit address — a per-user CREATE2 DepositForwarder on Robinhood Chain, derived deterministically from the 0x address by the vault's DepositForwarderFactory. {address} must be the lowercase 0x address (a bad EIP-55 checksum returns derivation_failed).

Response (200):

{
  "address": "0x…",
  "chain": "robinhood",
  "chain_id": 4663,
  "token": "USDG",
  "token_address": "0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168",
  "min_usdg_micro": 1000000
}

chain_id is 46630 and token_address is the testnet USDG on staging. Only USDG on the matching Robinhood Chain credits; other tokens/networks are lost.

The older GET /v1/deposit/bridge-address/{pubkey} route is the retired Solana/Fogo path and returns invalid_pubkey for a 0x address.

POST /v1/withdraw-signed

Withdraw USDG. Pass chain: "robinhood" and an EIP-712 Withdraw signature. See Withdraw — note the nonce is the sequential vault.nonces(user), not the order timestamp nonce.

TODO (verify): the legacy admin deposit-tier endpoints (POST /admin/bridge/user-tier, POST /admin/bridge/invalidate-tier-cache) and the per-tier / daily-aggregate deposit caps belonged to the retired Solana→Fogo bridge. Whether tiers/caps still apply to the direct-USDG Robinhood deposit path is not confirmed by the current reference client; treat as unverified until reconciled against the live deposit service.


Errors

{"error": "descriptive error message"}
Status Meaning
400 Validation error or engine rejection
401 Missing or invalid X-Admin-Key
404 Market/event/order not found
500 Internal error (Redis timeout, etc.)

Fee Structure

Protocol fees -- 100% to Parti treasury wallet:

Category Fee
Default 0.25% (25 bps)
Crypto 0.25%
Politics 0.50%
Sports 2.00% (200 bps)

Builder fees — opt-in and net-zero. When an order carries a builder_api_key, the builder's fee_bps is charged to the attributing party (whoever placed that order) and credited to the builder's wallet — the same amount in, out. It is additive (NOT taken from the protocol fee or maker rebate) and operator-capped via fee_bps; a builder must be enabled.

Builder Their Fee Charged To
Builder-set, enabled 0–2% (operator-capped) The party whose order carried the key

Fee formula: fee = fee_bps * price * quantity / (10000 * 10000)

Protocol fee split (ADR-003): the taker fee can optionally be split on-ledger into a maker rebate + creator revshare (creator_bps) + platform (platform_bps); the sum never exceeds the collected taker fee (nothing is minted), and the remainder is the protocol take. This split is off by default (zero rates) and set per-market by the operator.

Fee flow: 1. Taker pays the protocol taker fee; the undistributed remainder → Parti treasury. 2. If configured, creator_bps → market creator wallet, platform_bps → platform wallet (both out of the collected taker fee). 3. If a builder key is attached, its fee is charged to the attributing party → builder's wallet (net-zero).


Settlement Types

Each trade produces one of three settlement types:

Type Description
mint New YES/NO shares created. Buyer acquires YES, seller writes them.
burn Shares destroyed. Both sides close positions, collateral released.
transfer Secondary market trade. Existing shares change hands.