Skip to content

Prop-Firm Quickstart

The full lifecycle your backend orchestrates: register a trader → send a login link → create a $10,000 challenge → trader trades → pull the trade feed → close or reset. Every step has copy-paste curl, Python, and TypeScript. All calls are server-to-server with your tenant X-API-Key (Authentication); the system model lives in Overview.


Terminal window
export POLYSIM_TENANT_KEY="<your-tenant-key>"
export POLYSIM_BASE_URL="https://api.polysimulator.com" # or your staging base URL

  1. Register the trader

    Map your external_id + the trader’s email to a native PolySimulator user. Returns 201 on first registration, 200 on idempotent replay — either way you get a user_id.

    Terminal window
    curl -X POST "$POLYSIM_BASE_URL/v1/partner/users" \
    -H "X-API-Key: $POLYSIM_TENANT_KEY" \
    -H "Content-Type: application/json" \
    -d '{
    "external_id": "pc_trader_123",
    "email": "trader@example.com",
    "metadata": { "country": "ES" }
    }'
    Response (201)
    {
    "user_id": 12345,
    "tenant_user_id": "6a2f0c4e-1b7d-4e2a-9f3c-1a2b3c4d5e6f",
    "external_id": "pc_trader_123",
    "email": "trader@example.com",
    "created": true
    }
  2. Send a magic-link login

    Email the trader a one-time login link to sign in to the native PolySimulator UI. The link is never returned in the response.

    Terminal window
    curl -X POST "$POLYSIM_BASE_URL/v1/partner/users/12345/login-link" \
    -H "X-API-Key: $POLYSIM_TENANT_KEY" \
    -H "Content-Type: application/json" \
    -d '{ "redirect_url": "https://polysimulator.com/markets" }'
    Response (202)
    { "status": "accepted" }
  3. Create a $10,000 challenge account

    Provision an account funded to starting_balance. Pick a stage (demo/challenge/funded) and an external_id for the account.

    Terminal window
    curl -X POST "$POLYSIM_BASE_URL/v1/partner/accounts" \
    -H "X-API-Key: $POLYSIM_TENANT_KEY" \
    -H "Content-Type: application/json" \
    -d '{
    "user_id": 12345,
    "external_id": "pc_challenge_001",
    "stage": "challenge",
    "starting_balance": "10000.00",
    "metadata": { "plan": "10k", "attempt": 1 },
    "rules": { "profit_target_pct": 20, "max_drawdown_pct": 10 }
    }'
    Response (201)
    {
    "account_id": "0b1d8e2a-3c4f-5a6b-7c8d-9e0f1a2b3c4d",
    "wallet_id": 9911,
    "user_id": 12345,
    "external_id": "pc_challenge_001",
    "stage": "challenge",
    "status": "active",
    "starting_balance": "10000.00",
    "rules": { "profit_target_pct": 20, "max_drawdown_pct": 10 },
    "metadata": { "plan": "10k", "attempt": 1 }
    }
  4. The trader trades (native UI)

    The trader clicks the link, lands in the PolySimulator UI signed in, selects the challenge account, and trades against live Polymarket prices. There is no server-side order-placement endpoint in v1 — trading is the native UI, and your backend reads the fills in the next step. Challenge trades never touch the trader’s personal balance or the public leaderboard.

  5. Read live account state

    Poll the account-state envelope for balance, equity, and open positions (marked to market). Use this for live dashboards and drawdown checks.

    Terminal window
    curl -H "X-API-Key: $POLYSIM_TENANT_KEY" \
    "$POLYSIM_BASE_URL/v1/partner/accounts/0b1d8e2a-3c4f-5a6b-7c8d-9e0f1a2b3c4d"
    Response (200)
    {
    "account_id": "0b1d8e2a-3c4f-5a6b-7c8d-9e0f1a2b3c4d",
    "external_id": "pc_challenge_001",
    "stage": "challenge",
    "status": "active",
    "balance": "9875.12",
    "starting_balance": "10000.00",
    "equity": "10142.42",
    "unrealized_pnl": "267.30",
    "realized_pnl": null,
    "open_positions": [ { "market_id": "0xabc...", "outcome": "Yes", "quantity": "100.0000", "avg_entry_price": "0.4100", "current_price": "0.4350", "market_value": "43.50", "unrealized_pnl": "2.50" } ],
    "rules": { "profit_target_pct": 20, "max_drawdown_pct": 10 },
    "metadata": { "plan": "10k", "attempt": 1 },
    "updated_at": "2026-06-04T14:10:08.055516+00:00"
    }
  6. Pull the trade feed (your primary data source)

    Page through filled trades with keyset pagination — newest first. Follow next_cursor until it’s null. Filter with side (BUY/SELL) or market_id; limit defaults to 100, max 500.

    Terminal window
    # First page
    curl -H "X-API-Key: $POLYSIM_TENANT_KEY" \
    "$POLYSIM_BASE_URL/v1/partner/accounts/0b1d8e2a-3c4f-5a6b-7c8d-9e0f1a2b3c4d/trades?limit=500"
    # Next page — pass the previous response's next_cursor
    curl -H "X-API-Key: $POLYSIM_TENANT_KEY" \
    "$POLYSIM_BASE_URL/v1/partner/accounts/0b1d8e2a-3c4f-5a6b-7c8d-9e0f1a2b3c4d/trades?limit=500&cursor=eyJmaWxsZWRf..."
    Response (200)
    {
    "data": [
    { "trade_id": 8881, "order_id": 8881, "market_id": "0xabc...", "side": "BUY", "outcome": "Yes", "price": "0.4100", "quantity": "10.0000", "notional": "4.10", "fee": "0.00", "filled_at": "2026-06-04T14:09:31.220148+00:00", "client_order_id": "trader-key", "realized_pnl": null }
    ],
    "next_cursor": "eyJmaWxsZWRfYXQiOiIyMDI2LTA1LTMxVDEyOjMwOjAwKzAwOjAwIiwib3JkZXJfaWQiOjg4ODF9"
    }
  7. Close or reset the attempt

    When the trader passes, fails, or restarts, close or reset the account. Both take no request body and return the full account-state envelope.

    Close archives the account and freezes the wallet — non-destructive and idempotent (a second close is a 200 no-op):

    Terminal window
    curl -X POST "$POLYSIM_BASE_URL/v1/partner/accounts/0b1d8e2a-.../close" \
    -H "X-API-Key: $POLYSIM_TENANT_KEY"

    Reset restarts the same account (same account_id, same external_id) to its starting_balance, re-activating it if closed and preserving full history:

    Terminal window
    curl -X POST "$POLYSIM_BASE_URL/v1/partner/accounts/0b1d8e2a-.../reset" \
    -H "X-API-Key: $POLYSIM_TENANT_KEY"

    Both cancel any open orders first; if some can’t be cancelled (over the bulk-cancel limit or a transient lock) they return 409 ORDERS_NOT_CANCELLED — retry to drain the rest. See Endpoint Reference for the exact semantics. To keep one account per attempt instead, create a fresh account with a new external_id and close the old one.


