Product · Developers

Poly Syncer API and WebSocket signal feed

A REST surface for the leaderboard, strategies, and portfolio. A WebSocket for live trade signals. Pro authenticates with an API key; Elite authenticates with an EIP-712 signed request and gets the full raw signal feed.

Last reviewed · Poly Syncer engineering team

The Poly Syncer API is what the panel uses internally — same endpoints, same payload shapes, same rate limits. We ship the same API to developers because we’d rather you build on top of us than reverse-engineer us. Whether you are building a custom Polymarket bot on top of our signal feed, wiring the Polymarket leaderboard into a research notebook, or running automated Polymarket trading from your own infrastructure, the contract is identical. It is the same data layer that makes prediction market copy trading work end-to-end inside the panel. The surface is split into three families: /leaderboard for ranked wallet data, /signals for the live trade firehose, and /portfolio for your own book.

Definition. The Poly Syncer API is the public REST and WebSocket interface to the leaderboard, signal feed, and portfolio data that powers the Poly Syncer panel. It is read-mostly; trade execution is performed by the user’s own wallet via the on-chain Polymarket exchange, never by an API call to us.

Base URL and versioning

Production base: https://api.polysyncer.com/v1. The /v1 prefix is permanent — we will ship a /v2 rather than break a contract. Deprecation notices land in the changelog at least 90 days before any field is removed, and the X-PolySyncer-Deprecation response header surfaces field-level deprecations on every call.

Authentication

API key (Pro)

Pro plans get a 64-byte API key, generated from the panel and rotated whenever you choose. Pass it as the Authorization: Bearer <key> header on every request. Keys are scoped read-only to the leaderboard, the public signal feed (10-second delayed), and your own portfolio. Keys can be ACL-restricted to a CIDR range, and every key issues a per-request audit log row that you can fetch via /audit.

Signed request (Elite)

Elite unlocks the full raw signal feed — the same sub-second push the Elite panel consumes. To prevent leakage, the raw feed is gated behind an EIP-712 signed request rather than a bearer key. Each call (or each WebSocket subscription) carries a signature over a typed message that includes a nonce and a timestamp; the server verifies the signer matches a wallet on your Elite plan and rejects anything older than 30 seconds. There is no key to leak because there is no key.

REST endpoints (selected)

MethodPathPurpose
GET/leaderboardRanked wallets with composite score, Sharpe, ROI, drawdown.
GET/leaderboard/:walletFull profile: equity curve, per-category breakdown, open positions.
GET/strategiesPre-built strategy templates and your active configs.
GET/portfolio/positionsOpen positions for the authenticated account.
GET/portfolio/pnl?window=30dRealized + unrealized PnL series.
POST/portfolio/exportGenerate a signed CSV/JSON export URL (24h TTL).

WebSocket signal feed

Endpoint: wss://stream.polysyncer.com/v1/signals. After connect, send a subscribe frame naming the channels you want. Channels are: leaderboard.delta (rank changes), signals.public (10s-delayed trade signals, available to Pro), and signals.raw (sub-second raw feed, Elite only, signed-subscribe required).

TypeScript example — subscribe to the raw feed

// Elite — signed subscribe to the raw signal feed
import { WebSocket } from "ws";
import { signTypedData } from "./eip712";

const ws = new WebSocket("wss://stream.polysyncer.com/v1/signals");

ws.on("open", async () => {
  const nonce = crypto.randomUUID();
  const ts    = Math.floor(Date.now() / 1000);

  const sig = await signTypedData(walletPrivateKey, {
    domain: { name: "Poly Syncer", version: "1", chainId: 137 },
    types:  { Subscribe: [
      { name: "channel",   type: "string"  },
      { name: "nonce",     type: "string"  },
      { name: "timestamp", type: "uint64"  },
    ] },
    primaryType: "Subscribe",
    message: { channel: "signals.raw", nonce, timestamp: ts },
  });

  ws.send(JSON.stringify({
    op: "subscribe",
    channel: "signals.raw",
    auth:    { signer: walletAddress, signature: sig, nonce, timestamp: ts },
  }));
});

ws.on("message", (raw) => {
  const evt = JSON.parse(raw.toString());
  if (evt.type === "trade") {
    console.log(`${evt.wallet} ${evt.side} ${evt.shares} @ ${evt.prob} on ${evt.market}`);
  }
});

TypeScript example — fetch the leaderboard (Pro)

const res = await fetch("https://api.polysyncer.com/v1/leaderboard?limit=50&sort=sharpe", {
  headers: { Authorization: `Bearer ${process.env.POLYSYNCER_KEY}` },
});

const { wallets } = await res.json();
for (const w of wallets) {
  console.log(w.rank, w.address, w.sharpe.toFixed(2), w.roi30d, w.drawdown);
}

Rate limits

PlanRESTWebSocket subscriptionsSignal feed
Pro60 req/min3 concurrentPublic, 10s delayed
Elite600 req/min25 concurrentRaw, sub-second, signed

Limits are enforced as a token bucket with a 10-second refill, so a short burst above the per-minute average is tolerated. Rate-limit state is exposed on every response via X-RateLimit-Remaining and X-RateLimit-Reset. Sustained 429s after retry-after backoff indicate your client is mis-paginating — see the developer docs for cursor patterns.

Errors and idempotency

Errors return RFC 7807 problem documents with stable type URIs. Mutating endpoints accept an Idempotency-Key header and dedupe on it for 24 hours. WebSocket disconnects are surfaced with a typed close frame; reconnection should be exponential-backoff with jitter, capped at 30 seconds.

SLAs and status

The Elite raw feed is engineered for sub-second p99 from on-chain confirmation to subscriber receipt. The REST surface targets 99.9% monthly availability. Live operational status, regional latency, and incident history are published on the status page; security and threat-model details are on the security page.

Jonas Reiter Staff engineer · streaming infra · Rust + Go listener