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": "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 returnsinvalid_pubkeyfor a0xaddress.
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. |