Docs / API Reference

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.

Authentication

Two methods are supported:

  • Session JWT. Obtained from POST /v1/auth/login. Pass as Authorization: Bearer <jwt>. Used by the web UI.
  • API key. Created via 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.

Registration & KYC

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.

Markets

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.

Order Book

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
}

Candles

GET /v1/markets/{ticker}/candles?interval={interval}&limit={limit}

OHLCV candlestick data. Intervals: 1m, 5m, 15m, 1h, 4h, 1d. Default limit: 100.

Trades

GET /v1/markets/{ticker}/trades?limit={limit}

Recent trades for a compound ticker. Each trade has price, size, takerSide, and timestamp.

Orders

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.

Account

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.

Token Balances

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.

Mint

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.

Redeem

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
}

Deposits & Withdrawals

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).

Settlement

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.

WebSocket

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.

Fees

OperationFee
Maker (limit order filled)0%
Taker (order crosses spread)0.05% (5 bps)
Mint0.10% (10 bps)
RedeemFree
SettlementFree

Errors

Standard error response:

{
  "statusCode": 400,
  "message": "insufficient collateral",
  "error": "Bad Request"
}

Common error messages:

  • insufficient collateral — Not enough available USDC for the operation
  • insufficient tokens — Not enough token balance for a sell or redeem
  • tokens reserved — Tokens are locked in open sell orders, cannot redeem
  • KYC required — Identity verification needed to trade

Python Example

Minimal 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()
← DocsBot Building →