Docs / API Reference
Complete reference for the Polis Exchange REST API and WebSocket gateway. All endpoints are prefixed with /v1. The base URL in development is http://localhost:3001.
Two methods are supported:
POST /v1/auth/login. Pass as Authorization: Bearer <jwt>. Used by the web UI.POST /v1/auth/api-keys (requires JWT auth). Pass as Authorization: ApiKey <key>. Used by bots and automated systems.Both methods resolve to a user identity. Token operations (mint, redeem) require KYC tier 1. Trading and read-only endpoints require a valid session or API key.
POST /v1/auth/register
Create an account with email and password. Returns a JWT.
POST /v1/auth/kyc
Submit identity verification (full name, date of birth, address, document type). KYC tier 1 is required to trade, deposit, withdraw, mint, and redeem.
GET /v1/markets
List all markets. Each market has a base ticker (e.g. JCB), a BULL ticker (JCB-BULL), and a BEAR ticker (JCB-BEAR).
{
"markets": [{
"ticker": "JCB",
"name": "J C Bamford Excavators Ltd",
"sector": "Manufacturing",
"bullTicker": "JCB-BULL",
"bearTicker": "JCB-BEAR",
"mintPrice": 1000000,
"initialEbitda": 509700000
}]
}mintPrice is in micro-USDC (1,000,000 = 1 USDC). initialEbitda is the EBITDA baseline used for settlement payout calculations.
GET /v1/markets/{ticker}
Get market detail including order book summary, last price, 24h volume, and company info.
GET /v1/markets/{ticker}/summary
Ticker summary with last trade price, 24h change, volume, and bid/ask.
GET /v1/markets/{ticker}/book
Snapshot of the current order book. Use compound tickers (JCB-BULL, JCB-BEAR) to get the book for each token side.
{
"bids": [{"price": 0.58, "size": 1200}, ...],
"asks": [{"price": 0.62, "size": 800}, ...],
"ts": 1786886400000
}GET /v1/markets/{ticker}/candles?interval={interval}&limit={limit}
OHLCV candlestick data. Intervals: 1m, 5m, 15m, 1h, 4h, 1d. Default limit: 100.
GET /v1/markets/{ticker}/trades?limit={limit}
Recent trades for a compound ticker. Each trade has price, size, takerSide, and timestamp.
POST /v1/trading/orders
Place a limit or market order on a compound ticker (e.g. JCB-BULL).
{
"ticker": "JCB-BULL",
"side": "buy",
"type": "limit",
"price": 0.60,
"size": 100
}Response includes the order ID, status, and any fill details.
DELETE /v1/trading/orders/{orderId}
Cancel an open order. Reserved collateral is released back to available.
GET /v1/trading/orders?status={status}
List your orders. Filter by status: open, filled, cancelled.
GET /v1/trading/account
Your collateral account: total balance, reserved collateral (locked in open orders), and available collateral.
{
"userId": "abc123",
"total": 5000000000,
"reserved": 600000000,
"available": 4400000000
}All amounts in micro-USDC. available = total − reserved.
GET /v1/tokens/balances
Your token holdings (BULL and BEAR tokens across all markets).
{
"balances": [{
"userId": "abc123",
"ticker": "JCB-BULL",
"side": "bull",
"balance": 500,
"reserved": 100,
"mintPrice": 1000000,
"avgCost": 0
}]
}balance is the token count (integer, not USDC). reserved is tokens locked in open sell orders. mintPrice is always 1,000,000 (1 USDC). avgCost is the blended acquisition cost in micro-USDC through trading (0 if minted, not bought).
Also available at GET /v1/trading/balances (same data, different route). Admin variant: GET /v1/admin/tokens/balances/:userId.
POST /v1/tokens/mint
Mint BULL + BEAR token pairs by depositing USDC collateral. Each pair costs 2 USDC (1 USDC per token at the mint price). Requires KYC tier 1. A 0.10% (10 basis point) fee applies on the USDC deposited.
// Request
{
"baseTicker": "JCB",
"pairCount": 100
}
// Response
{
"baseTicker": "JCB",
"bullTicker": "JCB-BULL",
"bearTicker": "JCB-BEAR",
"pairsMinted": 100,
"bullMinted": 100,
"bearMinted": 100,
"usdcLocked": 200000000,
"feeCharged": 200000
}usdcLocked = 2 × mintPrice × pairCount (in micro-USDC). feeCharged = 10 bps of usdcLocked.
POST /v1/tokens/redeem
Burn matching BULL + BEAR pairs to recover USDC. Each pair returns 2 USDC. No fee. Requires KYC tier 1. You must hold at least pairCount of BOTH BULL and BEAR for the base ticker, and those tokens must not be reserved.
// Request
{
"baseTicker": "JCB",
"pairCount": 50
}
// Response
{
"baseTicker": "JCB",
"usdcReturned": 100000000,
"bullRedeemed": 50,
"bearRedeemed": 50,
"feeCharged": 0
}POST /v1/transfers/deposit
Deposit USDC to your collateral account. In development with chain mock, this credits immediately.
POST /v1/transfers/withdraw
Withdraw USDC from your available balance. If you have open orders, you may need to cancel them first to free up collateral. The system checks your engine balance before processing — if the engine is unreachable, the withdrawal is rejected (fail-closed).
POST /v1/admin/settle
Admin-only. Triggers settlement for a market using filed EBITDA from Companies House. Tokens settle to USDC based on the ratio of filed EBITDA to initial EBITDA. BULL payout = clamp(ratio, 0, 2) × mintPrice. BEAR payout = 2 × mintPrice − BULL payout.
WS /v1/ws?userId={userId}
Real-time updates. Authenticate with JWT or API key. Subscribe to channels by sending:
{ "op": "subscribe", "channels": ["trades.JCB-BULL", "book.JCB-BULL", "ticker.JCB-BULL", "user.orders"] }Channels:
trades.{ticker} — Trade events (price, size, takerSide, ts)book.{ticker} — Order book deltas (bids/asks added/removed)ticker.{ticker} — Ticker updates (last price, 24h change, volume)user.orders — Your order updates (status changes, fills)Messages have a channel, seq, ts, and data field.
| Operation | Fee |
|---|---|
| Maker (limit order filled) | 0% |
| Taker (order crosses spread) | 0.05% (5 bps) |
| Mint | 0.10% (10 bps) |
| Redeem | Free |
| Settlement | Free |
Standard error response:
{
"statusCode": 400,
"message": "insufficient collateral",
"error": "Bad Request"
}Common error messages:
insufficient collateral — Not enough available USDC for the operationinsufficient tokens — Not enough token balance for a sell or redeemtokens reserved — Tokens are locked in open sell orders, cannot redeemKYC required — Identity verification needed to tradeMinimal bot that mints tokens, places a limit order, and listens to trades:
import requests, json, websocket, threading
API = "http://localhost:3001/v1"
TICKER = "JCB-BULL"
# 1. Login
r = requests.post(f"{API}/auth/login", json={
"email": "founder@test.com",
"password": "Test1234!"
})
jwt = r.json()["accessToken"]
headers = {"Authorization": f"Bearer {jwt}"}
# 2. Mint 100 BULL+BEAR pairs (requires KYC tier 1)
r = requests.post(f"{API}/tokens/mint", json={
"baseTicker": "JCB",
"pairCount": 100
}, headers=headers)
print("Minted:", r.json())
# 3. Place a sell limit order on BULL tokens
r = requests.post(f"{API}/trading/orders", json={
"ticker": TICKER,
"side": "sell",
"type": "limit",
"price": 0.65,
"size": 50
}, headers=headers)
print("Order:", r.json())
# 4. Subscribe to live trades
def on_msg(ws, msg):
data = json.loads(msg)
if data.get("channel", "").startswith("trades."):
print(f"Trade: {data['data']}")
ws = websocket.WebSocketApp(
f"ws://localhost:3001/v1/ws?userId=founder",
on_message=on_msg
)
ws.on_open = lambda ws: ws.send(json.dumps({
"op": "subscribe",
"channels": [f"trades.{TICKER}"]
}))
ws.run_forever()