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": "4xR2kF7b...",
      "taker": "7yQ3mH9a...",
      "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.

Request:

{
  "market_id": "abc123...",
  "user": "4xR2kF7b8K...",
  "side": "buy",
  "outcome": "yes",
  "price": 6500,
  "size": 100,
  "order_type": "gtc",
  "signature": "deadbeef...",
  "nonce": 1
}
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 Ed25519 signature of order message
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": "7yQ3mH9a...",
      "taker": "4xR2kF7b8K...",
      "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.

Request:

{
  "market_id": "abc123...",
  "order_id": 42,
  "user": "4xR2kF7b8K...",
  "signature": "deadbeef..."
}

POST /v1/orders/cancel-all

Cancel all open orders for a user. Optionally scoped to one market.

Request:

{
  "user": "4xR2kF7b8K...",
  "market_id": "abc123...",
  "signature": "deadbeef...",
  "nonce": 1
}

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

Signature is Ed25519 over the canonical cancel-all message (74 bytes): tag(0x01) | user(32) | market_present(1) | market_id(32, zeros if absent) | nonce(8 LE).

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-USDC (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": "4xR2kF7b8K...",
  "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": "4xR2kF7b8K...", "amount": 10000000}

POST /v1/withdraw

Debit fUSD from a user.

{"user": "4xR2kF7b8K...", "amount": 5000000}

POST /v1/redeem

Redeem winning contracts after market resolution.

{"market_id": "abc123...", "user": "4xR2kF7b8K..."}

Response (200):

{
  "market_id": "abc123...",
  "user": "4xR2kF7b8K...",
  "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_fee_bps": 25,
  "fee_treasury_wallet": "4Mt4Szb...",
  "categories": [
    {"name": "sports", "subcategories": ["football","basketball"], "fee_bps": 200},
    {"name": "crypto", "subcategories": ["bitcoin","ethereum"], "fee_bps": 25}
  ],
  "builders": [
    {"name": "MyApp", "fee_bps": 100, "enabled": true}
  ]
}

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": "4Mt4Szb..."}

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": "7yQ3..."}

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-USDC (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 USDC from fee treasury → user's proxy wallet USDC ATA via the claim_rewards vault instruction on Fogo. X-Admin-Key required.

{
  "wallet":             "<bs58 user pubkey>",
  "amount_micro_usdc":  5000000
}

amount_micro_usdc is optional — defaults to the full claimable balance.

Response (200):

{
  "claimed_micro_usdc": 5000000,
  "remaining":          400000,
  "signature":          "5Kx8..."
}

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

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.


Bridge (admin)

Deposit / bridge service endpoints for tier management and manual operations.

POST /admin/bridge/user-tier

Set a user's on-chain deposit tier. Signs + submits a set_user_tier tx on Fogo. X-Admin-Key required.

{
  "user": "<bs58 fogo pubkey>",
  "tier": 1
}
tier Name Max USDC per tx
0 Retail (default) 10,000
1 LP 1,000,000
2 Institutional 10,000,000

Response (200):

{"user": "...", "tier": 1, "signature": "5Kx8..."}

The tier is cached in Redis for 60s. To force an immediate refresh for subsequent deposit-address reads:

POST /admin/bridge/invalidate-tier-cache

{"user": "<bs58 fogo pubkey>"}

GET /v1/deposit/bridge-address/{user}

Returns the user's Solana deposit address plus live limits (anti-dust minimums, tier-based per-tx cap, daily bridge cap remaining). See Deposits & Withdrawals for the full flow.

Response (200):

{
  "success": true,
  "data": {
    "chain":                          "solana",
    "address":                        "4xR2kF7b8K...",
    "usdc_token":                     "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
    "min_usdc_micro":                 1000000,
    "min_sol_lamports":               50000000,
    "max_usdc_per_tx_micro":          10000000000,
    "daily_cap_remaining_usdc_micro": 100000000000,
    "initialized":                    true
  }
}

All bridge deposits (any user, any tier) share a rolling 24h aggregate cap of 100,000 USDC. daily_cap_remaining_usdc_micro is the live remainder. Native Fogo deposits bypass this cap.


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 -- additional, on top of protocol fees. Set per-builder, collected to builder's wallet:

Builder Their Fee Charged To
Builder-set 0-2% (set by builder) Users routed through that builder

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

Fee flow: 1. User places order through a builder's frontend 2. Protocol fee (e.g. 0.25%) -> Parti treasury wallet 3. Builder fee (e.g. 1%) -> Builder's wallet


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.