Prediction markets are among the most technically demanding domains to build APIs for. You're dealing with financial instruments that expire, probabilities that price in real-time, multi-outcome events with complex capital relationships, and a user base that includes both humans clicking a UI and automated trading bots running arbitrage strategies. Every design decision gets stress-tested immediately.
Polymarket, currently the world's largest prediction market platform by volume, has built an API ecosystem that's worth studying for exactly this reason. It isn't just a CRUD API over a database. It's a carefully layered architecture that handles the fundamental tension between openness and security, between real-time and historical data, and between traditional finance patterns and crypto-native primitives.
Here are eight design patterns worth extracting from how they've done it.
Pattern 1: Domain-Separated API Layers
Polymarket exposes three distinct APIs, each with a clear domain:
-
Gamma API (
gamma-api.polymarket.com) — market discovery, events, tags, search -
CLOB API (
clob.polymarket.com) — order book data, pricing, order placement -
Data API (
data-api.polymarket.com) — user positions, trades, analytics, leaderboards
This isn't just a naming convention — each API has different authentication requirements, different update cadences, and different consumer profiles. The Gamma API is entirely public, optimized for browsing and discovery. The CLOB API has both public endpoints (anyone can read the order book) and authenticated endpoints (trading requires credentials). The Data API is public but wallet-addressed — you query positions by user address.
The design lesson here is that separating by domain rather than by entity produces more coherent APIs. A naïve approach would give you /markets, /orders, /users all under one roof. Polymarket instead asks: "What is this API for?" and then builds around that question. Discovery has different access patterns than trading. Trading has different latency requirements than analytics. Giving each its own base URL means each can evolve, scale, and authenticate independently.
Pattern 2: Public-First Data Access
Everything about market data — prices, order books, event metadata, historical trades — is fully public:
curl "https://gamma-api.polymarket.com/events?limit=5"
No API key. No OAuth. No rate limit walls on read endpoints. You get the data.
This is a deliberate choice that most financial platforms don't make. Traditional exchanges guard market data as a revenue source. Polymarket treats it as infrastructure — the more people can read and build on top of the data, the more liquid and useful the market becomes. It's public goods logic applied to an API.
The practical consequence for API designers is worth noting: separating read access from write access as a first-class concern, rather than applying authentication uniformly, is almost always the right call for platforms where data consumption vastly outnumbers data production. If a user can read market prices without credentials, you've removed friction from 95% of your potential audience. You only add friction at the point where it actually matters — when they want to place a real order.
Pattern 3: Two-Level Authentication That Reflects Real Trust
Trading endpoints require authentication, but Polymarket's auth model has a structure that most API designers haven't seen before: two levels with distinct purposes.
L1 Authentication uses an EIP-712 signature from the user's private key. It proves wallet ownership. You use it exactly once (or infrequently) to derive API credentials:
// L1: Use your private key to derive API credentials
const credentials = await client.createOrDeriveApiKey();
// → { key: "...", secret: "...", passphrase: "..." }
L2 Authentication uses HMAC-SHA256 with those derived credentials. It's what you attach to every trading request:
// L2 headers on every trading request
{
"POLY_ADDRESS": "0x...",
"POLY_SIGNATURE": "<hmac-sha256>",
"POLY_TIMESTAMP": "1716000000",
"POLY_API_KEY": "550e8400-...",
"POLY_PASSPHRASE": "..."
}
The insight is that different operations deserve different security ceremonies. Creating API keys requires proving wallet control — that's a high-stakes action that should demand a cryptographic signature from the private key. But once you've established that trust, routine trading requests shouldn't require re-signing with your private key on every call. L2 credentials are lightweight enough for high-frequency use while still being tied to L1 identity.
This pattern maps well beyond crypto: think of it as the difference between "prove you're this person" (L1, done infrequently with the strongest available credential) and "prove this request came from you" (L2, done constantly with a session credential). Most web apps collapse these into one auth flow and lose the security nuance.
Pattern 4: Orders as Signed Messages, Not API Calls
Here's where prediction markets diverge most sharply from conventional API design. When you place an order on Polymarket, you're not just sending data to a server — you're creating a cryptographically signed message that's an enforceable financial commitment:
const response = await client.createAndPostOrder(
{
tokenID: "71321045679...",
price: 0.65,
size: 100,
side: Side.BUY,
},
{
tickSize: "0.01",
negRisk: false,
},
OrderType.GTC
);
Under the hood, the SDK constructs an EIP-712 typed data structure, signs it with your private key, and submits the signature along with the order. The matching engine operates offchain, but when trades are matched, they settle on-chain via Polygon using those signatures. The operator cannot fabricate trades or move funds — the signed message is the authorization.
This changes the semantics of what an "API call" means. Normally, submitting to an endpoint means "please do this on my behalf." Here, submitting an order means "here is a signed instrument authorizing this trade." The API is not an intermediary making decisions — it's a relay for cryptographically self-authorizing messages.
For API designers outside the crypto space, the takeaway is this: when the payload itself can carry authorization rather than relying entirely on transport-layer credentials, you get non-repudiation and verifiability for free. Financial systems, legal documents, and high-stakes operations are all candidates for this pattern.
Pattern 5: Explicit Ontology in the Data Model
Polymarket structures its data around two objects: Events and Markets. The distinction matters.
An Event is a question: "Who will win the 2026 US Senate race in Pennsylvania?" It has a title, a category, a resolution date. A Market is a specific tradable binary outcome within that event: "Will Bob Casey win?" One event can contain many markets.
{
"id": "501",
"title": "2026 Pennsylvania Senate Race",
"negRisk": true,
"markets": [
{ "id": "2301", "question": "Will Bob Casey win?", "outcomePrices": "[\"0.42\", \"0.58\"]" },
{ "id": "2302", "question": "Will Dave McCormick win?", "outcomePrices": "[\"0.35\", \"0.65\"]" },
{ "id": "2303", "question": "Will a third candidate win?", "outcomePrices": "[\"0.23\", \"0.77\"]" }
]
}
This is explicit ontology — the API doesn't just store data, it encodes the conceptual relationships between entities. Prices are represented as parallel arrays where index position is the binding convention: outcomes[0] corresponds to outcomePrices[0]. The negRisk flag at the event level signals that the markets within it have capital relationships that don't exist in independent markets.
Most APIs flatten these relationships. Polymarket surfaces them because they're load-bearing for how the system works. If you're building an automated trader and you miss negRisk: true, you'll construct the wrong position model and potentially lose money. The API design makes the conceptual structure visible so that omitting it is a conscious choice, not a silent default.
Pattern 6: NegRisk — Capital Relationships as a First-Class Concern
The negRisk flag on events points to one of Polymarket's most interesting API design patterns: making financial equivalences programmable.
In a standard multi-outcome event, each market is independent. But in a NegRisk event, where exactly one outcome can win, a mathematical relationship exists between positions:
1 No token on outcome A ≡ 1 Yes token on every other outcome
This isn't just math — it's implemented in smart contracts and surfaced through the API. When you hold a No position on "Other" in the Pennsylvania Senate race, you can convert it:
| Before | After |
|---|---|
| 1× No (Other) | 1× Yes (Casey) + 1× Yes (McCormick) |
The API makes this explicit: negRisk: true in the market object, and negRisk: true required in your order options when trading these markets. Get it wrong and your order will be rejected or settled incorrectly.
The design pattern here is encoding domain invariants as typed API fields rather than leaving them as documentation footnotes. The NegRisk flag doesn't exist because it's convenient to have — it exists because omitting it causes incorrect behavior. When your domain has hard constraints (only one outcome can win, positions have conversion equivalences), those constraints should appear in the API surface, not just in the docs.
Pattern 7: Dynamic Tick Size as Market State
Most financial APIs treat tick size as a static configuration. Polymarket's does something more interesting: tick size changes dynamically based on market price, and the API exposes this as a real-time event stream.
When a market's price approaches the extremes (above 0.96 or below 0.04), the minimum tick size narrows from 0.01 to 0.001:
{
"event_type": "tick_size_change",
"asset_id": "65818619657...",
"old_tick_size": "0.01",
"new_tick_size": "0.001",
"timestamp": "100000000"
}
The reasoning is intuitive: at extreme probabilities, a 1-cent tick represents a 25% move (going from 0.04 to 0.03). That's too coarse for meaningful price discovery. Finer ticks near the extremes allow the market to express probabilities like 97.3% rather than rounding to 97%.
What makes this notable as an API design choice is that tick size isn't a parameter you fetch once — it's a state that changes and must be tracked. The WebSocket exposes tick_size_change events precisely so that clients can keep their order construction logic consistent with current market state. If you hard-code tick size and miss this event, your orders will be rejected.
This reflects a broader principle: API design for financial systems must embrace state as a first-class concept. Market parameters aren't static. Resolution rules change. Outcomes get clarified. The API needs to communicate these state transitions explicitly, not leave clients to discover them through rejected requests.
Pattern 8: Two WebSocket Layers for Different Consumer Profiles
Polymarket runs two separate WebSocket systems, and understanding why reveals a pattern about audience segmentation.
The Market Channel (wss://ws-subscriptions-clob.polymarket.com/ws/market) is built for trading consumers. Subscribe by token ID, receive order book snapshots, price changes, trade executions, and tick size changes. Everything is keyed to asset IDs and optimized for low-latency order construction:
{
"assets_ids": ["65818619657568813474341868652308942079804919287380422192892211131408793125422"],
"type": "market"
}
The Real-Time Data Socket (wss://ws-live-data.polymarket.com) is built for a completely different profile. It streams comments, crypto prices from Binance and Chainlink, equity prices, and social interaction events. Subscribe by topic:
{
"action": "subscribe",
"subscriptions": [
{ "topic": "crypto_prices", "type": "update", "filters": "btcusdt,ethusd" }
]
}
These two systems serve audiences with fundamentally different needs. A market maker needs microsecond-relevant order book deltas. A UI showing "what's happening on Polymarket right now" needs comment feeds and social activity. Combining them would mean either over-engineering the social feed with trading-grade latency requirements, or under-engineering the order book feed with social-grade reliability assumptions.
The lesson is simple but often ignored: when your real-time consumers have meaningfully different latency tolerance, data volumes, and failure modes, give them separate infrastructure. Shared WebSocket endpoints that try to serve multiple purposes tend to collapse to the highest common denominator for complexity and to the lowest common denominator for performance.
What These Patterns Have in Common
Polymarket's API design reflects a particular philosophy: the API should make the domain's actual structure visible, not abstract it away.
The three-layer architecture maps to real domain boundaries. The public-first access reflects how prediction market value works. The two-level auth mirrors the real difference between proving identity and authorizing an action. Orders as signed messages encode the non-custodial guarantee. The Event/Market hierarchy and the NegRisk flag expose relationships that would otherwise be invisible. Dynamic tick sizes keep client state consistent with market state. Separate WebSocket layers serve separate audiences.
Most API design advice focuses on ergonomics: make it easy to call, consistent in naming, predictable in error handling. Polymarket's API does all of that — but the more interesting choices are about faithfulness to the domain. When the domain has a meaningful distinction, the API surface it. When the domain has a constraint, the API enforces it. When the domain has a state that changes, the API broadcasts it.
The result is an API that requires more from its consumers, but one where getting it right means you actually understand the system you're trading on. That's not a coincidence — for a prediction market, where the whole point is that prices reflect information, an API that forces you to understand the structure of the market is doing exactly what it should.
Top comments (0)