Prediction market APIs are hard to design because they combine expiring financial instruments, real-time probability pricing, multi-outcome capital relationships, human users, and automated trading bots. Every API decision gets tested under latency, security, and correctness pressure.
Polymarket, one of the largest prediction market platforms by volume, is useful to study because its API is not just CRUD over markets and orders. It separates discovery, trading, analytics, authentication, signed orders, and real-time updates into distinct surfaces.
This article extracts eight implementation patterns you can apply when designing APIs for trading systems, crypto apps, fintech products, or any domain where state, trust, and data semantics matter.
Pattern 1: Separate APIs by domain, not by database entity
Polymarket exposes three main APIs:
-
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
Each API has a different purpose:
| API | Primary use | Auth model | Consumer |
|---|---|---|---|
| Gamma API | Browse and discover markets | Public | Apps, users, indexers |
| CLOB API | Read books and place orders | Public reads, authenticated writes | Traders, bots, market makers |
| Data API | Query wallet-based activity | Public, address-scoped | Dashboards, analytics tools |
A less deliberate design would put everything under one API:
/api/markets
/api/orders
/api/users
/api/trades
Polymarket instead separates by operational domain:
https://gamma-api.polymarket.com
https://clob.polymarket.com
https://data-api.polymarket.com
That separation matters because discovery, trading, and analytics have different requirements:
- Discovery optimizes for searchability and browsing.
- Trading optimizes for correctness, latency, and authentication.
- Analytics optimizes for historical reads and wallet-level aggregation.
Implementation takeaway
When designing your API, start with usage boundaries instead of tables.
Ask:
Who calls this API?
How often do they call it?
Does it need authentication?
Does it need low latency?
Can it scale independently?
Can it fail independently?
If the answers differ significantly, consider separate API surfaces.
Pattern 2: Make read access public when data liquidity matters
Polymarket makes market data public:
curl "https://gamma-api.polymarket.com/events?limit=5"
No API key is required for basic market discovery.
That includes data such as:
- Event metadata
- Market metadata
- Prices
- Order books
- Historical trades
This is a deliberate platform decision. Traditional financial exchanges often monetize market data directly. Polymarket treats market data as infrastructure: the more people can read it, analyze it, and build on it, the more useful the market becomes.
Implementation takeaway
Separate read access from write access.
A common mistake is to require authentication for everything:
GET /markets requires auth
GET /order-book requires auth
POST /orders requires auth
For many platforms, this creates unnecessary friction. A better model is:
GET /markets public
GET /order-book public
GET /trades public
POST /orders authenticated
DELETE /orders authenticated
Public reads are especially useful when:
- Data consumers vastly outnumber writers.
- Developers need to explore before integrating.
- Bots, dashboards, and indexers increase platform value.
- The sensitive action is mutation, not observation.
Add authentication at the point where risk appears: placing orders, moving funds, changing state, or accessing private account information.
Pattern 3: Use different authentication levels for different trust levels
Trading endpoints require authentication, but Polymarket uses two authentication levels with different responsibilities.
L1 authentication: prove wallet ownership
L1 authentication uses an EIP-712 signature from the user’s private key. It proves that the caller controls the wallet.
You use it to create or derive API credentials:
// L1: Use your private key to derive API credentials
const credentials = await client.createOrDeriveApiKey();
// Example result:
// {
// key: "...",
// secret: "...",
// passphrase: "..."
// }
This is a high-trust action. It should require the strongest credential: the private key signature.
L2 authentication: sign each API request
After API credentials exist, routine trading requests use HMAC-SHA256 headers:
{
"POLY_ADDRESS": "0x...",
"POLY_SIGNATURE": "<hmac-sha256>",
"POLY_TIMESTAMP": "1716000000",
"POLY_API_KEY": "550e8400-...",
"POLY_PASSPHRASE": "..."
}
L2 authentication proves that a specific request came from the credential holder without requiring a private-key signature on every API call.
Implementation takeaway
Do not use the same authentication ceremony for every action.
A practical model:
| Operation | Auth strength |
|---|---|
| Create API key | Strong identity proof |
| Rotate credentials | Strong identity proof |
| Place order | Request signature/session credential |
| Read public market data | No auth |
| Read private account data | Session credential |
| Withdraw funds | Strong identity proof |
This maps beyond crypto. In a traditional app:
- L1 is “prove you are the account owner.”
- L2 is “prove this request came from an active authorized session.”
That distinction improves both security and usability.
Pattern 4: Treat high-stakes actions as signed payloads, not just API calls
On Polymarket, placing an order is not merely sending JSON to a server. The order is a cryptographically signed financial instruction.
Example order:
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 creates an EIP-712 typed data structure, signs it with the user’s private key, and submits the signed order. The matching engine runs offchain, but matched trades settle on Polygon using those signatures.
The important design point: the operator cannot fabricate trades or move funds without user authorization. The signed message is the authorization.
Conventional API semantics
In a normal API, this means:
Please perform this action for me.
Example:
POST /orders
Authorization: Bearer <token>
The server decides whether to execute the action.
Signed-message semantics
With signed orders, the payload means:
Here is a signed instruction authorizing this exact action.
The API acts more like a relay than an authority.
Implementation takeaway
For high-stakes operations, consider making the payload itself verifiable.
Useful domains include:
- Financial transactions
- Legal approvals
- Contract signatures
- Permission grants
- Sensitive workflow approvals
- Cross-system authorization
Instead of relying only on transport-layer credentials, encode authorization into the payload:
{
"action": "transfer",
"amount": "100.00",
"asset": "USDC",
"recipient": "0x...",
"expiresAt": "2026-01-01T00:00:00Z",
"signature": "0x..."
}
This gives you better auditability, non-repudiation, and replay protection when designed correctly.
Pattern 5: Encode the domain ontology in the API model
Polymarket models prediction markets using two important objects:
- Event
- Market
An Event is the broader question:
“Who will win the 2026 US Senate race in Pennsylvania?”
A Market is a specific tradable binary outcome inside that event:
“Will Bob Casey win?”
One event can contain many markets.
Example structure:
{
"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 distinction is not cosmetic. It tells API consumers how the domain works.
The event groups related markets. The market represents a tradable outcome. The negRisk flag signals that markets inside the event have capital relationships.
Implementation takeaway
Avoid flattening important domain concepts into generic resources.
A weak model might expose only:
GET /markets
A stronger model exposes relationships:
GET /events
GET /events/:id/markets
GET /markets/:id
If the distinction matters to business logic, it should exist in the API.
Good domain modeling helps clients avoid incorrect assumptions. For example, if an automated trader ignores negRisk: true, it may construct the wrong position model.
Your API should make these relationships visible instead of hiding them in documentation.
Pattern 6: Represent domain invariants as API fields
The negRisk flag is one of Polymarket’s most interesting design choices.
In a standard multi-outcome event, each market can be treated independently. But in a NegRisk event, exactly one outcome can win. That creates mathematical relationships between positions:
1 No token on outcome A ≡ 1 Yes token on every other outcome
Example:
| Before | After |
|---|---|
| 1× No (Other) | 1× Yes (Casey) + 1× Yes (McCormick) |
This is not just theoretical. It affects trading and settlement behavior.
Polymarket exposes this through API fields:
{
"negRisk": true
}
And when placing orders, the client must pass the correct market options:
const response = await client.createAndPostOrder(
{
tokenID: "71321045679...",
price: 0.65,
size: 100,
side: Side.BUY,
},
{
tickSize: "0.01",
negRisk: true
},
OrderType.GTC
);
If the client gets this wrong, the order can be rejected or handled incorrectly.
Implementation takeaway
If your domain has hard rules, encode them as typed fields.
Do not leave critical invariants only in prose documentation.
Examples:
{
"requiresKyc": true,
"settlementMode": "on_chain",
"isMutuallyExclusive": true,
"minCollateralRatio": "1.50",
"supportsPartialFill": true,
"expiresAt": "2026-01-01T00:00:00Z"
}
Fields like these are valuable because clients can branch on them programmatically.
Documentation explains the rule. The API should expose the rule.
Pattern 7: Treat changing market parameters as state, not configuration
Many financial APIs treat tick size as static. Polymarket exposes tick size as dynamic market state.
When a market price approaches the extremes, above 0.96 or below 0.04, the minimum tick size narrows from 0.01 to 0.001.
Example WebSocket event:
{
"event_type": "tick_size_change",
"asset_id": "65818619657...",
"old_tick_size": "0.01",
"new_tick_size": "0.001",
"timestamp": "100000000"
}
The reason is practical. Near extreme probabilities, a 1-cent tick is too coarse. Moving from 0.04 to 0.03 is a large relative move. A smaller tick allows prices like 0.973 instead of forcing 0.97.
Implementation takeaway
Do not assume market parameters are static.
For trading clients, tick size should be part of the current market state:
type MarketState = {
assetId: string;
bestBid: string;
bestAsk: string;
tickSize: string;
};
When a tick_size_change event arrives, update local state:
function handleTickSizeChange(event: {
asset_id: string;
new_tick_size: string;
}) {
marketState[event.asset_id].tickSize = event.new_tick_size;
}
Then validate orders against the current tick size before submitting:
function isValidPrice(price: number, tickSize: number) {
return Number.isInteger(price / tickSize);
}
If your client hard-codes tick size, it will eventually submit invalid orders.
The broader principle: changing domain state should be broadcast explicitly, not discovered only through failed requests.
Pattern 8: Use separate WebSocket layers for different real-time consumers
Polymarket runs two separate WebSocket systems.
Market Channel
The Market Channel is designed for trading consumers:
wss://ws-subscriptions-clob.polymarket.com/ws/market
It streams data such as:
- Order book snapshots
- Price changes
- Trade executions
- Tick size changes
Subscription example:
{
"assets_ids": [
"65818619657568813474341868652308942079804919287380422192892211131408793125422"
],
"type": "market"
}
This channel is optimized around asset IDs and low-latency trading workflows.
Real-Time Data Socket
The Real-Time Data Socket serves a different use case:
wss://ws-live-data.polymarket.com
It streams broader platform activity, including comments, crypto prices, equity prices, and social interaction events.
Subscription example:
{
"action": "subscribe",
"subscriptions": [
{
"topic": "crypto_prices",
"type": "update",
"filters": "btcusdt,ethusd"
}
]
}
These consumers have different needs.
A market maker needs low-latency order book updates. A UI showing platform activity needs comments, prices, and social events. Combining both into one WebSocket system would force one infrastructure layer to serve conflicting requirements.
Implementation takeaway
Separate real-time infrastructure when consumers differ by:
- Latency requirements
- Message volume
- Failure tolerance
- Data shape
- Subscription model
- Operational priority
A practical split might look like this:
/ws/trading low latency, order books, fills
/ws/activity comments, notifications, social events
/ws/analytics aggregates, leaderboards, dashboards
Trying to make one WebSocket endpoint serve every use case usually creates unnecessary complexity and uneven performance.
What these patterns have in common
Polymarket’s API design makes the domain structure visible.
The main patterns are:
- Separate APIs by operational domain.
- Make public read access easy when data liquidity matters.
- Use different authentication levels for different trust levels.
- Represent high-stakes actions as signed payloads.
- Encode the domain ontology in the API model.
- Surface domain invariants as explicit fields.
- Treat changing parameters as real-time state.
- Split WebSocket infrastructure by consumer profile.
The broader design lesson: do not abstract away distinctions that matter.
If a concept affects client behavior, put it in the API. If a rule affects correctness, expose it as a field. If state changes over time, broadcast the change. If different consumers have different performance requirements, give them different interfaces.
Good API design is not only about clean routes and consistent naming. It is about making the system’s real constraints understandable and programmable.
Top comments (0)