Skip to content
Predictly

Developers

Build bots and tools against the Predictly API: the same REST and WebSocket endpoints the website uses. Prices are integer cents (1–99), YES + NO = 100, and balances are integer US cents ($1 = 100).

Open interactive API docs

Authentication

  1. Create a key on this page and save the private key (.pem).
  2. For each request, sign timestamp + METHOD + path with Ed25519. Use the timestamp in unix milliseconds and the path without its query string.
  3. Send FS-ACCESS-KEY, FS-ACCESS-TIMESTAMP and FS-ACCESS-SIGNATURE (base64). Timestamps must be within 30 seconds of server time.
# pip install requests cryptography
import base64, time, requests
from cryptography.hazmat.primitives import serialization

API = "http://localhost:3000/api"
KEY_ID = "your-api-key-id"
private_key = serialization.load_pem_private_key(open("foresight.pem", "rb").read(), password=None)

def signed(method: str, path: str) -> dict:
    ts = str(int(time.time() * 1000))
    message = (ts + method.upper() + path.split("?")[0]).encode()
    return {
        "FS-ACCESS-KEY": KEY_ID,
        "FS-ACCESS-TIMESTAMP": ts,
        "FS-ACCESS-SIGNATURE": base64.b64encode(private_key.sign(message)).decode(),
    }

print(requests.get(f"{API}/v1/portfolio/balance", headers=signed("GET", "/v1/portfolio/balance")).json())

# Buy 5 YES at up to 40¢ (limit order, rests if not filled)
order = {"ticker": "FEDDEC-26DEC-HOLD", "side": "yes", "action": "buy", "type": "limit", "count": 5, "price": 40}
r = requests.post(f"{API}/v1/portfolio/orders", json=order, headers=signed("POST", "/v1/portfolio/orders"))
print(r.status_code, r.json())

Real-time WebSocket

const ws = new WebSocket("ws://localhost:3000/api/v1/ws");
ws.onopen = () =>
  ws.send(JSON.stringify({
    id: 1,
    cmd: "subscribe",
    params: { channels: ["orderbook_delta", "ticker", "trade"], market_tickers: ["FEDDEC-26DEC-HOLD"] },
  }));
// Frames: { type, sid, seq, msg }. orderbook_delta starts with an orderbook_snapshot.
// seq increases by 1 per subscription. On a gap, unsubscribe + resubscribe for a fresh snapshot.
ws.onmessage = (e) => console.log(JSON.parse(e.data));

Public channels: orderbook_delta, orderbook_snapshot, ticker, trade, market_status. Private channels (logged-in session or signed upgrade request): fill, order_update, balance.

Order basics

  • price is in the order’s own side: NO at 30¢ is the same book level as YES at 70¢.
  • limit orders rest until filled or canceled. market orders fill immediately and cancel the rest; price is their worst acceptable price.
  • Buys reserve price × count cents; sells reserve the contracts. Holding YES and NO in one market nets to 100 cents ($1) per pair. All balances are USD cents.
  • Errors look like {"error":{"code","message"}}. Order placement is rate limited per key.