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): whentrue, 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. UsePOST /admin/resolve-eventto 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. |