A minimal end-to-end provisioning + reconciliation loop in Python:

import os, requests
KEY = os.environ["POLYSIM_TENANT_KEY"]
BASE = os.environ.get("POLYSIM_BASE_URL", "https://api.polysimulator.com")
H = {"X-API-Key": KEY, "Content-Type": "application/json"}
def register_trader(external_id, email):
r = requests.post(f"{BASE}/v1/partner/users", headers=H,
json={"external_id": external_id, "email": email})
r.raise_for_status()
return r.json()["user_id"]
def send_login(user_id, redirect="https://polysimulator.com/markets"):
requests.post(f"{BASE}/v1/partner/users/{user_id}/login-link",
headers=H, json={"redirect_url": redirect}).raise_for_status()
def create_challenge(user_id, external_id, balance="10000.00"):
r = requests.post(f"{BASE}/v1/partner/accounts", headers=H, json={
"user_id": user_id, "external_id": external_id,
"stage": "challenge", "starting_balance": balance,
})
r.raise_for_status()
return r.json()["account_id"]
def evaluate(account_id):
state = requests.get(f"{BASE}/v1/partner/accounts/{account_id}", headers=H).json()
cursor, fills = None, []
while True:
params = {"limit": 500, **({"cursor": cursor} if cursor else {})}
page = requests.get(f"{BASE}/v1/partner/accounts/{account_id}/trades",
headers=H, params=params).json()
fills.extend(page["data"])
cursor = page["next_cursor"]
if not cursor:
break
# Your rule engine runs here, on `state` + `fills`.
return state, fills
if __name__ == "__main__":
uid = register_trader("pc_trader_123", "trader@example.com")
send_login(uid)
acc = create_challenge(uid, "pc_challenge_001")
# ... trader trades in the native UI ...
state, fills = evaluate(acc)
print(f"equity={state['equity']} fills={len(fills)}")