# Get Account Balances Source: https://docs.polymarket.us/api-reference/account/get-account-balances /api-reference/oapi-schemas/portfolio-schema.json get /v1/account/balances Get user's current account balances including buying power, asset values, and pending transactions # Get who am I Source: https://docs.polymarket.us/api-reference/accounts/get-who-am-i /institutional/oapi-schemas/accounts-schema.json get /v1/whoami Returns the user information of the caller # List accounts Source: https://docs.polymarket.us/api-reference/accounts/list-accounts /institutional/oapi-schemas/accounts-schema.json get /v1/accounts Returns the accounts that the user may use to trade # List users Source: https://docs.polymarket.us/api-reference/accounts/list-users /institutional/oapi-schemas/accounts-schema.json get /v1/users Returns the users that the caller may trade on behalf of # Authentication Source: https://docs.polymarket.us/api-reference/authentication How to get API keys and make authenticated requests. Authenticated endpoints - trading, portfolio, and WebSocket - require an API key. Public endpoints like market data and events don't need one. ## Get your API keys 1. **Download the app** - Get the [Polymarket US app](https://apps.apple.com/us/app/polymarket/id6648798962) and create an account. 2. **Complete identity verification** - You'll be asked to verify your identity before you can trade or access the API. Once approved, you'll see a confirmation in the app. Approved to Start Trading 3. **Go to the developer portal** - Visit [polymarket.us/developer](https://polymarket.us/developer) and sign in with the same method you used in the app (Apple, Google, or email). Developer Portal 4. **Create an API key** - Click to create a new key. You'll get a **Key ID** and a **Secret Key**. Create API Key Your secret key is shown **only once**. Copy it somewhere safe before closing the dialog. If you need help getting set up or need an invite code to access the app, email [support@polymarket.us](mailto:support@polymarket.us). Always sign in with the same method (Apple, Google, or email). Switching between sign-in methods may break your API key access. *** ## Using the SDK If you're using the Python or TypeScript SDK, just pass your keys when creating the client - authentication is handled for you automatically. ```typescript TypeScript theme={null} import { PolymarketUS } from 'polymarket-us'; const client = new PolymarketUS({ keyId: process.env.POLYMARKET_KEY_ID, secretKey: process.env.POLYMARKET_SECRET_KEY, }); ``` ```python Python theme={null} import os from polymarket_us import PolymarketUS client = PolymarketUS( key_id=os.environ["POLYMARKET_KEY_ID"], secret_key=os.environ["POLYMARKET_SECRET_KEY"], ) ``` *** ## Making raw requests If you're not using an SDK, each request needs three headers: | Header | Value | | ----------------- | ------------------------------------------ | | `X-PM-Access-Key` | Your Key ID | | `X-PM-Timestamp` | Current time in milliseconds | | `X-PM-Signature` | A signature generated from your secret key | The signature is built by combining the timestamp, HTTP method, and path, then signing it with your secret key. Timestamps must be within **30 seconds** of server time. ```python theme={null} import time, base64, requests from cryptography.hazmat.primitives.asymmetric import ed25519 private_key = ed25519.Ed25519PrivateKey.from_private_bytes( base64.b64decode("YOUR_SECRET_KEY")[:32] ) def auth_headers(method, path): timestamp = str(int(time.time() * 1000)) message = f"{timestamp}{method}{path}" signature = base64.b64encode(private_key.sign(message.encode())).decode() return { "X-PM-Access-Key": "YOUR_KEY_ID", "X-PM-Timestamp": timestamp, "X-PM-Signature": signature, "Content-Type": "application/json", } response = requests.get( "https://api.polymarket.us/v1/portfolio/positions", headers=auth_headers("GET", "/v1/portfolio/positions") ) ``` *** ## Tips * Store your keys in environment variables, never in code * Don't commit keys to version control * Revoke compromised keys immediately at [polymarket.us/developer](https://polymarket.us/developer) # Create combo Source: https://docs.polymarket.us/api-reference/combos/create-combo /institutional/oapi-schemas/combos-schema.json post /v1/combos Creates or returns the combo instrument for the supplied legs. Each authenticated exchange participant may create up to 1,000 new combo instruments per week across all of its accounts. The quota resets Monday at 00:00 UTC. Returning an existing canonical combo does not consume the quota. # Get combos Source: https://docs.polymarket.us/api-reference/combos/get-combos /institutional/oapi-schemas/combos-schema.json get /v1/combos Returns the combo matching the exact symbol. # Combos API Overview Source: https://docs.polymarket.us/api-reference/combos/overview Create and read combo instruments through the Retail API **Beta access required.** The Retail Combos API is available only to explicitly enabled Retail API users. A combo is a user-defined instrument containing 2–10 legs. Each leg identifies an existing market symbol and whether the combo buys or sells that leg. Once open, a combo trades through the normal [Orders API](/api-reference/orders/overview); an RFQ is optional and provides a price-discovery and paired order-submission workflow over the same order book. All calls use normal [Retail API authentication](/api-reference/authentication) at: ```text theme={null} https://api.polymarket.us ``` ## Endpoints | Method | Endpoint | Description | | ------ | ---------------------------- | -------------------------------------------------------- | | `POST` | `/v1/combos` | Create or retrieve the canonical combo for a set of legs | | `GET` | `/v1/combos?symbol={symbol}` | Get a combo by exact symbol | On the Retail API, Combo and RFQ creation share an additional [edge rate limit](/api-reference/rate-limits) of 10 requests per 10 seconds, enforced per API key and per IP. Separately, combo creation has a participant-wide service quota of 1,000 new instruments per week across all accounts and both Retail and Institutional APIs. The quota resets Monday at 00:00 UTC; returning an existing canonical combo does not consume it. ## Create a Combo `POST /v1/combos` accepts 2–10 unique legs: ```json theme={null} { "legs": [ { "symbol": "market-a", "side": "SIDE_BUY" }, { "symbol": "market-b", "side": "SIDE_SELL" } ] } ``` Leg symbols must be open, tradable, supported instruments. Duplicate symbols and invalid combinations are rejected. A canonical set of legs always maps to the same `caoc-...` combo symbol, so creating an existing combo returns that instrument. ```json theme={null} { "combo": { "id": "caoc-...", "legs": [ { "symbol": "market-a", "side": "SIDE_BUY" }, { "symbol": "market-b", "side": "SIDE_SELL" } ], "state": "INSTRUMENT_STATE_OPEN", "createdTime": "2026-07-29T14:00:00Z", "tickSize": 0.001 } } ``` ## Get a Combo `GET /v1/combos?symbol=caoc-...` requires the exact combo symbol. The response contains a `combos` array; an unknown symbol returns an empty array. This endpoint does not paginate. ## See Also Create and manage combo RFQs and quotes Sign Retail API requests Receive RFQ and quote lifecycle events # Get Event By ID Source: https://docs.polymarket.us/api-reference/events/get-event-by-id /api-reference/oapi-schemas/events-schema.json get /v1/events/{id} Retrieve a specific event by its ID # Get Event By Slug Source: https://docs.polymarket.us/api-reference/events/get-event-by-slug /api-reference/oapi-schemas/events-schema.json get /v1/events/slug/{slug} Retrieve an event by its slug # Get Events Source: https://docs.polymarket.us/api-reference/events/get-events /api-reference/oapi-schemas/events-schema.json get /v1/events Retrieve all events # Events API Overview Source: https://docs.polymarket.us/api-reference/events/overview Event data endpoints # Events API The Events API provides access to event information. For search, see the Search API. ## Endpoints | Method | Endpoint | Description | | ------ | ----------------------------------------------- | -------------------------------- | | `GET` | `/v1/events` | Get all events with filtering | | `GET` | `/v1/events/{id}` | Get event by ID | | `GET` | `/v1/events/slug/{slug}` | Get event by slug | | `GET` | `/v1/partners/{partnerKey}/events/{externalId}` | Get partner event by external ID | ## Key Event Fields | Field | Description | | ------------- | ------------------------- | | `id` | Unique event identifier | | `slug` | URL-friendly identifier | | `title` | Event title | | `description` | Event description | | `category` | Event category | | `subcategory` | Event subcategory | | `active` | Whether event is active | | `closed` | Whether event is closed | | `archived` | Whether event is archived | ### Sports Event Fields | Field | Description | | ------------------ | ---------------------------------- | | `gameId` | Sports provider game ID | | `sportradarGameId` | Sportradar game ID | | `score` | Current event score | | `period` | Current period | | `live` | Whether event is live | | `ended` | Whether event has ended | | `eventState` | Detailed event state information | | `participants` | Event participants (teams/players) | ### Volume & Liquidity | Field | Description | | ------------ | -------------------- | | `liquidity` | Event liquidity | | `volume` | Total trading volume | | `volume24hr` | 24-hour volume | | `volume1wk` | 7-day volume | | `volume1mo` | 30-day volume | ## Filtering Events Query events with various filters: ```bash theme={null} GET /v1/events?active=true&categories=sports&limit=50 ``` ### Common Filters | Parameter | Type | Description | | ------------ | ------- | ------------------------- | | `active` | boolean | Filter by active status | | `closed` | boolean | Filter by closed status | | `archived` | boolean | Filter by archived status | | `featured` | boolean | Filter featured events | | `categories` | array | Filter by categories | | `seriesId` | array | Filter by series IDs | | `gameId` | integer | Filter by game ID | | `ended` | boolean | Filter by ended status | | `live` | boolean | Filter live events | ### Date Filters | Parameter | Type | Description | | -------------- | ------- | ------------------- | | `startDateMin` | string | Minimum start date | | `startDateMax` | string | Maximum start date | | `startTimeMin` | string | Minimum start time | | `startTimeMax` | string | Maximum start time | | `eventDate` | string | Specific event date | | `eventWeek` | integer | Event week number | ## Partner Events Retrieve an event using a partner's external ID: ```bash theme={null} GET /v1/partners/{partnerKey}/events/{externalId} ``` ### Path Parameters | Parameter | Type | Description | | ------------ | ------ | --------------------------- | | `partnerKey` | string | Partner key identifier | | `externalId` | string | Partner's external event ID | # Download account balance ledger as CSV Source: https://docs.polymarket.us/api-reference/funding/download-account-balance-ledger-as-csv /institutional/oapi-schemas/funding-schema.json get /v1/funding/balance-ledger/download Streams balance ledger as CSV. # Get account balance ledger Source: https://docs.polymarket.us/api-reference/funding/get-account-balance-ledger /institutional/oapi-schemas/funding-schema.json get /v1/funding/balance-ledger Returns historical balance changes for an account. # Health check Source: https://docs.polymarket.us/api-reference/health/health-check /institutional/oapi-schemas/health-schema.json get /v1/health Check service health status. Returns 200 OK when the service is healthy. # Get Incentive Earnings Source: https://docs.polymarket.us/api-reference/incentives/get-incentive-earnings /institutional/oapi-schemas/incentives-schema.json get /v1/incentives/earnings Get incentive earnings for the authenticated user. Returns reward records grouped by market, date (Eastern Time), and payout status. Dates are bucketed by ET midnight boundaries. # Get Incentive Programs Source: https://docs.polymarket.us/api-reference/incentives/get-incentive-programs /institutional/oapi-schemas/incentives-schema.json get /v1/incentives Get incentive programs for each market. This endpoint is public and requires no authentication. # Get Your Incentive Earnings Source: https://docs.polymarket.us/api-reference/incentives/get-your-incentive-earnings /api-reference/oapi-schemas/incentives-schema.json get /v1/incentives/earnings Returns rewards earned by the authenticated caller. Each entry sums all payouts for a single (market, date) pair, where date is in Eastern Time. # Incentives API Overview Source: https://docs.polymarket.us/api-reference/incentives/overview View incentive programs and your earned rewards # Incentives API The Incentives API exposes the active incentive programs and the rewards you have earned. For background on how programs work and their reward formulas, see the [Incentive Programs overview](/incentives/overview). ## Base URL ``` https://api.polymarket.us ``` **Field casing.** Query parameters are `snake_case` (e.g. `page_size`). Response bodies are `lowerCamelCase` (e.g. `marketSlug`). Sending a camelCase query parameter (e.g. `pageSize`) is silently ignored and the default is used. ## Endpoints | Method | Endpoint | Auth | Description | | ------ | ------------------------- | -------- | --------------------------- | | `GET` | `/v1/incentives` | Required | Get incentive programs | | `GET` | `/v1/incentives/earnings` | Required | Get your incentive earnings | **Authentication** Both endpoints require API key authentication — see the [Authentication guide](/api-reference/authentication). ## Get Incentive Programs Returns active and historical incentive programs grouped by market. ```bash theme={null} GET /v1/incentives?page_size=10&symbols=aec-nba-bos-nyk-2026-04-01 ``` ### Query Parameters | Parameter | Type | Required | Description | | ------------------- | --------- | -------- | ---------------------------------------------------------------------- | | `page_size` | integer | No | Number of markets per page. Use with `page_token` for pagination. | | `page_token` | string | No | Pagination token from a previous response's `nextPageToken`. | | `symbols` | string\[] | No | Filter by market symbols. | | `order_by` | string | No | Sort field. Defaults to `created_at`. | | `order_direction` | string | No | Sort direction: `asc` or `desc`. Defaults to `desc`. | | `statuses` | string\[] | No | Filter by status: `active`, `closed`, or `pending`. | | `program_type` | string | No | Filter by program type, such as `liquidityProgram` or `volumeProgram`. | | `query` | string | No | Case-insensitive substring match on market slug. | | `instrument_states` | string\[] | No | Filter by instrument lifecycle state. | | `category` | string | No | Filter by exact event category. | | `subcategory` | string | No | Filter by exact event subcategory. | ### Response ```json theme={null} { "programs": [ { "marketSlug": "aec-nba-bos-nyk-2026-04-01", "instrumentState": "INSTRUMENT_STATE_OPEN", "category": "sports", "subcategory": "basketball", "eventStartTime": "2026-04-01T23:30:00Z", "instrumentProduct": "moneyline", "timePeriods": [ { "programId": "nba_t1_ml_early", "programType": "liquidityProgram", "start": "2026-03-28T04:00:00Z", "end": "2026-04-01T21:00:00Z", "rewardPool": 3000.0, "status": "closed", "discountFactor": 0.40, "targetSize": 20000, "period": "early", "createdAt": "2026-03-28T01:00:00Z" }, { "programId": "nba_t1_ml_day_of", "programType": "volumeProgram", "start": "2026-04-01T21:00:00Z", "rewardPool": 3000.0, "status": "active", "minTakerNotional": 100, "period": "day_of", "createdAt": "2026-03-28T01:00:00Z" } ] } ], "nextPageToken": "abc123" } ``` Ongoing programs omit `end` until an end time is set. ### IncentiveProgram Fields | Field | Type | Description | | ------------------- | ------------- | ------------------------------------------ | | `marketSlug` | string | Market identifier | | `timePeriods` | TimePeriod\[] | Incentive periods for this market | | `instrumentState` | string | Exchange lifecycle state of the instrument | | `category` | string | Event category from instrument metadata | | `subcategory` | string | Event subcategory from instrument metadata | | `eventStartTime` | string | Event start time from instrument metadata | | `instrumentProduct` | string | Product from instrument metadata | ### TimePeriod Fields | Field | Type | Description | | ------------------ | ------- | --------------------------------------------------------------- | | `programId` | string | Unique program period identifier | | `programType` | string | Program type (e.g. `liquidityProgram`) | | `start` | string | ISO 8601 start timestamp | | `end` | string | ISO 8601 end timestamp (optional; omitted for ongoing programs) | | `rewardPool` | number | Total reward pool for this period in USD | | `status` | string | `active`, `closed`, or `pending` | | `discountFactor` | number | Discount factor for scoring (optional; omitted if unset) | | `targetSize` | integer | Minimum book size to qualify (optional; omitted if unset) | | `period` | string | Reward period type: `early`, `day_of`, `live`, etc. | | `createdAt` | string | ISO 8601 timestamp when the program was created | | `minTakerNotional` | integer | Minimum taker notional for volume programs (optional) | ## Get Incentive Earnings Returns rewards earned by the authenticated caller. Each entry sums all payouts for a single `(market, date)` pair, where `date` is in Eastern Time. ```bash theme={null} GET /v1/incentives/earnings?start_date=2026-03-21&market_slug=aec-nba-bos-nyk-2026-04-01 ``` ### Query Parameters | Parameter | Type | Required | Description | | -------------- | ------ | -------- | ------------------------------------------------------------- | | `start_date` | string | No | Start date filter (`YYYY-MM-DD`). Defaults to program launch. | | `end_date` | string | No | End date filter (`YYYY-MM-DD`). Omit for through-today. | | `market_slug` | string | No | Filter by market slug. | | `program_type` | string | No | Filter by program type (e.g. `liquidityProgram`). | ### Response ```json theme={null} { "rewards": [ { "reward": 1828.62, "programType": "liquidityProgram", "marketSlug": "tsc-nba-ny-okc-2026-03-29-223pt5", "date": "2026-03-30", "status": "PAID" }, { "reward": 325.97, "programType": "liquidityProgram", "marketSlug": "aec-cbb-cabap-kan-2026-03-20", "date": "2026-03-29", "status": "PENDING" } ] } ``` Each daily entry represents rewards earned from midnight-to-midnight ET. Callers with no rewards receive `{"rewards":[]}`. ### UserReward Fields | Field | Type | Description | | ------------- | ------ | ---------------------------------------------------------------------- | | `reward` | number | Reward amount in USD (sum of all payouts for this market on this date) | | `programType` | string | Program type (e.g. `liquidityProgram`) | | `marketSlug` | string | Market identifier | | `date` | string | Reward date in Eastern Time (`YYYY-MM-DD`) | | `status` | string | Payout disposition: `PAID`, `PENDING`, or `SKIPPED` | ## Rate Limits | Endpoint | Rate Limit | | ----------------------------- | ------------------- | | `GET /v1/incentives` | 5 requests / second | | `GET /v1/incentives/earnings` | 5 requests / second | # Introduction Source: https://docs.polymarket.us/api-reference/introduction Overview of the Polymarket US API The Polymarket US API is split into two parts: an authenticated API for trading and a public API for reading market data. For support, contact [support@polymarket.us](mailto:support@polymarket.us). ## Authenticated API ``` https://api.polymarket.us ``` Use the authenticated API to trade. This is where you place orders, check your positions, and manage your account. Every request requires an API key - see [Authentication](/api-reference/authentication) to get set up. | Group | What you get | | ------------- | --------------------------------------- | | **Orders** | Place, modify, cancel, and query orders | | **Portfolio** | View positions and trading activity | | **Account** | Check balances and buying power | The authenticated API also provides two WebSocket endpoints for real-time streaming: | Endpoint | Purpose | | --------------------------------------- | ---------------------------------------------- | | `wss://api.polymarket.us/v1/ws/private` | Real-time order, position, and balance updates | | `wss://api.polymarket.us/v1/ws/markets` | Real-time order book and trade streaming | ## Public API ``` https://gateway.polymarket.us ``` Use the public API to browse what's available on Polymarket US. No API key needed. This is where you fetch markets, events, series, sports data, and search results. If you're building something that displays market information - prices, odds, order books - this is all you need. | Group | What you get | | ----------- | ------------------------------------------------------------------------ | | **Markets** | List markets, get order books, BBO, settlement prices, and price history | | **Events** | List events, get event details | | **Series** | List series (e.g., NFL 2025-26 Season) | | **Sports** | Leagues, teams, game schedules | | **Search** | Full-text search across events and markets | # Markets API Overview Source: https://docs.polymarket.us/api-reference/market/overview Query market data and information # Markets API The Market API provides access to market information, pricing, and settlement data. ## Endpoints | Method | Endpoint | Description | | ------ | ------------------------------- | ---------------------------------------- | | `GET` | `/v1/markets` | Get all markets with filtering | | `GET` | `/v1/market/id/{id}` | Get market by ID | | `GET` | `/v1/market/slug/{slug}` | Get market by slug | | `GET` | `/v1/markets/{slug}/book` | Get full market order book and stats | | `GET` | `/v1/markets/{slug}/bbo` | Get best bid/offer (lightweight) | | `GET` | `/v1/markets/{slug}/settlement` | Get market settlement price | | `GET` | `/v1/price-history` | Get historical Yes and No display prices | ## Key Market Fields | Field | Description | | ------------- | ---------------------------------- | | `id` | Unique market identifier | | `slug` | URL-friendly identifier | | `question` | Market question | | `description` | Detailed market description | | `category` | Market category | | `subcategory` | Market subcategory | | `active` | Whether market is accepting orders | | `closed` | Whether market has closed | | `archived` | Whether market is archived | ### Pricing Fields | Field | Description | | ----------------------- | ------------------------------------------------------- | | `orderPriceMinTickSize` | Minimum valid price increment for orders on this market | | `minimumTradeQty` | Minimum order quantity in contracts | | `lastTradePrice` | Most recent trade price | | `bestBid` | Best bid price | | `bestAsk` | Best ask price | | `spread` | Current bid-ask spread | | `oneDayPriceChange` | 24-hour price change | | `oneWeekPriceChange` | 7-day price change | Use `orderPriceMinTickSize` and `minimumTradeQty` from the market response before submitting orders. Do not infer price tick size or minimum quantity from product type, symbol, or slug. For example, `minimumTradeQty: 0.01` means the market supports 1% contract increments, and `orderPriceMinTickSize: 0.005` means valid order prices move in half-cent increments. ### Volume & Liquidity | Field | Description | | -------------- | ------------------------ | | `liquidity` | Current market liquidity | | `liquidityNum` | Liquidity as number | | `volume` | Total trading volume | | `volumeNum` | Volume as number | | `volume24hr` | 24-hour volume | | `volume1wk` | 7-day volume | | `volume1mo` | 30-day volume | ### Sports Market Fields | Field | Type | Description | | -------------------- | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `sportsMarketType` | string | Fine-grained sports market type, for example `football_team_full_game_spread`. Same inventory as the Institutional `market_sport_type` field; see [Sports Schema](/trader-guide/sports-schema) | | `sportsMarketTypeV2` | string (enum) | Type: MONEYLINE, SPREAD, TOTAL, or PROP | | `gameId` | string | Sports provider game ID | | `line` | number | Line value for spread/total markets | ## Filtering Markets Query markets with various filters: ```bash theme={null} GET /v1/markets?active=true&categories=sports&limit=50 ``` ### Pagination & Ordering | Parameter | Type | Description | | ---------------- | --------- | ------------------------------------------------------------------------------------------------------------------------ | | `limit` | integer | Maximum number of markets to return per page. Default varies by endpoint. Example: `50` | | `offset` | integer | Number of markets to skip for pagination. Use with `limit` to page through results. Example: `100` to skip the first 100 | | `orderBy` | string\[] | Fields to sort results by. Supports multiple fields for multi-level sorting. Example: `["volumeNum", "createdAt"]` | | `orderDirection` | string | Sort direction for the `orderBy` fields. Values: `asc` (ascending) or `desc` (descending). Default: `desc` | ### Status Filters | Parameter | Type | Description | | ---------- | ------- | --------------------------------------------------------------------------------------------------------------------------------- | | `active` | boolean | Filter markets by active trading status. `true` returns only markets currently accepting orders, `false` returns inactive markets | | `closed` | boolean | Filter markets by closed status. `true` returns only markets that have closed (resolved or expired), `false` returns open markets | | `archived` | boolean | Filter markets by archived status. `true` returns only archived/hidden markets, `false` excludes archived markets from results | ### Category & Type Filters | Parameter | Type | Description | | ------------------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `categories` | string\[] | Filter by market categories. Example: `["sports", "politics", "crypto"]` | | `marketTypes` | string\[] | Filter by market format types. Example: `["binary", "scalar"]` | | `sportsMarketTypes` | enum\[] | Filter by sports market type. Values: `SPORTS_MARKET_TYPE_MONEYLINE` (winner), `SPORTS_MARKET_TYPE_SPREAD` (point spread), `SPORTS_MARKET_TYPE_TOTAL` (over/under), `SPORTS_MARKET_TYPE_PROP` (player/game props) | | `tagId` | integer | Filter markets associated with a specific tag ID. Returns markets that have this tag applied | | `relatedTags` | boolean | When `true` and `tagId` is provided, also includes markets with tags related to the specified tag | | `includeTag` | boolean | When `true`, includes full tag information in the response for each market | | `cyom` | boolean | Filter "Create Your Own Market" submissions. `true` returns only user-submitted markets, `false` excludes them | ### ID Filters | Parameter | Type | Description | | ------------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------- | | `id` | integer\[] | Filter by specific market IDs. Returns only markets matching these numeric IDs. Example: `[123, 456, 789]` | | `slug` | string\[] | Filter by market URL slugs. Returns only markets matching these slug identifiers. Example: `["will-team-a-win", "super-bowl-winner"]` | | `questionIds` | string\[] | Filter by question IDs (UUIDs). Returns markets associated with these question identifiers | | `gameId` | string | Filter by sports game ID from the data provider. Returns all markets associated with a specific sporting event | ### Volume & Liquidity Filters | Parameter | Type | Description | | ----------------- | ------ | ----------------------------------------------------------------------------------------------------------------------------- | | `volumeNumMin` | number | Minimum total trading volume in USD. Only returns markets with `volumeNum` >= this value. Example: `1000.00` | | `volumeNumMax` | number | Maximum total trading volume in USD. Only returns markets with `volumeNum` \<= this value. Example: `100000.00` | | `liquidityNumMin` | number | Minimum available liquidity in USD. Only returns markets with `liquidityNum` >= this value. Example: `500.00` | | `liquidityNumMax` | number | Maximum available liquidity in USD. Only returns markets with `liquidityNum` \<= this value. Example: `50000.00` | | `rewardsMinSize` | number | Minimum order size eligible for liquidity rewards. Filters to markets where reward-eligible orders must be at least this size | ### Date Filters All date parameters accept ISO 8601 format strings (e.g., `2025-01-20T00:00:00Z`). | Parameter | Type | Description | | -------------- | ----------------- | --------------------------------------------------------------------------------------------- | | `startDateMin` | string (ISO 8601) | Filter markets with a start date on or after this timestamp. Example: `2025-01-01T00:00:00Z` | | `startDateMax` | string (ISO 8601) | Filter markets with a start date on or before this timestamp. Example: `2025-12-31T23:59:59Z` | | `endDateMin` | string (ISO 8601) | Filter markets with an end/expiration date on or after this timestamp | | `endDateMax` | string (ISO 8601) | Filter markets with an end/expiration date on or before this timestamp | ## Market Sides Each market has sides representing the possible outcomes. Sides are returned nested on the market object as `marketSides` from: * `GET /v1/markets` * `GET /v1/market/id/{id}` * `GET /v1/market/slug/{slug}` There is no separate REST endpoint to fetch a side by ID or to list sides by market ID. ### Market Side Fields | Field | Description | | ---------------- | ----------------------------- | | `id` | Market side ID | | `marketSideType` | Type (ERC1155 or INSTRUMENT) | | `identifier` | Market side identifier | | `description` | Side description | | `long` | Whether this is the long side | | `participantId` | Associated participant ID | **Real-Time Market Data** For real-time price updates and order book data, use the [WebSocket Markets Stream](/api-reference/websocket/markets) instead of polling the REST API. ## Market Book (Full) Get real-time order book data and market statistics for a specific market: ```bash theme={null} GET /v1/markets/{slug}/book ``` ### Path Parameters | Parameter | Type | Required | Description | | --------- | ------ | -------- | ---------------------- | | `slug` | string | Yes | Market slug identifier | ### Response ```json theme={null} { "marketData": { "marketSlug": "will-team-a-win", "bids": [ { "px": { "value": "0.55", "currency": "USD" }, "qty": "1000" }, { "px": { "value": "0.54", "currency": "USD" }, "qty": "500" } ], "offers": [ { "px": { "value": "0.56", "currency": "USD" }, "qty": "750" }, { "px": { "value": "0.57", "currency": "USD" }, "qty": "1200" } ], "state": "MARKET_STATE_OPEN", "stats": { "lastTradePx": { "value": "0.55", "currency": "USD" }, "openPx": { "value": "0.50", "currency": "USD" }, "highPx": { "value": "0.58", "currency": "USD" }, "lowPx": { "value": "0.48", "currency": "USD" }, "sharesTraded": "50000", "openInterest": "125000", "notionalTraded": { "value": "27500.00", "currency": "USD" } }, "transactTime": "2025-01-20T12:30:45.123Z" } } ``` ### Market Data Fields | Field | Type | Description | | -------------- | ------ | -------------------------------- | | `marketSlug` | string | Market identifier | | `bids` | array | Buy orders (highest price first) | | `offers` | array | Sell orders (lowest price first) | | `state` | string | Current market state | | `stats` | object | Market statistics | | `transactTime` | string | Timestamp of data | ### Book Entry | Field | Type | Description | | ----- | ------ | ------------------------------------------------------------------------------------ | | `px` | Amount | Price level | | `qty` | string | Quantity available at this price. May contain decimals for partial-contract markets. | ### Market States | State | Description | | -------------------------------------- | ----------------------------- | | `MARKET_STATE_OPEN` | Market is open for trading | | `MARKET_STATE_PREOPEN` | Market is in pre-open phase | | `MARKET_STATE_SUSPENDED` | Trading temporarily suspended | | `MARKET_STATE_HALTED` | Trading halted | | `MARKET_STATE_EXPIRED` | Market has expired | | `MARKET_STATE_TERMINATED` | Market terminated | | `MARKET_STATE_MATCH_AND_CLOSE_AUCTION` | Market in closing auction | ### Market Stats | Field | Type | Description | | ------------------ | ------ | ----------------------------------- | | `openPx` | Amount | Opening price | | `closePx` | Amount | Closing price | | `highPx` | Amount | High price | | `lowPx` | Amount | Low price | | `lastTradePx` | Amount | Last trade price | | `indicativeOpenPx` | Amount | Indicative opening price (pre-open) | | `settlementPx` | Amount | Settlement price | | `sharesTraded` | string | Total shares traded | | `notionalTraded` | Amount | Total notional value traded | | `lastTradeQty` | string | Last trade quantity | | `openInterest` | string | Current open interest | | `currentPx` | Amount | Current market price | *** ## Market BBO (Lightweight) Get best bid/offer and basic market statistics in a lightweight format. Use this endpoint when you only need top-of-book prices without the full order book depth. ```bash theme={null} GET /v1/markets/{slug}/bbo ``` ### Path Parameters | Parameter | Type | Required | Description | | --------- | ------ | -------- | ---------------------- | | `slug` | string | Yes | Market slug identifier | ### Response ```json theme={null} { "marketData": { "marketSlug": "will-team-a-win", "currentPx": { "value": "0.55", "currency": "USD" }, "lastTradePx": { "value": "0.55", "currency": "USD" }, "bestBid": { "value": "0.54", "currency": "USD" }, "bestAsk": { "value": "0.56", "currency": "USD" }, "bidDepth": 5, "askDepth": 4, "sharesTraded": "50000", "openInterest": "125000", "settlementPx": { "value": "0.00", "currency": "USD" } } } ``` ### Market Data Lite Fields | Field | Type | Description | | -------------- | ------- | -------------------------------------- | | `marketSlug` | string | Market identifier | | `currentPx` | Amount | Current market price | | `lastTradePx` | Amount | Price of the most recent trade | | `bestBid` | Amount | Best (highest) bid price | | `bestAsk` | Amount | Best (lowest) ask price | | `bidDepth` | integer | Number of price levels on the bid side | | `askDepth` | integer | Number of price levels on the ask side | | `sharesTraded` | string | Total shares traded | | `openInterest` | string | Current open interest | | `settlementPx` | Amount | Settlement price (if resolved) | ### When to Use BBO vs Book | Endpoint | Use Case | | ------------------------- | ----------------------------------------------------------- | | `/v1/markets/{slug}/bbo` | Display current prices, check spreads, lightweight polling | | `/v1/markets/{slug}/book` | View full order book depth, analyze liquidity at each level | **Real-Time Updates** For continuous order book updates, use the [WebSocket Markets Stream](/api-reference/websocket/markets) instead of polling these endpoints. ## Settlement After a market resolves, query the settlement price: ```bash theme={null} GET /v1/markets/will-x-happen/settlement ``` Response: ```json theme={null} { "slug": "will-x-happen", "settlement": 1.00 } ``` Settlement values are typically `0.00` (No) or `1.00` (Yes). # Get Market BBO Source: https://docs.polymarket.us/api-reference/markets/get-market-bbo /api-reference/oapi-schemas/markets-schema.json get /v1/markets/{slug}/bbo Retrieve current market data (best bid/offer, stats) for a specific market by its slug in a lightweight format # Get Market Book Source: https://docs.polymarket.us/api-reference/markets/get-market-book /api-reference/oapi-schemas/markets-schema.json get /v1/markets/{slug}/book Retrieve current market data (order book, stats) for a specific market by its slug # Get Market By ID Source: https://docs.polymarket.us/api-reference/markets/get-market-by-id /api-reference/oapi-schemas/markets-schema.json get /v1/market/id/{id} Retrieve a specific market by its ID # Get Market By Slug Source: https://docs.polymarket.us/api-reference/markets/get-market-by-slug /api-reference/oapi-schemas/markets-schema.json get /v1/market/slug/{slug} Retrieve a specific market by its slug # Get Market Settlement Source: https://docs.polymarket.us/api-reference/markets/get-market-settlement /api-reference/oapi-schemas/markets-schema.json get /v1/markets/{slug}/settlement Retrieve the settlement price for a specific market by its slug # Get Markets Source: https://docs.polymarket.us/api-reference/markets/get-markets /api-reference/oapi-schemas/markets-schema.json get /v1/markets Retrieve all markets # Get best bid/offer Source: https://docs.polymarket.us/api-reference/order-book/get-best-bidoffer /institutional/oapi-schemas/orderbook-schema.json get /v1/orderbook/{symbol}/bbo Returns the top of book (best bid and offer) for a symbol # Get order book Source: https://docs.polymarket.us/api-reference/order-book/get-order-book /institutional/oapi-schemas/orderbook-schema.json get /v1/orderbook/{symbol} Returns the current aggregated order book for a symbol # Cancel All Open Orders Source: https://docs.polymarket.us/api-reference/orders/cancel-all-open-orders /api-reference/oapi-schemas/orders-schema.json post /v1/orders/open/cancel Cancel all open orders, optionally filtered by market slugs # Cancel Multiple Orders Source: https://docs.polymarket.us/api-reference/orders/cancel-multiple-orders /api-reference/oapi-schemas/orders-schema.json post /v1/orders/batched/cancel Cancel up to 20 orders in a single request. If any entry fails request-shape validation, the whole batch is rejected by the gateway before reaching the exchange. The exchange may silently ignore unknown `orderId`s. `canceledOrderIds` is an echo of the request, not a confirmation; observe actual outcomes on the order stream. # Cancel Order Source: https://docs.polymarket.us/api-reference/orders/cancel-order /api-reference/oapi-schemas/orders-schema.json post /v1/order/{orderId}/cancel Cancel a specific order by its exchange-assigned ID # Close Position Order Source: https://docs.polymarket.us/api-reference/orders/close-position-order /api-reference/oapi-schemas/orders-schema.json post /v1/order/close-position Create an order to close an existing position in a market. This will sell all contracts held in the specified market. # Create Multiple Orders Source: https://docs.polymarket.us/api-reference/orders/create-multiple-orders /api-reference/oapi-schemas/orders-schema.json post /v1/orders/batched Create up to 20 orders in a single request. If any entry fails request-shape validation, the whole batch is rejected by the gateway before reaching the exchange. Per-entry exchange outcomes (accept, fill, reject) are delivered on the order stream, not in this response. `createdOrderIds` are returned in request order. # Create Order Source: https://docs.polymarket.us/api-reference/orders/create-order /api-reference/oapi-schemas/orders-schema.json post /v1/orders Create a new order to enter into a market # Get Open Orders Source: https://docs.polymarket.us/api-reference/orders/get-open-orders /api-reference/oapi-schemas/orders-schema.json get /v1/orders/open Get all open orders for the authenticated user # Get Order Source: https://docs.polymarket.us/api-reference/orders/get-order /api-reference/oapi-schemas/orders-schema.json get /v1/order/{orderId} Get details for a specific order by its exchange-assigned ID # Modify Multiple Orders Source: https://docs.polymarket.us/api-reference/orders/modify-multiple-orders /api-reference/oapi-schemas/orders-schema.json post /v1/orders/batched/modify Modify up to 20 existing orders in a single request. Each entry is forwarded to the exchange as a cancel-replace. If any entry fails request-shape validation, the whole batch is rejected by the gateway before reaching the exchange. The exchange may silently ignore unknown `orderId`s. `modifiedOrderIds` is an echo of the request, not a confirmation; observe actual outcomes on the order stream. # Modify Order Source: https://docs.polymarket.us/api-reference/orders/modify-order /api-reference/oapi-schemas/orders-schema.json post /v1/order/{orderId}/modify Modify an existing order in the marketplace. Allows changing price, quantity, time in force, and other parameters. # Orders API Overview Source: https://docs.polymarket.us/api-reference/orders/overview Create, cancel, and manage orders # Orders API The Orders API provides order entry and management capabilities for trading on markets. **Authentication Required** All Orders API endpoints require API key authentication. See the [Authentication guide](/api/authentication) for details on signing requests. ## Base URL ``` https://api.polymarket.us ``` ## Endpoints ### Order Entry | Method | Endpoint | Description | | ------ | -------------------------- | ------------------------------- | | `POST` | `/v1/orders` | Create a new order | | `POST` | `/v1/order/preview` | Preview order before submission | | `POST` | `/v1/order/close-position` | Close an existing position | ### Order Query | Method | Endpoint | Description | | ------ | --------------------- | -------------------------- | | `GET` | `/v1/orders/open` | Get all open orders | | `GET` | `/v1/order/{orderId}` | Get a specific order by ID | ### Order Management | Method | Endpoint | Description | | ------ | ---------------------------- | ------------------------ | | `POST` | `/v1/order/{orderId}/modify` | Modify an existing order | | `POST` | `/v1/order/{orderId}/cancel` | Cancel a specific order | | `POST` | `/v1/orders/open/cancel` | Cancel all open orders | ### Batched Operations Up to 20 orders per call. | Method | Endpoint | Description | | ------ | --------------------------- | ------------------------------------------------------------------------------------ | | `POST` | `/v1/orders/batched` | Create up to 20 orders in a single request | | `POST` | `/v1/orders/batched/cancel` | Cancel up to 20 specific orders by ID | | `POST` | `/v1/orders/batched/modify` | Modify up to 20 existing orders (each forwarded to the exchange as a cancel-replace) | Note: `/v1/orders/open/cancel` cancels **all** of your open orders (optionally filtered by market). Use `/v1/orders/batched/cancel` when you want to cancel a specific list of order IDs. **Batched responses do not confirm per-entry success.** * Gateway validation is atomic: if any entry fails request-shape checks (missing `orderId`, batch > 20, etc.), the whole batch is rejected with `400`. * The exchange processes entries independently. An unknown `orderId` in a batched cancel or modify is silently ignored. * `canceledOrderIds` and `modifiedOrderIds` echo the request; they do not certify that each ID was acted on. For real outcomes, subscribe to the [Private WebSocket order stream](/api-reference/websocket/private) and watch for `EXECUTION_TYPE_REPLACE`, `EXECUTION_TYPE_CANCELED`, and `EXECUTION_TYPE_REJECTED`. * `createdOrderIds` from `/v1/orders/batched` are real exchange-assigned IDs, but per-order accept/fill/reject events still come via the order stream. ## Order Types All enum values are passed as **strings** in the request body: | Value | Description | | ------------------- | --------------------------------------------- | | `ORDER_TYPE_LIMIT` | Limit order at specified price | | `ORDER_TYPE_MARKET` | Market order executed at best available price | **Example:** ```json theme={null} { "type": "ORDER_TYPE_LIMIT" } ``` ## Order Intent Orders require an intent indicating position direction. Pass these as **string** values: | Value | Description | | ------------------------- | -------------------------------------------- | | `ORDER_INTENT_BUY_LONG` | Buy YES contracts (go long on Yes outcome) | | `ORDER_INTENT_SELL_LONG` | Sell YES contracts (close long Yes position) | | `ORDER_INTENT_BUY_SHORT` | Buy NO contracts (go long on No outcome) | | `ORDER_INTENT_SELL_SHORT` | Sell NO contracts (close long No position) | **Example - Buy NO contracts:** ```json theme={null} { "marketSlug": "your-market-slug", "type": "ORDER_TYPE_LIMIT", "price": { "value": "0.45", "currency": "USD" }, "quantity": 10, "tif": "TIME_IN_FORCE_GOOD_TILL_CANCEL", "intent": "ORDER_INTENT_BUY_SHORT" } ``` ### Alternative: Outcome Side + Action Instead of `intent`, you can specify the equivalent `outcomeSide` + `action` pair. Both forms are accepted on `CreateOrder` and the batched variants. If both are sent, `outcomeSide`+`action` wins. | `outcomeSide` | `action` | Equivalent `intent` | | ------------------ | ------------------- | ------------------------- | | `OUTCOME_SIDE_YES` | `ORDER_ACTION_BUY` | `ORDER_INTENT_BUY_LONG` | | `OUTCOME_SIDE_YES` | `ORDER_ACTION_SELL` | `ORDER_INTENT_SELL_LONG` | | `OUTCOME_SIDE_NO` | `ORDER_ACTION_BUY` | `ORDER_INTENT_BUY_SHORT` | | `OUTCOME_SIDE_NO` | `ORDER_ACTION_SELL` | `ORDER_INTENT_SELL_SHORT` | **Example: same "buy NO contracts" order, expressed with outcomeSide + action:** ```json theme={null} { "marketSlug": "your-market-slug", "type": "ORDER_TYPE_LIMIT", "price": { "value": "0.45", "currency": "USD" }, "quantity": 10, "tif": "TIME_IN_FORCE_GOOD_TILL_CANCEL", "outcomeSide": "OUTCOME_SIDE_NO", "action": "ORDER_ACTION_BUY" } ``` The `Order` returned by `GET /v1/order/{orderId}` and `GET /v1/orders/open` includes both `intent` and `outcomeSide`+`action`, so you can read whichever you prefer. The price-vs-side rules below apply to both forms. ### Understanding Price with Order Intent Only the long side (YES) is directly tradable. The short side (NO) is synthetic exposure created through positions in the long side. The `price.value` field always represents the long side's price, regardless of which order intent you use. In the market slug, the first team is always the long/YES side and the second team is the short/NO side. A common mistake is attempting to buy both YES at 0.60 and NO at 0.40, which causes a self-match error. Since YES and NO prices must sum to \$1.00, buying NO at 0.40 is equivalent to buying YES at 0.60 - you're placing two buy orders at the same price level on the same instrument. If you want exposure to both sides, use different price levels (e.g., buy YES at 0.55 and buy NO at 0.50). **Example:** For market `aec-cbb-usc-iowa-2026-01-28`: * YES (long side) = USC * NO (short side) = Iowa * `price.value` always refers to USC's price **How This Affects Your Orders:** | You Want To | Order Intent | price.value | | ----------------- | -------------------------- | ----------- | | Buy USC at 0.83 | ORDER\_INTENT\_BUY\_LONG | 0.83 | | Sell USC at 0.83 | ORDER\_INTENT\_SELL\_LONG | 0.83 | | Buy Iowa at 0.83 | ORDER\_INTENT\_BUY\_SHORT | 0.17 | | Sell Iowa at 0.83 | ORDER\_INTENT\_SELL\_SHORT | 0.17 | In binary markets, YES and NO are inverses: buying NO at 0.83 is equivalent to buying YES at 0.17 (1.00 - 0.83). Since `price.value` always represents the YES side, you must set it to 0.17 when trading Iowa (NO) at 0.83. To trade the NO side at any price X, set `price.value = 1.00 - X`. ## Price Validation Orders must have `price.value` between 0.01 and 0.99 (the exchange's absolute price limits). **Invalid prices (below 0.01 or above 0.99) are restricted at the exchange level.** Since the order is sent to the exchange, you will still receive an orderID, but the order will never fill because it gets rejected during validation. **Example:** ```json theme={null} { "price": {"value": "45", "currency": "USD"} // Invalid - will receive orderID but order rejected } ``` Always validate price bounds client-side before submission to avoid unnecessary orderIDs for rejected orders. ### Quantity and Tick Size by Market Markets can differ in both minimum order quantity and minimum price increment. Read these fields from the market response before submitting or modifying an order: | Market field | Use | | ----------------------- | -------------------------------------------------------------------------------------------- | | `minimumTradeQty` | Smallest valid `quantity`, expressed in contracts. A value of `0.01` means 1% of a contract. | | `orderPriceMinTickSize` | Smallest valid `price.value` increment. A value of `0.005` means half-cent ticks. | The `quantity` field on order requests and order responses is a number and can contain decimals for partial-contract markets. Submit `quantity` and `price.value` already aligned to the market's `minimumTradeQty` and `orderPriceMinTickSize`. Extra precision is not part of the public contract and can be normalized to the market precision; for example, on a market with `minimumTradeQty: 0.01` and `orderPriceMinTickSize: 0.01`, `quantity: 0.015` can be accepted and returned as `0.01`, and `price.value: "0.515"` can be returned as `"0.51"`. ## Order Side The order side indicates buy or sell direction: | Value | Description | | ----------------- | ----------- | | `ORDER_SIDE_BUY` | Buy order | | `ORDER_SIDE_SELL` | Sell order | ## Order States Orders progress through these states: ``` PENDING_NEW → PARTIALLY_FILLED → FILLED ↓ CANCELED / REJECTED / EXPIRED ``` | Value | Description | | ------------------------------ | --------------------------------------------------------- | | `ORDER_STATE_PENDING_NEW` | Order received, not yet processed by matching engine | | `ORDER_STATE_NEW` | Order accepted by matching engine and resting on the book | | `ORDER_STATE_PENDING_REPLACE` | Modify request received, not yet processed | | `ORDER_STATE_PENDING_CANCEL` | Cancel request received, not yet processed | | `ORDER_STATE_PENDING_RISK` | Order pending risk approval | | `ORDER_STATE_PARTIALLY_FILLED` | Order partially executed | | `ORDER_STATE_FILLED` | Order fully executed | | `ORDER_STATE_CANCELED` | Order canceled | | `ORDER_STATE_REPLACED` | Order replaced via modify (cancel-replace) | | `ORDER_STATE_REJECTED` | Order rejected by exchange | | `ORDER_STATE_EXPIRED` | Order expired (GTD orders) | ## Time in Force | Value | Description | | ----------------------------------- | -------------------------------------------------------- | | `TIME_IN_FORCE_DAY` | DAY - Expires at the end of the trading day | | `TIME_IN_FORCE_GOOD_TILL_CANCEL` | GTC - Remains active until filled or canceled | | `TIME_IN_FORCE_GOOD_TILL_DATE` | GTD - Expires at specified `goodTillTime` | | `TIME_IN_FORCE_IMMEDIATE_OR_CANCEL` | IOC - Fills immediately available quantity, cancels rest | | `TIME_IN_FORCE_FILL_OR_KILL` | FOK - Must fill entirely or cancel completely | ## Manual Order Indicator Required to indicate whether the order is placed by a human or automated system: | Value | Description | | ---------------------------------- | ------------------------------------------- | | `MANUAL_ORDER_INDICATOR_MANUAL` | Order placed manually by a user | | `MANUAL_ORDER_INDICATOR_AUTOMATIC` | Order placed by an automated trading system | ## Execution Types Execution events returned in synchronous order responses: | Value | Description | | ----------------------------- | ----------------------------------------------- | | `EXECUTION_TYPE_NEW` | Order accepted (new working order confirmation) | | `EXECUTION_TYPE_PARTIAL_FILL` | Order partially filled | | `EXECUTION_TYPE_FILL` | Order fully filled | | `EXECUTION_TYPE_CANCELED` | Order canceled | | `EXECUTION_TYPE_REPLACE` | Order replaced/modified | | `EXECUTION_TYPE_REJECTED` | Order rejected | | `EXECUTION_TYPE_EXPIRED` | Order expired | | `EXECUTION_TYPE_DONE_FOR_DAY` | Order done for the trading day | ## Order Reject Reasons If an order is rejected, the reason will be one of: | Value | Description | | ------------------------------------------- | ------------------------------------------------------------------------- | | `ORD_REJECT_REASON_EXCHANGE_OPTION` | Generic exchange-defined reason (used when no more specific code applies) | | `ORD_REJECT_REASON_UNKNOWN_MARKET` | Unknown or invalid market | | `ORD_REJECT_REASON_EXCHANGE_CLOSED` | Exchange/market is closed | | `ORD_REJECT_REASON_INCORRECT_QUANTITY` | Invalid quantity | | `ORD_REJECT_REASON_INVALID_PRICE_INCREMENT` | Price not on valid increment | | `ORD_REJECT_REASON_INCORRECT_ORDER_TYPE` | Invalid order type for market | | `ORD_REJECT_REASON_PRICE_OUT_OF_BOUNDS` | Price outside valid range | | `ORD_REJECT_REASON_NO_LIQUIDITY` | No liquidity for market order | ## Slippage Tolerance For market orders or close position orders, you can specify slippage tolerance: ```json theme={null} { "slippageTolerance": { "currentPrice": { "value": "0.50", "currency": "USD" }, "ticks": 5 } } ``` | Field | Type | Description | | -------------- | ------- | ------------------------------------------------------------ | | `currentPrice` | Amount | Reference price for slippage calculation | | `bips` | integer | Slippage tolerance in basis points (1 bip = 0.01%) | | `ticks` | integer | Slippage tolerance in price ticks (takes priority over bips) | ### Default Values `slippageTolerance` is optional and defaults to: * **Market orders**: Unlimited (no slippage protection by default) * **Limit orders**: Not applicable (price is fixed) Slippage tolerance defines the maximum price movement you'll accept. For example, if you submit a market order to buy at current price 0.50 with `ticks: 5`, the order will reject if the best ask moves above 0.55 before execution. **Real-Time Order Updates** After submitting orders via REST, use the [WebSocket Private Stream](/api-reference/websocket/private) to receive real-time updates on order status, fills, and cancellations. ## Complete Create Order Example ```json theme={null} { "marketSlug": "your-market-slug", "type": "ORDER_TYPE_LIMIT", "price": { "value": "0.555", "currency": "USD" }, "quantity": 0.5, "tif": "TIME_IN_FORCE_GOOD_TILL_CANCEL", "intent": "ORDER_INTENT_BUY_LONG", "manualOrderIndicator": "MANUAL_ORDER_INDICATOR_MANUAL", "participateDontInitiate": false } ``` ## Rate Limits The API enforces a global rate limit of **20 requests per second** per API key across all endpoints. **Rate Limit Exceeded** When rate limits are exceeded, the API returns HTTP status `429 Too Many Requests`. **Notes:** * Rate limits are enforced at the edge (Cloudflare) before requests reach the API * Limits are applied per API key * Implement exponential backoff and request throttling in your application ## Best Practices 1. **Use string enum values** - All enums are passed as strings (e.g., `"ORDER_TYPE_LIMIT"`, not `1`) 2. **Use WebSocket for updates** - Subscribe to order updates instead of polling 3. **Preview before submit** - Use the preview endpoint for order validation 4. **Handle rejects** - Implement proper error handling for rejected orders 5. **Use asynchronous execution for limit orders** - For market-making and resting limit orders, avoid `synchronousExecution: true` as it waits up to 10 seconds for final order state. Instead, submit orders asynchronously (the default) and poll with `GET /v1/order/{orderId}` to check status (\~100ms). Only use `synchronousExecution: true` for immediately-fillable orders where you need to wait for fill confirmation. 6. **Specify manual order indicator** - Required for regulatory compliance 7. **Respect rate limits** - Implement request throttling to stay within rate limits and avoid 429 errors # Preview Order Source: https://docs.polymarket.us/api-reference/orders/preview-order /api-reference/oapi-schemas/orders-schema.json post /v1/order/preview Preview an order before submission to validate parameters and see expected fills # Get Event By Partner External ID Source: https://docs.polymarket.us/api-reference/partners/get-event-by-partner-external-id /api-reference/oapi-schemas/events-schema.json get /v1/partners/{partnerKey}/events/{externalId} Resolve a partner external event ID (e.g. OpticOdds fixture ID) to the internal event and its mapped market sides # Get Activities Source: https://docs.polymarket.us/api-reference/portfolio/get-activities /api-reference/oapi-schemas/portfolio-schema.json get /v1/portfolio/activities Get activities for a user including trades, position resolutions, and account balance changes # Get User Positions Source: https://docs.polymarket.us/api-reference/portfolio/get-user-positions /api-reference/oapi-schemas/portfolio-schema.json get /v1/portfolio/positions Get user's trading positions across all markets or filtered by specific market # Portfolio API Overview Source: https://docs.polymarket.us/api-reference/portfolio/overview View positions, balances, and trading activity # Portfolio API The Portfolio API provides access to user positions, account balances, and trading activity history. **Authentication Required** All Portfolio API endpoints require API key authentication. See the [Authentication guide](/api/authentication) for details on signing requests. ## Base URL ``` https://api.polymarket.us ``` ## Endpoints ### Positions | Method | Endpoint | Description | | ------ | ------------------------- | ---------------------------- | | `GET` | `/v1/portfolio/positions` | Get user's trading positions | ### Activities | Method | Endpoint | Description | | ------ | -------------------------- | ---------------------------- | | `GET` | `/v1/portfolio/activities` | Get trading activity history | ### Account | Method | Endpoint | Description | | ------ | ---------------------- | -------------------- | | `GET` | `/v1/account/balances` | Get account balances | ## Positions Response The positions response returns a **map of market slug to position**, not an array: ```json theme={null} { "positions": { "will-x-happen": { "netPosition": "20", "netPositionDecimal": "19.6000", "qtyBought": "20", "qtyBoughtDecimal": "19.6000", "qtySold": "0", "qtySoldDecimal": "0.0000", ... }, "another-market": { "netPosition": "-50", ... } }, "nextCursor": "abc123", "eof": false } ``` ### Position Fields | Field | Type | Description | | --------------------- | ------------------ | ---------------------------------------------------------------------- | | `netPositionDecimal` | string (decimal) | Net position quantity in contracts (positive = long, negative = short) | | `qtyBoughtDecimal` | string (decimal) | Total quantity bought in contracts | | `qtySoldDecimal` | string (decimal) | Total quantity sold in contracts | | `qtyAvailableDecimal` | string (decimal) | Quantity available to trade in contracts | | `bodPositionDecimal` | string (decimal) | Beginning of day position in contracts | | `netPosition` | string (int64) | Deprecated rounded quantity; use `netPositionDecimal` | | `qtyBought` | string (int64) | Deprecated rounded quantity; use `qtyBoughtDecimal` | | `qtySold` | string (int64) | Deprecated rounded quantity; use `qtySoldDecimal` | | `cost` | Amount | Total cost basis | | `realized` | Amount | Realized profit/loss | | `cashValue` | Amount | Current unrealized value | | `qtyAvailable` | string (int64) | Deprecated rounded quantity; use `qtyAvailableDecimal` | | `bodPosition` | string (int64) | Deprecated rounded quantity; use `bodPositionDecimal` | | `expired` | boolean | Whether the position has expired | | `updateTime` | string (date-time) | Last update timestamp | | `marketMetadata` | object | Market information (slug, title, outcome) | Use the `*Decimal` quantity fields for display and calculations. The older integer fields remain for backward compatibility and should not be used for partial-contract markets. ## Activities Response Activities are returned as an array with pagination: ```json theme={null} { "activities": [ { "type": "ACTIVITY_TYPE_TRADE", "trade": { ... } } ], "nextCursor": "xyz789", "eof": false } ``` ### Activity Structure Each activity has a `type` field and a corresponding nested object: | Type | Nested Field | Description | | ---------------------------------------- | ---------------------- | ---------------------------------------- | | `ACTIVITY_TYPE_TRADE` | `trade` | Trade execution details | | `ACTIVITY_TYPE_POSITION_RESOLUTION` | `positionResolution` | Market settlement details | | `ACTIVITY_TYPE_ACCOUNT_DEPOSIT` | `accountBalanceChange` | Deposit details | | `ACTIVITY_TYPE_ACCOUNT_ADVANCED_DEPOSIT` | `accountBalanceChange` | Advance issued against a pending deposit | | `ACTIVITY_TYPE_ACCOUNT_WITHDRAWAL` | `accountBalanceChange` | Withdrawal details | | `ACTIVITY_TYPE_TRANSFER` | `accountBalanceChange` | Transfer details | | `ACTIVITY_TYPE_REFERRAL_BONUS` | `accountBalanceChange` | Referral incentive credit | | `ACTIVITY_TYPE_TAKER_FEE_REBATE` | `accountBalanceChange` | Taker fee rebate credit | | `ACTIVITY_TYPE_LIQUIDITY_PROGRAM` | `accountBalanceChange` | Liquidity program payout | ### Trade Object | Field | Type | Description | | ------------- | ------------------ | ---------------------------------------------- | | `id` | string | Exchange-assigned trade ID | | `marketSlug` | string | Market slug | | `state` | string | Trade state; see [Trade States](#trade-states) | | `price` | Amount | Trade price | | `qtyDecimal` | string (decimal) | Trade quantity in contracts | | `qty` | string | Deprecated rounded quantity; use `qtyDecimal` | | `isAggressor` | boolean | True if user's order was the taker | | `costBasis` | Amount | Cost basis for the trade | | `realizedPnl` | Amount | Realized profit/loss | | `createTime` | string (date-time) | Creation timestamp | | `updateTime` | string (date-time) | Last update timestamp | ### Trade States The `state` field on a trade progresses through the following values: | State | Value | Description | | ----------------------------------- | ----- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `TRADE_STATE_NEW` | 1 | Trade created. | | `TRADE_STATE_CLEARED` | 2 | Trade successfully cleared by the clearing house. | | `TRADE_STATE_BUSTED` | 3 | The trade was voided post-execution by the exchange (an error trade) and the resulting position was rolled back. This is a terminal reversal, **not** a pending state. | | `TRADE_STATE_INFLIGHT` | 4 | Trade information sent to the clearinghouse. | | `TRADE_STATE_PENDING_RISK` | 5 | Clearinghouse is pending at least one DCM claim for the trade. | | `TRADE_STATE_PENDING_CLEARED` | 6 | Clearinghouse is pending the counterparty DCM claim. | | `TRADE_STATE_REJECTED` | 7 | Clearinghouse rejected the trade. | | `TRADE_STATE_CLEARING_ACKNOWLEDGED` | 8 | Clearing request acknowledged by the clearing house. | | `TRADE_STATE_RETRY_REQUEST` | 9 | Retry requested; pending resubmission to the clearing house. | **Handling `TRADE_STATE_BUSTED`** A busted trade was executed and then voided by the exchange (via an error-trade bust), and its position impact was reversed. Busted trades remain visible in trade history — treat them as reversed for position, P\&L, and reconciliation purposes. Do not silently drop them. `TRADE_STATE_UNDEFINED` (value `0`) is the unset/unknown sentinel. It is omitted from API responses and does not appear in the published enum lists. Treat any unrecognized state value defensively rather than assuming the values above are exhaustive. ### Position Resolution Object | Field | Type | Description | | ---------------- | ------------------ | -------------------------------------- | | `marketSlug` | string | Market slug | | `beforePosition` | UserPosition | Position before resolution | | `afterPosition` | UserPosition | Position after resolution | | `side` | string | Resolution side (LONG, SHORT, NEUTRAL) | | `tradeId` | string | Associated trade ID | | `updateTime` | string (date-time) | Resolution timestamp | ### Account Balance Change Object | Field | Type | Description | | --------------- | ------------------ | ------------------------------------- | | `transactionId` | string | Transaction ID | | `status` | string | Status (PENDING, COMPLETED, REJECTED) | | `amount` | Amount | Amount of the balance change | | `createTime` | string (date-time) | Creation timestamp | | `updateTime` | string (date-time) | Last update timestamp | ## Pagination Both endpoints support cursor-based pagination. Use `cursor` parameter with the `nextCursor` value to fetch the next page. When `eof` is `true`, there are no more results. **Real-Time Position Updates** For real-time position changes, use the [WebSocket Private Stream](/api-reference/websocket/private) with the `SUBSCRIPTION_TYPE_POSITION` subscription instead of polling. ## Filtering Activities Filter activities by type and market: ```bash theme={null} GET /v1/portfolio/activities?types=ACTIVITY_TYPE_TRADE&marketSlug=will-x-happen ``` ## Sort Order Activities can be sorted ascending or descending by time: | Sort Order | Description | | ----------------------- | ---------------------- | | `SORT_ORDER_DESCENDING` | Newest first (default) | | `SORT_ORDER_ASCENDING` | Oldest first | ## Account Balances The account balances endpoint returns current balance information: ```json theme={null} { "balances": [ { "currentBalance": 1000.00, "currency": "USD", "buyingPower": 850.00, "assetNotional": 500.00, "assetAvailable": 250.00, "openOrders": 400.00, "unsettledFunds": 0, "marginRequirement": 0, "lastUpdated": "2024-01-15T10:30:00Z" } ] } ``` ### Balance Fields | Field | Description | | ------------------- | ---------------------------------- | | `currentBalance` | Current fiat currency balance | | `currency` | Currency code (e.g., USD) | | `buyingPower` | Capital available for trading | | `assetNotional` | Total notional value of securities | | `assetAvailable` | Available collateral value | | `pendingCredit` | Pending credit amounts | | `openOrders` | Value tied up in open orders | | `unsettledFunds` | Unsettled funds not yet available | | `marginRequirement` | Required margin for positions | ### Buying Power The `buyingPower` field represents unencumbered capital available for trading: ``` buyingPower = currentBalance + assetAvailable - openOrders - marginRequirement ``` **Real-Time Balance Updates** For real-time balance changes, use the [WebSocket Private Stream](/api-reference/websocket/private) with the `SUBSCRIPTION_TYPE_ACCOUNT_BALANCE` subscription instead of polling. # Download position ledger as CSV Source: https://docs.polymarket.us/api-reference/positions/download-position-ledger-as-csv /institutional/oapi-schemas/positions-schema.json get /v1/positions/ledger/download Streams position ledger as CSV. # Get account balance Source: https://docs.polymarket.us/api-reference/positions/get-account-balance /institutional/oapi-schemas/positions-schema.json post /v1/positions/balance Gets the balance for a currency in an account # Get position ledger Source: https://docs.polymarket.us/api-reference/positions/get-position-ledger /institutional/oapi-schemas/positions-schema.json get /v1/positions/ledger Returns historical position changes for an account. # List account balances Source: https://docs.polymarket.us/api-reference/positions/list-account-balances /institutional/oapi-schemas/positions-schema.json post /v1/positions/balances Lists all currency balances in an account # List account positions Source: https://docs.polymarket.us/api-reference/positions/list-account-positions /institutional/oapi-schemas/positions-schema.json get /v1/positions Lists all positions within an account # Get Price History Source: https://docs.polymarket.us/api-reference/price-history/get-price-history /api-reference/oapi-schemas/price-history-schema.json get /v1/price-history Returns book-derived Yes and No display prices for one market. Use the market slug as `symbol`. Supported fixed profiles: - `INTERVAL_1H` with `fidelity=1`: approximately one-minute points for the last hour - `INTERVAL_6H` with `fidelity=1`: approximately one-minute points for the last six hours - `INTERVAL_1D` with `fidelity=5`: five-minute points for the last day - `INTERVAL_1W` with `fidelity=180`: three-hour points for the last week - `INTERVAL_1M` with `fidelity=180`: three-hour points for the last 30 days - `INTERVAL_ALL` with `fidelity=180`: three-hour points for newer markets and daily points for longer histories - `INTERVAL_LIVE` with `fidelity=1`: from 15 minutes before the event starts through now For a custom range, provide both Unix timestamps and use `fidelity=1`. Custom ranges are intended for short windows of up to 24 hours and return stored observations, which may be irregular or more frequent than once per minute. `longPrice` is the Yes display price, normally derived from the best ask. `shortPrice` is the No display price, normally derived from one minus the best bid. They can sum to more than 1 because they preserve the bid-ask spread; these are not individual trades. The endpoint supports one market per request. Cache identical responses for at least 30 seconds, stagger refreshes across markets, and observe the [20 requests/second/IP public limit](/api-reference/rate-limits). For live updates after loading history, use the [Markets WebSocket](/api-reference/websocket/markets). # Rate Limits Source: https://docs.polymarket.us/api-reference/rate-limits API rate limits and how to stay within them. Rate limits are enforced per API key. Exceeding them returns `429 Too Many Requests`. *** ## Limits The Retail API enforces a global rate limit of **20 requests per second** per API key across all endpoints. | Limit | Value | | ---------------------------------------- | ---------------------------------- | | **Global (all authenticated endpoints)** | 20 requests per second per API key | | **Public (unauthenticated)** | 20 requests per second per IP | *** ## When you're rate limited ```json theme={null} { "status": 429, "message": "Too Many Requests" } ``` Stop immediately, wait at least 1 second, then retry with exponential backoff: ```python theme={null} import time def make_request_with_retry(fn, max_retries=3): for attempt in range(max_retries): response = fn() if response.status_code != 429: return response time.sleep(2 ** attempt) raise Exception("Max retries exceeded") ``` *** ## Latency stopgap on orders During periods of increased latency, Polymarket US applies a **5-second stopgap** to inbound orders. If an order has been received by Polymarket US but has not been processed within 5 seconds, we reject it to protect you from a bad fill at a stale price. These rejects carry the message **`Global Rate Limit Exceeded`**, but they are **not** an actual rate limit. You do **not** need to throttle your traffic in response to them. Treat them as a transient latency reject, not a signal to back off. What it applies to: * **New orders** — rejected if not processed within 5 seconds. * **Order modifications via cancel/replace** — also subject to the stopgap. * **Pure cancels are not affected** — a standalone cancel is never rejected by this stopgap. You can always cancel an order before you have received an acknowledgement, and even before it has been processed. *** ## Use WebSocket instead of polling The single most effective way to stay within limits is to stop polling and use WebSocket streams. One persistent connection replaces hundreds of repeated REST calls. | Don't poll | Use instead | | ---------------------------------------- | ------------------------------------------------------- | | `GET /v1/orders/open` repeatedly | `/v1/ws/private` - `SUBSCRIPTION_TYPE_ORDER` | | `GET /v1/portfolio/positions` repeatedly | `/v1/ws/private` - `SUBSCRIPTION_TYPE_POSITION` | | `GET /v1/account/balances` repeatedly | `/v1/ws/private` - `SUBSCRIPTION_TYPE_ACCOUNT_BALANCE` | | `GET /v1/markets/{slug}/bbo` repeatedly | `/v1/ws/markets` - `SUBSCRIPTION_TYPE_MARKET_DATA_LITE` | | `GET /v1/markets/{slug}/book` repeatedly | `/v1/ws/markets` - `SUBSCRIPTION_TYPE_MARKET_DATA` | *** ## Cache reference data Market and event metadata changes infrequently. Fetch it once on startup and refresh periodically rather than on every request. ```python theme={null} import time class MarketCache: def __init__(self, client, ttl=300): self._client = client self._cache = {} self._ttl = ttl self._last_refresh = None def get(self, slug): if self._needs_refresh(): self._refresh() return self._cache.get(slug) def _needs_refresh(self): return not self._last_refresh or (time.time() - self._last_refresh) > self._ttl def _refresh(self): markets = self._client.markets.list({"limit": 100, "active": True}) self._cache = {m["slug"]: m for m in markets["markets"]} self._last_refresh = time.time() ``` *** ## For automated systems If you're running an automated trading system and need higher limits for production: 1. Document your use case and expected request volume 2. Email [support@polymarket.us](mailto:support@polymarket.us) 3. Include which endpoints you need higher limits for # Get instrument metadata Source: https://docs.polymarket.us/api-reference/referencedata/get-instrument-metadata /institutional/oapi-schemas/refdata-schema.json post /v1/refdata/metadata Returns miscellaneous instrument metadata # List instruments Source: https://docs.polymarket.us/api-reference/referencedata/list-instruments /institutional/oapi-schemas/refdata-schema.json post /v1/refdata/instruments Returns a list of instruments matching the request # List symbols Source: https://docs.polymarket.us/api-reference/referencedata/list-symbols /institutional/oapi-schemas/refdata-schema.json post /v1/refdata/symbols Returns a list of symbols on the exchange # Download executions CSV Source: https://docs.polymarket.us/api-reference/report/download-executions-csv /institutional/oapi-schemas/report-schema.json post /v1/report/executions/csv Downloads executions as a CSV file stream # Download orders CSV Source: https://docs.polymarket.us/api-reference/report/download-orders-csv /institutional/oapi-schemas/report-schema.json post /v1/report/orders/csv Downloads orders as a CSV file stream # Download trades CSV Source: https://docs.polymarket.us/api-reference/report/download-trades-csv /institutional/oapi-schemas/report-schema.json post /v1/report/trades/csv Downloads trades as a CSV file stream # Get trade stats Source: https://docs.polymarket.us/api-reference/report/get-trade-stats /institutional/oapi-schemas/report-schema.json post /v1/report/trades/stats Gets aggregated trade data for a given period of time # Search executions Source: https://docs.polymarket.us/api-reference/report/search-executions /institutional/oapi-schemas/report-schema.json post /v1/report/executions/search Searches for exchange executions using the given details to filter # Search orders Source: https://docs.polymarket.us/api-reference/report/search-orders /institutional/oapi-schemas/report-schema.json post /v1/report/orders/search Searches for exchange orders using the given details to filter # Search trades Source: https://docs.polymarket.us/api-reference/report/search-trades /institutional/oapi-schemas/report-schema.json post /v1/report/trades/search Searches for exchange trades using the given details to filter # Accept quote Source: https://docs.polymarket.us/api-reference/rfqs/accept-quote /institutional/oapi-schemas/rfqs-schema.json put /v1/rfqs/{rfqId}/quotes/{quoteId}/accept Accepts one side of a quote. # Confirm quote Source: https://docs.polymarket.us/api-reference/rfqs/confirm-quote /institutional/oapi-schemas/rfqs-schema.json put /v1/rfqs/{rfqId}/quotes/{quoteId}/confirm Confirms an accepted quote during last look. # Create quote Source: https://docs.polymarket.us/api-reference/rfqs/create-quote /institutional/oapi-schemas/rfqs-schema.json post /v1/rfqs/quotes Creates a quote for an RFQ. # Create RFQ Source: https://docs.polymarket.us/api-reference/rfqs/create-rfq /institutional/oapi-schemas/rfqs-schema.json post /v1/rfqs Creates a combo RFQ. # Delete quote Source: https://docs.polymarket.us/api-reference/rfqs/delete-quote /institutional/oapi-schemas/rfqs-schema.json delete /v1/rfqs/{rfqId}/quotes/{quoteId} Deletes a quote so it can no longer be accepted. # Delete RFQ Source: https://docs.polymarket.us/api-reference/rfqs/delete-rfq /institutional/oapi-schemas/rfqs-schema.json delete /v1/rfqs/{rfqId} Closes an open RFQ. # Get quotes Source: https://docs.polymarket.us/api-reference/rfqs/get-quotes /institutional/oapi-schemas/rfqs-schema.json get /v1/rfqs/quotes Returns quotes matching the query filters. # Get RFQ user ID Source: https://docs.polymarket.us/api-reference/rfqs/get-rfq-user-id /institutional/oapi-schemas/rfqs-schema.json get /v1/rfqs/user-id Returns the public RFQ user ID for the authenticated participant. # Get RFQs Source: https://docs.polymarket.us/api-reference/rfqs/get-rfqs /institutional/oapi-schemas/rfqs-schema.json get /v1/rfqs Returns RFQs matching the query filters. # RFQ API Overview Source: https://docs.polymarket.us/api-reference/rfqs/overview Create and manage combo RFQs and quotes through the Retail API **Beta access required.** The Retail RFQ API is available only to explicitly enabled Retail API users. An RFQ requests two-sided liquidity for the exact symbol of a [combo instrument](/api-reference/combos/overview). All calls use normal [Retail API authentication](/api-reference/authentication) at `https://api.polymarket.us`. The Retail API derives the participant and account from the API key; clients do not send an account. ## Endpoints | Method | Endpoint | Description | | -------- | ------------------------------------------- | ------------------------------------------ | | `GET` | `/v1/rfqs/user-id` | Get your pseudonymous RFQ user ID | | `GET` | `/v1/rfqs` | Query visible RFQs | | `POST` | `/v1/rfqs` | Create an RFQ | | `DELETE` | `/v1/rfqs/{rfqId}` | Close your open RFQ | | `GET` | `/v1/rfqs/quotes` | Query visible quotes | | `POST` | `/v1/rfqs/quotes` | Create or replace your quote | | `DELETE` | `/v1/rfqs/{rfqId}/quotes/{quoteId}` | Delete your quote | | `PUT` | `/v1/rfqs/{rfqId}/quotes/{quoteId}/accept` | Accept one side of a quote | | `PUT` | `/v1/rfqs/{rfqId}/quotes/{quoteId}/confirm` | Confirm an accepted quote during last look | On the Retail API, Combo and RFQ creation share an additional [edge rate limit](/api-reference/rate-limits) of 10 requests per 10 seconds, enforced per API key and per IP. RFQ-specific business limits and participant restrictions are enforced separately by the RFQ service. For `GET /v1/rfqs/quotes`, provide an `rfqId` or exactly one of `userFilter=USER_FILTER_SELF` and `rfqUserFilter=USER_FILTER_SELF`. Cursors are opaque and must be reused with the same filters and authenticated participant. ## Real-time stream RFQ and quote lifecycle events are available on the [Private WebSocket](/api-reference/websocket/private#rfq-subscriptions). Subscribe with `SUBSCRIPTION_TYPE_RFQ` to receive all seven RFQ and quote event types, from `rfqCreated` through `quoteExecuted`. The stream is live and best effort, with no replay or separate subscription acknowledgment. On startup or reconnect, reconcile state with `GET /v1/rfqs` and `GET /v1/rfqs/quotes`. ## Execution `QUOTE_STATUS_EXECUTED` and `quoteExecuted` mean the paired exchange orders were submitted and their order IDs were recorded. They do not mean the orders filled. Use `SUBSCRIPTION_TYPE_RFQ` for the RFQ lifecycle and `SUBSCRIPTION_TYPE_ORDER` for fills, rejections, cancellations, and expirations. Correlate the orders using `creatorOrderId` for the maker and `rfqCreatorOrderId` for the requester; both IDs are also returned by `GET /v1/rfqs/quotes`. RFQ orders enter the normal combo order book and may trade with other resting liquidity. Combo instruments can also be traded directly through the [Orders API](/api-reference/orders/overview); using an RFQ is optional. If either `restRemainder` setting is true, unfilled quantity on that side may remain on the book. ## See Also Create and read combo instruments Receive all seven RFQ event variants Sign Retail API requests # Introduction Source: https://docs.polymarket.us/api-reference/sdks/introduction Official SDKs for the Polymarket US API Python and TypeScript SDKs for integrating with the Polymarket US API. Both libraries handle authentication, request signing, and provide typed interfaces for their supported endpoints. You must download the [Polymarket US iOS app](https://apps.apple.com/us/app/polymarket/id6648798962), create an account, and complete identity verification before you can generate API keys. Visit the developer portal to generate your API keys. Your private key will be shown only once. ## Choose Your SDK Sync and async support. Python 3.10+. [GitHub](https://github.com/Polymarket/polymarket-us-python) · [PyPI](https://pypi.org/project/polymarket-us/) Full TypeScript types. Node.js 18+. [GitHub](https://github.com/Polymarket/polymarket-us-typescript) · [npm](https://www.npmjs.com/package/polymarket-us) ## Installation ```bash Python theme={null} pip install polymarket-us ``` ```bash TypeScript theme={null} npm install polymarket-us ``` ## Features * **Automatic authentication** - request signing handled internally * **Type safety** - Full typing for all requests and responses * **WebSocket support** - Real-time market data and order updates * **Error handling** - Typed exceptions for all error cases ## API Coverage | Resource | Methods | | --------- | ----------------------------------------------------------------------------------------- | | Events | `list`, `retrieve`, `retrieveBySlug` | | Markets | `list`, `retrieve`, `retrieveBySlug`, `book`, `bbo`, `settlement` | | Orders | `create`, `list`, `retrieve`, `cancel`, `modify`, `cancelAll`, `preview`, `closePosition` | | Portfolio | `positions`, `activities` | | Account | `balances` | | Series | `list`, `retrieve` | | Sports | `list`, `teams` | | Search | `query` | | WebSocket | `private`, `markets` | ## Quick Example ```python Python theme={null} from polymarket_us import PolymarketUS client = PolymarketUS( key_id="your-key-id", secret_key="your-secret-key", ) # Get markets and place an order markets = client.markets.list({"limit": 10}) order = client.orders.create({ "marketSlug": "your-market-slug", "intent": "ORDER_INTENT_BUY_LONG", "type": "ORDER_TYPE_LIMIT", "price": {"value": "0.555", "currency": "USD"}, "quantity": 0.5, }) ``` ```typescript TypeScript theme={null} import { PolymarketUS } from 'polymarket-us'; const client = new PolymarketUS({ keyId: 'your-key-id', secretKey: 'your-secret-key', }); // Get markets and place an order const markets = await client.markets.list({ limit: 10 }); const order = await client.orders.create({ marketSlug: 'your-market-slug', intent: 'ORDER_INTENT_BUY_LONG', type: 'ORDER_TYPE_LIMIT', price: { value: '0.555', currency: 'USD' }, quantity: 0.5, }); ``` # Account Source: https://docs.polymarket.us/api-reference/sdks/python/account View account balances and buying power Requires authentication. The Account resource provides access to your account balances and financial information. ## Methods | Method | Endpoint | Description | | ------------ | -------------------------- | -------------------- | | `balances()` | `GET /v1/account/balances` | Get account balances | *** ## balances Retrieve your current account balances, buying power, and pending withdrawals. ```python theme={null} balances = client.account.balances() print(f"Current Balance: ${balances['currentBalance']}") print(f"Buying Power: ${balances['buyingPower']}") print(f"Open Orders: ${balances['openOrders']}") ``` ### Response Fields | Field | Type | Description | | -------------------- | ----- | --------------------------------- | | `currentBalance` | float | Current fiat currency balance | | `currency` | str | Currency code (e.g., "USD") | | `buyingPower` | float | Capital available for trading | | `assetNotional` | float | Total notional value of positions | | `assetAvailable` | float | Available collateral value | | `openOrders` | float | Value tied up in open orders | | `unsettledFunds` | float | Unsettled funds not yet available | | `marginRequirement` | float | Required margin for positions | | `pendingWithdrawals` | list | Active withdrawal requests | ### Buying Power The `buyingPower` field represents unencumbered capital available for trading: ``` buyingPower = currentBalance + assetAvailable - openOrders - marginRequirement ``` For real-time balance updates, use the [WebSocket](/api-reference/sdks/python/websocket) with `SUBSCRIPTION_TYPE_ACCOUNT_BALANCE` instead of polling. # Events Source: https://docs.polymarket.us/api-reference/sdks/python/events Retrieve and filter events The Events resource provides access to event data. Events contain one or more markets and represent the underlying question or competition being predicted. ## Methods | Method | Endpoint | Description | | ------------------------ | ---------------------------- | -------------------------- | | `list(params?)` | `GET /v1/events` | List events with filtering | | `retrieve(id)` | `GET /v1/events/{id}` | Get event by ID | | `retrieve_by_slug(slug)` | `GET /v1/events/slug/{slug}` | Get event by URL slug | *** ## list Fetch a paginated list of events with optional filters. ```python theme={null} events = client.events.list({ "limit": 10, "offset": 0, "active": True, "categories": ["sports", "crypto"], }) for event in events["events"]: print(f"{event['title']} - {len(event.get('markets', []))} markets") ``` ### Parameters | Parameter | Type | Description | | ------------ | ---------- | ---------------------------------------- | | `limit` | int | Maximum results to return (default: 100) | | `offset` | int | Number of results to skip for pagination | | `active` | bool | Filter by active events | | `closed` | bool | Filter by closed events | | `archived` | bool | Filter by archived events | | `featured` | bool | Filter featured events only | | `categories` | list\[str] | Filter by category slugs | | `seriesId` | list\[int] | Filter by series IDs | | `live` | bool | Filter live sports events | | `ended` | bool | Filter ended sports events | ### Response Fields | Field | Type | Description | | ------------- | ---- | ----------------------------------- | | `id` | int | Unique event identifier | | `slug` | str | URL-friendly identifier | | `title` | str | Event title | | `description` | str | Event description | | `category` | str | Primary category | | `active` | bool | Whether event is active for trading | | `closed` | bool | Whether event is closed | | `markets` | list | Associated markets | *** ## retrieve Get a single event by its numeric ID. ```python theme={null} event = client.events.retrieve(12345) print(f"Title: {event['title']}") print(f"Category: {event['category']}") print(f"Markets: {len(event.get('markets', []))}") ``` ### Parameters | Parameter | Type | Description | | --------- | ---- | ----------- | | `id` | int | Event ID | *** ## retrieve\_by\_slug Get an event by its URL slug. Useful when you have the slug from a URL or API response. ```python theme={null} event = client.events.retrieve_by_slug("super-bowl-2025") print(f"Title: {event['title']}") for market in event.get("markets", []): print(f" - {market['title']}: {market['slug']}") ``` ### Parameters | Parameter | Type | Description | | --------- | ---- | -------------- | | `slug` | str | Event URL slug | *** ## Sports Event Fields Sports events include additional real-time data: | Field | Type | Description | | -------------- | ------ | --------------------------- | | `gameId` | str | Sports provider game ID | | `live` | bool | Whether game is in progress | | `ended` | bool | Whether game has ended | | `score` | object | Current score | | `period` | str | Current period/quarter/half | | `participants` | list | Teams or players | ```python theme={null} events = client.events.list({"live": True, "categories": ["sports"]}) for event in events["events"]: if event.get("live"): score = event.get("score", {}) print(f"{event['title']}: {score}") ``` # Markets Source: https://docs.polymarket.us/api-reference/sdks/python/markets Query market data, order books, and prices The Markets resource provides access to market information, pricing, and order book data. Markets represent individual tradeable contracts within an event. ## Methods | Method | Endpoint | Description | | ------------------------ | ----------------------------------- | --------------------------- | | `list(params?)` | `GET /v1/markets` | List markets with filtering | | `retrieve(id)` | `GET /v1/market/id/{id}` | Get market by ID | | `retrieve_by_slug(slug)` | `GET /v1/market/slug/{slug}` | Get market by slug | | `book(slug)` | `GET /v1/markets/{slug}/book` | Get full order book | | `bbo(slug)` | `GET /v1/markets/{slug}/bbo` | Get best bid/offer | | `settlement(slug)` | `GET /v1/markets/{slug}/settlement` | Get settlement price | *** ## list Fetch a paginated list of markets with optional filters. ```python theme={null} markets = client.markets.list({ "limit": 20, "active": True, "categories": ["sports", "crypto"], }) for market in markets["markets"]: print(f"{market['slug']}: {market['question']}") ``` ### Parameters | Parameter | Type | Description | | ------------------- | ---------- | ------------------------------------------------------------- | | `limit` | int | Maximum results to return | | `offset` | int | Pagination offset | | `active` | bool | Filter by active trading status | | `closed` | bool | Filter by closed status | | `archived` | bool | Filter by archived status | | `categories` | list\[str] | Filter by category slugs | | `sportsMarketTypes` | list\[str] | Filter by sports market type (MONEYLINE, SPREAD, TOTAL, PROP) | | `volumeNumMin` | float | Minimum trading volume | | `liquidityNumMin` | float | Minimum liquidity | ### Response Fields | Field | Type | Description | | ---------------- | ----- | ----------------------------- | | `id` | int | Unique market identifier | | `slug` | str | URL-friendly identifier | | `question` | str | Market question | | `description` | str | Detailed description | | `active` | bool | Whether market accepts orders | | `lastTradePrice` | float | Most recent trade price | | `bestBid` | float | Best bid price | | `bestAsk` | float | Best ask price | | `volume` | str | Total trading volume | | `liquidity` | str | Current liquidity | *** ## retrieve\_by\_slug Get a single market by its URL slug. ```python theme={null} market = client.markets.retrieve_by_slug("btc-100k-2025") print(f"Question: {market['question']}") print(f"Status: {market['active']}") print(f"Last Price: {market['lastTradePrice']}") ``` *** ## book Get the full order book with all bid and offer levels. ```python theme={null} book = client.markets.book("btc-100k-2025") print(f"State: {book['marketData']['state']}") print(f"Bids: {len(book['marketData']['bids'])}") print(f"Offers: {len(book['marketData']['offers'])}") for bid in book["marketData"]["bids"][:5]: print(f" ${bid['px']['value']} x {bid['qty']}") ``` ### Response Fields | Field | Type | Description | | ------------ | ------ | ------------------------------------ | | `marketSlug` | str | Market identifier | | `bids` | list | Buy orders (highest price first) | | `offers` | list | Sell orders (lowest price first) | | `state` | str | Market state (OPEN, SUSPENDED, etc.) | | `stats` | object | Market statistics | *** ## bbo Get best bid/offer only. Use this lightweight endpoint when you only need top-of-book prices. ```python theme={null} bbo = client.markets.bbo("btc-100k-2025") data = bbo["marketData"] print(f"Best Bid: ${data['bestBid']['value']}") print(f"Best Ask: ${data['bestAsk']['value']}") print(f"Last Trade: ${data['lastTradePx']['value']}") ``` ### Response Fields | Field | Type | Description | | -------------- | ------ | ------------------------ | | `bestBid` | Amount | Best (highest) bid price | | `bestAsk` | Amount | Best (lowest) ask price | | `lastTradePx` | Amount | Last trade price | | `bidDepth` | int | Number of bid levels | | `askDepth` | int | Number of ask levels | | `openInterest` | str | Current open interest | *** ## settlement Get the settlement price for a resolved market. ```python theme={null} settlement = client.markets.settlement("btc-100k-2025") print(f"Settlement: ${settlement['settlement']}") ``` Settlement values are typically `0.00` (No) or `1.00` (Yes). For real-time market data, use the [WebSocket](/api-reference/sdks/python/websocket) markets stream instead of polling. # Orders Source: https://docs.polymarket.us/api-reference/sdks/python/orders Create, cancel, and manage orders Requires authentication. The Orders resource provides order entry and management capabilities for trading on markets. ## Methods | Method | Endpoint | Description | | -------------------------- | --------------------------------- | ------------------------------- | | `create(params)` | `POST /v1/orders` | Create a new order | | `list(params?)` | `GET /v1/orders/open` | Get open orders | | `retrieve(order_id)` | `GET /v1/order/{orderId}` | Get order by ID | | `cancel(order_id, params)` | `POST /v1/order/{orderId}/cancel` | Cancel an order | | `modify(order_id, params)` | `POST /v1/order/{orderId}/modify` | Modify an order | | `cancel_all(params?)` | `POST /v1/orders/open/cancel` | Cancel all open orders | | `preview(params)` | `POST /v1/order/preview` | Preview order before submission | | `close_position(params)` | `POST /v1/order/close-position` | Close an existing position | *** ## create Create a new order on a market. ```python theme={null} order = client.orders.create({ "marketSlug": "btc-100k-2025", "intent": "ORDER_INTENT_BUY_LONG", "type": "ORDER_TYPE_LIMIT", "price": {"value": "0.555", "currency": "USD"}, "quantity": 0.5, "tif": "TIME_IN_FORCE_GOOD_TILL_CANCEL", }) print(f"Order ID: {order['id']}") print(f"State: {order['state']}") ``` ### Parameters | Parameter | Type | Required | Description | | ------------ | ------ | ---------- | ------------------------------------------------------------------------------------- | | `marketSlug` | str | Yes | Market to trade | | `intent` | str | Yes | Order intent (see below) | | `type` | str | Yes | `ORDER_TYPE_LIMIT` or `ORDER_TYPE_MARKET` | | `price` | Amount | Limit only | Limit price | | `quantity` | float | Yes | Number of contracts. Can be decimal when the market `minimumTradeQty` is less than 1. | | `tif` | str | Yes | Time in force (see below) | ### Order Intent | Value | Description | | ------------------------- | ------------------ | | `ORDER_INTENT_BUY_LONG` | Buy YES contracts | | `ORDER_INTENT_SELL_LONG` | Sell YES contracts | | `ORDER_INTENT_BUY_SHORT` | Buy NO contracts | | `ORDER_INTENT_SELL_SHORT` | Sell NO contracts | ### Time in Force | Value | Description | | ----------------------------------- | ------------------------------------------------ | | `TIME_IN_FORCE_GOOD_TILL_CANCEL` | Remains active until filled or canceled | | `TIME_IN_FORCE_GOOD_TILL_DATE` | Expires at specified time | | `TIME_IN_FORCE_IMMEDIATE_OR_CANCEL` | Fill immediately available quantity, cancel rest | | `TIME_IN_FORCE_FILL_OR_KILL` | Fill entirely or cancel completely | *** ## list Get all open orders. ```python theme={null} orders = client.orders.list() for order in orders["orders"]: print(f"{order['id']}: {order['marketSlug']} - {order['state']}") ``` *** ## cancel Cancel a specific order. ```python theme={null} client.orders.cancel("order-id-123", { "marketSlug": "btc-100k-2025" }) ``` *** ## cancel\_all Cancel all open orders, optionally filtered by market. ```python theme={null} result = client.orders.cancel_all() print(f"Canceled: {result['canceledOrderIds']}") # Or cancel for a specific market result = client.orders.cancel_all({"marketSlug": "btc-100k-2025"}) ``` *** ## preview Preview an order before submitting. Returns estimated fills and costs. ```python theme={null} preview = client.orders.preview({ "marketSlug": "your-market-slug", "intent": "ORDER_INTENT_BUY_LONG", "type": "ORDER_TYPE_LIMIT", "price": {"value": "0.555", "currency": "USD"}, "quantity": 0.5, }) print(f"Estimated Cost: ${preview['estimatedCost']}") ``` *** ## close\_position Close an existing position at market price. This sells your entire position in a single call. ```python theme={null} result = client.orders.close_position({ "marketSlug": "btc-100k-2025", }) ``` ### close\_position vs Sell Order | | `close_position` | Sell Order (`create`) | | ----------------- | ---------------- | --------------------------- | | **Position size** | Entire position | Any quantity | | **Order type** | Market only | Limit or market | | **Use case** | Quick full exit | Partial sells, limit prices | Use `close_position` when you want to fully exit a position at market price. Use a sell order (`ORDER_INTENT_SELL_LONG` or `ORDER_INTENT_SELL_SHORT`) when you need to sell a specific quantity or set a limit price. ### Slippage Tolerance For market orders and close position, you can specify slippage tolerance: ```python theme={null} result = client.orders.close_position({ "marketSlug": "btc-100k-2025", "slippageTolerance": { "currentPrice": {"value": "0.50", "currency": "USD"}, "ticks": 5 } }) ``` *** ## Order States Orders progress through these states: | State | Description | | ------------------------------ | --------------------------- | | `ORDER_STATE_PENDING_NEW` | Received, not yet processed | | `ORDER_STATE_PARTIALLY_FILLED` | Partially executed | | `ORDER_STATE_FILLED` | Fully executed | | `ORDER_STATE_CANCELED` | Canceled | | `ORDER_STATE_REJECTED` | Rejected by exchange | | `ORDER_STATE_EXPIRED` | Expired (GTD orders) | For real-time order updates, use the [WebSocket](/api-reference/sdks/python/websocket) with `SUBSCRIPTION_TYPE_ORDER` instead of polling. # Portfolio Source: https://docs.polymarket.us/api-reference/sdks/python/portfolio View positions and trading activity Requires authentication. The Portfolio resource provides access to your trading positions and activity history. ## Methods | Method | Endpoint | Description | | --------------------- | ------------------------------ | --------------------- | | `positions(params?)` | `GET /v1/portfolio/positions` | Get trading positions | | `activities(params?)` | `GET /v1/portfolio/activities` | Get activity history | *** ## positions Get your current trading positions. Returns a map of market slug to position data. ```python theme={null} positions = client.portfolio.positions() for slug, pos in positions["positions"].items(): meta = pos["marketMetadata"] print(f"{meta['title']}") print(f" Net Position: {pos['netPositionDecimal']}") print(f" Cost: ${pos['cost']['value']}") print(f" Cash Value: ${pos['cashValue']['value']}") ``` ### Parameters | Parameter | Type | Description | | --------- | ---- | ----------------- | | `cursor` | str | Pagination cursor | | `limit` | int | Maximum results | ### Position Fields | Field | Type | Description | | --------------------- | ------ | ------------------------------------------------------------- | | `netPositionDecimal` | str | Net quantity in contracts (positive = long, negative = short) | | `qtyBoughtDecimal` | str | Total quantity bought in contracts | | `qtySoldDecimal` | str | Total quantity sold in contracts | | `qtyAvailableDecimal` | str | Quantity available to trade in contracts | | `netPosition` | str | Deprecated rounded quantity; use `netPositionDecimal` | | `qtyBought` | str | Deprecated rounded quantity; use `qtyBoughtDecimal` | | `qtySold` | str | Deprecated rounded quantity; use `qtySoldDecimal` | | `cost` | Amount | Total cost basis | | `realized` | Amount | Realized profit/loss | | `cashValue` | Amount | Current unrealized value | | `qtyAvailable` | str | Deprecated rounded quantity; use `qtyAvailableDecimal` | | `expired` | bool | Whether position has expired | | `marketMetadata` | object | Market information | *** ## activities Get your trading activity history including trades, settlements, deposits, and withdrawals. ```python theme={null} activities = client.portfolio.activities({"limit": 20}) for act in activities["activities"]: print(f"{act['type']}: {act.get('trade', act.get('accountBalanceChange', {}))}") ``` ### Parameters | Parameter | Type | Description | | ------------ | ---------- | ----------------------------------------------------------- | | `limit` | int | Maximum results | | `cursor` | str | Pagination cursor | | `types` | list\[str] | Filter by activity types | | `marketSlug` | str | Filter by market | | `sortOrder` | str | `SORT_ORDER_DESCENDING` (default) or `SORT_ORDER_ASCENDING` | ### Activity Types | Type | Nested Field | Description | | ---------------------------------------- | ---------------------- | ---------------------------------------- | | `ACTIVITY_TYPE_TRADE` | `trade` | Trade execution | | `ACTIVITY_TYPE_POSITION_RESOLUTION` | `positionResolution` | Market settlement | | `ACTIVITY_TYPE_ACCOUNT_DEPOSIT` | `accountBalanceChange` | Deposit | | `ACTIVITY_TYPE_ACCOUNT_ADVANCED_DEPOSIT` | `accountBalanceChange` | Advance issued against a pending deposit | | `ACTIVITY_TYPE_ACCOUNT_WITHDRAWAL` | `accountBalanceChange` | Withdrawal | | `ACTIVITY_TYPE_TRANSFER` | `accountBalanceChange` | Internal transfer | | `ACTIVITY_TYPE_REFERRAL_BONUS` | `accountBalanceChange` | Referral incentive credit | | `ACTIVITY_TYPE_TAKER_FEE_REBATE` | `accountBalanceChange` | Taker fee rebate credit | | `ACTIVITY_TYPE_LIQUIDITY_PROGRAM` | `accountBalanceChange` | Liquidity program payout | ### Trade Fields | Field | Type | Description | | ------------- | ------ | --------------------------------------------- | | `id` | str | Trade ID | | `marketSlug` | str | Market slug | | `price` | Amount | Trade price | | `qtyDecimal` | str | Trade quantity in contracts | | `qty` | str | Deprecated rounded quantity; use `qtyDecimal` | | `isAggressor` | bool | True if taker | | `realizedPnl` | Amount | Realized P\&L | For real-time position updates, use the [WebSocket](/api-reference/sdks/python/websocket) with `SUBSCRIPTION_TYPE_POSITION` instead of polling. # Quickstart Source: https://docs.polymarket.us/api-reference/sdks/python/quickstart Get started with the Python SDK ## Installation ```bash theme={null} pip install polymarket-us ``` Requires Python 3.10+. [GitHub](https://github.com/Polymarket/polymarket-us-python) · [PyPI](https://pypi.org/project/polymarket-us/) *** ## Configuration ```python theme={null} import os from polymarket_us import PolymarketUS client = PolymarketUS( key_id=os.environ["POLYMARKET_KEY_ID"], secret_key=os.environ["POLYMARKET_SECRET_KEY"], timeout=30.0, # optional, default 30s ) ``` Generate API keys at [polymarket.us/developer](https://polymarket.us/developer). *** ## Public Endpoints No authentication required for market data: ```python theme={null} from polymarket_us import PolymarketUS client = PolymarketUS() # Events events = client.events.list({"limit": 10, "active": True}) event = client.events.retrieve_by_slug("super-bowl-2025") # Markets markets = client.markets.list({"limit": 10}) market = client.markets.retrieve_by_slug("btc-100k") book = client.markets.book("btc-100k") bbo = client.markets.bbo("btc-100k") # Search results = client.search.query({"query": "bitcoin"}) # Series and Sports series = client.series.list() sports = client.sports.list() client.close() ``` *** ## Authenticated Endpoints Trading requires API credentials: ```python theme={null} import os from polymarket_us import PolymarketUS client = PolymarketUS( key_id=os.environ["POLYMARKET_KEY_ID"], secret_key=os.environ["POLYMARKET_SECRET_KEY"], ) # Account balances = client.account.balances() # Portfolio positions = client.portfolio.positions() activities = client.portfolio.activities() # Orders open_orders = client.orders.list() order = client.orders.create({ "marketSlug": "your-market-slug", "intent": "ORDER_INTENT_BUY_LONG", "type": "ORDER_TYPE_LIMIT", "price": {"value": "0.555", "currency": "USD"}, "quantity": 0.5, "tif": "TIME_IN_FORCE_GOOD_TILL_CANCEL", }) client.close() ``` *** ## Async Usage ```python theme={null} import asyncio from polymarket_us import AsyncPolymarketUS async def main(): async with AsyncPolymarketUS( key_id="your-key-id", secret_key="your-secret-key", ) as client: # Concurrent requests events, markets = await asyncio.gather( client.events.list({"limit": 10}), client.markets.list({"limit": 10}), ) print(f"Found {len(events['events'])} events") asyncio.run(main()) ``` *** ## Error Handling ```python theme={null} from polymarket_us import ( PolymarketUS, APIConnectionError, APITimeoutError, AuthenticationError, BadRequestError, NotFoundError, RateLimitError, ) client = PolymarketUS(key_id="...", secret_key="...") try: order = client.orders.create({"marketSlug": "..."}) except AuthenticationError as e: print(f"Invalid credentials: {e.message}") except BadRequestError as e: print(f"Invalid parameters: {e.message}") except RateLimitError as e: print(f"Rate limited: {e.message}") except NotFoundError as e: print(f"Not found: {e.message}") except APITimeoutError: print("Request timed out") except APIConnectionError as e: print(f"Connection error: {e.message}") ``` ### Error Types | Exception | Description | | --------------------- | ------------------------------ | | `AuthenticationError` | Invalid or missing credentials | | `BadRequestError` | Invalid request parameters | | `NotFoundError` | Resource not found | | `RateLimitError` | Rate limit exceeded | | `APITimeoutError` | Request timed out | | `APIConnectionError` | Network connection error | # Search Source: https://docs.polymarket.us/api-reference/sdks/python/search Search for events and markets The Search resource provides full-text search across events and markets. ## Methods | Method | Endpoint | Description | | ---------------- | ---------------- | ------------------------- | | `query(params?)` | `GET /v1/search` | Search events and markets | *** ## query Search for events and markets by text query. ```python theme={null} results = client.search.query({ "query": "bitcoin", "limit": 10, }) for event in results["events"]: print(f"{event['title']}") for market in event.get("markets", []): print(f" - {market['question']}") ``` ### Parameters | Parameter | Type | Description | | ------------ | ---------- | -------------------------- | | `query` | str | Search query text | | `limit` | int | Maximum results to return | | `page` | int | Page number for pagination | | `seriesIds` | list\[int] | Filter by series IDs | | `marketType` | list\[str] | Filter by market types | | `status` | str | Filter by status | ### Response Returns events with their associated markets that match the search query: ```python theme={null} { "events": [ { "id": 123, "title": "Bitcoin Price Markets", "slug": "bitcoin-price", "markets": [ { "id": 456, "question": "Will Bitcoin reach $100k?", "slug": "btc-100k-2025" } ] } ] } ``` # Series Source: https://docs.polymarket.us/api-reference/sdks/python/series Browse event series The Series resource provides access to series information. Series group related events together (e.g., NFL games, election cycles, daily crypto markets). ## Methods | Method | Endpoint | Description | | --------------- | ------------------------ | ---------------- | | `list(params?)` | `GET /v1/series` | List all series | | `retrieve(id)` | `GET /v1/series/id/{id}` | Get series by ID | *** ## list Get a list of all series with optional filtering. ```python theme={null} series = client.series.list({ "active": True, "limit": 20, }) for s in series["series"]: print(f"{s['title']} ({s['recurrence']})") ``` ### Parameters | Parameter | Type | Description | | ------------ | ---------- | ------------------------------------------ | | `limit` | int | Maximum results | | `offset` | int | Pagination offset | | `slug` | list\[str] | Filter by slugs | | `active` | bool | Filter by active status | | `closed` | bool | Filter by closed status | | `archived` | bool | Filter by archived status | | `recurrence` | str | Filter by recurrence (daily, weekly, etc.) | ### Response Fields | Field | Type | Description | | ------------- | ---- | ------------------------ | | `id` | int | Unique series identifier | | `slug` | str | URL-friendly identifier | | `title` | str | Series title | | `subtitle` | str | Series subtitle | | `description` | str | Series description | | `seriesType` | str | Type of series | | `recurrence` | str | Recurrence pattern | | `active` | bool | Whether series is active | | `volume` | str | Total trading volume | | `liquidity` | str | Current liquidity | *** ## retrieve Get a single series by ID. ```python theme={null} series = client.series.retrieve(123) print(f"Title: {series['title']}") print(f"Description: {series['description']}") print(f"Recurrence: {series['recurrence']}") ``` # Sports Source: https://docs.polymarket.us/api-reference/sdks/python/sports Sports configuration and team data The Sports resource provides access to sports configuration and team information from data providers. ## Methods | Method | Endpoint | Description | | ---------------- | ------------------------------- | --------------------- | | `list()` | `GET /v1/sports` | List all sports | | `teams(params?)` | `GET /v1/sports/teams/provider` | Get teams by provider | *** ## list Get all available sports and their configuration. ```python theme={null} sports = client.sports.list() for sport in sports["sports"]: print(f"{sport['sport']} - Operational: {sport['isOperational']}") ``` ### Response Fields | Field | Type | Description | | --------------------- | ---- | --------------------------------------- | | `sport` | str | Sport name | | `image` | str | Sport image URL | | `isOperational` | bool | Whether sport is operational | | `automaticResolution` | bool | Whether automatic resolution is enabled | | `ordering` | str | Display ordering | *** ## teams Get team information from a specific data provider. ```python theme={null} teams = client.sports.teams({ "provider": "PROVIDER_SPORTRADAR", "league": "NFL", }) for team in teams["teams"]: print(f"{team['name']} ({team['abbreviation']})") print(f" Conference: {team['conference']}") print(f" Record: {team['record']}") ``` ### Parameters | Parameter | Type | Description | | ---------- | ---------- | --------------------------------- | | `provider` | str | Data provider (see below) | | `league` | str | League name (NFL, NBA, MLB, etc.) | | `teamIds` | list\[int] | Filter by specific team IDs | ### Data Providers | Provider | Description | | ----------------------- | ------------- | | `PROVIDER_SPORTRADAR` | Sportradar | | `PROVIDER_SPORTSDATAIO` | SportsData.io | ### Team Fields | Field | Type | Description | | -------------- | ---- | ------------------ | | `id` | int | Team identifier | | `name` | str | Team name | | `abbreviation` | str | Team abbreviation | | `league` | str | League name | | `conference` | str | Conference name | | `record` | str | Team record | | `ranking` | int | Team ranking | | `logo` | str | Logo URL | | `colorPrimary` | str | Primary team color | # WebSocket Source: https://docs.polymarket.us/api-reference/sdks/python/websocket Real-time streaming data WebSocket is async-only. Use `asyncio.run()` or `AsyncPolymarketUS`. The WebSocket resource provides real-time streaming data for market information and private user data. ## Methods | Method | Endpoint | Description | | ----------- | --------------------------------------- | ---------------------------------- | | `private()` | `wss://api.polymarket.us/v1/ws/private` | Orders, positions, balance updates | | `markets()` | `wss://api.polymarket.us/v1/ws/markets` | Market data and trades | *** ## private Connect to the private WebSocket for real-time order, position, and balance updates. ```python theme={null} import asyncio from polymarket_us import PolymarketUS async def main(): client = PolymarketUS( key_id="your-key-id", secret_key="your-secret-key", ) ws = client.ws.private() # Register event handlers ws.on("order_snapshot", lambda d: print(f"Orders: {d}")) ws.on("order_update", lambda d: print(f"Order update: {d}")) ws.on("position_snapshot", lambda d: print(f"Positions: {d}")) ws.on("position_update", lambda d: print(f"Position update: {d}")) ws.on("account_balance_snapshot", lambda d: print(f"Balance: {d}")) ws.on("error", lambda e: print(f"Error: {e}")) await ws.connect() # Subscribe to order updates await ws.subscribe("my-orders", "SUBSCRIPTION_TYPE_ORDER") # Subscribe to position updates await ws.subscribe("my-positions", "SUBSCRIPTION_TYPE_POSITION") # Subscribe to balance updates await ws.subscribe("my-balance", "SUBSCRIPTION_TYPE_ACCOUNT_BALANCE") await asyncio.sleep(3600) await ws.close() asyncio.run(main()) ``` ### Private Subscription Types | Type | Description | | ----------------------------------- | --------------------------- | | `SUBSCRIPTION_TYPE_ORDER` | Order updates and snapshots | | `SUBSCRIPTION_TYPE_POSITION` | Position changes | | `SUBSCRIPTION_TYPE_ACCOUNT_BALANCE` | Balance updates | ### Private Events | Event | Description | | -------------------------- | --------------------------------- | | `order_snapshot` | Initial snapshot of all orders | | `order_update` | Order state change | | `position_snapshot` | Initial snapshot of all positions | | `position_update` | Position change | | `account_balance_snapshot` | Initial balance snapshot | | `account_balance_update` | Balance change | *** ## markets Connect to the markets WebSocket for real-time market data and trades. ```python theme={null} import asyncio from polymarket_us import PolymarketUS async def main(): client = PolymarketUS( key_id="your-key-id", secret_key="your-secret-key", ) ws = client.ws.markets() # Register event handlers ws.on("market_data", lambda d: print(f"Book: {d}")) ws.on("market_data_lite", lambda d: print(f"BBO: {d}")) ws.on("trade", lambda d: print(f"Trade: {d}")) await ws.connect() # Subscribe to full order book updates await ws.subscribe("book", "SUBSCRIPTION_TYPE_MARKET_DATA", ["btc-100k-2025"]) # Subscribe to lightweight price updates await ws.subscribe("prices", "SUBSCRIPTION_TYPE_MARKET_DATA_LITE", ["btc-100k-2025"]) # Subscribe to trade notifications await ws.subscribe("trades", "SUBSCRIPTION_TYPE_TRADE", ["btc-100k-2025"]) await asyncio.sleep(3600) await ws.close() asyncio.run(main()) ``` ### Market Subscription Types | Type | Description | | ------------------------------------ | ----------------------------- | | `SUBSCRIPTION_TYPE_MARKET_DATA` | Full order book and stats | | `SUBSCRIPTION_TYPE_MARKET_DATA_LITE` | Lightweight price data (BBO) | | `SUBSCRIPTION_TYPE_TRADE` | Real-time trade notifications | ### Market Events | Event | Description | | ------------------ | ---------------------- | | `market_data` | Full order book update | | `market_data_lite` | BBO and price update | | `trade` | Trade execution | *** ## Best Practices 1. **Use unique request IDs** - Track subscriptions with unique identifiers 2. **Handle reconnection** - Implement automatic reconnection with exponential backoff 3. **Process messages in order** - Messages are delivered in sequence 4. **Limit subscriptions** - Only subscribe to markets you need # Account Source: https://docs.polymarket.us/api-reference/sdks/typescript/account View account balances and buying power Requires authentication. The Account resource provides access to your account balances and financial information. ## Methods | Method | Endpoint | Description | | ------------ | -------------------------- | -------------------- | | `balances()` | `GET /v1/account/balances` | Get account balances | *** ## balances Retrieve your current account balances, buying power, and pending withdrawals. ```typescript theme={null} const balances = await client.account.balances(); console.log(`Current Balance: $${balances.currentBalance}`); console.log(`Buying Power: $${balances.buyingPower}`); console.log(`Open Orders: $${balances.openOrders}`); ``` ### Response Fields | Field | Type | Description | | -------------------- | ------ | --------------------------------- | | `currentBalance` | number | Current fiat currency balance | | `currency` | string | Currency code (e.g., "USD") | | `buyingPower` | number | Capital available for trading | | `assetNotional` | number | Total notional value of positions | | `assetAvailable` | number | Available collateral value | | `openOrders` | number | Value tied up in open orders | | `unsettledFunds` | number | Unsettled funds not yet available | | `marginRequirement` | number | Required margin for positions | | `pendingWithdrawals` | array | Active withdrawal requests | ### Buying Power The `buyingPower` field represents unencumbered capital available for trading: ``` buyingPower = currentBalance + assetAvailable - openOrders - marginRequirement ``` For real-time balance updates, use the [WebSocket](/api-reference/sdks/typescript/websocket) with `SUBSCRIPTION_TYPE_ACCOUNT_BALANCE` instead of polling. # Events Source: https://docs.polymarket.us/api-reference/sdks/typescript/events Retrieve and filter events The Events resource provides access to event data. Events contain one or more markets and represent the underlying question or competition being predicted. ## Methods | Method | Endpoint | Description | | ---------------------- | ---------------------------- | -------------------------- | | `list(params?)` | `GET /v1/events` | List events with filtering | | `retrieve(id)` | `GET /v1/events/{id}` | Get event by ID | | `retrieveBySlug(slug)` | `GET /v1/events/slug/{slug}` | Get event by URL slug | *** ## list Fetch a paginated list of events with optional filters. ```typescript theme={null} const events = await client.events.list({ limit: 10, offset: 0, active: true, categories: ['sports', 'crypto'], }); for (const event of events.events) { console.log(`${event.title} - ${event.markets?.length ?? 0} markets`); } ``` ### Parameters | Parameter | Type | Description | | ------------ | --------- | ---------------------------------------- | | `limit` | number | Maximum results to return (default: 100) | | `offset` | number | Number of results to skip for pagination | | `active` | boolean | Filter by active events | | `closed` | boolean | Filter by closed events | | `archived` | boolean | Filter by archived events | | `featured` | boolean | Filter featured events only | | `categories` | string\[] | Filter by category slugs | | `seriesId` | number\[] | Filter by series IDs | | `live` | boolean | Filter live sports events | | `ended` | boolean | Filter ended sports events | ### Response Fields | Field | Type | Description | | ------------- | ------- | ----------------------------------- | | `id` | number | Unique event identifier | | `slug` | string | URL-friendly identifier | | `title` | string | Event title | | `description` | string | Event description | | `category` | string | Primary category | | `active` | boolean | Whether event is active for trading | | `closed` | boolean | Whether event is closed | | `markets` | array | Associated markets | *** ## retrieve Get a single event by its numeric ID. ```typescript theme={null} const event = await client.events.retrieve(12345); console.log(`Title: ${event.title}`); console.log(`Category: ${event.category}`); console.log(`Markets: ${event.markets?.length ?? 0}`); ``` ### Parameters | Parameter | Type | Description | | --------- | ------ | ----------- | | `id` | number | Event ID | *** ## retrieveBySlug Get an event by its URL slug. Useful when you have the slug from a URL or API response. ```typescript theme={null} const event = await client.events.retrieveBySlug('super-bowl-2025'); console.log(`Title: ${event.title}`); for (const market of event.markets ?? []) { console.log(` - ${market.title}: ${market.slug}`); } ``` ### Parameters | Parameter | Type | Description | | --------- | ------ | -------------- | | `slug` | string | Event URL slug | *** ## Sports Event Fields Sports events include additional real-time data: | Field | Type | Description | | -------------- | ------- | --------------------------- | | `gameId` | string | Sports provider game ID | | `live` | boolean | Whether game is in progress | | `ended` | boolean | Whether game has ended | | `score` | object | Current score | | `period` | string | Current period/quarter/half | | `participants` | array | Teams or players | ```typescript theme={null} const events = await client.events.list({ live: true, categories: ['sports'] }); for (const event of events.events) { if (event.live) { console.log(`${event.title}: ${JSON.stringify(event.score)}`); } } ``` # Markets Source: https://docs.polymarket.us/api-reference/sdks/typescript/markets Query market data, order books, and prices The Markets resource provides access to market information, pricing, and order book data. Markets represent individual tradeable contracts within an event. ## Methods | Method | Endpoint | Description | | ---------------------- | ----------------------------------- | --------------------------- | | `list(params?)` | `GET /v1/markets` | List markets with filtering | | `retrieve(id)` | `GET /v1/market/id/{id}` | Get market by ID | | `retrieveBySlug(slug)` | `GET /v1/market/slug/{slug}` | Get market by slug | | `book(slug)` | `GET /v1/markets/{slug}/book` | Get full order book | | `bbo(slug)` | `GET /v1/markets/{slug}/bbo` | Get best bid/offer | | `settlement(slug)` | `GET /v1/markets/{slug}/settlement` | Get settlement price | *** ## list Fetch a paginated list of markets with optional filters. ```typescript theme={null} const markets = await client.markets.list({ limit: 20, active: true, categories: ['sports', 'crypto'], }); for (const market of markets.markets) { console.log(`${market.slug}: ${market.question}`); } ``` ### Parameters | Parameter | Type | Description | | ------------------- | --------- | ------------------------------------------------------------- | | `limit` | number | Maximum results to return | | `offset` | number | Pagination offset | | `active` | boolean | Filter by active trading status | | `closed` | boolean | Filter by closed status | | `archived` | boolean | Filter by archived status | | `categories` | string\[] | Filter by category slugs | | `sportsMarketTypes` | string\[] | Filter by sports market type (MONEYLINE, SPREAD, TOTAL, PROP) | | `volumeNumMin` | number | Minimum trading volume | | `liquidityNumMin` | number | Minimum liquidity | ### Response Fields | Field | Type | Description | | ---------------- | ------- | ----------------------------- | | `id` | number | Unique market identifier | | `slug` | string | URL-friendly identifier | | `question` | string | Market question | | `description` | string | Detailed description | | `active` | boolean | Whether market accepts orders | | `lastTradePrice` | number | Most recent trade price | | `bestBid` | number | Best bid price | | `bestAsk` | number | Best ask price | | `volume` | string | Total trading volume | | `liquidity` | string | Current liquidity | *** ## retrieveBySlug Get a single market by its URL slug. ```typescript theme={null} const market = await client.markets.retrieveBySlug('btc-100k-2025'); console.log(`Question: ${market.question}`); console.log(`Status: ${market.active}`); console.log(`Last Price: ${market.lastTradePrice}`); ``` *** ## book Get the full order book with all bid and offer levels. ```typescript theme={null} const book = await client.markets.book('btc-100k-2025'); console.log(`State: ${book.marketData.state}`); console.log(`Bids: ${book.marketData.bids.length}`); console.log(`Offers: ${book.marketData.offers.length}`); for (const bid of book.marketData.bids.slice(0, 5)) { console.log(` $${bid.px.value} x ${bid.qty}`); } ``` ### Response Fields | Field | Type | Description | | ------------ | ------ | ------------------------------------ | | `marketSlug` | string | Market identifier | | `bids` | array | Buy orders (highest price first) | | `offers` | array | Sell orders (lowest price first) | | `state` | string | Market state (OPEN, SUSPENDED, etc.) | | `stats` | object | Market statistics | *** ## bbo Get best bid/offer only. Use this lightweight endpoint when you only need top-of-book prices. ```typescript theme={null} const bbo = await client.markets.bbo('btc-100k-2025'); const data = bbo.marketData; console.log(`Best Bid: $${data.bestBid.value}`); console.log(`Best Ask: $${data.bestAsk.value}`); console.log(`Last Trade: $${data.lastTradePx.value}`); ``` ### Response Fields | Field | Type | Description | | -------------- | ------ | ------------------------ | | `bestBid` | Amount | Best (highest) bid price | | `bestAsk` | Amount | Best (lowest) ask price | | `lastTradePx` | Amount | Last trade price | | `bidDepth` | number | Number of bid levels | | `askDepth` | number | Number of ask levels | | `openInterest` | string | Current open interest | *** ## settlement Get the settlement price for a resolved market. ```typescript theme={null} const settlement = await client.markets.settlement('btc-100k-2025'); console.log(`Settlement: $${settlement.settlement}`); ``` Settlement values are typically `0.00` (No) or `1.00` (Yes). For real-time market data, use the [WebSocket](/api-reference/sdks/typescript/websocket) markets stream instead of polling. # Orders Source: https://docs.polymarket.us/api-reference/sdks/typescript/orders Create, cancel, and manage orders Requires authentication. The Orders resource provides order entry and management capabilities for trading on markets. ## Methods | Method | Endpoint | Description | | ------------------------- | --------------------------------- | ------------------------------- | | `create(params)` | `POST /v1/orders` | Create a new order | | `list(params?)` | `GET /v1/orders/open` | Get open orders | | `retrieve(orderId)` | `GET /v1/order/{orderId}` | Get order by ID | | `cancel(orderId, params)` | `POST /v1/order/{orderId}/cancel` | Cancel an order | | `modify(orderId, params)` | `POST /v1/order/{orderId}/modify` | Modify an order | | `cancelAll(params?)` | `POST /v1/orders/open/cancel` | Cancel all open orders | | `preview(params)` | `POST /v1/order/preview` | Preview order before submission | | `closePosition(params)` | `POST /v1/order/close-position` | Close an existing position | *** ## create Create a new order on a market. ```typescript theme={null} const order = await client.orders.create({ marketSlug: 'btc-100k-2025', intent: 'ORDER_INTENT_BUY_LONG', type: 'ORDER_TYPE_LIMIT', price: { value: '0.555', currency: 'USD' }, quantity: 0.5, tif: 'TIME_IN_FORCE_GOOD_TILL_CANCEL', }); console.log(`Order ID: ${order.id}`); console.log(`State: ${order.state}`); ``` ### Parameters | Parameter | Type | Required | Description | | ------------ | ------ | ---------- | ------------------------------------------------------------------------------------- | | `marketSlug` | string | Yes | Market to trade | | `intent` | string | Yes | Order intent (see below) | | `type` | string | Yes | `ORDER_TYPE_LIMIT` or `ORDER_TYPE_MARKET` | | `price` | Amount | Limit only | Limit price | | `quantity` | number | Yes | Number of contracts. Can be decimal when the market `minimumTradeQty` is less than 1. | | `tif` | string | Yes | Time in force (see below) | ### Order Intent | Value | Description | | ------------------------- | ------------------ | | `ORDER_INTENT_BUY_LONG` | Buy YES contracts | | `ORDER_INTENT_SELL_LONG` | Sell YES contracts | | `ORDER_INTENT_BUY_SHORT` | Buy NO contracts | | `ORDER_INTENT_SELL_SHORT` | Sell NO contracts | ### Time in Force | Value | Description | | ----------------------------------- | ------------------------------------------------ | | `TIME_IN_FORCE_GOOD_TILL_CANCEL` | Remains active until filled or canceled | | `TIME_IN_FORCE_GOOD_TILL_DATE` | Expires at specified time | | `TIME_IN_FORCE_IMMEDIATE_OR_CANCEL` | Fill immediately available quantity, cancel rest | | `TIME_IN_FORCE_FILL_OR_KILL` | Fill entirely or cancel completely | *** ## list Get all open orders. ```typescript theme={null} const orders = await client.orders.list(); for (const order of orders.orders) { console.log(`${order.id}: ${order.marketSlug} - ${order.state}`); } ``` *** ## cancel Cancel a specific order. ```typescript theme={null} await client.orders.cancel('order-id-123', { marketSlug: 'btc-100k-2025', }); ``` *** ## cancelAll Cancel all open orders, optionally filtered by market. ```typescript theme={null} const result = await client.orders.cancelAll(); console.log(`Canceled: ${result.canceledOrderIds}`); // Or cancel for a specific market const result = await client.orders.cancelAll({ marketSlug: 'btc-100k-2025' }); ``` *** ## preview Preview an order before submitting. Returns estimated fills and costs. ```typescript theme={null} const preview = await client.orders.preview({ marketSlug: 'your-market-slug', intent: 'ORDER_INTENT_BUY_LONG', type: 'ORDER_TYPE_LIMIT', price: { value: '0.555', currency: 'USD' }, quantity: 0.5, }); console.log(`Estimated Cost: $${preview.estimatedCost}`); ``` *** ## closePosition Close an existing position at market price. This sells your entire position in a single call. ```typescript theme={null} const result = await client.orders.closePosition({ marketSlug: 'btc-100k-2025', }); ``` ### closePosition vs Sell Order | | `closePosition` | Sell Order (`create`) | | ----------------- | --------------- | --------------------------- | | **Position size** | Entire position | Any quantity | | **Order type** | Market only | Limit or market | | **Use case** | Quick full exit | Partial sells, limit prices | Use `closePosition` when you want to fully exit a position at market price. Use a sell order (`ORDER_INTENT_SELL_LONG` or `ORDER_INTENT_SELL_SHORT`) when you need to sell a specific quantity or set a limit price. ### Slippage Tolerance For market orders and close position, you can specify slippage tolerance: ```typescript theme={null} const result = await client.orders.closePosition({ marketSlug: 'btc-100k-2025', slippageTolerance: { currentPrice: { value: '0.50', currency: 'USD' }, ticks: 5, }, }); ``` *** ## Order States Orders progress through these states: | State | Description | | ------------------------------ | --------------------------- | | `ORDER_STATE_PENDING_NEW` | Received, not yet processed | | `ORDER_STATE_PARTIALLY_FILLED` | Partially executed | | `ORDER_STATE_FILLED` | Fully executed | | `ORDER_STATE_CANCELED` | Canceled | | `ORDER_STATE_REJECTED` | Rejected by exchange | | `ORDER_STATE_EXPIRED` | Expired (GTD orders) | For real-time order updates, use the [WebSocket](/api-reference/sdks/typescript/websocket) with `SUBSCRIPTION_TYPE_ORDER` instead of polling. # Portfolio Source: https://docs.polymarket.us/api-reference/sdks/typescript/portfolio View positions and trading activity Requires authentication. The Portfolio resource provides access to your trading positions and activity history. ## Methods | Method | Endpoint | Description | | --------------------- | ------------------------------ | --------------------- | | `positions(params?)` | `GET /v1/portfolio/positions` | Get trading positions | | `activities(params?)` | `GET /v1/portfolio/activities` | Get activity history | *** ## positions Get your current trading positions. Returns a map of market slug to position data. ```typescript theme={null} const positions = await client.portfolio.positions(); for (const [slug, pos] of Object.entries(positions.positions)) { const meta = pos.marketMetadata; console.log(meta.title); console.log(` Net Position: ${pos.netPositionDecimal}`); console.log(` Cost: $${pos.cost.value}`); console.log(` Cash Value: $${pos.cashValue.value}`); } ``` ### Parameters | Parameter | Type | Description | | --------- | ------ | ----------------- | | `cursor` | string | Pagination cursor | | `limit` | number | Maximum results | ### Position Fields | Field | Type | Description | | --------------------- | ------- | ------------------------------------------------------------- | | `netPositionDecimal` | string | Net quantity in contracts (positive = long, negative = short) | | `qtyBoughtDecimal` | string | Total quantity bought in contracts | | `qtySoldDecimal` | string | Total quantity sold in contracts | | `qtyAvailableDecimal` | string | Quantity available to trade in contracts | | `netPosition` | string | Deprecated rounded quantity; use `netPositionDecimal` | | `qtyBought` | string | Deprecated rounded quantity; use `qtyBoughtDecimal` | | `qtySold` | string | Deprecated rounded quantity; use `qtySoldDecimal` | | `cost` | Amount | Total cost basis | | `realized` | Amount | Realized profit/loss | | `cashValue` | Amount | Current unrealized value | | `qtyAvailable` | string | Deprecated rounded quantity; use `qtyAvailableDecimal` | | `expired` | boolean | Whether position has expired | | `marketMetadata` | object | Market information | *** ## activities Get your trading activity history including trades, settlements, deposits, and withdrawals. ```typescript theme={null} const activities = await client.portfolio.activities({ limit: 20 }); for (const act of activities.activities) { console.log(`${act.type}: ${JSON.stringify(act.trade ?? act.accountBalanceChange)}`); } ``` ### Parameters | Parameter | Type | Description | | ------------ | --------- | ----------------------------------------------------------- | | `limit` | number | Maximum results | | `cursor` | string | Pagination cursor | | `types` | string\[] | Filter by activity types | | `marketSlug` | string | Filter by market | | `sortOrder` | string | `SORT_ORDER_DESCENDING` (default) or `SORT_ORDER_ASCENDING` | ### Activity Types | Type | Nested Field | Description | | ---------------------------------------- | ---------------------- | ---------------------------------------- | | `ACTIVITY_TYPE_TRADE` | `trade` | Trade execution | | `ACTIVITY_TYPE_POSITION_RESOLUTION` | `positionResolution` | Market settlement | | `ACTIVITY_TYPE_ACCOUNT_DEPOSIT` | `accountBalanceChange` | Deposit | | `ACTIVITY_TYPE_ACCOUNT_ADVANCED_DEPOSIT` | `accountBalanceChange` | Advance issued against a pending deposit | | `ACTIVITY_TYPE_ACCOUNT_WITHDRAWAL` | `accountBalanceChange` | Withdrawal | | `ACTIVITY_TYPE_TRANSFER` | `accountBalanceChange` | Internal transfer | | `ACTIVITY_TYPE_REFERRAL_BONUS` | `accountBalanceChange` | Referral incentive credit | | `ACTIVITY_TYPE_TAKER_FEE_REBATE` | `accountBalanceChange` | Taker fee rebate credit | | `ACTIVITY_TYPE_LIQUIDITY_PROGRAM` | `accountBalanceChange` | Liquidity program payout | ### Trade Fields | Field | Type | Description | | ------------- | ------- | --------------------------------------------- | | `id` | string | Trade ID | | `marketSlug` | string | Market slug | | `price` | Amount | Trade price | | `qtyDecimal` | string | Trade quantity in contracts | | `qty` | string | Deprecated rounded quantity; use `qtyDecimal` | | `isAggressor` | boolean | True if taker | | `realizedPnl` | Amount | Realized P\&L | For real-time position updates, use the [WebSocket](/api-reference/sdks/typescript/websocket) with `SUBSCRIPTION_TYPE_POSITION` instead of polling. # Quickstart Source: https://docs.polymarket.us/api-reference/sdks/typescript/quickstart Get started with the TypeScript SDK ## Installation ```bash theme={null} npm install polymarket-us ``` Requires Node.js 18+. For WebSocket on Node \< 22, also install `ws`. [GitHub](https://github.com/Polymarket/polymarket-us-typescript) · [npm](https://www.npmjs.com/package/polymarket-us) *** ## Configuration ```typescript theme={null} import { PolymarketUS } from 'polymarket-us'; const client = new PolymarketUS({ keyId: process.env.POLYMARKET_KEY_ID, secretKey: process.env.POLYMARKET_SECRET_KEY, timeout: 30000, // optional, default 30000ms }); ``` Generate API keys at [polymarket.us/developer](https://polymarket.us/developer). *** ## Public Endpoints No authentication required for market data: ```typescript theme={null} import { PolymarketUS } from 'polymarket-us'; const client = new PolymarketUS(); // Events const events = await client.events.list({ limit: 10, active: true }); const event = await client.events.retrieveBySlug('super-bowl-2025'); // Markets const markets = await client.markets.list({ limit: 10 }); const market = await client.markets.retrieveBySlug('btc-100k'); const book = await client.markets.book('btc-100k'); const bbo = await client.markets.bbo('btc-100k'); // Search const results = await client.search.query({ query: 'bitcoin' }); // Series and Sports const series = await client.series.list(); const sports = await client.sports.list(); ``` *** ## Authenticated Endpoints Trading requires API credentials: ```typescript theme={null} import { PolymarketUS } from 'polymarket-us'; const client = new PolymarketUS({ keyId: process.env.POLYMARKET_KEY_ID, secretKey: process.env.POLYMARKET_SECRET_KEY, }); // Account const balances = await client.account.balances(); // Portfolio const positions = await client.portfolio.positions(); const activities = await client.portfolio.activities(); // Orders const openOrders = await client.orders.list(); const order = await client.orders.create({ marketSlug: 'your-market-slug', intent: 'ORDER_INTENT_BUY_LONG', type: 'ORDER_TYPE_LIMIT', price: { value: '0.555', currency: 'USD' }, quantity: 0.5, tif: 'TIME_IN_FORCE_GOOD_TILL_CANCEL', }); ``` *** ## Error Handling ```typescript theme={null} import { PolymarketUS, AuthenticationError, BadRequestError, NotFoundError, RateLimitError, } from 'polymarket-us'; try { const order = await client.orders.create({ marketSlug: '...' }); } catch (error) { if (error instanceof AuthenticationError) { console.error('Invalid credentials'); } else if (error instanceof BadRequestError) { console.error('Invalid parameters:', error.message); } else if (error instanceof RateLimitError) { console.error('Rate limited'); } else if (error instanceof NotFoundError) { console.error('Not found'); } } ``` ### Error Types | Exception | Description | | --------------------- | ------------------------------ | | `AuthenticationError` | Invalid or missing credentials | | `BadRequestError` | Invalid request parameters | | `NotFoundError` | Resource not found | | `RateLimitError` | Rate limit exceeded | | `APITimeoutError` | Request timed out | | `APIConnectionError` | Network connection error | # Search Source: https://docs.polymarket.us/api-reference/sdks/typescript/search Search for events and markets The Search resource provides full-text search across events and markets. ## Methods | Method | Endpoint | Description | | ---------------- | ---------------- | ------------------------- | | `query(params?)` | `GET /v1/search` | Search events and markets | *** ## query Search for events and markets by text query. ```typescript theme={null} const results = await client.search.query({ query: 'bitcoin', limit: 10, }); for (const event of results.events) { console.log(event.title); for (const market of event.markets ?? []) { console.log(` - ${market.question}`); } } ``` ### Parameters | Parameter | Type | Description | | ------------ | --------- | -------------------------- | | `query` | string | Search query text | | `limit` | number | Maximum results to return | | `page` | number | Page number for pagination | | `seriesIds` | number\[] | Filter by series IDs | | `marketType` | string\[] | Filter by market types | | `status` | string | Filter by status | ### Response Returns events with their associated markets that match the search query: ```typescript theme={null} { events: [ { id: 123, title: 'Bitcoin Price Markets', slug: 'bitcoin-price', markets: [ { id: 456, question: 'Will Bitcoin reach $100k?', slug: 'btc-100k-2025', }, ], }, ], } ``` # Series Source: https://docs.polymarket.us/api-reference/sdks/typescript/series Browse event series The Series resource provides access to series information. Series group related events together (e.g., NFL games, election cycles, daily crypto markets). ## Methods | Method | Endpoint | Description | | --------------- | ------------------------ | ---------------- | | `list(params?)` | `GET /v1/series` | List all series | | `retrieve(id)` | `GET /v1/series/id/{id}` | Get series by ID | *** ## list Get a list of all series with optional filtering. ```typescript theme={null} const series = await client.series.list({ active: true, limit: 20, }); for (const s of series.series) { console.log(`${s.title} (${s.recurrence})`); } ``` ### Parameters | Parameter | Type | Description | | ------------ | --------- | ------------------------------------------ | | `limit` | number | Maximum results | | `offset` | number | Pagination offset | | `slug` | string\[] | Filter by slugs | | `active` | boolean | Filter by active status | | `closed` | boolean | Filter by closed status | | `archived` | boolean | Filter by archived status | | `recurrence` | string | Filter by recurrence (daily, weekly, etc.) | ### Response Fields | Field | Type | Description | | ------------- | ------- | ------------------------ | | `id` | number | Unique series identifier | | `slug` | string | URL-friendly identifier | | `title` | string | Series title | | `subtitle` | string | Series subtitle | | `description` | string | Series description | | `seriesType` | string | Type of series | | `recurrence` | string | Recurrence pattern | | `active` | boolean | Whether series is active | | `volume` | string | Total trading volume | | `liquidity` | string | Current liquidity | *** ## retrieve Get a single series by ID. ```typescript theme={null} const series = await client.series.retrieve(123); console.log(`Title: ${series.title}`); console.log(`Description: ${series.description}`); console.log(`Recurrence: ${series.recurrence}`); ``` # Sports Source: https://docs.polymarket.us/api-reference/sdks/typescript/sports Sports configuration and team data The Sports resource provides access to sports configuration and team information from data providers. ## Methods | Method | Endpoint | Description | | ---------------- | ------------------------------- | --------------------- | | `list()` | `GET /v1/sports` | List all sports | | `teams(params?)` | `GET /v1/sports/teams/provider` | Get teams by provider | *** ## list Get all available sports and their configuration. ```typescript theme={null} const sports = await client.sports.list(); for (const sport of sports.sports) { console.log(`${sport.sport} - Operational: ${sport.isOperational}`); } ``` ### Response Fields | Field | Type | Description | | --------------------- | ------- | --------------------------------------- | | `sport` | string | Sport name | | `image` | string | Sport image URL | | `isOperational` | boolean | Whether sport is operational | | `automaticResolution` | boolean | Whether automatic resolution is enabled | | `ordering` | string | Display ordering | *** ## teams Get team information from a specific data provider. ```typescript theme={null} const teams = await client.sports.teams({ provider: 'PROVIDER_SPORTRADAR', league: 'NFL', }); for (const team of teams.teams) { console.log(`${team.name} (${team.abbreviation})`); console.log(` Conference: ${team.conference}`); console.log(` Record: ${team.record}`); } ``` ### Parameters | Parameter | Type | Description | | ---------- | --------- | --------------------------------- | | `provider` | string | Data provider (see below) | | `league` | string | League name (NFL, NBA, MLB, etc.) | | `teamIds` | number\[] | Filter by specific team IDs | ### Data Providers | Provider | Description | | ----------------------- | ------------- | | `PROVIDER_SPORTRADAR` | Sportradar | | `PROVIDER_SPORTSDATAIO` | SportsData.io | ### Team Fields | Field | Type | Description | | -------------- | ------ | ------------------ | | `id` | number | Team identifier | | `name` | string | Team name | | `abbreviation` | string | Team abbreviation | | `league` | string | League name | | `conference` | string | Conference name | | `record` | string | Team record | | `ranking` | number | Team ranking | | `logo` | string | Logo URL | | `colorPrimary` | string | Primary team color | # WebSocket Source: https://docs.polymarket.us/api-reference/sdks/typescript/websocket Real-time streaming data The WebSocket resource provides real-time streaming data for market information and private user data. ## Methods | Method | Endpoint | Description | | ----------- | --------------------------------------- | ---------------------------------- | | `private()` | `wss://api.polymarket.us/v1/ws/private` | Orders, positions, balance updates | | `markets()` | `wss://api.polymarket.us/v1/ws/markets` | Market data and trades | *** ## private Connect to the private WebSocket for real-time order, position, and balance updates. ```typescript theme={null} import { PolymarketUS } from 'polymarket-us'; const client = new PolymarketUS({ keyId: process.env.POLYMARKET_KEY_ID, secretKey: process.env.POLYMARKET_SECRET_KEY, }); const ws = client.ws.private(); // Register event handlers ws.on('orderSnapshot', (data) => console.log('Orders:', data)); ws.on('orderUpdate', (data) => console.log('Order update:', data)); ws.on('positionSnapshot', (data) => console.log('Positions:', data)); ws.on('positionUpdate', (data) => console.log('Position update:', data)); ws.on('accountBalanceSnapshot', (data) => console.log('Balance:', data)); ws.on('error', (e) => console.error('Error:', e)); await ws.connect(); // Subscribe to updates ws.subscribeOrders('my-orders'); ws.subscribePositions('my-positions'); ws.subscribeAccountBalance('my-balance'); ``` ### Private Subscription Types | Type | Description | | ----------------------------------- | --------------------------- | | `SUBSCRIPTION_TYPE_ORDER` | Order updates and snapshots | | `SUBSCRIPTION_TYPE_POSITION` | Position changes | | `SUBSCRIPTION_TYPE_ACCOUNT_BALANCE` | Balance updates | ### Private Events | Event | Description | | ------------------------ | --------------------------------- | | `orderSnapshot` | Initial snapshot of all orders | | `orderUpdate` | Order state change | | `positionSnapshot` | Initial snapshot of all positions | | `positionUpdate` | Position change | | `accountBalanceSnapshot` | Initial balance snapshot | | `accountBalanceUpdate` | Balance change | *** ## markets Connect to the markets WebSocket for real-time market data and trades. ```typescript theme={null} const ws = client.ws.markets(); // Register event handlers ws.on('marketData', (data) => console.log('Book:', data)); ws.on('marketDataLite', (data) => console.log('BBO:', data)); ws.on('trade', (data) => console.log('Trade:', data)); await ws.connect(); // Subscribe to market data ws.subscribeMarketData('book', ['btc-100k-2025']); ws.subscribeMarketDataLite('prices', ['btc-100k-2025']); ws.subscribeTrades('trades', ['btc-100k-2025']); ``` ### Market Subscription Types | Type | Description | | ------------------------------------ | ----------------------------- | | `SUBSCRIPTION_TYPE_MARKET_DATA` | Full order book and stats | | `SUBSCRIPTION_TYPE_MARKET_DATA_LITE` | Lightweight price data (BBO) | | `SUBSCRIPTION_TYPE_TRADE` | Real-time trade notifications | ### Market Events | Event | Description | | ---------------- | ---------------------- | | `marketData` | Full order book update | | `marketDataLite` | BBO and price update | | `trade` | Trade execution | *** ## Best Practices 1. **Use unique request IDs** - Track subscriptions with unique identifiers 2. **Handle reconnection** - Implement automatic reconnection with exponential backoff 3. **Process messages in order** - Messages are delivered in sequence 4. **Limit subscriptions** - Only subscribe to markets you need # Search API Overview Source: https://docs.polymarket.us/api-reference/search/overview Search for events and markets # Search API The Search API allows you to search for events and markets by query string. ## Endpoints | Method | Endpoint | Description | | ------ | ------------ | ----------------------------- | | `GET` | `/v1/search` | Search for events and markets | ## Search Find events and markets matching a query: ```bash theme={null} GET /v1/search?query=super+bowl&limit=10 ``` ### Query Parameters | Parameter | Type | Description | | --------------- | ------- | ----------------------------------- | | `query` | string | Search query | | `limit` | integer | Maximum number of results to return | | `page` | integer | Page number for pagination | | `seriesIds` | array | Filter by series IDs | | `marketType` | array | Filter by market types | | `status` | string | Filter by status | | `startTimeMin` | string | Minimum start time filter | | `startTimeMax` | string | Maximum start time filter | | `closedTimeMin` | string | Minimum closed time filter | | `closedTimeMax` | string | Maximum closed time filter | ### Response Returns matching events with their associated markets: ```json theme={null} { "events": [ { "id": "123", "slug": "super-bowl-winner", "title": "Super Bowl Winner", "category": "sports", "active": true, "markets": [...] } ] } ``` # Search Source: https://docs.polymarket.us/api-reference/search/search /api-reference/oapi-schemas/search-schema.json get /v1/search Search for a given query # Get Series Source: https://docs.polymarket.us/api-reference/series/get-series /api-reference/oapi-schemas/series-schema.json get /v1/series Retrieve all series # Get Series By ID Source: https://docs.polymarket.us/api-reference/series/get-series-by-id /api-reference/oapi-schemas/series-schema.json get /v1/series/id/{id} Retrieve a specific series by its ID # Series API Overview Source: https://docs.polymarket.us/api-reference/series/overview Series data endpoints # Series API The Series API provides access to series information. Series group related events together (e.g., NFL games, election cycles). ## Endpoints | Method | Endpoint | Description | | ------ | -------------------- | ----------------------------- | | `GET` | `/v1/series` | Get all series with filtering | | `GET` | `/v1/series/id/{id}` | Get series by ID | ## Key Series Fields | Field | Description | | ------------- | ---------------------------------------- | | `id` | Unique series identifier | | `slug` | URL-friendly identifier | | `title` | Series title | | `subtitle` | Series subtitle | | `description` | Series description | | `seriesType` | Type of series | | `recurrence` | Recurrence pattern (e.g., daily, weekly) | | `active` | Whether series is active | | `closed` | Whether series is closed | | `archived` | Whether series is archived | ### Volume & Liquidity | Field | Description | | ------------ | -------------------- | | `liquidity` | Series liquidity | | `volume` | Total trading volume | | `volume24hr` | 24-hour volume | ## Filtering Series Query series with various filters: ```bash theme={null} GET /v1/series?active=true&recurrence=daily&limit=20 ``` ### Common Filters | Parameter | Type | Description | | ------------ | ------- | ---------------------------- | | `slug` | array | Filter by slugs | | `active` | boolean | Filter by active status | | `closed` | boolean | Filter by closed status | | `archived` | boolean | Filter by archived status | | `recurrence` | string | Filter by recurrence pattern | ## Pagination | Parameter | Type | Description | | ---------------- | ------- | -------------------------- | | `limit` | integer | Page size | | `offset` | integer | Page offset | | `orderBy` | array | Fields to order by | | `orderDirection` | string | Order direction (asc/desc) | # Sports API Overview (Legacy) Source: https://docs.polymarket.us/api-reference/sports-legacy/overview Legacy v1 sports data endpoints # Sports API (Legacy) The v1 Sports API provides access to sports configuration, player and team information, and sports events. The players lookup remains supported and is not deprecated despite this section's legacy label; it has no standalone v2 replacement. ## Endpoints | Method | Endpoint | Description | | ------ | ------------------------------ | -------------------------------------------------------------------- | | `GET` | `/v1/sports` | Get all sports | | `GET` | `/v1/sports/{seriesId}/events` | Get events for a series | | `GET` | `/v1/sports/teams` | Get sports teams | | `GET` | `/v1/sports/teams/provider` | Get teams by provider | | `GET` | `/v1/sports/players` | List players or look up players by internal ID, team, or provider ID | ## Get Sports Players Retrieve player reference data without loading an event. Use `filters.id` for a specific player or `filters.teamId` for players on a team. See [Players Endpoint](/data-guide/sports-data#players-endpoint) for all filters, provider lookup behavior, pagination, response fields, and curl examples. ## Get Sports Retrieve available sports and their configuration: ```bash theme={null} GET /v1/sports ``` ### Sport Fields | Field | Type | Description | | --------------------- | ------- | --------------------------------------- | | `sport` | string | Sport name | | `image` | string | Sport image URL | | `resolution` | string | Resolution configuration | | `ordering` | string | Display ordering | | `tags` | string | Associated tags | | `series` | string | Associated series | | `isOperational` | boolean | Whether sport is operational | | `automaticResolution` | boolean | Whether automatic resolution is enabled | ## Get Sports Events Retrieve events for a specific series: ```bash theme={null} GET /v1/sports/{seriesId}/events ``` ### Path Parameters | Parameter | Type | Description | | ---------- | ------- | ----------- | | `seriesId` | integer | Series ID | ## Get Sports Teams Retrieve all sports teams: ```bash theme={null} GET /v1/sports/teams ``` ### Parameters | Parameter | Type | Description | | --------- | ----- | ------------------ | | `teamIds` | array | Filter by team IDs | ## Get Teams by Provider Retrieve team information from a specific data provider: ```bash theme={null} GET /v1/sports/teams/provider?provider=PROVIDER_SPORTRADAR&league=NFL ``` ### Parameters | Parameter | Type | Description | | ---------- | ------ | ---------------------------------------------------------------- | | `teamIds` | array | Filter by team IDs | | `provider` | string | Data provider (`PROVIDER_SPORTSDATAIO` or `PROVIDER_SPORTRADAR`) | | `league` | string | League name (e.g., NFL, NBA, MLB) | ### Team Fields | Field | Type | Description | | --------------------- | ------- | -------------------- | | `id` | integer | Team identifier | | `name` | string | Team name | | `abbreviation` | string | Team abbreviation | | `displayAbbreviation` | string | Display abbreviation | | `league` | string | League name | | `record` | string | Team record | | `logo` | string | Logo URL | | `alias` | string | Team alias | | `safeName` | string | Safe name for URLs | | `homeIcon` | string | Home icon URL | | `awayIcon` | string | Away icon URL | | `colorPrimary` | string | Primary team color | | `ranking` | integer | Team ranking | | `conference` | string | Conference name | | `providerIds` | array | Provider ID mappings | ## Data Providers | Provider | Description | | ----------------------- | ------------- | | `PROVIDER_SPORTSDATAIO` | SportsData.io | | `PROVIDER_SPORTRADAR` | Sportradar | # Get All Leagues Source: https://docs.polymarket.us/api-reference/sports/get-all-leagues /api-reference/oapi-schemas/sports-schema.json get /v2/leagues Retrieve leagues with pagination (max 50 per page) # Get All Sports Source: https://docs.polymarket.us/api-reference/sports/get-all-sports /api-reference/oapi-schemas/sports-schema.json get /v2/sports Retrieve all sports # Get Events By League Slug Source: https://docs.polymarket.us/api-reference/sports/get-events-by-league-slug /api-reference/oapi-schemas/sports-schema.json get /v2/leagues/{slug}/events Retrieve events for a league by its slug (e.g., 'nfl', 'nba'). Resolves the league to its active series and returns events. # Get Events By Sport Slug Source: https://docs.polymarket.us/api-reference/sports/get-events-by-sport-slug /api-reference/oapi-schemas/sports-schema.json get /v2/sports/{slug}/events Retrieve events for a sport by its slug (e.g., 'football', 'basketball'). Fetches all leagues for the sport, collects their active series, and returns combined events. # Get League By Slug Source: https://docs.polymarket.us/api-reference/sports/get-league-by-slug /api-reference/oapi-schemas/sports-schema.json get /v2/leagues/{slug} Retrieve a league by its slug # Get Sport By Slug Source: https://docs.polymarket.us/api-reference/sports/get-sport-by-slug /api-reference/oapi-schemas/sports-schema.json get /v2/sports/{slug} Retrieve a sport by its slug, with all leagues attached # Get Sports Source: https://docs.polymarket.us/api-reference/sports/get-sports /api-reference/oapi-schemas/sports-legacy-schema.json get /v1/sports Fetch sports data # Get Sports Events Source: https://docs.polymarket.us/api-reference/sports/get-sports-events /api-reference/oapi-schemas/sports-legacy-schema.json get /v1/sports/{seriesId}/events Retrieve events sections for a sport series (optional trending, optional live, and sport section). Same structure as home page. # Get Sports Players Source: https://docs.polymarket.us/api-reference/sports/get-sports-players /api-reference/oapi-schemas/sports-legacy-schema.json get /v1/sports/players Fetch sports players, filterable by abbreviation, name, id, team, or provider reference # Get Sports Teams Source: https://docs.polymarket.us/api-reference/sports/get-sports-teams /api-reference/oapi-schemas/sports-legacy-schema.json get /v1/sports/teams Fetch sports teams data for enriching sports events # Get Sports Teams For Provider Source: https://docs.polymarket.us/api-reference/sports/get-sports-teams-for-provider /api-reference/oapi-schemas/sports-legacy-schema.json get /v1/sports/teams/provider Fetch sports teams data for a specific provider # Sports API Overview Source: https://docs.polymarket.us/api-reference/sports/overview Sports event data by league and sport, plus direct player lookup # Sports API The Sports API provides access to sporting events organized by league or sport, plus player reference data. ## Endpoints | Method | Endpoint | Description | | ------ | --------------------------- | -------------------------------------------------------------------- | | `GET` | `/v2/leagues/{slug}/events` | Get events by league slug | | `GET` | `/v2/sports/{slug}/events` | Get events by sport slug | | `GET` | `/v1/sports/players` | List players or look up players by internal ID, team, or provider ID | ## Players Use `/v1/sports/players?filters.id=781` to retrieve a specific player directly, or `filters.teamId` to list players on a team. This public endpoint is supported and is not deprecated; there is no standalone v2 players lookup endpoint. See [Players Endpoint](/data-guide/sports-data#players-endpoint) for filters, pagination, response fields, and examples for player props and combos. ## Get League Events Retrieve events for a specific league: ```bash theme={null} GET /v2/leagues/nfl/events ``` ### Path Parameters | Parameter | Type | Description | | --------- | ------ | --------------------------------------- | | `slug` | string | League slug (e.g., `nfl`, `nba`, `mlb`) | ### Query Parameters | Parameter | Type | Description | | ---------------- | ------- | ------------------------------------------ | | `limit` | integer | Pagination limit | | `offset` | integer | Pagination offset | | `excludeEventId` | array | Event IDs to exclude | | `type` | string | Type: `sport` (default) or `futures` | | `section` | string | Section: `general` (default) or `trending` | ## Get Sport Events Retrieve events for all leagues under a sport: ```bash theme={null} GET /v2/sports/football/events ``` ### Path Parameters | Parameter | Type | Description | | --------- | ------ | ------------------------------------------- | | `slug` | string | Sport slug (e.g., `football`, `basketball`) | ### Query Parameters | Parameter | Type | Description | | ---------------- | ------- | ------------------------------------------ | | `limit` | integer | Pagination limit | | `offset` | integer | Pagination offset | | `excludeEventId` | array | Event IDs to exclude | | `type` | string | Type: `sport` (default) or `futures` | | `section` | string | Section: `general` (default) or `trending` | ## Teams For fetching team information, use the [Sports (Legacy) API](/api-reference/sports-legacy/overview). # Get Events For A Tag Source: https://docs.polymarket.us/api-reference/tags/get-events-for-a-tag /api-reference/oapi-schemas/tags-schema.json get /v2/tags/{slug}/events Get events filtered by tag slug, sorted by volume descending by default # Get Tag By ID Source: https://docs.polymarket.us/api-reference/tags/get-tag-by-id /api-reference/oapi-schemas/tags-schema.json get /v2/tags/{id} Get a tag by its ID # Get Tag By Slug Source: https://docs.polymarket.us/api-reference/tags/get-tag-by-slug /api-reference/oapi-schemas/tags-schema.json get /v2/tags/slug/{slug} Get a tag by its slug # Get Tags Source: https://docs.polymarket.us/api-reference/tags/get-tags /api-reference/oapi-schemas/tags-schema.json get /v2/tags Get ranked tags with optional filtering # Tags API Overview Source: https://docs.polymarket.us/api-reference/tags/overview Query tags and featured tags # Tags API The Tags API provides access to tags used to categorize and organize events and markets. Tags enable filtering and discovery of related content. ## Endpoints | Method | Endpoint | Description | | ------ | ---------------------- | ----------------- | | `GET` | `/v2/tags` | Get all tags | | `GET` | `/v2/tags/{id}` | Get tag by ID | | `GET` | `/v2/tags/slug/{slug}` | Get tag by slug | | `GET` | `/v2/tags/featured` | Get featured tags | # Cancel order Source: https://docs.polymarket.us/api-reference/trading/cancel-order /institutional/oapi-schemas/trading-schema.json post /v1/trading/orders/cancel Requests cancellation of a working order # Cancel order list Source: https://docs.polymarket.us/api-reference/trading/cancel-order-list /institutional/oapi-schemas/trading-schema.json post /v1/trading/orders/cancel/list Requests cancellation of multiple working orders. Maximum batch size is 20 orders; requests exceeding this limit will be rejected. # Cancel replace order Source: https://docs.polymarket.us/api-reference/trading/cancel-replace-order /institutional/oapi-schemas/trading-schema.json post /v1/trading/orders/replace Requests modification of a working order # Cancel replace order list Source: https://docs.polymarket.us/api-reference/trading/cancel-replace-order-list /institutional/oapi-schemas/trading-schema.json post /v1/trading/orders/replace/list Requests modification of multiple working orders. Maximum batch size is 20 orders; requests exceeding this limit will be rejected. # Get open orders Source: https://docs.polymarket.us/api-reference/trading/get-open-orders /institutional/oapi-schemas/trading-schema.json get /v1/trading/orders/open Returns a snapshot of working orders # Insert order Source: https://docs.polymarket.us/api-reference/trading/insert-order /institutional/oapi-schemas/trading-schema.json post /v1/trading/orders Inserts an order into the exchange # Insert order cross Source: https://docs.polymarket.us/api-reference/trading/insert-order-cross /institutional/oapi-schemas/trading-schema.json post /v1/trading/orders/cross Creates a new order cross # Insert order list Source: https://docs.polymarket.us/api-reference/trading/insert-order-list /institutional/oapi-schemas/trading-schema.json post /v1/trading/orders/list Inserts multiple orders into the exchange. Maximum batch size is 20 orders; requests exceeding this limit will be rejected. # Preview order Source: https://docs.polymarket.us/api-reference/trading/preview-order /institutional/oapi-schemas/trading-schema.json post /v1/trading/orders/preview Creates an order preview without inserting it # Markets WebSocket Source: https://docs.polymarket.us/api-reference/websocket/markets Real-time market data, order book, and trades # Markets WebSocket The Markets WebSocket endpoint provides real-time market data including order book updates, price changes, and trade notifications. **Authentication Required** This WebSocket endpoint requires API key authentication in the connection handshake. See the [Authentication guide](/api/authentication) for details. ## Endpoint ``` wss://api.polymarket.us/v1/ws/markets ``` ## Subscription Types | Value | Description | | ------------------------------------ | -------------------------------- | | `SUBSCRIPTION_TYPE_MARKET_DATA` | Full order book and market stats | | `SUBSCRIPTION_TYPE_MARKET_DATA_LITE` | Lightweight price data only | | `SUBSCRIPTION_TYPE_TRADE` | Real-time trade notifications | ## Market Data Subscription ### Subscribe to Full Market Data ```json theme={null} { "subscribe": { "requestId": "md-sub-1", "subscriptionType": "SUBSCRIPTION_TYPE_MARKET_DATA", "marketSlugs": ["market-slug-1", "market-slug-2"] } } ``` ### Market Data Response Full order book with market statistics: ```json theme={null} { "requestId": "md-sub-1", "subscriptionType": "SUBSCRIPTION_TYPE_MARKET_DATA", "marketData": { "marketSlug": "market-slug-1", "bids": [ {"px": {"value": "0.555", "currency": "USD"}, "qty": "0.50"}, {"px": {"value": "0.550", "currency": "USD"}, "qty": "2.50"} ], "offers": [ {"px": {"value": "0.560", "currency": "USD"}, "qty": "0.80"}, {"px": {"value": "0.565", "currency": "USD"}, "qty": "1.50"} ], "state": "MARKET_STATE_OPEN", "stats": { "lastTradePx": {"value": "0.55", "currency": "USD"}, "sharesTraded": "150000", "openInterest": "500000", "highPx": {"value": "0.58", "currency": "USD"}, "lowPx": {"value": "0.52", "currency": "USD"} }, "transactTime": "2024-01-15T10:30:00Z" } } ``` ## Market Data Lite Subscription ### Subscribe to Lightweight Data For reduced bandwidth, use the lite subscription: ```json theme={null} { "subscribe": { "requestId": "mdl-sub-1", "subscriptionType": "SUBSCRIPTION_TYPE_MARKET_DATA_LITE", "marketSlugs": ["market-slug-1"] } } ``` ### Market Data Lite Response ```json theme={null} { "requestId": "mdl-sub-1", "subscriptionType": "SUBSCRIPTION_TYPE_MARKET_DATA_LITE", "marketDataLite": { "marketSlug": "market-slug-1", "currentPx": {"value": "0.55", "currency": "USD"}, "lastTradePx": {"value": "0.55", "currency": "USD"}, "bestBid": {"value": "0.54", "currency": "USD"}, "bestAsk": {"value": "0.56", "currency": "USD"}, "bidDepth": 5, "askDepth": 4, "sharesTraded": "150000", "openInterest": "500000" } } ``` ## Trade Subscription ### Subscribe to Trades ```json theme={null} { "subscribe": { "requestId": "trade-sub-1", "subscriptionType": "SUBSCRIPTION_TYPE_TRADE", "marketSlugs": ["market-slug-1"] } } ``` ### Trade Response ```json theme={null} { "requestId": "trade-sub-1", "subscriptionType": "SUBSCRIPTION_TYPE_TRADE", "trade": { "marketSlug": "market-slug-1", "price": {"value": "0.555", "currency": "USD"}, "quantity": {"value": "0.50", "currency": "USD"}, "tradeTime": "2024-01-15T10:30:00Z", "maker": { "side": "ORDER_SIDE_BUY", "intent": "ORDER_INTENT_BUY_LONG" }, "taker": { "side": "ORDER_SIDE_SELL", "intent": "ORDER_INTENT_SELL_LONG" } } } ``` ## Market States | Value | Description | | -------------------------------------- | ----------------------------- | | `MARKET_STATE_OPEN` | Market open for trading | | `MARKET_STATE_PREOPEN` | Market in pre-open phase | | `MARKET_STATE_SUSPENDED` | Trading temporarily suspended | | `MARKET_STATE_HALTED` | Trading halted | | `MARKET_STATE_EXPIRED` | Market has expired | | `MARKET_STATE_TERMINATED` | Market terminated | | `MARKET_STATE_MATCH_AND_CLOSE_AUCTION` | Market in closing auction | ## Order Side | Value | Description | | ----------------- | ----------- | | `ORDER_SIDE_BUY` | Buy order | | `ORDER_SIDE_SELL` | Sell order | ## Order Intent | Value | Description | | ------------------------- | ------------------ | | `ORDER_INTENT_BUY_LONG` | Buy YES contracts | | `ORDER_INTENT_SELL_LONG` | Sell YES contracts | | `ORDER_INTENT_BUY_SHORT` | Buy NO contracts | | `ORDER_INTENT_SELL_SHORT` | Sell NO contracts | ## Debouncing For high-frequency markets, enable response debouncing to reduce message volume: ```json theme={null} { "subscribe": { "requestId": "md-sub-1", "subscriptionType": "SUBSCRIPTION_TYPE_MARKET_DATA", "marketSlugs": ["market-slug-1"], "responsesDebounced": true } } ``` When debouncing is enabled, updates are batched and sent at regular intervals rather than on every change. **Subscription Limits** You can subscribe to a maximum of 100 markets per subscription. Use multiple subscriptions if you need more. ## Order Book Depth The full market data subscription includes the top levels of the order book. Each level shows: | Field | Description | | ----- | -------------------------------------------------------------------------------- | | `px` | Price level | | `qty` | Total quantity at this price. May contain decimals for partial-contract markets. | Order book levels are sorted best-to-worst (highest bid first, lowest ask first). # WebSocket API Overview Source: https://docs.polymarket.us/api-reference/websocket/overview Real-time streaming data via WebSocket # WebSocket API The WebSocket API provides real-time streaming data for market information and private user data. ## Endpoints | Endpoint | Description | Authentication | | ---------------- | ------------------------------------------ | -------------- | | `/v1/ws/private` | Orders, positions, account balance updates | API Key | | `/v1/ws/markets` | Market data, order book, trades | API Key | ## Connection Connect to the WebSocket endpoints with your API key credentials: ``` wss://api.polymarket.us/v1/ws/private wss://api.polymarket.us/v1/ws/markets ``` ## Authentication WebSocket connections use the same API key authentication as the REST API. Include these headers in the WebSocket handshake: ``` X-PM-Access-Key: X-PM-Timestamp: X-PM-Signature: ``` The signature is constructed from: `timestamp + "GET" + path` where path is `/v1/ws/private` or `/v1/ws/markets`. See [Authentication](/api/authentication) for details on request signing. ## Message Format All WebSocket messages are JSON formatted with snake\_case field names. ### Request Format ```json theme={null} { "subscribe": { "request_id": "unique-request-id", "subscription_type": 1, "market_slugs": ["market-slug-1", "market-slug-2"] } } ``` ### Subscription Types **Private WebSocket (`/v1/ws/private`):** | Value | Type | Description | | ----- | ---------------- | ---------------- | | 1 | ORDER | Order updates | | 3 | POSITION | Position changes | | 4 | ACCOUNT\_BALANCE | Balance updates | **Markets WebSocket (`/v1/ws/markets`):** | Value | Type | Description | | ----- | ------------------ | -------------------------------- | | 1 | MARKET\_DATA | Full order book and market stats | | 2 | MARKET\_DATA\_LITE | Lightweight price data | | 3 | TRADE | Real-time trade notifications | ### Response Format ```json theme={null} { "request_id": "unique-request-id", "subscription_type": 1, "order_subscription_snapshot": { "orders": [...], "eof": true } } ``` ## Heartbeats The server sends periodic heartbeat messages to keep the connection alive: ```json theme={null} { "heartbeat": {} } ``` Clients should respond to heartbeats or implement their own keep-alive mechanism. ## Error Handling If a subscription request fails, the response will include an error field: ```json theme={null} { "request_id": "unique-request-id", "error": "Error description" } ``` ## Unsubscribing To unsubscribe from a stream: ```json theme={null} { "unsubscribe": { "request_id": "original-request-id" } } ``` ## Best Practices 1. **Use unique request IDs** - Track subscriptions with unique identifiers 2. **Handle reconnection** - Implement automatic reconnection with exponential backoff 3. **Process messages in order** - Messages are delivered in sequence 4. **Monitor heartbeats** - Reconnect if heartbeats stop 5. **Limit subscriptions** - Only subscribe to markets you need # Private WebSocket Source: https://docs.polymarket.us/api-reference/websocket/private Real-time orders, positions, balances, and RFQ updates # Private WebSocket The Private WebSocket endpoint provides real-time updates for user-specific data including orders, positions, account balances, and RFQs. **Authentication Required** This WebSocket endpoint requires API key authentication in the connection handshake. See the [Authentication guide](/api/authentication) for details. ## Endpoint ``` wss://api.polymarket.us/v1/ws/private ``` ## Subscription Types | Value | Description | | ----------------------------------- | ------------------------------------------------------- | | `SUBSCRIPTION_TYPE_ORDER` | Order updates (new, filled, canceled) | | `SUBSCRIPTION_TYPE_ORDER_SNAPSHOT` | Initial snapshot of open orders | | `SUBSCRIPTION_TYPE_POSITION` | Position changes | | `SUBSCRIPTION_TYPE_ACCOUNT_BALANCE` | Account balance changes | | `SUBSCRIPTION_TYPE_RFQ` | Combo RFQ and quote lifecycle events (allowlisted beta) | ## Order Subscriptions ### Subscribe to Orders ```json theme={null} { "subscribe": { "requestId": "order-sub-1", "subscriptionType": "SUBSCRIPTION_TYPE_ORDER", "marketSlugs": ["market-slug-1"] } } ``` Leave `marketSlugs` empty to subscribe to all markets. ### Order Snapshot Response Initial snapshot of open orders: ```json theme={null} { "requestId": "order-sub-1", "subscriptionType": "SUBSCRIPTION_TYPE_ORDER", "orderSubscriptionSnapshot": { "orders": [ { "id": "order-123", "marketSlug": "market-slug-1", "side": "ORDER_SIDE_BUY", "type": "ORDER_TYPE_LIMIT", "price": {"value": "0.555", "currency": "USD"}, "quantity": 0.5, "leavesQuantity": 0.5, "state": "ORDER_STATE_PENDING_NEW", "intent": "ORDER_INTENT_BUY_LONG", "tif": "TIME_IN_FORCE_GOOD_TILL_CANCEL" } ], "eof": true } } ``` ### Order Update Response Real-time order execution updates: ```json theme={null} { "requestId": "order-sub-1", "subscriptionType": "SUBSCRIPTION_TYPE_ORDER", "orderSubscriptionUpdate": { "execution": { "id": "exec-456", "order": {...}, "lastShares": "0.25", "lastPx": {"value": "0.555", "currency": "USD"}, "type": "EXECUTION_TYPE_PARTIAL_FILL", "tradeId": "trade-789" } } } ``` Order quantities can contain decimals for partial-contract markets. `lastShares` is a string and may also contain a decimal quantity. ## Position Subscriptions ### Subscribe to Positions ```json theme={null} { "subscribe": { "requestId": "pos-sub-1", "subscriptionType": "SUBSCRIPTION_TYPE_POSITION", "marketSlugs": ["market-slug-1"] } } ``` ### Position Update Response ```json theme={null} { "requestId": "pos-sub-1", "subscriptionType": "SUBSCRIPTION_TYPE_POSITION", "positionSubscription": { "beforePosition": { "netPosition": "1", "netPositionDecimal": "1.0000", "cost": {"value": "55.00", "currency": "USD"} }, "afterPosition": { "netPosition": "2", "netPositionDecimal": "1.5000", "cost": {"value": "82.50", "currency": "USD"} }, "updateTime": "2024-01-15T10:30:00Z", "entryType": "LEDGER_ENTRY_TYPE_ORDER_EXECUTION", "tradeId": "trade-789" } } ``` Position messages can include `netPositionDecimal`, `qtyBoughtDecimal`, `qtySoldDecimal`, `bodPositionDecimal`, and `qtyAvailableDecimal`, matching `GET /v1/portfolio/positions`. Use those fields when present; the older integer fields are rounded and remain for backward compatibility. ## Account Balance Subscriptions ### Subscribe to Balances ```json theme={null} { "subscribe": { "requestId": "balance-sub-1", "subscriptionType": "SUBSCRIPTION_TYPE_ACCOUNT_BALANCE" } } ``` ### Balance Snapshot Response ```json theme={null} { "requestId": "balance-sub-1", "subscriptionType": "SUBSCRIPTION_TYPE_ACCOUNT_BALANCE", "accountBalancesSnapshot": { "balances": [ { "currentBalance": 1000.00, "currency": "USD", "buyingPower": 850.00 } ] } } ``` ### Balance Update Response ```json theme={null} { "requestId": "balance-sub-1", "subscriptionType": "SUBSCRIPTION_TYPE_ACCOUNT_BALANCE", "accountBalancesUpdate": { "balanceChange": { "beforeBalance": {...}, "afterBalance": {...}, "description": "Order execution", "updateTime": "2024-01-15T10:30:00Z", "entryType": "LEDGER_ENTRY_TYPE_ORDER_EXECUTION" } } } ``` ## RFQ Subscriptions **Beta access required.** RFQ subscriptions are available only to Retail API users enabled for the Combo and RFQ beta. The same access gate applies to the [REST RFQ API](/api-reference/rfqs/overview). Subscribe without `marketSlugs`; the stream is private to the participant associated with the authenticated API key. ```json theme={null} { "subscribe": { "requestId": "rfq-sub-1", "subscriptionType": "SUBSCRIPTION_TYPE_RFQ" } } ``` No separate success acknowledgment is sent. Events use the `rfqEvent` envelope, and each message contains exactly one event variant: ```json theme={null} { "requestId": "rfq-sub-1", "subscriptionType": "SUBSCRIPTION_TYPE_RFQ", "rfqEvent": { "rfqCreated": { "rfq": { "id": "rfq_...", "symbol": "caoc-...", "status": "RFQ_STATUS_OPEN" } } } } ``` | Event | Meaning | | ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `rfqCreated` | An RFQ became available to quote. | | `rfqClosed` | An RFQ closed or a quote was selected. | | `quoteCreated` | A visible quote was created or replaced. | | `quoteDeleted` | A visible quote was deleted or declined. | | `quoteAccepted` | The requester selected a quote and maker last look began. | | `quoteConfirmed` | The selected maker confirmed and paired execution was scheduled. | | `quoteExecuted` | Paired order submission completed and generated order IDs became available. This does not guarantee a fill; track the generated orders with `SUBSCRIPTION_TYPE_ORDER`. | The stream is live and best effort: it has no replay or durable cursor. Unsubscribing, disconnecting, or an upstream failure ends the corresponding stream. Reconnect and reconcile with `GET /v1/rfqs` and `GET /v1/rfqs/quotes`; do not treat WebSocket delivery as the source of truth. ## Execution Types | Value | Description | | ----------------------------- | ------------------------------ | | `EXECUTION_TYPE_PARTIAL_FILL` | Order partially filled | | `EXECUTION_TYPE_FILL` | Order fully filled | | `EXECUTION_TYPE_CANCELED` | Order canceled | | `EXECUTION_TYPE_REPLACE` | Order replaced/modified | | `EXECUTION_TYPE_REJECTED` | Order rejected | | `EXECUTION_TYPE_EXPIRED` | Order expired | | `EXECUTION_TYPE_DONE_FOR_DAY` | Order done for the trading day | ## Ledger Entry Types | Value | Description | | --------------------------------------- | ------------------------- | | `LEDGER_ENTRY_TYPE_ORDER_EXECUTION` | Trade execution | | `LEDGER_ENTRY_TYPE_DEPOSIT` | Account deposit | | `LEDGER_ENTRY_TYPE_WITHDRAWAL` | Account withdrawal | | `LEDGER_ENTRY_TYPE_RESOLUTION` | Market resolution | | `LEDGER_ENTRY_TYPE_COMMISSION` | Commission charge | | `LEDGER_ENTRY_TYPE_CORRECTION` | Balance correction | | `LEDGER_ENTRY_TYPE_NETTING` | Netting adjustment | | `LEDGER_ENTRY_TYPE_MANUAL_ADJUSTMENT` | Manual balance adjustment | | `LEDGER_ENTRY_TYPE_CONTRACT_EXPIRATION` | Contract expiration | ## Order States | Value | Description | | ------------------------------ | ------------------------------------------ | | `ORDER_STATE_PENDING_NEW` | Order received, not yet processed | | `ORDER_STATE_PENDING_REPLACE` | Modify request received, not yet processed | | `ORDER_STATE_PENDING_CANCEL` | Cancel request received, not yet processed | | `ORDER_STATE_PENDING_RISK` | Order pending risk approval | | `ORDER_STATE_PARTIALLY_FILLED` | Order partially executed | | `ORDER_STATE_FILLED` | Order fully executed | | `ORDER_STATE_CANCELED` | Order canceled | | `ORDER_STATE_REPLACED` | Order replaced | | `ORDER_STATE_REJECTED` | Order rejected | | `ORDER_STATE_EXPIRED` | Order expired | ## Order Intent | Value | Description | | ------------------------- | ------------------ | | `ORDER_INTENT_BUY_LONG` | Buy YES contracts | | `ORDER_INTENT_SELL_LONG` | Sell YES contracts | | `ORDER_INTENT_BUY_SHORT` | Buy NO contracts | | `ORDER_INTENT_SELL_SHORT` | Sell NO contracts | **Subscription Limits** You can subscribe to a maximum of 100 markets per subscription. Use multiple subscriptions if you need more. # Changelog Source: https://docs.polymarket.us/changelog Updates and Announcements for all APIs, tagged and filterable ### Subscribe to all Changes: * Add to any RSS reader using the URL: `https://docs.polymarket.us/changelog/rss.xml` * Slack has a built-in reader: use `/feed subscribe https://docs.polymarket.us/changelog/rss.xml` - **Maintenance window — Friday, September 11, 4:00am–7:00am ET.** Affects both the Institutional API and Retail API. Please note the schedule change, as this is outside our normal recurring maintenance schedule. - **Live status:** [status.polymarketexchange.com](https://status.polymarketexchange.com). * **Eight additional football sports market types are now documented:** * **Player props:** `football_player_passing_attempts`, `football_player_passing_completions`, `football_player_rushing_attempts`, `football_player_longest_rush`, and `football_player_longest_reception` * **First touchdown for a team:** `football_player_team_first_touchdown` identifies whether a player scores the selected team's first touchdown. Rushing, receiving, and return touchdowns count; passing touchdowns do not. * **Selected-team total:** `football_team_total_fourth_down_conversions` * **Game outcome:** `football_game_double_result` combines the first-half result and final-game result into nine mutually exclusive outcomes. * **Identify the market from structured metadata:** Retail uses `sportsMarketType`; Institutional uses `market_sport_type`. * See [Sports Schema](/trader-guide/sports-schema) for the complete inventory and football metadata profiles. * **71 additional sports market types are now documented.** The `market_sport_type` inventory on [Sports Schema](/trader-guide/sports-schema) now matches the full set of sports market types registered by the exchange. Some values are already listed; others are registered ahead of their first listing. Treat every value in the inventory as one that can appear on an instrument. Instruments with no sport-specific value show their `outcome_type` value in the Retail `sportsMarketType` field (for example `futures`); see the new Generic Values section. * **Football (35 values):** * **Player props:** `football_player_receptions`, `football_player_passer_rating`, `football_player_interceptions_thrown`, `football_player_sacks`, `football_player_defensive_interceptions`, `football_player_field_goals_made`, `football_player_50_plus_yard_field_goals_made`, `football_player_fantasy_points_ppr`, and `football_player_scrimmage_yards`, plus the yes/no props `football_player_first_touchdown`, `football_player_most_passing_yards`, `football_player_most_rushing_yards`, and `football_player_most_receiving_yards`. * **Combined game totals:** passing and rushing touchdowns, passing and rushing yards, offensive yards, turnovers, defensive interceptions, fourth-down conversions, and a successful two-point conversion yes/no. `football_game_total_pass_yards` and `football_game_total_pass_touchdowns` are also reserved for combined receiving yards and receiving touchdowns. * **Selected-team totals:** `football_team_total_offensive_yards`, `football_team_total_first_downs`, and `football_team_total_defensive_special_teams_touchdowns`. * **Game outcomes:** `football_game_last_score`, `football_game_last_touchdown`, `football_game_race_to_points` (`outcome_strike` carries the points target; each target has three legs: away team, home team, Neither Team), `football_game_highest_scoring_quarter`, `football_game_possession_winner`, `football_game_tie`, `football_game_safety`, and `football_game_onside_kick_attempt`. * **Live in-game markets (registered, not yet listed):** `football_next_team_touchdown` and `football_next_team_field_goal`. When these list, each touchdown or field-goal number is one three-leg product (each team plus No TD or No FG); the first instance lists at kickoff and a new instance lists after each touchdown or made field goal. * **Baseball (3 values):** the player props `baseball_player_rbis` and `baseball_player_stolen_bases`, and the selected-team total `baseball_team_total_runs`. * **Esports (24 values):** series handicaps and totals (`esports_series_map_handicap`, `esports_series_total_maps`, `esports_series_game_handicap`, `esports_series_total_games`); per-map rounds handicap and total rounds for maps 1–4; per-game first blood, total kills, and kills odd/even for games 1–4. Map markets apply to round-based titles such as Counter-Strike 2 and Valorant; game markets apply to series-of-games titles such as League of Legends and Dota 2. * **New sports (9 values):** `boxing_match_winner`, `darts_match_winner`, `pickleball_match_winner`, `lacrosse_team_full_game_winner`, and table tennis (`table_tennis_match_winner` plus `table_tennis_set_1_winner` through `table_tennis_set_4_winner`). * **Generic values:** `moneyline`, `spreads`, `totals`, and `drawable_outcome` are documented as valid `market_sport_type` values. They have the same structure as the matching `outcome_type` value and appear on instruments that carry no sport-specific value, such as instruments created before the sport-specific values existed or hand-listed markets. * **Identify the market from structured metadata:** Retail uses `sportsMarketType`; Institutional uses `market_sport_type`. See [Sports Schema](/trader-guide/sports-schema) for the full inventory, the esports map/game note, and the football metadata profiles. * **Maintenance window — Wednesday, September 9, 3:00am–7:00am ET.** Affects both the Institutional API and Retail API. Please note the schedule change, as this is outside our normal recurring maintenance schedule. * **Live status:** [status.polymarketexchange.com](https://status.polymarketexchange.com). * **Maintenance window — Tuesday, September 8, 2:00am–6:00am ET.** Affects both the Institutional API and Retail API. Please note the schedule change, as this is outside our normal recurring maintenance schedule. * **Live status:** [status.polymarketexchange.com](https://status.polymarketexchange.com). * **Maintenance windows — Saturday, September 5, 2:00am–6:00am ET, and Sunday, September 6, 2:00am–6:00am ET.** Affects both the Institutional API and Retail API. Please note the schedule change, as these are outside our normal recurring maintenance schedule. * **Live status:** [status.polymarketexchange.com](https://status.polymarketexchange.com). * **Maintenance window — Wednesday, September 2, 2:00am–8:00am ET.** Affects both the Institutional API and Retail API. Please note the time change, as this is different than our normal hours. * **Execution history reset:** we archive the stored execution history during the maintenance window. After maintenance, queries for pre-maintenance executions return empty results. * **Action required:** export and retain any execution data you need before the window begins. To retrieve every page, pass each `nextPageToken` value back as `pageToken` until the response returns `eof: true`. * **Live status:** [status.polymarketexchange.com](https://status.polymarketexchange.com). * **Maintenance window — Tuesday, September 1, 2:00am–7:00am ET.** Affects both the Institutional API and Retail API. Please note the time change, as this is different than our normal hours. * **Execution history reset:** we archive the stored execution history during the maintenance window. After maintenance, queries for pre-maintenance executions return empty results. * **Action required:** export and retain any execution data you need before the window begins. To retrieve every page, pass each `nextPageToken` value back as `pageToken` until the response returns `eof: true`. * **Live status:** [status.polymarketexchange.com](https://status.polymarketexchange.com). * **16 additional football sports market types are being listed** with college football (CFB) games, covering: * **First touchdown:** `football_game_first_half_first_touchdown`, joining the existing full-game and second-half variants * **Game events:** `football_game_pick_six`, `football_game_kickoff_punt_return_touchdown`, and `football_game_overtime` * **Game totals:** `football_game_total_touchdowns` (combined across both teams) * **Selected-team statistical totals:** passing and rushing touchdowns, passing and rushing yards, scrimmage yards, receptions, takeaways, defensive interceptions, sacks, field goals made, and 40+ yard field goals made * **Receiving markets reuse the passing types:** `football_team_total_pass_yards` is also used for team receiving yards, and `football_team_total_pass_touchdowns` for team receiving touchdowns. * **Identify the market from structured metadata:** Retail uses `sportsMarketType`; Institutional uses `market_sport_type`. * See [Sports Schema](/trader-guide/sports-schema) for the full `market_sport_type` inventory and football metadata profiles. * **One-off maintenance extension — Thursday, August 27, 2:00am–8:00am ET.** This week's recurring maintenance window will be extended to 8:00am ET. Normal recurring maintenance hours are unchanged. Affects both the Institutional API and Retail API. * **Execution history reset:** we clear the stored execution history during the maintenance window. Queries for pre-maintenance executions return empty results afterwards. * **Action required:** pull and store the execution data you need before the window opens. Please use pagination: follow the `nextPageToken` value (sent as `pageToken`) until the response returns `eof: true`. * **Live status:** [status.polymarketexchange.com](https://status.polymarketexchange.com). * **Maintenance window — Tuesday, August 25, 2:00am–8:00am ET.** Affects both the Institutional API and Retail API. Please note the time change, as this is different than our normal hours. * **Live status:** [status.polymarketexchange.com](https://status.polymarketexchange.com). * **Maintenance window — Tuesday, August 11, 6:00am–10:00am ET.** Affects both the Institutional API and Retail API. Please note the time change, as this is different than our normal hours. * **Live status:** [status.polymarketexchange.com](https://status.polymarketexchange.com). * **50 NFL sports market types are being listed**, covering: * **Primary game lines:** full-game winner, spread, and total * **Period markets:** first/second-half and first-through-fourth-quarter winners, spreads, and combined-points totals * **Team totals:** full-game and first/second-half selected-team points * **Margin and first score:** exact margin; first score by game or half; first touchdown by full game or second half * **Both teams to score:** any points by half or quarter, and a touchdown by full game, half, or quarter * **Statistical touchdown totals:** combined defensive/special-teams touchdowns and selected-team total touchdowns * **Player props:** touchdowns from scrimmage (passing touchdowns excluded), rushing yards, passing yards, receiving yards, and passing touchdowns * **Identify the market from structured metadata:** Retail uses `sportsMarketType`; Institutional uses `market_sport_type`. All secondary game and player markets use `outcome_type=props`; player markets also use `prop_type=player`. * **Market identity:** use `event_external_id_sportradar`, `market_sport_type`, `long_participant_id`, `short_participant_id`, and `outcome_strike` as the canonical lookup and deduplication key. Participant IDs can be empty for game-wide totals; also use `external_participant_id` when resolving a player. * See [Sports Schema](/trader-guide/sports-schema) for the complete 50-type inventory and football metadata profiles. * **Scheduled maintenance window — Thursday, July 30, 2:00am–4:00am ET.** Affects both the Institutional API and Retail API. * **Live status:** [status.polymarketexchange.com](https://status.polymarketexchange.com). * **Nine new MLB inning-winner sports market types:** `BASEBALL_TEAM_INNING1_WINNER` through `BASEBALL_TEAM_INNING9_WINNER`. * **Inning number:** derive it from mapping the `market_sport_type` enum rather than parsing the instrument ID. * **Three outcomes per inning:** home team, away team, and draw. * **Instrument ID format:** `atc--i-`. * **Doubleheaders:** IDs include `-dh1` or `-dh2` before the inning suffix. * **Outcome type:** `Props`. * **Preprod maintenance window — Tuesday, July 29, 8:00am–10:00am ET (\~2 hours).** The preprod environment will be placed into maintenance mode for scheduled database maintenance. * **Preprod only — production is not affected.** * During the window, preprod FIX sessions will disconnect and preprod Institutional API requests will be rejected. * No action needed — reconnect and resume testing once the window closes. * **Scheduled maintenance window — Saturday, July 25, 2:00am–4:00am ET.** Affects both the Institutional API and Retail API. * **Live status:** [status.polymarketexchange.com](https://status.polymarketexchange.com). * **Three new instrument metadata fields on sports instruments.** The `metadata` map returned by `SearchInstruments` / `GetInstrument` now includes: * `home_team_name` — display name of the home side (e.g., "Kansas City Chiefs") * `away_team_name` — display name of the away side (e.g., "Buffalo Bills") * `tournament_name` — human-readable competition name (e.g., "MLS", "NBA", "MLB") * **Home/away is independent of long/short.** `long_participant_id` / `short_participant_id` ordering varies by league convention; these new fields always identify the true home and away sides of the matchup. * **`tournament_name` reflects the specific competition where the data provider supplies one** — e.g., esports carries the league and season ("LEC Summer 2026"), ITF tennis carries the tournament, golf carries the tournament edition ("2026 The Open Championship"), and UFC carries the event name ("UFC 320"). Futures instruments without a home/away matchup (e.g., golf winner) carry `tournament_name` only. * **All newly created sports instruments carry the fields.** Instruments created before this change do not — treat them as optional keys when reading the metadata map. * During the **Thursday, July 16, 2026 maintenance window**, we will prune all executions, market data beyond 45 days, and orders beyond 90 days. * **Trades and positions remain unchanged.** * Store what you need before the window. * **Backfill:** if you require old executions, we can provide a **one-time-only** backfill on request. * **Going-forward retention periods:** * orders — **90 days** * market data — **45 days** * executions — **7 days** * **Liquidity rewards reductions, effective 12:00am ET, Monday July 13, 2026:** * **WNBA:** \$5,000 → **\$2,000** per game. * **MLB:** \$20,000 → **\$12,500** per game (incl. all props). * **MLB futures:** \$1,000/day → **\$500/day**. * **Motorsports:** \$5,000 → **\$1,000**. * **UFC:** Moneyline \$15,000 → **\$10,000**; prelims \$2,500 → **\$1,000**. * **PGA Tour:** \$150,000 → **\$50,000** per tournament. * **MLS futures:** \$1,000/day → **\$500/day**. * **ATP/WTA:** \$2,500 → **\$1,000** per match. * **ITF:** \$500 → **\$300** per match. * **Esports:** \$3,000 → **\$1,500**. * **Discount factors and target sizes unchanged.** * **Weekly maintenance window moved to Thursday 2:00am–6:00am ET**, effective July 9, 2026. Previously, the window was every Thursday, 6:00am–8:00am ET. Affects both the Institutional API and Retail API. * **Live status:** [status.polymarketexchange.com](https://status.polymarketexchange.com). * **Scheduled maintenance window — Tuesday, July 7, 2:00am–6:00am EDT.** Affects both the Institutional API and Retail API. Please note the time change, as this is different than our normal hours. * **Live status:** [status.polymarketexchange.com](https://status.polymarketexchange.com). * **Soccer To Advance is now a single two-sided instrument.** `soccer_game_to_advance` markets are created as **one moneyline instrument** carrying both teams (long and short), instead of two separate per-team Yes/No instruments. Live starting with the **World Cup quarter-finals**. * **First instrument:** `aadc-fwc-fra-mar-2026-07-09-to-advance` (France vs Morocco, July 9) — a single id. It is **not** two instruments (`aadc-fwc-fra-mar-2026-07-09-to-advance-fra` and `aadc-fwc-fra-mar-2026-07-09-to-advance-mar`). * **Fields:** `outcome_type` is `moneyline` and `market_sport_type` is `soccer_game_to_advance`. * **All To Advance games from here on use this format.** Read both sides off the one instrument — Retail: `marketSides` from `GET /v1/market/slug/{slug}`; Institutional: `long_participant_id` / `short_participant_id` from `SearchInstruments` / `GetInstrument`. * **Politics and tennis winner futures liquidity rewards are reduced, effective 8:00pm ET, Thursday July 2 (00:00 UTC, Friday July 3):** * **Politics**: \$500/day → **\$250/day** per event, pro-rated across all markets within the event. * **ATP & WTA winner futures**: \$1,000/day → **\$500/day** per tournament winner futures event. * **Wimbledon winner futures**: \$5,000 per draw per day → **\$2,500 per draw per day** (\$5,000/day total across the men's and women's draws, down from \$10,000/day). * **Discount factors and target sizes are unchanged.** Full details on the [live liquidity rewards page](https://polymarket.us/rewards). * **Upcoming — these markets become a single two-sided instrument.** Instead of two separate per-team instruments, each of the following is created as **one instrument with both participants** (a single long/short winner market): * `soccer_game_to_advance` — Soccer To Advance, starting with the **World Cup quarter-finals onward** * `esports_map_winner_1` / `esports_map_winner_2` / `esports_map_winner_3` and `esports_game_winner_1` / `esports_game_winner_2` / `esports_game_winner_3` — esports map / game winner\*\* * `tennis_set_1_winner` / `tennis_set_2_winner` / `tennis_set_3_winner` — tennis set winner\*\* * **One market, two sides.** Each market now exposes a single instrument carrying both teams/players (`long_participant_id` / `short_participant_id`; Retail two-sided `marketSides`) rather than one Yes/No market per participant. Read both sides off the one instrument instead of expecting two separate markets per event. * **Effective for newly created instruments only.** Existing instruments keep their current shape. Soccer To Advance applies **starting with the World Cup quarter-finals onward**; esports map/game winner and tennis set winner apply to instruments created from **Friday night (July 3, 2026)** onward. * **Where to read it:** Retail — `marketSides` and `sportsMarketType` from `GET /v1/market/slug/{slug}`; Institutional — `long_participant_id` / `short_participant_id` and `market_sport_type` from `SearchInstruments` / `GetInstrument`. See [Sports Schema](/trader-guide/sports-schema). * **Nathan's Hot Dog Eating Contest markets (July 4, 2026) now carry \$13,000 in liquidity rewards**, effective July 2. * **Men's contest — \$10,000.** Each of the four men's events — Chestnut to win, winner without Chestnut, winner's total hot dogs & buns, and men's record broken — has **\$2,500**: \$1,250 Early + \$1,250 Day-of (discount factor 0.40/0.35, target size 2,500), distributed pro-rata across eligible instruments. * **Women's contest — \$1,000 per day.** Each of the four women's events — Sudo to win, winner without Sudo, winner's total hot dogs & buns, and women's record broken — has **\$250 per day** (discount factor 0.35, target size 2,500), distributed pro-rata across eligible instruments. * Full details on the [live liquidity rewards page](https://polymarket.us/rewards). * **`lastPriceSample` is being removed.** As of **Friday, July 3, 2026**, this field should no longer be considered supported — do not rely on it in your integration going forward. * **Where it appears:** * Retail Markets WebSocket (`wss://api.polymarket.us/v1/ws/markets`) — both `SUBSCRIPTION_TYPE_MARKET_DATA` and `SUBSCRIPTION_TYPE_MARKET_DATA_LITE` responses. * REST — `GET /v1/markets/{slug}/bbo` and `GET /v1/markets/{slug}/book`. * **Use `longQuote`/`shortQuote` instead** on the lite response (`SUBSCRIPTION_TYPE_MARKET_DATA_LITE` and `GET /v1/markets/{slug}/bbo`) — these fields already carry the equivalent data. The full response (`SUBSCRIPTION_TYPE_MARKET_DATA` and `GET /v1/markets/{slug}/book`) has no equivalent replacement field. * **Scheduled maintenance window — Thursday, July 2, 2:00am–6:00am EDT.** Affects both the Institutional API and Retail API. Please note the time change, as this is different than our normal hours. * **Live status:** [status.polymarketexchange.com](https://status.polymarketexchange.com). * **Wimbledon match winner markets are now decimalized.** Wimbledon and Wimbledon qualifiers **match winner** markets (`tennis_match_winner`) were initially listed with full cent ticks. New markets for the rest of the tournament are decimalized with a **0.5 cent (`$0.005`)** tick size. * **Existing instruments are unaffected** — they keep the tick they were created with. * **Read the tick per instrument before trading.** Retail — `market.orderPriceMinTickSize` from `GET /v1/market/slug/{slug}`; Institutional — `instrument.tickSize` from `SearchInstruments` / `GetInstrument`. Do not assume 1 cent ticks. * **Tennis props:** * **All tennis prop markets** (alongside the `tennis_match_winner` moneyline): * `tennis_match_games_spread` — handicap on games won * `tennis_match_sets_spread` — handicap on sets won * `tennis_match_total_games` — total games played across the match * `tennis_match_total_sets` — total sets played across the match * `tennis_match_exact_score` — exact set score * `tennis_set_1_winner` / `tennis_set_2_winner` / `tennis_set_3_winner` — per-set winner * **Soccer half BTTS and First Team to Score are live.** The following enums are added to `market_sport_type` (Retail `sportsMarketType`): * `soccer_game_first_half_btts` — both teams to score in the first half * `soccer_game_first_half_first_team_to_score` — first team to score in the first half (per-team plus a "None" outcome) * `soccer_game_second_half_btts` — both teams to score in the second half * `soccer_game_second_half_first_team_to_score` — first team to score in the second half (per-team plus a "None" outcome) * **Each market counts its own half only.** First-half markets count goals up to and including minute 45 (plus first-half stoppage time); second-half markets count minutes 46 through 90 (plus second-half stoppage time). Goals scored in extra time never count toward either half. * **Settlement timing.** First-half markets settle once the first half is complete (halftime); second-half markets settle at full time. If a half is goalless, the First Team to Score market resolves **None**. * **Standard 1 cent (`$0.01`) tick size** — these are not decimalized. * **Where to read it:** Retail — `sportsMarketType` from `GET /v1/market/slug/{slug}`; Institutional — `market_sport_type` from `SearchInstruments` / `GetInstrument`. See [Sports Schema](/trader-guide/sports-schema). * **Subjects API is deprecated.** The Subjects endpoints — `GET /v1/subjects`, `GET /v1/subjects/{id}`, `GET /v1/subjects/slug/{slug}`, and their `/markets` variants — are deprecated and will be removed on **June 29, 2026**. They are no longer used, and their documentation has been removed from the API reference. If you currently depend on them, please reach out before the removal. * **Deprecated Market, MarketSide, and Event fields.** The following response fields are now marked deprecated in the API reference and will be removed on **June 29, 2026**. They continue to work until then — please migrate to the structured replacements: * Market `marketType` and `sportsMarketTypeV2` → `sportsMarketType` * Market `outcomes` / `outcomePrices` (JSON strings) → `marketSides[]` * Event `participants` → `teams` * `MarketSide.team` → the market side's participant data * `MarketSide.participantId`, and Market `archived`, `manualActivation`, `gameStartTime` → being removed from public responses * Deprecated fields are flagged in the [API reference](/api-reference/introduction) so you can identify them while migrating. Removal is scheduled for **June 29, 2026**. * **Soccer extra-time markets are live for the World Cup.** The following enums are added to `market_sport_type` (Retail `sportsMarketType`) on knockout fixtures: * `soccer_game_goes_to_extra_time` — binary Yes/No: will the match go to extra time? * `soccer_team_extra_time_spread` — spread on the extra-time goal margin * `soccer_game_extra_time_total` — Over/Under total goals in extra time * `soccer_game_extra_time_btts` — both teams to score in extra time * `soccer_game_extra_time_first_team_to_score` — first team to score in extra time (per-team plus a "None" outcome) * **Extra time only.** The spread, total, both-teams-to-score, and first-team-to-score markets count **only goals scored in extra time** — they exclude 90 minutes plus stoppage time and any penalty shootout. `soccer_game_goes_to_extra_time` settles **Yes** once the tie is level after regulation and proceeds to extra time. * **If the match does not reach extra time**, the four extra-time scoring markets settle to the **last fair market price** (`soccer_game_goes_to_extra_time` settles **No**). * **Created pre-match for knockout games**, alongside the other team props, once the main match market is open. Group-stage fixtures do not list these markets. * **Standard 1 cent (`$0.01`) tick size** — these extra-time markets are **not** decimalized. (Only the full-game World Cup spreads and totals use the 0.5 cent tick.) * **Example slugs** (Round of 32, South Africa vs Canada, `fwc-rsa-can-2026-06-28`): * Goes to extra time: `astatc-fwc-rsa-can-2026-06-28-goes-et` * Extra-time spread: `asc-fwc-rsa-can-2026-06-28-et-neg-1pt5` (and `-neg-0pt5`, `-pos-0pt5`, `-pos-1pt5`) * Extra-time total: `tsc-fwc-rsa-can-2026-06-28-et-1pt5` * Both teams to score (ET): `astatc-fwc-rsa-can-2026-06-28-et-btts` * First team to score (ET): `astatc-fwc-rsa-can-2026-06-28-et-ftts-rsa` (and `-can`, `-none`) * **Where to read it:** Retail — `sportsMarketType` from `GET /v1/market/slug/{slug}`; Institutional — `market_sport_type` from `SearchInstruments` / `GetInstrument`. See [Sports Schema](/trader-guide/sports-schema). * **Cricket now covers international & domestic T20.** In addition to IPL, the match winner market is now live for **T20 Internationals**, **Major League Cricket**, the **T20 Blast (men's and women's)**, and the **Women's T20 World Cup**. * **Same market type — no integration changes.** Each match lists one match winner market (`market_sport_type = "cricket_match_winner"`, Retail `sportsMarketType = "cricket_match_winner"`), identical in structure to IPL. It resolves to the official match winner; a no result, tie, or abandonment with no declared winner resolves to **\$0.50**. * **Liquidity rewards — \$500 per match** on the match winner market, split **early / day-of / live = \$25 / \$75 / \$400** (discount factors 0.40 / 0.35 / 0.30, target size 10,000 each). IPL keeps its own \$20,000 structure. * **Example slugs** (Major League Cricket): `aec-mlc-sfu-soe-2026-06-27` (San Francisco Unicorns vs Seattle Orcas), `aec-mlc-mny-lakr-2026-06-27` (MI New York vs Los Angeles Knight Riders). * **Where to read it:** Retail — `sportsMarketType` from `GET /v1/market/slug/{slug}`; Institutional — `market_sport_type` from `SearchInstruments` / `GetInstrument`. See [Sports Schema](/trader-guide/sports-schema) and [Liquidity Rewards](https://polymarket.us/rewards). * **Soccer To Advance is live for the World Cup.** Knockout fixtures now list a To Advance market (`market_sport_type = soccer_game_to_advance`, Retail `sportsMarketType = "soccer_game_to_advance"`). It is created per knockout game once the main match market is open. * **Structure:** each knockout tie has **two separate instruments — one per team** (e.g. "Will South Africa advance?" and "Will Canada advance?"), each a binary Yes/No market. They resolve on the team that progresses over the **whole tie — regulation, extra time, and penalties** — not the 90-minute result. * **Decimalized 0.5 cent tick size (`$0.005`).** Do not assume 1 cent ticks. Read tick size per instrument before validating or submitting orders: * **Retail API:** `market.orderPriceMinTickSize` from `GET /v1/market/slug/{slug}` (expect `0.005`). * **Institutional API:** `instrument.tickSize` from `SearchInstruments` / `GetInstrument` (expect `0.005`); divide integer prices by `instrument.priceScale` (e.g. `priceScale == 1000` → `price = 5` is `$0.005`). * **Example slugs** (Round of 32, South Africa vs Canada, event `aadc-fwc-rsa-can-2026-06-28-to-advance`): * South Africa to advance: `aadc-fwc-rsa-can-2026-06-28-to-advance-rsa` * Canada to advance: `aadc-fwc-rsa-can-2026-06-28-to-advance-can` * **Where to read it:** Retail — `sportsMarketType` from `GET /v1/market/slug/{slug}`; Institutional — `market_sport_type` from `SearchInstruments` / `GetInstrument`. See [Sports Schema](/trader-guide/sports-schema). * **Maintenance window — Friday, June 26, 2:30am–4:30am EST.** Affects both the Institutional API and Retail API. Please note the time change, as this is different than our normal hours. * **Live status:** [status.polymarketexchange.com/incidents/d936p4wqsp77](https://status.polymarketexchange.com/incidents/d936p4wqsp77). * **`GET /v1/portfolio/positions` will be paginated.** The endpoint will return up to **100 positions per page** instead of the entire set in a single response. * **Action required for large accounts.** To retrieve all of your positions, follow the `nextCursor` value (sent as the `cursor` query parameter) until the response returns `eof: true`. Accounts with more than 100 positions that do not paginate will only receive the first page. * **No change for smaller accounts.** Accounts with 100 or fewer positions still receive every position in a single response, now with `eof: true`. * **Why:** returning every position in one response caused request timeouts and out-of-memory errors for accounts with very large position lists (tens of thousands of positions). * **Rollout:** rolling out soon — we'll announce the enablement date here in advance. Subscribe to the RSS feed to be notified before it ships. * **Where to read it:** `GET /v1/portfolio/positions`. See [Portfolio API](/api-reference/portfolio/overview). * **Backfill complete.** Every open instrument in production now carries `market_sport_type`, including full-game winner/spread/total and full-time/match/fight winner markets. * **Map on `market_sport_type` alone — you no longer need to check `outcome_type`.** `market_sport_type` fully identifies a market's structure and period on its own. * **`outcome_type` is still there, just don't rely on it for mapping.** It remains populated on every instrument but is subject to change; treat it as informational only and migrate any mapping logic to `market_sport_type`. * **One exception:** season/event-long **futures** carry no `market_sport_type` and are still identified by `outcome_type = "futures"`. * **Where to read it:** Retail — `sportsMarketType` from `GET /v1/market/slug/{slug}`; Institutional — `market_sport_type` from `SearchInstruments` / `GetInstrument`. See [Sports Schema](/trader-guide/sports-schema). * **Starting Monday, June 22, 2026, every instrument in production will have `market_sport_type` filled in** — including full-game winner/spread/total (and full-time / match / fight winner), which previously left it unset (see v0.0.48 for the enum list). * **Backfill:** we'll backfill all open instruments with `market_sport_type` on Monday, June 22. * **`outcome_type` remains on the instrument** and is unchanged (`moneyline` / `spreads` / `totals` / `drawable_outcome`). However, please do not use this for mappings and use sport\_market\_type instead, outcome\_type will be deprecated in the coming weeks. * **Where to read it:** Retail — `sportsMarketType` from `GET /v1/market/slug/{slug}`; Institutional — `market_sport_type` from `SearchInstruments` / `GetInstrument`. See [Sports Schema](/trader-guide/sports-schema). * **Every instrument now carries an explicit `market_sport_type` — production Tuesday, June 23, 2026.** This adds full-game winner/spread/total and full-time/match winner markets, which previously left it unset (see v0.0.48 for the enum list). * **Backfill:** we'll backfill all open instruments with the new `market_sport_type` on **Tuesday, June 23 at 12:00pm ET**. * **You can now fully identify an instrument from `market_sport_type` alone** — it encodes both market structure (winner/spread/total) and period (full game, first half, etc.). Use it as your single source of truth going forward. * **`outcome_type` is deprecated.** Please don't rely on it — it's subject to change. Migrate any logic keyed off `outcome_type` to `market_sport_type` before Tuesday. * **Where to read it:** Retail — `sportsMarketType` from `GET /v1/market/slug/{slug}`; Institutional — `market_sport_type` from `SearchInstruments` / `GetInstrument`. See [Sports Schema](/trader-guide/sports-schema). * **New tennis match props (coming soon).** The following enums are added to `market_sport_type` (Retail `sportsMarketType`): * **Games:** `tennis_match_games_spread`, `tennis_match_total_games` * **Match:** `tennis_match_exact_score` * **Set winner (one type per set):** `tennis_set_1_winner`, `tennis_set_2_winner`, `tennis_set_3_winner` * **Set Winner** covers only the guaranteed sets — best-of-3: sets 1-2 (`tennis_set_1_winner`, `tennis_set_2_winner`); best-of-5: sets 1-3 (adds `tennis_set_3_winner`). * **Exact Match Score** resolves on the final score in sets (best-of-3: `2-0` / `2-1`; best-of-5: `3-0` / `3-1` / `3-2`). * **Resolution:** games spread and total games settle on total games across the completed match; set winner settles per set; exact match score settles on the final sets score. If a match is not completed (walkover, retirement, cancellation, or postponement beyond the scheduled window), the market settles at the last fair market price. * **Coverage:** ATP and WTA singles matches. * **Example slugs** (ATP, event `atp-novdjo-caralc-2026-06-06`): * Games spread: `asc-atp-novdjo-caralc-2026-06-06-gs-neg-3pt5` * Total games: `tsc-atp-novdjo-caralc-2026-06-06-tg-22pt5` * Exact match score: `astatc-atp-novdjo-caralc-2026-06-06-es-2-0` * Set winner (Set 1): `astatc-atp-novdjo-caralc-2026-06-06-set1-sw1` * **Where to read it:** Retail — `sportsMarketType` from `GET /v1/market/slug/{slug}`; Institutional — `market_sport_type` from `SearchInstruments` / `GetInstrument`. * **Full-game and full-match markets now carry an explicit `market_sport_type` — live in preprod now.** Previously these markets left `market_sport_type` unset and were identifiable only by `outcome_type` (`moneyline`/`spreads`/`totals`/`drawable_outcome`). Newly created instruments now also carry a fine-grained `market_sport_type`, consistent with the existing period markets (e.g. `basketball_team_first_half_spread`). The following enums are added to `market_sport_type` (Retail `sportsMarketType`): * **Basketball — full game (NBA, WNBA, CBB, WCBB):** `basketball_team_full_game_winner`, `basketball_team_full_game_spread`, `basketball_team_full_game_total` * **Football — full game (NFL, CFB):** `football_team_full_game_winner`, `football_team_full_game_spread`, `football_team_full_game_total` * **Baseball — full game (MLB):** `baseball_team_full_game_winner`, `baseball_team_full_game_spread`, `baseball_team_full_game_total` * **Hockey — full game (NHL):** `hockey_team_full_game_winner`, `hockey_team_full_game_spread`, `hockey_team_full_game_total` * **Match / fight winner:** `tennis_match_winner`, `cricket_match_winner`, `esports_match_winner`, `ufc_fight_winner` * **Soccer — full-time 3-way winner:** `soccer_team_full_time_winner` (`outcome_type` stays `drawable_outcome`). Soccer full-game spread/total already carried `soccer_team_full_game_spread`/`soccer_team_full_game_total` and are unchanged. * **`outcome_type` is unchanged** and continues to be populated on new instruments (`moneyline`/`spreads`/`totals`/`drawable_outcome`), so existing structural logic keeps working. * **Existing instruments are not modified.** Instruments created before this change keep their current values and leave `market_sport_type` unset; the new enums appear only on newly created instruments. During the transition, treat a full-game market with an unset `market_sport_type` as full-game. * **Rollout:** live in **preprod** now. We'll announce the production date here in advance — subscribe to the RSS feed to be notified before it ships to production. * **Where to read it:** Retail — `sportsMarketType` from `GET /v1/market/slug/{slug}`; Institutional — `market_sport_type` from `SearchInstruments` / `GetInstrument`. * **Maintenance window — Wednesday, June 17, 6:00am–9:00am EST.** Affects both the Institutional API and Retail API. Please note the time change, as this is different than our normal hours. This is a one-off time change. * **World Cup futures liquidity rewards increased (effective 12:00am ET, Friday June 12):** * **Tournament Winner Futures:** \$1,500/day → **\$5,000/day**. * **Group Winners & Golden Boot Futures:** \$750/day → **\$1,500/day**. * **Exotic Futures:** \$500/day → **\$1,000/day**. * **Discount factors and target sizes unchanged.** * **Soccer spread/total markets now use the `spreads`/`totals` `outcome_type` for every period.** Previously, soccer first-half, second-half, and team-total markets were listed with `outcome_type = "props"`. They now use the same structural `outcome_type` as full-game spreads/totals, matching basketball and baseball period markets. The period is encoded by `market_sport_type`. * **Spread → `outcome_type = "spreads"`:** `soccer_team_first_half_spread`, `soccer_team_second_half_spread`. * **Total → `outcome_type = "totals"`:** `soccer_team_first_half_total`, `soccer_team_second_half_total`, `soccer_team_total_goals`, `soccer_team_total_goals_first_half`. * **Action recommended:** identify market structure from `outcome_type` and the period from `market_sport_type`. Do not assume soccer period spread/total markets are `props`. See [Sports Schema](/trader-guide/sports-schema). * **Where to read it:** Retail — `sportsMarketType` from `GET /v1/market/slug/{slug}`; Institutional — `outcome_type` and `market_sport_type` from `SearchInstruments` / `GetInstrument`. * **World Cup liquidity rewards increased (effective 2:00pm ET, Thursday June 11):** Total per game raised from \$30,000 → **\$50,000**. * **Moneyline/spreads/totals:** \$20,000 → **\$35,000** (Moneyline \$26,250, Spreads \$4,375, Totals \$4,375). * **Player Props:** \$5,000 → **\$7,500** (\$3,750 Pre-game + \$3,750 Live). * **Team Props:** \$5,000 → **\$7,500** (\$3,750 Pre-game + \$3,750 Live). * **Discount factors and target sizes unchanged.** * **Maintenance window — Thursday, June 11, 3:00am–5:00am EST.** Please note the time change, as this is different than our normal hours. This is a one-off time change. We heard feedback from some of our users that they needed more time to fully migrate to partial contracts, so we pushed back full rollout. All newly listed instruments will become partial-contract markets on **Thursday, June 11, 2026 at 5:00 PM ET (21:00 UTC)**. Long-dated futures markets listed before then and all World Cup instruments are partial-contract markets, so all market makers and API users should support partial-contract instruments now. * **NHL hockey market types are live.** The following enums are added to `market_sport_type` (Retail `sportsMarketType`): * **Game:** * `hockey_game_overtime` (will the game go to overtime?) * `hockey_game_double_overtime` (will the game go to double overtime?) * **Player props:** * `hockey_player_goals` * `hockey_player_assists` * `hockey_player_points` * **Where to read it:** Retail — `sportsMarketType` from `GET /v1/market/slug/{slug}`; Institutional — `market_sport_type` from `SearchInstruments` / `GetInstrument`. * **UFC market types are live.** The following enums are added to `market_sport_type` (Retail `sportsMarketType`): * `ufc_method_of_victory` * `ufc_go_the_distance` * `ufc_round_of_victory` * `ufc_round_of_finish` * `ufc_method_of_finish` * **Where to read it:** Retail — `sportsMarketType` from `GET /v1/market/slug/{slug}`; Institutional — `market_sport_type` from `SearchInstruments` / `GetInstrument`. * **Soccer market types are live.** The following enums are added to `market_sport_type` (Retail `sportsMarketType`): * **Team — full game:** * `soccer_team_full_time_winner` * `soccer_team_full_game_spread` * `soccer_team_full_game_total` * **Team — first half:** * `soccer_team_first_half_winner` * `soccer_team_first_half_spread` * `soccer_team_first_half_total` * **Game props:** * `soccer_game_btts` (both teams to score) * `soccer_game_first_team_to_score` * `soccer_game_exact_score` * `soccer_game_total_corners` * **Player props:** * `soccer_player_goals` * `soccer_player_assists` * **Where to read it:** Retail — `sportsMarketType` from `GET /v1/market/slug/{slug}`; Institutional — `market_sport_type` from `SearchInstruments` / `GetInstrument`. * **NBA Playoffs props expansion (effective 1:00pm ET, Friday June 5):** Props pool increased from \$10,000 → \$20,000 per game. Categories reorganized: * **Player Props:** \$10,000/game (\$5,000 Day-of + \$5,000 Live). * **Game Props (new):** \$5,000/game (\$2,500 Day-of + \$2,500 Live). * **Other Props (new):** \$5,000/game (\$2,500 Day-of + \$2,500 Live). * **Team Props removed.** * **NBA Playoffs Moneyline reduction (Live):** \$56,500 → **\$50,000** per game. * **NBA Playoffs total liquidity per game:** \$100,000 → **\$103,500** (\$83,500 moneyline/spreads/totals + \$20,000 props). * **NBA Pool row updated:** Early \$4,000 / Day-of \$14,000 / Live \$85,500. * **Props discount factor and target size** are consistent across all categories: **0.35 / 2,500** for both Day-of and Live. * **World Cup liquidity rewards:** start time pushed to **6:00pm ET, Thursday June 4** (was June 3). * **Starting Monday, June 8, 2026 at 12:00 PM EST, every newly listed instrument will be a partial-contract market.** Existing instruments are unchanged. Do not assume whole-contract quantities — derive the partial scale per instrument before submitting orders. * **Institutional API — derive the scale, then convert:** * `instrument.fractionalQtyScale` — divide raw integer quantities by this to get decimal contracts. For example, with `fractionalQtyScale == 100`, `quantity = 1` is `0.01` contracts and `quantity = 100` is `1` full contract. * `instrument.minimumTradeQty` — the smallest tradable integer quantity. * Initial partials use `fractionalQtyScale == 100` and `minimumTradeQty == 1`, so the minimum order is **1% of a contract**. * On the `Order` message, `fractional_quantity_scale` (field 49) carries the same scale for converting `order_qty`, `cum_qty`, and `leaves_qty`. * **Retail API — read the minimum, then handle decimals:** * `minimumTradeQty` on the market object (for example `GET /v1/market/slug/{slug}`) is expressed in contracts, so `0.01` means a **1%-of-a-contract** minimum. * Treat `quantity`, `cumQuantity`, and `leavesQuantity` as decimals, and use the decimal portfolio fields (`netPositionDecimal`, `qtyBoughtDecimal`, …). * **NBA quarter spread + total markets and game-to-overtime (preprod now, production by midnight EST on June 2, 2026).** The following enums are added to `market_sport_type` (Retail `sportsMarketType`): * **Spread:** `basketball_team_first_quarter_spread`, `basketball_team_second_quarter_spread`, `basketball_team_third_quarter_spread`, `basketball_team_fourth_quarter_spread` * **Total:** `basketball_team_first_quarter_total`, `basketball_team_second_quarter_total`, `basketball_team_third_quarter_total`, `basketball_team_fourth_quarter_total` * **Game-to-overtime:** `basketball_game_overtime` * **Resolution:** each quarter market settles on points scored in that quarter only; 4th-quarter markets exclude overtime. The game-to-overtime market settles at the conclusion of the game. * **Example slugs (NBA Finals, New York vs. San Antonio, `nba-ny-sa-2026-06-03`):** * 1st quarter spread: `asc-nba-ny-sa-2026-06-03-q1-neg-2pt5`, total: `tsc-nba-ny-sa-2026-06-03-q1-56pt5` * 4th quarter spread (excl. OT): `asc-nba-ny-sa-2026-06-03-q4-neg-1pt5`, total: `tsc-nba-ny-sa-2026-06-03-q4-51pt5` * Game-to-overtime: `astatc-nba-ny-sa-2026-06-03-ot` * **Where to read it:** Retail — `sportsMarketType` from `GET /v1/market/slug/{slug}`; Institutional — `market_sport_type` from `SearchInstruments` / `GetInstrument`. * **NBA second half + new player props (preprod now, production June 2, 2026 at 6:00 PM EST):** Basketball second-half team markets and six additional player props are live in preprod and will be deployed to production on **June 2, 2026 at 6:00 PM EST**. The following enums are added to the instrument `market_sport_type` field (Retail `sportsMarketType`): * **Team — second half:** * `basketball_team_second_half_winner` * `basketball_team_second_half_spread` * `basketball_team_second_half_total` * **Player props:** * `basketball_player_rebounds` * `basketball_player_threes` * `basketball_player_steals` * `basketball_player_blocks` * `basketball_player_double_double` * `basketball_player_triple_double` * **Example slugs (NBA Finals, New York vs. San Antonio, `nba-ny-sa-2026-06-03`):** * **Team — second half:** * 2H moneyline: `atc-nba-ny-sa-2026-06-03-sh-ny`, `atc-nba-ny-sa-2026-06-03-sh-sa`, `atc-nba-ny-sa-2026-06-03-sh-draw` * 2H spread: `asc-nba-ny-sa-2026-06-03-sh-neg-10pt5`, `asc-nba-ny-sa-2026-06-03-sh-pos-1pt5` * 2H total: `tsc-nba-ny-sa-2026-06-03-sh-105pt5` * **Player props** (each strike is its own market; player segment is first-3-of-first + first-3-of-last name): * Rebounds: `astatc-nba-ny-sa-2026-06-03-reb-vicwem-gte11` * Three-pointers made: `astatc-nba-ny-sa-2026-06-03-threes-jalbru-gte3` * Steals: `astatc-nba-ny-sa-2026-06-03-stl-jalbru-gte2` * Blocks: `astatc-nba-ny-sa-2026-06-03-blk-vicwem-gte2` * Double-double: `astatc-nba-ny-sa-2026-06-03-dd-vicwem-gte1` * Triple-double: `astatc-nba-ny-sa-2026-06-03-td-vicwem-gte1` * **Resolution — second half excludes overtime:** Second-half team markets (spread, total, moneyline) settle on points scored in the **third and fourth quarters only**; overtime is **not** included. * **Resolution — player props:** The new counting props (rebounds, three-pointers made, steals, blocks) settle on full-game box-score totals **including overtime**, consistent with the existing points and assists props. **Double-double** and **triple-double** resolve **Yes/No** — Yes when the player records 10 or more in at least two (double-double) or three (triple-double) of points, rebounds, assists, steals, or blocks. * **Where to read it:** * **Retail API** — read `sportsMarketType` from the market object (for example `GET /v1/market/slug/{slug}`). * **Institutional API** — read `market_sport_type` from instrument reference data (`SearchInstruments` / `GetInstrument`). * **NBA Finals Game 2:** New York vs. San Antonio Game 2 of the NBA Finals (`aec-nba-ny-sa-2026-06-05`) will be listed on **Monday, June 1, 2026** and will be the first market with a **0.5 cent** tick size (`$0.005`). The remainder of NBA Finals markets will also use **0.5 cent** ticks. * **Retail API:** read `market.orderPriceMinTickSize` from the market response before submitting orders. For this market, use `GET /v1/market/slug/aec-nba-ny-sa-2026-06-05` and expect `orderPriceMinTickSize: 0.005`. * **Institutional API:** read `instrument.tickSize` from instrument reference data (`SearchInstruments` / `GetInstrument`). For this instrument, `instrument.tickSize = 0.005`. * **Institutional price scale:** prices submitted to the Institutional API are integer values. Read `instrument.priceScale` from the same instrument reference data response and divide submitted or returned integer prices by that value to get dollar prices. For example, if `instrument.priceScale == 1000`, `price = 5` means `$0.005`, `price = 500` means `$0.50`, and `price = 1000` means `$1.00`. With a 0.5 cent tick and `priceScale == 1000`, valid integer prices move in 5-unit increments. * **Action recommended:** do not assume 1 cent ticks. Read tick size and price scale per market or instrument before validating or submitting orders. * **Markets API:** market responses now document `minimumTradeQty` alongside `orderPriceMinTickSize`. * Applies to `GET /v1/markets`, `GET /v1/market/id/{id}`, `GET /v1/market/slug/{slug}`, and documented Retail API responses that embed the market object, including Events, Search, Sports, Sports Legacy, and Subjects. * `minimumTradeQty` is expressed in contracts. For example, `0.01` means the minimum order size is 1% of a contract. * `orderPriceMinTickSize` is expressed in dollars. For example, `0.005` means half-cent ticks. * **Market data:** order book and trade quantity fields can contain decimal contract quantities. * `GET /v1/markets/{slug}/book` and Markets WebSocket book levels return `qty` as a decimal string. * Markets WebSocket trade `quantity.value` is also a decimal string. * **Orders API:** order `quantity` fields support decimal contract quantities on partial-contract markets. * Applies to `POST /v1/orders`, `POST /v1/order/preview`, `POST /v1/order/{orderId}/modify`, `POST /v1/orders/batched`, and `POST /v1/orders/batched/modify`. * Order request and response `quantity`, `cumQuantity`, and `leavesQuantity` fields are JSON numbers and can contain decimals. * Private WebSocket order snapshots and updates use the same order quantity fields; execution `lastShares` is a decimal string. * Multi-leg execution `legPrices[].qty` is a decimal string. * Submit prices and quantities already aligned to the market's documented precision. Extra precision can be normalized in responses rather than rejected. * **Portfolio API:** use decimal quantity fields for positions and trades. * `GET /v1/portfolio/positions` returns `netPositionDecimal`, `qtyBoughtDecimal`, `qtySoldDecimal`, `bodPositionDecimal`, and `qtyAvailableDecimal`. * `GET /v1/portfolio/activities` trade payloads return `qtyDecimal`; the older trade `qty` field is rounded and deprecated. * Private WebSocket position messages can include `netPositionDecimal`, `qtyBoughtDecimal`, `qtySoldDecimal`, `bodPositionDecimal`, and `qtyAvailableDecimal`. * The older integer position fields `netPosition`, `qtyBought`, `qtySold`, `bodPosition`, and `qtyAvailable` remain for backward compatibility but are rounded and deprecated for partial-contract markets. `availablePositions` is also deprecated. * **Action recommended:** regenerate clients from the updated OpenAPI schemas and read quantity/tick constraints from each market before submitting orders. Do not assume whole-contract quantities or 1-cent price ticks, and do not rely on server-side rejection for extra decimal precision. * **Two additive fields on the `Order` message:** * `fractional_quantity_scale` (field 49, `int64`) — the fractional quantity scale copied from the instrument at order creation time. Divide raw integer quantities (`order_qty`, `cum_qty`, `leaves_qty`, etc.) by this value to get the properly scaled decimal quantity. * `price_to_quantity_filled` (field 41, `map`) — quantity filled at each price point over the life of the order. The key is the price, the value is the quantity filled at that price. * **Where they appear:** every response that returns an `Order` or an `Execution` (which embeds `Order`), across the Institutional Trading and Report APIs and the gRPC order stream: * Trading API: `GET /v1/trading/orders/open` (`GetOpenOrders`) and the `CreateOrderSubscription` stream (snapshot orders and `update.executions[].order`). * Report API: `POST /v1/report/orders/search` (`SearchOrders`), `GET /v1/report/orders/{order_id}` (`GetOrder`), `POST /v1/report/executions/search` (`SearchExecutions`), and `GET /v1/report/executions/{exec_id}` (`GetExecution`). * **Backward compatible:** both fields are additive. Existing clients are unaffected; unset values decode as the proto defaults (`0` and an empty map). * **Action recommended:** rebuild your gRPC clients from the latest proto bundle to pick up the new fields. * **Partial contracts in preprod:** `aec-mlb-az-mil-2026-06-15` is open in preprod as a dummy partial contract instrument. * Read `instrument.fractionalQtyScale` to determine how submitted integer order quantities are scaled. For example, if `instrument.fractionalQtyScale == 100`, submitting `quantity = 1` means 0.01 contracts, `quantity = 50` means 0.50 contracts, and `quantity = 100` means 1 full contract. * Read `instrument.minimumTradeQty` for the lowest scaled integer quantity that can be traded. For example, if `instrument.minimumTradeQty == 1` and `instrument.fractionalQtyScale == 100`, the minimum valid order quantity is `1`, which represents 0.01 contracts, or 1% of a contract. * Initial partial contract instruments will have `instrument.fractionalQtyScale == 100` and `instrument.minimumTradeQty == 1`, meaning the minimum order size is **1% of a contract**. * **Decimalization in preprod:** dummy instruments are open in preprod for smaller tick-size handling: * `aec-nba-mil-was-2026-06-15` has a **0.5c** tick size. * `aec-nhl-edm-ana-2026-06-15` has a **0.25c** tick size. * Read `instrument.priceScale` to determine how submitted integer order prices are scaled. For example, if `instrument.priceScale == 1000`, submitting `price = 5` means \$0.005, `price = 500` means \$0.50, and `price = 1000` means \$1.00. * Read `instrument.tickSize` for the tick size in dollars. For example, a 0.5c tick size is expressed as `instrument.tickSize = 0.005`, and a 0.25c tick size is expressed as `instrument.tickSize = 0.0025`. * **Action recommended:** read these values from the instrument before submitting orders. Do not infer quantity scale, price scale, or tick size from symbol, product category, or market type. * **New ledger endpoints** for reconciliation, point-in-time replay, and end-of-day reporting: * **Position ledger (REST):** `GET /v1/positions/ledger`, `GET /v1/positions/ledger/download` — paginated query + streamed CSV of position changes (with both deltas and post-change cumulative state). See [Position Ledger](/institutional/positions/overview#position-ledger). * **Balance ledger (REST):** `GET /v1/funding/balance-ledger`, `GET /v1/funding/balance-ledger/download` — paginated query + streamed CSV of cash balance changes (deposits, withdrawals, fills, fees, corrections). See [Balance Ledger](/institutional/funding/overview). * **Balance ledger (gRPC):** `CreateBalanceLedgerSubscription` for real-time push of balance ledger entries. See [Balance Ledger Stream](/streaming-endpoints/balance-ledger-stream). * All three are scoped under `read:positions`. Both ledgers enforce a hard historical floor of **`2026-05-01T00:00:00Z`**; pre-floor entries are not retrievable. * **`InstrumentStats` additions** on the market data stream and `GetOrderBook` / `GetBBO` responses: * `last_trade_qty` (field 14, `optional int64`) — quantity of the most recent trade. Populated after any trade executes on the instrument. * `settlement_set_time` (field 15, `optional google.protobuf.Timestamp`) — timestamp when the settlement price was set. Populated only when the instrument is in a settled state. * **New `KeepAliveCommand`** on `BiDirectionalStreamMarketDataRequest` (field `keepalive = 7`). Sending one puts a client-to-server frame on the wire without modifying subscription state; the server returns no response. Solves the AWS Application Load Balancer 1-hour idle timeout (`RST_STREAM`) for long-lived bidirectional subscriptions with no client-to-server traffic. Recommended cadence: every **30–60 minutes** (well below the 3600s ALB timeout). Only applies to `BiDirectionalStreamMarketData`; server-streaming `CreateMarketDataSubscription` is not affected. * **Stream limits relaxed:** the per-firm cap is now **20 concurrent streams** with no per-stream-type restrictions. Previously, some stream types had individual caps; now the 20-stream budget is pooled across all gRPC subscriptions. * **Action recommended:** rebuild your gRPC clients from the latest proto bundle to pick up the new endpoints and additive fields above. * **Portfolio Activities API:** added two activity types now returned by `GET /v1/portfolio/activities`: * `ACTIVITY_TYPE_TAKER_FEE_REBATE` — taker fee rebate credit. Previously surfaced under `ACTIVITY_TYPE_REFERRAL_BONUS`. * `ACTIVITY_TYPE_LIQUIDITY_PROGRAM` — liquidity program payout. Previously surfaced under `ACTIVITY_TYPE_TRANSFER`. * Both carry an `accountBalanceChange` payload identical in shape to other balance-change activities. * Clients that have not regenerated against the updated OpenAPI schema will decode the new values as unknown enum members. Regenerate to surface the proper label. * **Volume Incentive Program is now live:** Program status moved from coming soon to open, with rewards based on share of eligible **taker-side notional** volume. * **Increase / new reward launch:** Added **NBA Playoffs Moneyline Volume Rewards** with a **\$100,000 in-game reward pool per market** (live May 21, 2026). * **Volume eligibility details:** only trades executed between **\$0.03 and \$0.97** count; minimum **\$500 notional** required to qualify for payout. * **Reduction — MLB Futures:** reduced from **\$5,000/day** (pooled across instruments) to **\$1,000/day**. * **Reduction — IPL Games:** reduced from **\$40,000/game** to **\$20,000/game**; moneyline split updated to **\$500 / \$1,500 / \$18,000** (Early / Day-of / Live). * **Reduction — Politics events:** reduced from **\$5,000/day** to **\$1,000/day**. * **NBA Props (production):** Basketball player props and first half markets are going live in production the morning of **May 22, 2026**. The `market_sport_type` enums previously released to preprod will be active in production: * `basketball_player_points` * `basketball_player_assists` * `basketball_team_first_half_winner` * `basketball_team_first_half_spread` * `basketball_team_first_half_total` * **Tick size — always read from the instrument, not the contract type:** Do not assume that every instrument under a given contract type shares the same minimum price increment. Notably, **upcoming World Cup futures are Title Event Contracts (TEC) but will not be decimalized**, so they will not share a tick size with existing TEC futures. Pull the tick from the instrument before submitting any order. * **Retail API** — read `market.orderPriceMinTickSize` from `GET /v1/market/slug/{slug}`. * **Institutional API** — read `instrument.tickSize` from the instrument reference data response (`SearchInstruments` / `GetInstrument`). * **Retail API:** Removing usernames from trade tape responses * **NBA Props (preprod):** Added the following enums to the instrument `market_sport_type` field: * `basketball_player_points` * `basketball_player_assists` * `basketball_team_first_half_winner` * `basketball_team_first_half_spread` * `basketball_team_first_half_total` * **Execution responses:** Now exposing commission and trade date fields on all execution-level responses: * `commissionNotionalCollected` - Commission amount collected * `commissionSpreadPx` - Commission spread price * `transactTradeDate` - Trade transaction date * Applies to: SearchExecutions, DownloadExecutions, and CreateOrderSubscription execution updates * **Retail Orders API:** documented three batched endpoints: `POST /v1/orders/batched`, `/v1/orders/batched/cancel`, `/v1/orders/batched/modify`. The first two were already shipped; the third is new. * **Retail Orders API:** documented `outcomeSide` + `action` as an alternative to `intent` on `CreateOrderRequest`, and added both fields to the `Order` response. `intent` is no longer marked `required` on `CreateOrderRequest`. Existing requests that send `intent` keep working; regenerated clients will see it flip from required to optional. * **Retail Orders API:** added enum members that were already on the wire but missing from the schema: `TIME_IN_FORCE_DAY`, `ORDER_STATE_NEW`, `EXECUTION_TYPE_NEW`, `ORD_REJECT_REASON_EXCHANGE_OPTION`. * Corrected production gRPC endpoint from `grpc-api.polymarketexchange.com` to `grpc-api.prod.polymarketexchange.com` * Weekly maintenance window moved from **Tuesday 4am–6am ET** to **Thursday 6am–8am ET**, effective April 16, 2026 * Updated rate limits across all APIs: * Institutional Gateway (REST/gRPC): reduced to **100 messages per second** per firm * FIX Protocol: reduced to **150 messages per second** per session (all participants) * Retail API: reduced to **20 requests per second** per API key * FIX API: `Product` field (tag 460) changed from required to optional on New Order Single. All current products on Polymarket are `Product=12` (OTHER). * Corrected REST API routes: `/v1/accounts/whoami` → `/v1/whoami`, `/v1/accounts/users` → `/v1/users`, `/v1/accounts/accounts` → `/v1/accounts` * Fixed price scale examples across documentation to reflect correct values * Corrected production API base URLs to `api.prod.polymarketexchange.com` across all documentation * Edited proto files to improve the gRPC streaming experience * `state` field changed from required to optional in three messages: * `MarketDataUpdate.state` (field 4) in `marketdatasubscription.proto` * `GetOrderBookResponse.state` (field 4) in `orderbook.proto` * `GetBBOResponse.state` (field 6) in `orderbook.proto` * Participants should utilize the instrument state change subscription for state changes * Updated settlement responses in `marketdatasubscription`, adding `settlement_price_calculation_text` * Added `price_scale` to order message * Added Bidirectional Market Data Streaming API: `BiDirectionalStreamMarketData` RPC * Dynamically add and remove symbols during subscription lifetime without reconnecting * New response types: `SubscriptionAck` and `SubscriptionError` for subscription management * Updated client sample code with new Go and Python examples (Example 20) * Updated proto packages with bidirectional streaming support * Added Account Valuation APIs for book-close accounting use cases * `POST /v1/valuations/accounts/statement/download`: Multi-account summaries as CSV * All new endpoints support historical queries via `as_of_time` or `as_of_date` * Cross-ISV protection enforced on all valuation endpoints * Documented configurable instrument queries: pagination, state filtering, and metadata filters * Added sports league filtering via `metadata.sports_game_league` (nfl, nba, mlb, nhl, cbb, cfb) * Added instrument metadata field documentation with sports-specific attributes * Added Historical Positions API: query positions at any point in time using `as_of_time` (RFC3339 timestamp) or `as_of_date` (trade date) * Use cases: end-of-day reporting, regulatory snapshots, position reconciliation * Documentation deployment refresh * Added slow consumer handling option for streaming endpoints with skip-to-head behavior * Proto files now available for direct download (polymarket-protos.zip) * Added FAQ clarifying ISV-Participant relationship and participant\_id usage * Added 25 REST API endpoints with full OpenAPI documentation * New sections: Authentication, Accounts, Orders, Positions, Market Data, Drop Copy * Organized API documentation by functional category * Added complete gRPC streaming API documentation with Python code examples for market data and order execution streams * Introduced Protocol Buffer reference documentation with detailed message structures and field definitions * Added VPC connection setup guide with AWS PrivateLink configuration instructions * Created common pitfalls troubleshooting guide for integration issues * Aesthetic changes including new figures and cleaner formatting of FIX examples. * First DRAFT of Polymarket Exchange Documentation # Events & Markets Source: https://docs.polymarket.us/concepts/events-and-markets Understanding how prediction markets are structured on Polymarket US. Every prediction on Polymarket US is structured around three levels: **series**, **events**, and **markets**. Understanding how they relate is essential for finding what you want to trade. ## Series A series is a broad grouping of related events — like a sports league or a season. Think of it as a folder that contains many games or occurrences. **Examples:** NFL 2025-26 Season, NBA 2025-26 Season, March Madness 2026 ## Events An event is a specific occurrence within a series — usually a single game, match, or contest. Each event has a start time, participants, and one or more markets attached to it. **Examples:** Chiefs vs Eagles — Feb 9, 2026, Lakers vs Celtics — Jan 15, 2026 ## Markets A market is the actual thing you trade. It's a single yes/no question about an event. Each market settles at \$1.00 if the outcome happens and \$0.00 if it doesn't. One event can have multiple markets. For example, a single NFL game might have: | Market type | Question | Example | | ------------- | ----------------------------------------- | ---------------------- | | **Moneyline** | Who wins? | Will the Chiefs win? | | **Spread** | Will they win by more than X points? | Chiefs -3.5 | | **Total** | Will the combined score be over/under X? | Total points over 47.5 | | **Prop** | Will a specific thing happen in the game? | Mahomes over 2.5 TDs | ``` Series: NFL 2025-26 Season └── Event: Chiefs vs Eagles — Feb 9, 2026 ├── Market: Will the Chiefs win? (moneyline) ├── Market: Chiefs -3.5 (spread) └── Market: Total points over 47.5 (total) ``` ## Market slugs Every market has a **slug** — a URL-friendly identifier like `aec-nfl-kc-phi-2026-02-09`. This is what you use everywhere: placing orders, fetching order books, subscribing to WebSocket streams. You can find slugs by searching or browsing markets through the API. ## Live sports data For sports events that are in progress, you get real-time metadata like the current score, period, and whether the game has ended. This is useful if you're building applications that react to live game state. # Prices & Market Data Source: https://docs.polymarket.us/concepts/market-data How prices work and how the order book enables trading on Polymarket US. ## Prices are probabilities Every contract on Polymarket US is priced between \$0 and \$1. The price represents the market's collective belief about how likely an outcome is. | Price | What it means | | ------ | ---------------------------------------------- | | \$0.25 | The market thinks there's roughly a 25% chance | | \$0.50 | Coin flip — the market is undecided | | \$0.75 | The market thinks it's likely (75% chance) | If you buy a YES contract at \$0.55 and the outcome happens, the contract settles at \$1.00 — you profit \$0.45 per contract. If it doesn't happen, it settles at \$0.00 and you lose your \$0.55. ## The order book Polymarket US runs a **central limit order book**. Prices aren't set by Polymarket — they come from traders placing buy and sell orders against each other. The order book has two sides: | Side | What it means | | ----------------- | ------------------------------------------------------ | | **Bids** | Buy orders — the prices traders are willing to pay | | **Asks (offers)** | Sell orders — the prices traders are willing to accept | The **spread** is the gap between the best bid and the best ask. A tight spread means the market is liquid. A wide spread means fewer people are trading. The **BBO (best bid and offer)** is the tightest price on each side — the highest bid and the lowest ask. This is the price you'd trade at if you placed a market order right now. ## How trades happen When you place a **market order**, it fills immediately at the best available price on the other side of the book. You're a **taker** — you're taking liquidity. When you place a **limit order** at a specific price and it doesn't fill immediately, it sits on the book waiting. You're a **maker** — you're providing liquidity for others to trade against. If your limit order's price crosses the best available price on the other side, it fills immediately (like a market order). Otherwise, it rests on the book. ## Settlement When a market resolves, every contract settles at either \$1.00 (YES won) or \$0.00 (NO won). The exchange handles this automatically — winning contracts are credited to your balance, losing contracts go to zero. ## Market states A market goes through different states during its lifecycle: | State | What it means | | ------------- | --------------------------------------------------- | | **Open** | The market is accepting orders and actively trading | | **Pre-open** | The market exists but trading hasn't started yet | | **Suspended** | Trading is temporarily paused | | **Halted** | Trading has been stopped | | **Expired** | The market has ended and settled | # Orders & Trading Source: https://docs.polymarket.us/concepts/orders How trading works on Polymarket US — the YES/NO model, order types, and how positions work. ## The YES/NO model Every market on Polymarket US has two sides: **YES** and **NO**. They always add up to \$1.00. If YES is priced at \$0.60, NO is priced at \$0.40. You don't trade YES and NO as separate things. There's only one instrument per market — the YES side. To trade against an outcome, you **sell** YES (which is the same as buying NO). | What you want to do | How you do it | | -------------------------------------- | ---------------------------------- | | Trade on the outcome **happening** | Buy YES | | Trade on the outcome **not happening** | Sell YES (equivalent to buying NO) | | Close a winning YES position | Sell YES | | Close a losing NO position | Buy YES back | This matters because when you place an order, the price always refers to the YES side. If you want to buy NO at \$0.40, you're really selling YES at \$0.60 — the system handles this, but you need to understand it to set the right price. ## Order types | Type | How it works | | ---------------- | ---------------------------------------------------------------------------------------------- | | **Limit order** | You set a price. The order sits on the book until someone trades against it, or you cancel it. | | **Market order** | Fills immediately at the best available price. You get instant execution but pay the spread. | Most traders use limit orders. Market orders are useful when you need to get in or out quickly and don't mind paying a slightly worse price. ## Time in force When you place a limit order, you choose how long it stays active: | Option | What it means | | ----------------------------- | ------------------------------------------------------------ | | **Good till cancel (GTC)** | Stays open until it fills or you cancel it | | **Good till date (GTD)** | Stays open until a specific time, then cancels automatically | | **Immediate or cancel (IOC)** | Fills whatever is available right now, cancels the rest | | **Fill or kill (FOK)** | Must fill completely or not at all — no partial fills | ## What happens after you place an order Your order goes through a lifecycle: 1. **Pending** — the exchange has received your order 2. **Open** — it's resting on the book, waiting for a match 3. **Partially filled** — some of your order has matched, the rest is still open 4. **Filled** — your entire order has matched 5. **Canceled / Expired / Rejected** — the order didn't fill ## Positions Once your order fills, you have a **position**. A position is simply the contracts you hold in a market. * A **long position** means you own YES contracts — you profit if the outcome happens * A **short position** means you've sold YES contracts — you profit if the outcome doesn't happen Your **buying power** is the cash you have available to open new positions. When you buy contracts, your buying power decreases. When you sell or a market settles in your favor, it increases. ## Closing a position You can close a position at any time by taking the opposite action: * If you're long (bought YES), sell YES to close * If you're short (sold YES), buy YES to close You don't have to wait for the market to settle. If the price has moved in your favor, you can lock in a profit early. # Asset Master Source: https://docs.polymarket.us/data-guide/asset-master Hierarchical structure of instruments on the Polymarket Exchange Every instrument on the Polymarket Exchange follows a hierarchical structure that organizes markets from broad categories down to specific tradable outcomes. ## Ontology Overview The structure is: `Category → Subcategory → Series → Event → Product → Instrument(s)` * **Category, Subcategory, Series** classify events (e.g., Sports → Soccer → MLS) * **Events** represent real-world occurrences (games, elections, price movements) * **Products** are templates that define different ways to trade those events * **Instruments** are the specific tradable outcomes A single event can have multiple products applied to it, creating different groups of instruments. **Example:** * Event: `mls-atl-clt-2026-03-22` (Atlanta United vs Charlotte FC) * Products applied: ATC (3-way outcome), ASC (spreads), TSC (totals) * Each product creates its own set of instruments for the same underlying event ## Complete Example Here's a real CBB moneyline instrument showing the complete structure with all metadata: ```json theme={null} { "id": "aec-cbb-char-hamp-2026-02-26", "product_id": "aec-cbb-char-hamp-2026-02-26", "description": "Who will win the college basketball event, scheduled for 2026-02-26, Charleston or Hampton?", "price_scale": 100, "attributes": { "tick_size": 0.01, "minimum_trade_qty": 1, "start_date": "2026-02-05", "expiration_date": "2026-03-12", "expiration_time": "12:00:00", "last_trade_date": "2026-03-12", "last_trade_time": "12:00:00", "price_limit": { "low": 1, "high": 99, "low_set": true, "high_set": true }, "base_currency": "USD", "multiplier": 1, "clearing_house": "QCC", "cfi_code": "OMXXXX", "settlement_price_logic": "SETTLEMENT_PRICE_LOGIC_EVENT", "trade_day_roll_schedule": { "days_of_week": ["MONDAY", "TUESDAY", "WEDNESDAY", "THURSDAY", "FRIDAY", "SATURDAY", "SUNDAY"], "time_of_day": "17:00:00" } }, "metadata": { "cftc_instrument_id": "aec-cbb-char-hamp-2026-02-26", "clearing_sym": "AEC-NCAA", "event_category": "SPR", "event_subcategory": "ncaa", "event_series": "cbb", "event_id": "cbb-char-hamp-2026-02-26", "event_start_time": "2026-02-26 16:00:00+00", "instrument_product": "aec", "instrument_product_series": "aec-cbb", "product_id": "aec-cbb-char-hamp-2026-02-26", "instrument_rules": "Who will win the upcoming college basketball event, scheduled for February 26 at 11:00AM ET, Charleston or Hampton? In the case of a tie or draw, the Exchange may, in its sole discretion, settle the instrument as it deems fair and appropriate (e.g., at last-traded prices, $0.50 per instrument, or other fair and equitable valuations). If the event is postponed, delayed, or rescheduled, the settlement date will be amended to the rescheduled date. If the rescheduled date is not within two weeks of the originally scheduled date, the Exchange may, in its sole discretion, settle the instrument as it deems fair and appropriate. Should the event be rescheduled to an earlier date than originally scheduled, the settlement date will be the rescheduled date. If the event is canceled, the Exchange may, in its sole discretion, settle the instrument as it deems fair and appropriate. If the event concludes early, or is shortened or truncated, the outcome shall be the declared official result. In the case of a withdrawal, walkover, forfeit, no-contest, or the removal of a participant in the event, the Exchange may, in its sole discretion, settle the instrument as it deems fair and appropriate.", "participant_type": "team", "long_participant_id": "cbb-char", "long_participant_name": "Charleston", "short_participant_id": "cbb-hamp", "short_participant_name": "Hampton", "outcome_type": "moneyline", "outcome_strike": "char", "event_external_id_sportsdataio": "60068117", "event_external_id_sportradar": "5c486835-c9a3-423a-b47a-c3fc48fda799" }, "event_attributes": { "position_accountability_value": 50000, "payout_value": 100, "question": "CBB 2026 Moneyline Game - Charleston vs Hampton 2026-02-26", "event_display_name": "Charleston vs. Hampton 2026-02-26", "event_id": "aec-cbb-char-hamp-2026-02-26", "strike_value": "0.0", "evaluation_type": "==", "strike_unit": "decimal", "calculation_method": "CALCULATION_METHOD_VALUE", "time_specifier": "2026-02-26" } } ``` This example shows how all levels (Category → Subcategory → Series → Event → Product → Instrument → Participants → Outcome) come together in a single tradable instrument. ## Event Events represent specific real-world occurrences (games, elections, price movements). Each event is classified by category, subcategory, and series, and can have multiple products applied to create different groups of instruments. **Event ID Format:** `{series}-{descriptors}-{date/time}` Where descriptors vary by event type: * **Sports**: `mls-atl-clt-2026-03-22` * **Politics**: `uspres-2028-11-05` * **Crypto**: `btc-hit-2026-12-31` * **Culture**: `oscars-2027-03-28` **Metadata Fields:** * `event_id` - Unique event identifier (e.g., `nfl-hou-mia-2025-12-16`) * `event_start_time` - Event start timestamp (UTC) * `event_category` - Category code (e.g., `SPR`, `POL`, `CRY`, `FIN`) * `event_subcategory` - Subcategory code (e.g., `football`, `soccer`, `coin`, `uspres`) **Example:** Event `mls-atl-clt-2026-03-22` with multiple products applied: * ATC → 3 instruments (atl, draw, clt) * ASC → multiple instruments (various spreads) * TSC → multiple instruments (various totals) ### Classification Reference **Categories:** * **SPR** - Sports * **POL** - Politics * **CRY** - Crypto * **CUL** - Culture * **FIN** - Finance * **MAC** - Macro * **CLI** - Climate * **GEO** - Geopolitics * **TECH** - Technology * **MEN** - Mentions **Subcategories by Category:** * **SPR**: soccer, football, basketball, baseball, combat, tennis, icehockey, ncaa, cricket, esports, golf, motorsport, olympics, rugby * **POL**: uspres, ushse, usstate, ussen, intlpol, legislate, cabinet, fedagency, uscrt * **CRY**: coin, cryptomkt, nft * **CUL**: movies, music, tv, video, awd, people, entertain * **FIN**: indices, forex, treasuries, bankruptcy, corpaction, earnings, ipo, commod * **CLI**: weather, climate, geological, health, space * **GEO**: namer, eur, apac, mena, latam, ssa, conflict, unitednat * **MAC**: growth, inflation, monetary, employment, fiscal * **TECH**: ai, autonom, mobile, platforms, social, compute, cybersec, industry, wear * **MEN**: statement, pressconf, earncall **Series by Subcategory:** * **soccer**: epl, laliga, seriea, bund, ligue1, ucl, uel, facup, cara, wcup * **football**: nfl * **basketball**: nba * **baseball**: mlb * **ncaa**: cfb, cbb * **coin**: btc, eth, sol, xrp, bnb, doge, usdc, usdt * **uspres**: usp, prm, pres ## Product Products are templates that define types of tradable outcomes. The same product can be applied to events across different series — products define *how* you trade an event, not *what* event you're trading. **Metadata Fields:** * `instrument_product` - Product code (e.g., `aec`, `asc`, `atc`, `tsc`) * `instrument_product_series` - Combined product and series (e.g., `aec-nfl`, `atc-mls`). This is the canonical field for identifying the series of an instrument. Use this field for filtering by series rather than `event_series` **Market Structures:** * **Single (Binary)** - One instrument representing yes and no outcomes * **Group (Exclusive)** - Multiple instruments, where only one outcome can be true * **Group (Directional)** - Multiple instruments, where multiple can be true, and the outcomes sit in directional relation * **Group (Independent)** - Multiple instruments, where multiple can be true, but the outcomes do not sit in directional relation ### Example Products | Product | Code | Type | Example | Description | | --------------- | ---- | ------------------- | ------------------------------------- | -------------------------------------- | | Athletic Event | AEC | Single (Binary) | `aec-nfl-buf-nyj-2025-01-15` | Moneyline: Will team A win? | | Athletic Tie | ATC | Group (Exclusive) | `atc-mls-atl-clt-2026-03-22-draw` | 3-way: Team A, Draw, or Team B wins | | Athletic Spread | ASC | Group (Directional) | `asc-nfl-hou-mia-2025-12-16-pos-4pt5` | Will team A win by more than X points? | | Total Score | TSC | Group (Directional) | `tsc-nfl-ne-den-2026-01-25-47-5` | Will combined score be over X? | | Title Event | TEC | Group (Exclusive) | `tec-ggb-bmpd-2026-01-11` | Will participant win title? | | Title Award | TAC | Group (Exclusive) | `tac-ggb-bmpd-2026-01-11-sinners` | Which nominee will win award? | | Election Winner | EWC | Group (Exclusive) | `ewc-usp-pres-2028-11-07` | Which candidate will win election? | | Crypto Price | CPC | Single (Binary) | `cpc-btc-2026-12-31` | Price movement in period? | ### Product Reusability The same product can create instruments across different series: **ATC (Athletic Tie Contract)** - 3-way match outcome: * `atc-mls-atl-clt-2026-03-22-draw` (MLS) * `atc-epl-liv-mci-2026-01-15-draw` (EPL) * `atc-ucl-bar-psg-2026-04-20-draw` (UCL) **ASC (Athletic Spread Contract)** - Point spreads: * `asc-nfl-hou-mia-2025-12-16-pos-4pt5` * `asc-nba-bos-lal-2026-01-20-pos-6pt5` * `asc-cbb-duke-unc-2026-02-15-pos-3pt5` ## Instrument Instruments are specific tradable outcomes created by applying a Product to an Event. Each instrument has a globally unique ID that combines the product code, event details, and specific outcome/strike. **Instrument ID Format:** `{product_code}-{event_id}-{strike/outcome}` **Metadata Fields:** * `instrument_rules` - Resolution rules specific to this instrument **Examples:** * `aec-nfl-buf-nyj-2025-01-15` - Moneyline (no additional outcome specified) * `atc-mls-atl-clt-2026-03-22-draw` - 3-way outcome: draw * `asc-nfl-hou-mia-2025-12-16-pos-4pt5` - Spread: 4.5 points * `tsc-nfl-ne-den-2026-01-25-47-5` - Total: 47.5 points ### Participants and Outcome Each instrument has participants representing the possible outcomes. The long participant represents the "Yes" outcome that traders buy and sell. This is surfaced in instrument metadata: * `participant_type` - Type of participant (team, player, nominee, candidate, etc.) * `long_participant_id` - Globally unique ID for the long side (e.g., `cbb-akron`, `nfl-buf`) * `long_participant_name` - Full display name for the long side (e.g., "Akron", "Buffalo Bills") * `short_participant_id` - Globally unique ID for the opposing outcome (e.g., `cbb-murst`, `nfl-nyj`) * `short_participant_name` - Full display name for the opposing outcome (e.g., "Murray State", "New York Jets") Instruments are traded by buying and selling the long participant (the "Yes" outcome). For example, in an NFL moneyline contract like `nfl-hou-mia-2025-12-16`, traders buy and sell HOU. Buying HOU means taking a long position on Houston winning, while selling HOU creates a synthetic long position on Miami. There is no direct way to trade the short participant - all positions on the opposing outcome are achieved synthetically by selling the long participant. When you sell (short) an instrument, the cash flows differ from buying. Selling 10 contracts at \$0.60 means you receive \$6 from the buyer, but a margin requirement equal to the maximum payout (\$10 in this case) is imposed on your account. Therefore, you need \$4 in available funds to enter this short position (\$10 margin requirement minus \$6 received). This margin requirement ensures you can cover the full payout if the outcome occurs. # Naming Conventions Source: https://docs.polymarket.us/data-guide/asset-naming-conventions ID formats and naming patterns for instruments and events IDs follow consistent patterns based on the hierarchy: ## Complete Hierarchy ``` Category → Subcategory → Series → Event + Product → Instrument ``` **Example:** ``` SPR → SOCCER → MLS → mls-atl-clt-2026-03-22 + ATC → atc-mls-atl-clt-2026-03-22-draw ``` ## Event ID Format Events are identified by series, descriptors, and date/time: ``` {series}-{descriptors}-{date/time} ``` **Sports**: * `nfl-ne-den-2026-01-25` * `mls-atl-clt-2026-03-22` **Politics**: * `uspres-2028-11-05` * `ussen-2026-11-03` **Crypto**: * `btc-hit-2026-12-31` * `eth-hit-2027-06-30` **Culture**: * `oscars-2027-03-28` * `grammys-2027-02-14` ## Instrument ID Format Instruments combine product, event, and outcome: ``` {product_code}-{event_id}-{strike/outcome} ``` **Examples:** * `aec-nfl-ne-den-2026-01-25` - Moneyline (no additional outcome) * `atc-mls-atl-clt-2026-03-22-draw` - 3-way outcome: draw * `atc-mls-atl-clt-2026-03-22-atl` - 3-way outcome: Atlanta wins * `asc-nfl-hou-mia-2025-12-16-pos-4pt5` - Spread: 4.5 points * `tsc-nfl-ne-den-2026-01-25-47-5` - Total: 47.5 points ## Participant ID Format ``` {series}-{abbreviation} ``` **Examples:** * `nfl-ne` (New England) * `nfl-den` (Denver) * `nba-bos` (Boston Celtics) * `usp-harris` (Kamala Harris) Participant abbreviations are standardized across all instruments in a series to enable consistent querying and position tracking. ### Participant Lookup API Participant abbreviations and display names can be looked up via the public teams endpoint: ```bash theme={null} GET https://gateway.polymarket.us/v1/sports/teams?limit=500&filters.league={series} ``` **Example:** Fetch all MLS teams: ```bash theme={null} GET https://gateway.polymarket.us/v1/sports/teams?limit=500&filters.league=mls ``` **Response:** ```json theme={null} { "teams": [ { "id": "2263", "name": "Chicago Fire FC", "abbreviation": "chi", "league": "mls", "displayAbbreviation": "CHI", "alias": "The Fire", "logo": "https://polymarket-upload.s3.us-east-2.amazonaws.com/us-logos/MLS/Team%3DChicago+Fire+FC.png", "colorPrimary": "#5FC1EA", "record": "0-0-0", "providerIds": [ {"provider": "PROVIDER_SPORTRADAR", "providerId": "sr:competitor:2505"}, {"provider": "PROVIDER_SPORTSDATAIO", "providerId": "694"} ] } ] } ``` **Mapping to instrument metadata:** | Teams endpoint | Instrument metadata | Example | | --------------------------- | ----------------------------------------------------------------------------- | --------------------------------------- | | `abbreviation` | `long_participant_id` / `short_participant_id` (as `{league}-{abbreviation}`) | `chi` → `mls-chi` | | `name` | `long_participant_name` / `short_participant_name` | `Chicago Fire FC` | | `league` | `instrument_product_series` (series component) | `mls` (from `aec-mls`, `atc-mls`, etc.) | | `providerIds[SPORTSDATAIO]` | `event_external_id_sportsdataio` | `694` | | `providerIds[SPORTRADAR]` | `event_external_id_sportradar` | `sr:competitor:2505` | The endpoint also provides display data not present in instrument metadata: `logo`, `colorPrimary`, `alias`, `displayAbbreviation`, and `record`. ## Uniqueness Rules Each level of the hierarchy has specific uniqueness scoping: | Level | Uniqueness Scope | Example | | --------------- | --------------------------------------------------------- | ---------------------------------- | | **Category** | Globally unique | SPR, POL, CRY | | **Subcategory** | Unique within Category | SOCCER within SPR, COIN within CRY | | **Series** | Unique within Subcategory | MLS within SOCCER, BTC within COIN | | **Event** | Unique within Series (globally unique with series prefix) | `mls-atl-clt-2026-03-22` | | **Product** | Globally unique by Code | ATC, AEC, ASC | | **Instrument** | Globally unique | `atc-mls-atl-clt-2026-03-22-draw` | **Key Points:** * Events belong to exactly one Series * Products are independent and can be applied to events across any Series * Instruments are globally unique combinations of Product + Event + Outcome ## Multi-Product Events A single event can have multiple products applied to it, each creating its own set of instruments. This allows traders to speculate on the same event in different ways. **Hierarchy:** ``` SPR → FOOTBALL → NFL → nfl-ne-den-2026-01-25 ``` **Event:** `nfl-ne-den-2026-01-25` (New England vs Denver on Jan 25, 2026) **Products Applied:** 1. **AEC (Athletic Event Contract)** - Single Binary * Instrument: `aec-nfl-ne-den-2026-01-25` * Question: "Will New England win?" * Outcome: moneyline, strike: 0.0 2. **ASC (Athletic Spread Contract)** - Group Directional * Instrument: `asc-nfl-ne-den-2026-01-25-pos-3pt5` * Question: "Will New England win by more than 3.5 points?" * Outcome: spread, strike: 3.5 3. **TSC (Total Score Contract)** - Group Directional * Instrument: `tsc-nfl-ne-den-2026-01-25-47-5` * Question: "Will total points be over 47.5?" * Outcome: total, strike: 47.5 All three instruments reference the same `event_id` but have different product codes, `outcome_type`, and `outcome_strike` values. ### Soccer Example with 3-Way Markets **Hierarchy:** ``` SPR → SOCCER → MLS → mls-atl-clt-2026-03-22 ``` **Event:** `mls-atl-clt-2026-03-22` (Atlanta United vs Charlotte FC) **Products Applied:** 1. **ATC (Athletic Tie Contract)** - Group Exclusive (3-way) * `atc-mls-atl-clt-2026-03-22-atl` (Atlanta wins) * `atc-mls-atl-clt-2026-03-22-draw` (Draw) * `atc-mls-atl-clt-2026-03-22-clt` (Charlotte wins) 2. **AEC (Athletic Event Contract)** - Single Binary * `aec-mls-atl-clt-2026-03-22` (Will Atlanta win?) 3. **TSC (Total Score Contract)** - Group Directional * `tsc-mls-atl-clt-2026-03-22-2-5` (Over 2.5 goals) * `tsc-mls-atl-clt-2026-03-22-3-5` (Over 3.5 goals) The same event supports both 3-way markets (ATC) and binary markets (AEC), plus totals (TSC). ## Complete Metadata Reference All metadata fields available on instruments: | Field | Level | Required | Type | Example | Description | | -------------------------------- | ----------- | -------- | --------- | ----------------------------- | --------------------------------------------------------------------------------- | | `cftc_instrument_id` | Instrument | Yes | String | `"aec-nfl-ne-den-2026-01-25"` | CFTC registered instrument ID | | `clearing_sym` | Instrument | Yes | String | `"AEC-NFL"` | Clearing symbol prefix | | `event_category` | Category | Yes | String | `"SPR"` | Category code (SPR, POL, CRY, etc.) | | `event_series` | Series | Yes | String | `"nfl"` | Series code within category | | `instrument_product` | Product | Yes | String | `"aec"` | Product type code | | `instrument_product_series` | Product | Yes | String | `"aec-nfl"` | Combined product and series | | `product_id` | Product | Yes | String | `"aec-nfl-ne-den-2026-01-25"` | Product identifier | | `event_id` | Event | Yes | String | `"nfl-ne-den-2026-01-25"` | Unique event identifier | | `event_start_time` | Event | Yes | Timestamp | `"2026-01-25 20:00:00+00"` | Event start time (UTC) | | `event_external_id_sportsdataio` | Event | No | String | `"19449"` | SportsDataIO ID | | `event_external_id_sportradar` | Event | No | String | `"5848514c-..."` | Sportradar ID | | `instrument_rules` | Instrument | Yes | String | `"Who will win..."` | Instrument-specific rules | | `participant_type` | Participant | Yes | String | `"team"` | Type: team, player, nominee, etc. | | `long_participant_id` | Participant | Yes | String | `"nfl-ne"` | Long side participant ID | | `long_participant_name` | Participant | Yes | String | `"New England"` | Long side display name | | `short_participant_id` | Participant | Yes | String | `"nfl-den"` | Short side participant ID | | `short_participant_name` | Participant | Yes | String | `"Denver"` | Short side display name | | `outcome_type` | Outcome | Yes | String | `"moneyline"` | Outcome type | | `outcome_strike` | Outcome | Yes | String | `"ne"` | Strike value (participant abbreviation for moneyline, numeric for spreads/totals) | ### Attributes vs Metadata vs Event Attributes **Attributes** contain trading and settlement parameters: * `tick_size` - Minimum price increment * `minimum_trade_qty` - Minimum order size * `price_limit` - Price bounds (low, high, low\_set, high\_set) * `expiration_date` - Contract expiration * `expiration_time` - Settlement time * `last_trade_date` - Final trading day * `last_trade_time` - Final trading time * `base_currency` - Settlement currency * `clearing_house` - Clearing organization * `cfi_code` - Classification of Financial Instruments code * `settlement_price_logic` - Settlement logic type * `trade_day_roll_schedule` - Trading day rollover schedule **Metadata** contains descriptive and reference information: * `cftc_instrument_id` - CFTC registered instrument identifier * `clearing_sym` - Clearing symbol prefix * Event details (category, series, participants) * Resolution rules * External data provider IDs * Display names and formatting **Event Attributes** contain event-specific trading parameters: * `position_accountability_value` - Position limit threshold * `payout_value` - Contract payout amount * `question` - Human-readable question * `event_display_name` - Formatted display name for the event * `event_id` - Event identifier * `strike_value` - Strike value for the outcome * `evaluation_type` - Comparison operator for resolution * `strike_unit` - Unit type for strike (string, decimal) * `calculation_method` - Settlement calculation method * `time_specifier` - Date for event occurrence ### External Data Provider IDs External IDs enable integration with third-party data sources: **`event_external_id_sportsdataio`** * SportsDataIO event identifier * Used for real-time scores and statistics * Example: `"19449"` **`event_external_id_sportradar`** * Sportradar event identifier (UUID format) * Used for official data feeds and settlement * Example: `"5848514c-3977-4aa3-9db0-94ed5d0ebb34"` These IDs allow automated resolution based on official data provider results. ## Common Query Patterns Use metadata fields to filter and find specific instruments: ### Filter by Series Use `instrument_product_series` for reliable series filtering. This field combines the product code and series (e.g., `aec-nfl`, `atc-mls`) and is consistently populated across all instrument types. Find all NFL moneyline instruments: ``` metadata.instrument_product_series = "aec-nfl" ``` Find all NBA moneyline instruments: ``` metadata.instrument_product_series = "aec-nba" ``` Find all NFL instruments (any product): ``` metadata.instrument_product_series LIKE "%-nfl" ``` ### Filter by Product Type Find all moneyline contracts: ``` metadata.outcome_type = "moneyline" ``` Find all spread contracts: ``` metadata.outcome_type = "spread" ``` ### Filter by Participant Find all instruments involving New England: ``` metadata.long_participant_id = "nfl-ne" OR metadata.short_participant_id = "nfl-ne" ``` ### Filter by Event Find all instruments for a specific game: ``` metadata.event_id = "nfl-ne-den-2026-01-25" ``` ### Combined Filters Find all NFL moneyline contracts: ``` metadata.instrument_product_series = "aec-nfl" ``` Find all spread contracts with New England: ``` metadata.outcome_type = "spread" AND (metadata.long_participant_id = "nfl-ne" OR metadata.short_participant_id = "nfl-ne") ``` # Candlestick Data Source: https://docs.polymarket.us/data-guide/candlestick-data Generate candlestick data for market analysis and charting ## Overview Polymarket Exchange provides multiple methods for obtaining candlestick (OHLC - Open, High, Low, Close) data for market analysis and charting applications. ## Method 1: Pre-Aggregated Statistics (Recommended) Use the REST API's trade statistics endpoint to get pre-calculated OHLC data: ```bash theme={null} POST /v1beta1/report/trades/stats ``` **Request Example:** ```json theme={null} { "symbol": "aec-nfl-buf-kc-2026-01-26", "start_time": "2026-01-25T00:00:00Z", "end_time": "2026-01-26T23:59:59Z", "interval": "1h" } ``` **Response Example:** ```json theme={null} { "stats": [ { "interval_start": "2026-01-25T00:00:00Z", "interval_end": "2026-01-25T01:00:00Z", "open": "550", "high": "580", "low": "545", "close": "575", "volume": "15000", "notional": "8625000" } ] } ``` **Benefits:** * Server-side aggregation (faster, more efficient) * Ready-to-use candlestick data * Configurable time intervals * No client-side computation required **Common Intervals:** * `1m` - 1 minute * `5m` - 5 minutes * `15m` - 15 minutes * `1h` - 1 hour * `4h` - 4 hours * `1d` - 1 day ## Method 2: Manual Aggregation from Trade Data Query individual trades and calculate OHLC values client-side: ```bash theme={null} POST /v1beta1/report/trades/search ``` **Request Example:** ```json theme={null} { "symbol": "aec-nfl-buf-kc-2026-01-26", "start_time": "2026-01-25T00:00:00Z", "end_time": "2026-01-26T23:59:59Z", "limit": 1000 } ``` **Response Example:** ```json theme={null} { "trades": [ { "trade_id": "12345", "symbol": "aec-nfl-buf-kc-2026-01-26", "price": "550", "quantity": "100", "timestamp": "2026-01-25T00:05:32Z" }, { "trade_id": "12346", "symbol": "aec-nfl-buf-kc-2026-01-26", "price": "560", "quantity": "200", "timestamp": "2026-01-25T00:12:18Z" } ] } ``` ### Aggregation Logic 1. Query trades for the desired time range 2. Group trades by your chosen interval (e.g., 5min, 1h, 1d) 3. For each interval, calculate: | Metric | Calculation | | ------------ | ---------------------------------------- | | **Open** | First trade price in the interval | | **High** | Maximum trade price in the interval | | **Low** | Minimum trade price in the interval | | **Close** | Last trade price in the interval | | **Volume** | Sum of quantities traded in the interval | | **Notional** | Sum of (price × quantity) for all trades | ### Python Example ```python theme={null} from datetime import datetime, timedelta import requests def aggregate_candles(trades, interval_minutes=5): """ Aggregate trades into OHLC candles. Args: trades: List of trade dicts with 'timestamp', 'price', 'quantity' interval_minutes: Candle interval in minutes Returns: List of OHLC candle dicts """ candles = {} for trade in trades: # Round timestamp down to interval ts = datetime.fromisoformat(trade['timestamp'].replace('Z', '+00:00')) interval_start = ts.replace( minute=(ts.minute // interval_minutes) * interval_minutes, second=0, microsecond=0 ) key = interval_start.isoformat() if key not in candles: candles[key] = { 'open': float(trade['price']), 'high': float(trade['price']), 'low': float(trade['price']), 'close': float(trade['price']), 'volume': 0, 'notional': 0 } # Update candle candle = candles[key] price = float(trade['price']) qty = float(trade['quantity']) candle['high'] = max(candle['high'], price) candle['low'] = min(candle['low'], price) candle['close'] = price # Last trade price candle['volume'] += qty candle['notional'] += price * qty return [{'timestamp': k, **v} for k, v in sorted(candles.items())] # Usage trades = get_trades(symbol="aec-nfl-buf-kc-2026-01-26") candles_5m = aggregate_candles(trades, interval_minutes=5) ``` **When to use:** * Custom aggregation logic needed * Non-standard time intervals * Additional trade metadata required * Complex filtering or weighting logic ## Method 3: Real-Time Streaming Subscribe to market data via gRPC to build live candlestick charts that update as trades occur. ### Subscribe to Market Data ```python theme={null} import grpc from polymarket_pb2 import MarketDataRequest from polymarket_pb2_grpc import MarketDataServiceStub # Create channel with credentials channel = grpc.secure_channel( 'grpc.preprod.polymarketexchange.com:443', grpc.ssl_channel_credentials() ) stub = MarketDataServiceStub(channel) # Subscribe to symbols request = MarketDataRequest( symbols=["aec-nfl-buf-kc-2026-01-26"] ) # Stream updates for update in stub.Subscribe(request, metadata=[('authorization', f'Bearer {token}')]): # Extract last trade price and volume last_price = update.last_px last_volume = update.last_qty timestamp = update.transact_time # Update your current candlestick update_candlestick(last_price, last_volume, timestamp) ``` ### Building Real-Time Candles ```python theme={null} from datetime import datetime, timedelta class RealtimeCandleBuilder: def __init__(self, interval_seconds=60): self.interval_seconds = interval_seconds self.current_candle = None self.candles = [] def on_trade(self, price, volume, timestamp): """Process incoming trade from stream""" interval_start = self._get_interval_start(timestamp) # Start new candle if needed if self.current_candle is None or self.current_candle['start'] != interval_start: if self.current_candle: self.candles.append(self.current_candle) self.current_candle = { 'start': interval_start, 'open': price, 'high': price, 'low': price, 'close': price, 'volume': 0 } # Update current candle self.current_candle['high'] = max(self.current_candle['high'], price) self.current_candle['low'] = min(self.current_candle['low'], price) self.current_candle['close'] = price self.current_candle['volume'] += volume def _get_interval_start(self, timestamp): """Round timestamp down to interval""" ts = datetime.fromisoformat(timestamp.replace('Z', '+00:00')) seconds_since_epoch = int(ts.timestamp()) interval_start = (seconds_since_epoch // self.interval_seconds) * self.interval_seconds return datetime.fromtimestamp(interval_start) # Usage builder = RealtimeCandleBuilder(interval_seconds=300) # 5-minute candles for market_update in stream: builder.on_trade( price=market_update.last_px, volume=market_update.last_qty, timestamp=market_update.transact_time ) # Get current candle for display current = builder.current_candle ``` **Features:** * Real-time price updates * Last trade price and volume * Aggregated statistics (cumulative volume, notional traded) * Best bid/offer data **Use cases:** * Live trading dashboards * Real-time candlestick charts * Automated trading strategies * Market monitoring systems ## Choosing the Right Method | Method | Best For | Latency | Complexity | | ----------------------- | --------------------------------------- | --------- | ---------- | | **Pre-Aggregated** | Historical analysis, standard intervals | Low | Low | | **Manual Aggregation** | Custom intervals, special calculations | Medium | Medium | | **Real-Time Streaming** | Live charts, automated trading | Real-time | High | ## Best Practices ### Time Zones All timestamps are in UTC. Ensure your client handles timezone conversion appropriately: ```python theme={null} from datetime import datetime import pytz # Convert UTC to local time utc_time = datetime.fromisoformat('2026-01-25T00:00:00Z'.replace('Z', '+00:00')) local_time = utc_time.astimezone(pytz.timezone('America/New_York')) ``` ### Price Scaling Prices in the API use a `price_scale` multiplier. Check the instrument's reference data: ```python theme={null} # If price_scale = 1000, then price "550" represents $0.55 actual_price = int(price) / price_scale ``` ### Data Gaps Handle gaps in data gracefully: * Pre-aggregated: Missing intervals indicate no trades occurred * Manual: Empty intervals should show previous close as O/H/L/C * Streaming: Implement reconnection logic for dropped connections ### Caching For historical data: * Cache pre-aggregated candles locally * Only query new intervals since last update * Use `start_time` filters to avoid redundant data # Market Data Source: https://docs.polymarket.us/data-guide/market-data Access real-time and historical market data from the Polymarket Exchange ## Overview Public market data available to all users: * **Best Bid/Offer (BBO)** - Current best prices * **L2 Order Book** - Full depth of market Private trade data (requires account access): * **Trades** - Executed transactions for your account ## Required Scopes | Scope | Data Access | | ------------------- | -------------------------- | | `read:marketdata` | BBO, streaming market data | | `read:l2marketdata` | Full L2 order book depth | ## REST API Endpoints ### Get Best Bid/Offer Retrieve the current best bid and offer for a symbol: ```bash theme={null} curl -X GET "https://api.preprod.polymarketexchange.com/v1/orderbook/{symbol}/bbo" \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" ``` **Response:** ```json theme={null} { "symbol": "aec-nfl-buf-nyj-2025-01-15", "bestBid": { "px": "650", "qty": "1000" }, "bestOffer": { "px": "670", "qty": "500" }, "spread": "20", "midPrice": "660", "state": "INSTRUMENT_STATE_OPEN", "transactTime": "2025-01-15T10:30:00Z" } ``` ### Get L2 Order Book Retrieve the full order book depth: ```bash theme={null} curl -X GET "https://api.preprod.polymarketexchange.com/v1/orderbook/{symbol}" \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" ``` **Response:** ```json theme={null} { "symbol": "aec-nfl-buf-nyj-2025-01-15", "bids": [ {"px": "650", "qty": "1000"}, {"px": "640", "qty": "2000"}, {"px": "630", "qty": "1500"} ], "offers": [ {"px": "670", "qty": "500"}, {"px": "680", "qty": "800"}, {"px": "690", "qty": "1200"} ], "state": "INSTRUMENT_STATE_OPEN", "transactTime": "2025-01-15T10:30:00Z" } ``` For OHLC/candlestick data generation, see the [Candlestick Data Guide](/data-guide/candlestick-data). ## Streaming Market Data (gRPC) For real-time updates, use the gRPC [Market Data Stream](/streaming-endpoints/market-data-stream). ### Subscribe to Market Data ```python theme={null} import grpc from polymarket_pb2 import MarketDataRequest from polymarket_pb2_grpc import MarketDataServiceStub # Create channel with credentials channel = grpc.secure_channel( 'grpc.preprod.polymarketexchange.com:443', grpc.ssl_channel_credentials() ) stub = MarketDataServiceStub(channel) # Subscribe to symbols request = MarketDataRequest( symbols=["aec-nfl-buf-nyj-2025-01-15", "aec-nba-bos-lal-2025-01-20"] ) # Stream updates for update in stub.Subscribe(request, metadata=[('authorization', f'Bearer {token}')]): print(f"Symbol: {update.symbol}, Bid: {update.bid}, Ask: {update.ask}") ``` ## Best Practices ### Rate Limits * REST endpoints are subject to [rate limits](/trader-guide/rate-limits) * Use streaming (gRPC) for real-time data to reduce API calls * Cache reference data locally ### Connection Management * Implement reconnection logic for streaming connections * Handle network interruptions gracefully * Use heartbeats to detect connection issues ### Data Handling * Validate timestamps to detect stale data * Handle gaps in sequence numbers appropriately * Store historical data locally for analysis # Onboarding Source: https://docs.polymarket.us/data-guide/onboarding Get started with the Polymarket Exchange API ## Step 1: Generate Your Key Pairs Generate an RSA key pair for each environment you need access to. You will share only the **public keys** with Polymarket. ```bash theme={null} # Replace 'acme' with your company name # Development openssl genrsa -out acme_dev_private_key.pem 2048 openssl rsa -in acme_dev_private_key.pem -pubout -out acme_dev_public_key.pem # Pre-production openssl genrsa -out acme_preprod_private_key.pem 2048 openssl rsa -in acme_preprod_private_key.pem -pubout -out acme_preprod_public_key.pem # Production openssl genrsa -out acme_prod_private_key.pem 2048 openssl rsa -in acme_prod_private_key.pem -pubout -out acme_prod_public_key.pem ``` Keep your private keys secure. Never share them with anyone. ## Step 2: Submit Your Onboarding Request Contact [data@polymarket.us](mailto:data@polymarket.us) to receive the Market Data Agreement for read-only market data access. Once you have completed the Market Data Agreement, create a Google Drive folder containing your public key file(s) and completed document, then email [data@polymarket.us](mailto:data@polymarket.us) with your name or the name of your firm and a Google Drive link to your folder (grant Editor access to [data@polymarket.us](mailto:data@polymarket.us)). For read-only data access, request these scopes: | Scope | Description | | ------------------- | ---------------------------------------------- | | `read:marketdata` | BBO (best bid/offer) and streaming market data | | `read:l2marketdata` | L2 orderbook depth | | `read:instruments` | Instrument listings and metadata | ## Step 3: Receive Your Credentials The Polymarket team will review your submission and provide your Client ID credentials via email for both pre-production and production environments. # Data Guide Overview Source: https://docs.polymarket.us/data-guide/overview Connect to the Polymarket Exchange for read-only market data consumption This guide is for users who want to connect to the Polymarket Exchange for **read-only market data consumption** without trading functionality. ## Who Is This For? The Data Guide is designed for: * **Data vendors** building market data products * **Research teams** analyzing prediction market data * **Analytics platforms** displaying market information * **Developers** building read-only applications ## What You Can Access With read-only access, you can consume: | Data Type | Description | Access Method | | --------------------- | --------------------------------------------- | ------------------------ | | **Market Data** | Real-time quotes, BBO, L2 order book | REST API, gRPC Streaming | | **Reference Data** | Instruments, symbols, metadata | REST API | | **Market Statistics** | OHLC, last trade price, volume, open interest | REST API, gRPC Streaming | ## Available Endpoints ### REST API | Endpoint | Description | | ---------------------------- | --------------------------- | | `/v1/orderbook/{symbol}/bbo` | Best bid/offer for a symbol | | `/v1/orderbook/{symbol}` | L2 order book depth | | `/v1/refdata/instruments` | List all instruments | | `/v1/refdata/symbols` | List all symbols | | `/v1/refdata/metadata` | Instrument metadata | ### gRPC Streaming For real-time data, use the [Market Data Stream](/streaming-endpoints/market-data-stream): * Subscribe to BBO updates * Subscribe to L2 order book changes * Receive market statistics updates (OHLC, last trade, volume) # Reference Data Source: https://docs.polymarket.us/data-guide/reference-data Query instruments, symbols, and metadata from the Polymarket Exchange Reference data provides information about available instruments and symbols on the Polymarket Exchange. ## Overview The Reference Data API provides three endpoints: * **List Instruments** - Returns complete instrument definitions including symbol, trading rules, state, dates, price limits, and market-specific metadata * **List Symbols** - Returns just the symbol identifiers (trading symbols) without full instrument details * **Get Metadata** - Returns server-level metadata about the exchange (not instrument-specific) ## Required Scope | Scope | Data Access | | ------------------ | ---------------------------- | | `read:instruments` | All reference data endpoints | For details on the hierarchical structure of instruments (categories, series, events, products), see the [Asset Master](/data-guide/asset-master) guide. ## REST API Endpoints ### List Instruments Retrieve all available instruments: ```bash theme={null} curl -X POST "https://api.preprod.polymarketexchange.com/v1/refdata/instruments" \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{}' ``` **Response:** ```json theme={null} { "instruments": [ { "symbol": "aec-cbb-alcst-alast-2026-03-09", "tickSize": 0.01, "baseCurrency": "USD", "multiplier": 1, "minimumTradeQty": "1", "startDate": { "year": 2026, "month": 3, "day": 7 }, "expirationDate": { "year": 2026, "month": 3, "day": 23 }, "terminationDate": null, "tradingSchedule": [], "description": "Who will win the upcoming college basketball event...", "clearingHouse": "QCC", "minimumUnaffiliatedFirms": "0", "nonTradable": false, "jsonAttributes": "", "productId": "aec-cbb-alcst-alast-2026-03-09", "priceLimit": { "low": "1", "high": "99", "lowSet": true, "highSet": true, "relativeLow": 0, "relativeHigh": 0, "relativeLowSet": false, "relativeHighSet": false }, "orderSizeLimit": null, "expirationTime": { "hours": 14, "minutes": 0, "seconds": 0 }, "tradeSettlementPeriod": "0", "state": "INSTRUMENT_STATE_OPEN", "priceScale": "100", "fractionalQtyScale": "0", "settlementCurrency": "", "settlementPriceScale": "0", "metadata": { "cftc_instrument_id": "aec-cbb-alcst-alast-2026-03-09", "cftc_product_desc": "Athletic Event Contracts", "clearing_sym": "AEC-BASKETBALL", "event_category": "SPR", "event_external_id_sportradar": "4a55e75b-30b3-48c5-a90e-ecd3ae6f9de9", "event_external_id_sportsdataio": "60073199", "event_id": "cbb-alcst-alast-2026-03-09", "event_product_id": "aec-cbb-alcst-alast-2026-03-09", "event_series": "cbb", "event_start_time": "2026-03-09 18:00:00+00", "event_subcategory": "BASKETBALL", "instrument_product": "aec", "instrument_product_series": "aec-cbb", "instrument_rules": "Who will win the upcoming college basketball event...", "long_participant_id": "cbb-alcst", "long_participant_name": "Alcorn State", "outcome_strike": "0.0", "outcome_type": "moneyline", "participant_type": "team", "product_id": "aec-cbb-alcst-alast-2026-03-09", "short_participant_id": "cbb-alast", "short_participant_name": "Alabama State" }, "eventAttributes": { "question": "Alcorn State vs. Alabama State", "payoutValue": "100", "evaluationType": ">", "eventId": "aec-cbb-alcst-alast-2026-03-09", "eventDisplayName": "Alcorn State vs. Alabama State 2026-03-09", "strikeValue": "0.0", "strikeUnit": "decimal", "calculationMethod": "CALCULATION_METHOD_VALUE", "positionAccountabilityValue": "50000", "timeSpecifier": { "year": 2026, "month": 3, "day": 9 } }, "createTime": "2026-03-07T05:00:22.055331308Z", "updateTime": "2026-03-07T05:00:22.055331308Z" } ], "nextPageToken": "eyJvIjoyfQ==", "eof": false } ``` **Integer Fields Encoded as Strings** Fields typed as `int64` (such as `minimumTradeQty`, `priceScale`, `fractionalQtyScale`, `priceLimit.low`, `priceLimit.high`) are serialized as **strings** in JSON responses per the proto3 JSON specification. Parse these values as numbers in your client code. The `settlementCurrency` and `settlementPriceScale` fields are reserved for future use and currently return empty string and `"0"` respectively. ### List Symbols Retrieve all trading symbols: ```bash theme={null} curl -X POST "https://api.preprod.polymarketexchange.com/v1/refdata/symbols" \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{}' ``` **Response:** ```json theme={null} { "symbols": [ "aec-nfl-buf-nyj-2025-01-15", "aec-nba-bos-lal-2025-01-20", "aec-nhl-tor-mtl-2025-01-18" ] } ``` ### Get Exchange Metadata Retrieve server-level metadata about the exchange: ```bash theme={null} curl -X POST "https://api.preprod.polymarketexchange.com/v1/refdata/metadata" \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{}' ``` **Response:** ```json theme={null} { "metadata": { "exchange_name": "Polymarket Exchange", "server_version": "v1.0.0", "timezone": "America/New_York" } } ``` Note: For instrument-specific metadata (participant names, event details, etc.), use the `/v1/refdata/instruments` endpoint which includes a `metadata` field in each instrument object. ## Instrument Lifecycle Instruments follow the primary lifecycle: PENDING → OPEN → CLOSED → EXPIRED → TERMINATED. Instruments may also be SUSPENDED or HALTED during their lifecycle. State values are prefixed with `INSTRUMENT_STATE_` in the API (e.g., `INSTRUMENT_STATE_OPEN`). ### Primary State Flow | State                                               | Description | | --------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `PENDING` | Initial state for a newly created instrument which has not yet begun trading. | | `OPEN` | In this state, the instrument is open for continuous order entry and matching. | | `CLOSED` | In this state, orders can not be entered, modified, or canceled, and no matching occurs. Any existing Day orders will be expired. | | `EXPIRED` | An instrument moves to this state when its Expiration Date/Time is reached. In this state, any resting orders are expired and no new orders can be entered. | | `TERMINATED` | When an instrument's Termination Date is reached, the order book is removed from the matching engine, orders are canceled, and positions are closed. Historical data will still remain in Polymarket US ledgers. | ### Exception States | State                                               | Description | | --------------------------------------------------- | --------------------------------------------------------------------------------------------- | | `SUSPENDED` | Orders can be canceled but no matching occurs, and no order entry or modification is allowed. | | `HALTED` | This state is similar to SUSPENDED, with the exception that orders cannot be canceled. | ### Other Possible States | State                                               | Description | | --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `PREOPEN` | Orders can be entered and modified, but no matching occurs. When the instrument transitions to an OPEN state, the orders entered during PREOPEN will match at a single opening price that is automatically determined by an algorithm that is designed to maximize the volume traded at the open. | | `MATCH_AND_CLOSE_AUCTION` | This state is similar to PREOPEN, with the exception that matching will occur upon the transition of this state to any other state. This state is useful if you want matching to occur at the end of the state, but you don't want the instrument to be open after. | ## Best Practices ### Caching Reference data changes infrequently. Cache locally and refresh periodically: ```python theme={null} import time class ReferenceDataCache: def __init__(self, api_client, refresh_interval=300): self.api_client = api_client self.refresh_interval = refresh_interval self.instruments = {} self.last_refresh = 0 def get_instruments(self): if time.time() - self.last_refresh > self.refresh_interval: self._refresh() return self.instruments def _refresh(self): response = self.api_client.list_instruments() self.instruments = {i['symbol']: i for i in response['instruments']} self.last_refresh = time.time() ``` ### Filtering Filter by instrument state: ```bash theme={null} curl -X POST "https://api.preprod.polymarketexchange.com/v1/refdata/instruments" \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "states": ["INSTRUMENT_STATE_OPEN"] }' ``` Filter by event series: ```bash theme={null} curl -X POST "https://api.preprod.polymarketexchange.com/v1/refdata/instruments" \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "event_series": "nfl" }' ``` ### Handling Updates * Subscribe to instrument updates for real-time changes * Check instrument status before placing orders * Monitor for new instruments being listed # Sports Data Source: https://docs.polymarket.us/data-guide/sports-data Sports players, teams, and league data available through the ISV gateway The ISV gateway exposes sports reference data - players, teams, logos, colors, records, and provider mappings - that complement the exchange-level [Reference Data](/data-guide/reference-data). ## Players Endpoint ```text theme={null} GET https://gateway.polymarket.us/v1/sports/players ``` Fetch player reference data directly for player props, combos, and provider ID mapping, without loading an event. This is a public endpoint and requires no authentication. It remains supported at v1 and is not deprecated; there is no standalone v2 players lookup endpoint. Player information embedded in `/v2/events` complements this endpoint. ### Query Parameters Use dotted, camelCase query parameter names. For array filters, repeat the parameter for each value, for example `filters.id=781&filters.id=15877`. | Parameter | Type | Description | | -------------------------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `limit` | int32 | Maximum players to return in list mode. Set an explicit positive limit when paging. | | `offset` | int32 | Players to skip in list mode. Start at `0` and increase by the requested limit for subsequent pages. | | `filters.id` | int64\[] | Internal player IDs. Use this to look up a specific player. | | `filters.teamId` | int64\[] | Internal team IDs, such as the `id` returned by `/v1/sports/teams`. | | `filters.name` | string\[] | Filter by player name. | | `filters.abbreviation` | string\[] | Filter by player abbreviation. | | `filters.provider` | string | Provider enum, such as `PROVIDER_SPORTSDATAIO`, `PROVIDER_SPORTRADAR`, or `PROVIDER_OPTICODDS`. Selects provider lookup mode when set to a value other than `PROVIDER_UNSPECIFIED`. | | `filters.providerPlayerId` | string\[] | Player IDs in the selected provider's namespace. Requires `filters.provider`. | | `filters.league` | string | Restrict provider lookups to players on teams in this league (for example, `mlb`). Ignored in list mode. | Provider lookup uses only `filters.provider`, `filters.providerPlayerId`, and `filters.league`. It ignores `limit`, `offset`, `filters.id`, `filters.teamId`, `filters.name`, and `filters.abbreviation`, and returns results sorted by internal player ID. For list mode, leave `filters.provider` unset; `filters.providerPlayerId` and `filters.league` then have no effect. ### Examples List players: ```bash theme={null} curl -sS 'https://gateway.polymarket.us/v1/sports/players?limit=20' | jq ``` Look up a specific player by internal ID. The response still contains a `players` array; there is no public `/v1/sports/players/{id}` route. ```bash theme={null} curl -sS --get 'https://gateway.polymarket.us/v1/sports/players' \ --data-urlencode 'filters.id=781' | jq ``` List players on a team: ```bash theme={null} curl -sS --get 'https://gateway.polymarket.us/v1/sports/players' \ --data-urlencode 'filters.teamId=3006' \ --data-urlencode 'limit=100' | jq ``` Resolve a provider's player ID to an internal player record: ```bash theme={null} curl -sS --get 'https://gateway.polymarket.us/v1/sports/players' \ --data-urlencode 'filters.provider=PROVIDER_SPORTSDATAIO' \ --data-urlencode 'filters.providerPlayerId=10007155' \ --data-urlencode 'filters.league=mlb' | jq ``` ### Response Fields The response is an object containing a `players` array. No matches return HTTP `200` with `{"players": []}`. There is no total count or next-page token; in list mode, request subsequent offsets until a page contains fewer players than the requested limit. Each player includes the following fields when available. Internal player and team IDs are serialized as JSON strings because they are protobuf `int64` values; provider IDs are strings in the provider's own namespace. | Field | Type | Description | | ----------------- | --------- | ----------------------------------------------------------------- | | `id` | string | Internal player ID, used with `filters.id`. | | `name` | string | Player name. | | `abbreviation` | string | Player abbreviation. | | `teamId` | string | Internal team ID; may be absent. | | `image` | string | Player image URL; may be absent or empty. | | `darkImage` | string | Dark-mode player image URL; empty when unavailable. | | `providerIds` | object\[] | Provider references, each containing `provider` and `providerId`. | | `jerseyNumber` | int32 | Jersey number; may be absent. `0` is a valid jersey number. | | `jerseyImage` | string | Jersey image URL; empty when unavailable. | | `jerseyDarkImage` | string | Dark-mode jersey image URL; empty when unavailable. | These are player reference records. A returned player does not imply that player has an active prop market or an eligible combo leg; use event and market data to discover those markets. ## Teams Endpoint ``` GET https://gateway.polymarket.us/v1/sports/teams ``` Returns team data for a given series. Use the `filters.league` parameter to specify the series, which corresponds to the `event_series` value in instrument metadata (e.g., `nfl`, `nba`, `nhl`, `mlb`, `mls`, `cbb`, `cfb`). ### Query Parameters | Parameter | Type | Description | | ---------------------- | ------ | ---------------------------------------- | | `limit` | int32 | Maximum number of teams to return | | `offset` | int32 | Number of teams to skip for pagination | | `filters.league` | string | Series to filter by (see examples below) | | `filters.name` | string | Filter by team name | | `filters.abbreviation` | string | Filter by team abbreviation | | `filters.id` | int64 | Filter by team ID | ### Examples by Series Substitute the series value in `filters.league` to get teams for different leagues: | Series | URL | | ------ | ---------------------------------------------------------------------------- | | NFL | `https://gateway.polymarket.us/v1/sports/teams?limit=500&filters.league=nfl` | | NBA | `https://gateway.polymarket.us/v1/sports/teams?limit=500&filters.league=nba` | | NHL | `https://gateway.polymarket.us/v1/sports/teams?limit=500&filters.league=nhl` | | MLB | `https://gateway.polymarket.us/v1/sports/teams?limit=500&filters.league=mlb` | | MLS | `https://gateway.polymarket.us/v1/sports/teams?limit=500&filters.league=mls` | | CBB | `https://gateway.polymarket.us/v1/sports/teams?limit=500&filters.league=cbb` | | CFB | `https://gateway.polymarket.us/v1/sports/teams?limit=500&filters.league=cfb` | | UFC | `https://gateway.polymarket.us/v1/sports/teams?limit=500&filters.league=ufc` | | UCL | `https://gateway.polymarket.us/v1/sports/teams?limit=500&filters.league=ucl` | | EPL | `https://gateway.polymarket.us/v1/sports/teams?limit=500&filters.league=epl` | | ATP | `https://gateway.polymarket.us/v1/sports/teams?limit=500&filters.league=atp` | | WTA | `https://gateway.polymarket.us/v1/sports/teams?limit=500&filters.league=wta` | ### Response Fields Each team object includes: | Field | Type | Description | | -------------- | ------ | -------------------------------------- | | `id` | string | Internal team ID | | `name` | string | Full team name (e.g., "Buffalo Bills") | | `abbreviation` | string | Team abbreviation (e.g., `buf`) | | `league` | string | Series identifier (e.g., `nfl`) | | `record` | string | Current win-loss record | | `logo` | string | URL to team logo image | | `alias` | string | Team nickname | | `colorPrimary` | string | Team primary color (hex) | | `conference` | string | Conference or division | | `providerRefs` | array | External data provider ID mappings | ## Events Endpoint ``` GET https://gateway.polymarket.us/v2/leagues/{slug}/events ``` Returns active events for a given league. ### Query Parameters | Parameter | Type | Description | | --------- | ------- | ---------------------------------- | | `limit` | integer | Maximum number of events to return | | `active` | boolean | Filter to active events | | `closed` | boolean | Filter by closed status | ### Examples by League | League | URL | | ------ | ----------------------------------------------------------------------------------------- | | NFL | `https://gateway.polymarket.us/v2/leagues/nfl/events?limit=1000&active=true&closed=false` | | NBA | `https://gateway.polymarket.us/v2/leagues/nba/events?limit=1000&active=true&closed=false` | | NHL | `https://gateway.polymarket.us/v2/leagues/nhl/events?limit=1000&active=true&closed=false` | | MLB | `https://gateway.polymarket.us/v2/leagues/mlb/events?limit=1000&active=true&closed=false` | | MLS | `https://gateway.polymarket.us/v2/leagues/mls/events?limit=1000&active=true&closed=false` | | CBB | `https://gateway.polymarket.us/v2/leagues/cbb/events?limit=1000&active=true&closed=false` | | CFB | `https://gateway.polymarket.us/v2/leagues/cfb/events?limit=1000&active=true&closed=false` | | UFC | `https://gateway.polymarket.us/v2/leagues/ufc/events?limit=1000&active=true&closed=false` | | UCL | `https://gateway.polymarket.us/v2/leagues/ucl/events?limit=1000&active=true&closed=false` | | EPL | `https://gateway.polymarket.us/v2/leagues/epl/events?limit=1000&active=true&closed=false` | | ATP | `https://gateway.polymarket.us/v2/leagues/atp/events?limit=1000&active=true&closed=false` | | WTA | `https://gateway.polymarket.us/v2/leagues/wta/events?limit=1000&active=true&closed=false` | ## When Sports Markets Use Subjects Instead of Teams Teams are used for standard game markets (moneylines, spreads, totals) where two teams are competing in a specific game. In these cases, team data is attached directly to the event as participants. However, non-championship futures markets for sports — such as MVP awards, season win totals, and other prop futures that aren't tied to a specific game outcome — use **subjects** instead of teams. Subjects represent the individual player, team, or entity that the futures market is about. # Combos FAQs Source: https://docs.polymarket.us/faqs/combos-faqs Frequently asked questions about how combos work and how they settle on Polymarket US ## Basics ### What is a combo? A combo combines 2 to 10 markets into a single position. Each market you add is a leg, and each leg carries the side you took on it, buy or sell. Every leg has to resolve the way you took it for the combo to pay. A combo is a single position that settles once, at a single value. ### Why does a combo pay more than a single market? Because all of the legs have to come in together. A combo pays more than any one of those legs would on its own, and it carries correspondingly more risk. ### Does being part of a combo change what a leg is worth? No. A leg settles inside a combo exactly as it would settle on its own. ## Payouts ### How is a combo payout calculated? Your payout is the full potential payout multiplied by the value of every leg: * A leg that resolves the way you took it is worth \$1.00 * A leg that resolves against you is worth \$0.00 * A leg that cannot resolve at all is worth its last fair market price (LFMP) On an ordinary market, a winning Contract settles at \$1.00 and a losing one at \$0.00. A combo works the same way, except that its value depends on all of its legs at once. ### What happens if every leg wins? The multipliers are all \$1.00 and you receive the full potential payout. On a \$10 combo quoted to pay \$80, that is \$80. ### What happens if one leg resolves against me? That leg is worth \$0.00 and the combo pays \$0.00. This holds however the other legs turn out. One leg going against you is enough, and the remaining legs cannot make up for it. ## When a leg cannot resolve ### What does it mean for a leg to be unable to resolve? Occasionally a leg cannot resolve to Yes or No at all. The usual reasons are that a match is walked over or cancelled before it begins, a listed player does not take part, or an event is postponed and not replayed. ### What happens to that leg? It settles at its last fair market price (LFMP). The leg is not removed from your combo. It is assigned a fair price, and your payout is multiplied by that price. ### What is last fair market price? Last fair market price is the prevailing fair market price on the exchange at a specified moment in time, typically the moment an official announcement is made, such as a cancellation, walkover, or no-contest. ### Who decides the price? That price is determined by the Settlement Committee. Its decisions are final. ### Can a leg at LFMP rescue a combo that already has a losing leg? No. A leg at LFMP reduces the payout rather than voiding the combo, and it never rescues a combo that already has a losing leg in it. ### How does the payout work out in practice? Take a \$10 combo with 3 legs that would pay \$80: | Outcome | Calculation | Payout | | ------------------------------------------------------- | ----------------------------------- | ------- | | All three legs win | \$80 × 1.00 × 1.00 × 1.00 | \$80.00 | | Two legs win, third goes to LFMP at \$0.60 | \$80 × 1.00 × 1.00 × 0.60 | \$48.00 | | One leg wins, other two go to LFMP at \$0.60 and \$0.25 | \$80 × 1.00 × 0.60 × 0.25 | \$12.00 | | Any leg resolves against you | Whatever happened to the other legs | \$0.00 | # Daily Market Report Source: https://docs.polymarket.us/faqs/eod-reporting EOD contract summary Access historical Daily Market Reports on polymarketexchange.com The Daily Market Report is an EOD contract summary containing 21 columns. It includes volume decomposition, price ranges, open interest, and settlement prices. **File format:** `YYYYMMDD-daily-market-report.csv` **Example:** `20260112-daily-market-report.csv` ## Fields | Field | Description | | -------------------------------- | ----------------------------------------------------- | | **Report ID** | Internal identifier for the report row | | **Business Date** | Trading date the data applies to | | **Symbol** | Contract identifier | | **Maturity Date** | Contract expiration date | | **Maturity Time** | Contract expiration time | | **Strike Price** | Strike (used for option-style or threshold contracts) | | **Description** | Human-readable contract description | | **Open Interest** | Outstanding open contracts | | **Trade Volume** | Total traded volume for the day | | **Block Volume** | Volume from block trades | | **Exchange for Physical Volume** | EFP volume (physical-style settlement) | | **Exchange for Risk Volume** | EFR volume (risk transfer trades) | | **Threshold Volume** | Volume attributed to threshold-style trades | | **Other Volume** | Residual volume not classified above | | **Low Bid Price** | Lowest bid observed during the day | | **High Bid Price** | Highest bid observed during the day | | **Low Offer Price** | Lowest offer observed during the day | | **High Offer Price** | Highest offer observed during the day | | **Low Trade Price** | Lowest execution price of the day | | **High Trade Price** | Highest execution price of the day | | **Settlement Price** | Official settlement price | ## Key Characteristics The Daily Market Report is a comprehensive EOD contract summary containing: * Volume decomposition by trade type (Block, EFP, EFR, Threshold, Other) * Price ranges (bid, offer, trade) * Open interest * Settlement price # Time & Sales Report Source: https://docs.polymarket.us/faqs/execution-tape Execution tape with time, price, size, and symbol Access historical Time & Sales Reports on polymarketexchange.com The Time & Sales Report is a minimal execution tape containing exactly 4 columns. It provides a pure log of executed trades without side, aggressor flag, or buyer/seller information. **File format:** `YYYYMMDD-time-and-sales.csv` **Example:** `20260113-time-and-sales.csv` ## Fields | Field | Description | | -------------------- | -------------------------------------------------- | | **Transaction Time** | Timestamp of the executed trade | | **Symbol** | Contract identifier | | **Last Price** | Execution price of the trade (implied probability) | | **Last Quantity** | Size of the trade (number of contracts) | ## Key Characteristics Time & Sales is a pure execution log containing only: * Time * Price * Size * Symbol It does not include side, aggressor flag, or buyer/seller information. # General FAQs Source: https://docs.polymarket.us/faqs/general-faqs Frequently asked questions about trading on Polymarket US ### What are the trading hours? Polymarket US operates nearly 24/7, with a recurring weekly maintenance window every Thursday from 2am–6am ET. Specific markets may have different trading hours based on the underlying event. ### How do I fund my account? Deposit via debit card or bank transfer (ACH) through the Polymarket US app. See the app's funding section for deposit limits and processing times. ### How do I get support? Email [support@polymarket.us](mailto:support@polymarket.us) or use the in-app chat. ### Where can I check system status? View live system status, incidents, and scheduled maintenance at [status.polymarketexchange.com](https://status.polymarketexchange.com). ### When are maintenance windows? Every Thursday, 2am–6am ET is the recurring weekly maintenance window, effective July 9, 2026. Previously, the window was every Thursday, 6am–8am ET. ### What happens to open orders during maintenance? All open orders are canceled before maintenance begins. Leaving resting orders on the book during maintenance would expose traders to stale fills when the book reopens. ### What happens to connections during maintenance? All API requests return **503 Service Unavailable** during maintenance. An explicit rejection is preferable to leaving connections open but non-functional. ### When do markets reopen? Connections are re-enabled first, then markets move from SUSPENDED to OPEN. Order books reopen empty since all orders were cancelled before maintenance. The state change signals that maintenance is complete. ### What are `fractionalQtyScale` and `priceScale`, and how do I convert quantities and prices? On the Institutional REST and gRPC APIs, quantities and prices are transmitted as **integers**. To convert them into whole shares and whole dollars, divide by the instrument's scale factors: * **Quantities** — divide by `fractionalQtyScale`. For example, with `fractionalQtyScale = 100`, an integer quantity of `100` is `1` whole share, and `1` is `0.01` of a share (1% of a contract). * **Prices** — divide by `priceScale`. For example, with `priceScale = 100`, an integer price of `50` is `$0.50`. Both `fractionalQtyScale` and `priceScale` are returned per instrument in [reference data](/institutional/refdata/overview). Always read them from the instrument before submitting or interpreting orders — do not assume a fixed scale across instruments. Note that `int64` fields like `priceScale` and `fractionalQtyScale` are serialized as **strings** in JSON, so parse them as numbers in your client. **On FIX, prices and quantities are pre-scaled.** FIX messages carry decimal values directly, so you do **not** apply `priceScale` or `fractionalQtyScale` to them. Scaling only applies to the integer values on the REST and gRPC APIs. # Market Integrity FAQs Source: https://docs.polymarket.us/faqs/market-integrity-faqs Frequently asked questions about market integrity rules on Polymarket US ### Where can I read the official Market Integrity policy? Read the official policy here: [Polymarket US Market Integrity](https://integrity.polymarket.us/). # Refer-A-Friend FAQs Source: https://docs.polymarket.us/faqs/refer-a-friend-faqs Frequently asked questions about Polymarket US' Refer-A-Friend incentive program ### What is the Refer-A-Friend program? The Refer-A-Friend program lets you and your friends earn credits to trade with on Polymarket US. When your friend signs up with your referral code, successfully onboards, and makes a deposit of at least \$10, you both receive a credit. ### What is my referral code? Your referral code is your Polymarket US username. You can find it, along with your invite link, on the Invite Friends screen in the app. ### How do I refer a friend? Open the Invite Friends screen in the app and share your invite link via text message, X, your share sheet, or copy the link directly. You can also simply tell your friend your code (your username) and have them enter it when they sign up. ### Can I withdraw my bonus credit? Credits can only be used towards trades, and may be withdrawn upon position settlement or liquidation. ### What do I earn? What does my friend earn? You receive a $25 bonus credit for each qualifying referral, and your friend receives a $25 bonus credit. ### What does my friend need to do for us to qualify? Your friend must: * Sign up as a new Polymarket US user using your invite link or referral code * Deposit at least \$10 Bonuses are credited after all steps are complete. ### My friend signed up but forgot to enter my code. Can we still get the bonus? Referral codes must be applied during sign-up. If your friend uses your invite link, the code is usually applied automatically; if it is not (for example, if they decline app tracking), they can enter your username manually during onboarding. ### Is there a limit to how many friends I can refer? Yes. Each user can earn up to 14 referral bonuses. Each new user can only be referred once, and only one bonus is paid per verified identity. ### When do I get my bonus? Your bonus is credited to your account after your friend completes all qualifying steps. Bonuses are funded and paid through Polymarket US's incentive program system and may be subject to review before payout. ### What is not allowed? Self-referrals, referring accounts you control, creating multiple accounts, and any other attempt to exploit the program are prohibited. Polymarket US may withhold or claw back bonuses obtained through fraud or abuse, and may suspend accounts involved. Polymarket US reserves the right to modify, pause, or end the program at any time. ### Who is eligible? The program is available to all users who are eligible to trade on Polymarket US through a participating interface, such as the Polymarket App. Standard eligibility and identity verification requirements apply. # Sports FAQs Source: https://docs.polymarket.us/faqs/sports-faqs Frequently asked questions about trading sports Contracts on Polymarket US ## General Rules ### What kinds of markets are offered? Polymarket US offers several types of sports Contracts: | Contract | What it covers | Example | | --------------------- | ------------------------------------------------------------------------------------------ | ---------------------------------------------------------- | | **Winner (w/o ties)** | Which team or player wins a game | “Which team will win, Lakers vs Celtics?” | | **Winner (w/ ties)** | Which team or player wins a game or match where a draw is a standard outcome (e.g. soccer) | “Will Man City vs Arsenal end in a draw?” | | **Spread** | Whether a team wins by a certain margin | “Will the Chiefs win by more than 7?” | | **Total** | Whether the combined score is over or under a threshold | “Will the total score in Patriots vs Chiefs be over 47.5?” | | **Future** | Who will win a championship, award, or tournament | “Will the Lakers win the 2026 NBA Championship?” | | **Qualifier** | Whether a team or player qualifies for an event | “Will Duke make the NCAA Tournament Final Four?” | | **Player Prop** | Whether an individual player’s stat reaches a threshold | “Will LeBron score over 25.5 points vs the Celtics?” | ### How are markets settled? All markets are settled using the official result obtained through a hierarchy of sources: * **Primary source.** The official governing body or sanctioning organization responsible for the event. * **Secondary sources.** If the primary source is unavailable, Polymarket US may reference official competition scorecards, referee or umpire reports, press releases, and results databases maintained by the governing body. * **Tertiary sources.** If secondary sources are unavailable, Polymarket US may reference the Associated Press, Reuters, ESPN, BBC Sport, official team and league websites, and major sports wire services or data providers. Settlement is delayed if the official result is under review. If no official result is declared by the Contract’s expiration date, the Contract settles at last fair market prices. ### What is “last fair market price” and how does it differ from the last traded price? Last fair market price (LFMP) is the prevailing fair market price on the Exchange at a specified moment in time, typically the moment an official announcement is made (e.g. a cancellation, walkover, or no-contest). It is **not** the last traded price at market close. Markets may remain open for a period after an announcement, and trades that occur during this window are executed at the trader’s own risk. The settlement price reflects LFMP at the time of the announcement, not any prices that print afterward. Traders are responsible for monitoring official announcements prior to and during trading. ### How do ties and draws work? It depends on the Contract type: * **Winner (w/o tie):** If the game ends in a tie with no winner declared, the Contract settles at \$0.50. Example: an NFL game enters overtime and no team scores. The game ends in a tie and each Contract settles at \$0.50. * **Winner (w/ tie):** Three Contracts per game (Team A Win, Team B Win, Draw). Exactly one settles at \$1.00, the others at \$0.00. Example: an EPL match is tied after 90 minutes plus stoppage time. The Draw Contract settles at \$1.00 and the two team Contracts settle at \$0.00. * **Spread:** Half-point spreads (e.g. -3.5) are used to avoid ties. Example: the Chiefs are -3.5 vs the Eagles. The Chiefs Contract settles at \$1.00 if they win by 4 or more. The Eagles Contract settles at \$1.00 if they lose by 3 or fewer or win outright. * **Total:** Half-point totals (e.g. 47.5) are used to avoid ties. Example: total of 47.5 in Chiefs vs Eagles. If the combined score is 47 or fewer, Under settles at \$1.00. If 48 or more, Over settles at \$1.00. * **Co-winners:** If multiple participants are declared co-winners by the governing body without a playoff or tiebreaker, each winner’s Contract settles at \$1.00 divided by the number of winners, rounded down to the nearest tick. All non-winning Contracts settle at \$0.00. ### How does overtime work? For game-level Contracts (Athletic Event, Athletic Spread, Total Score), overtime, extra time, extra innings, penalty shootouts, and tiebreakers are **included** by default unless otherwise specified in the Contract Terms. The official final result governs settlement. The exception is **soccer**: for league matches (e.g. EPL, MLS regular season) the result at the end of regulation time (90 minutes plus stoppage) governs. Extra time and penalties are excluded because draws are a valid outcome. Sport-specific overtime mechanics (e.g. NHL regular-season vs playoff overtime, NBA continuous overtime) are described in each sport’s section. ### What if a game is postponed or rescheduled? If a game is postponed before the start of play and rescheduled before the Contract’s expiration date (typically two weeks from the original event date), the official result of the rescheduled game governs settlement. If the rescheduled game falls outside the expiration date, all Contracts on the original game settle at last fair market prices. Postponed games do not automatically carry over to the rescheduled date. For soccer specifically, if a postponed match is rescheduled with the home and away team designations reversed, the original Contract settles at LFMP because the nature of the match has fundamentally changed. ### What if a game is suspended, abandoned, or shortened mid-play? Settlement depends on whether the game has reached the **official game threshold** required by the governing body to declare a result (each sport defines its own threshold): * **Threshold reached at the time of stoppage.** The official result at the time of stoppage governs settlement for all markets. * **Threshold not reached and game completed before expiration.** The official final result governs settlement. * **Threshold not reached and game not completed before expiration.** All Contracts settle at last fair market prices. ### What if a game is canceled and never replayed? If a game is canceled and not rescheduled before the Contract’s expiration date, the Contract settles at last fair market prices as of the time the cancellation was officially announced. This includes games abandoned or suspended mid-play without the governing body declaring an official result. ### What if a participant withdraws? It depends on the Contract type and the timing of the withdrawal: * **Game-level Contracts (Winner, Spread, Total).** Pre-event participant withdrawal: Contract settles at last fair market prices. * **Futures and Qualifiers.** The withdrawn participant’s Contract settles at \$0.00. Sport-specific definitions of when a participant has “entered” the event (e.g. tee-off in golf, first serve in tennis, opening bell in UFC) are described in each sport’s section. If a participant retires, defaults, or is disqualified mid-event and the governing body declares a winner or official result, that result governs settlement. ### What if there’s a forfeit? The participant awarded the forfeit is the winner. For Spread and Total Contracts, the official forfeit score (e.g. 9-0 in baseball) governs. For Winner (w/ tie) Contracts, the participant awarded the forfeit settles at \$1.00 and the “Tie” Contract settles at \$0.00. ### What if there’s a no contest? If the governing body declares the event a no contest, the Contract settles at last fair market prices as of the announcement. Example: a UFC fight or boxing match ends in a no contest. The Contract settles at LFMP at the time the no contest was announced. ### What about replayed or protested games? If a game is replayed in its entirety pursuant to a protest or governing body decision before the Contract’s expiration date, the replay result governs. If the replay happens after expiration, the original result stands and a new market is created for the replayed game. ### Does a venue change affect Contracts? A venue change has no impact on any active markets. All Contracts remain valid and orders remain open regardless of any change to the location, including changes to a neutral site or different stadium. *** ## Futures & Qualifiers ### How does settlement work? A Futures market is settled using results from multiple games or rounds, such as a championship or season-long award. Futures settle on the participant officially declared winner by the governing body, typically at the moment of the trophy presentation or final awards announcement. A team or player Contract may settle at \$0.00 as soon as it is mathematically impossible for them to win or qualify and that elimination has been officially recognized by the governing body. A Qualifier Contract settles at \$1.00 when the governing body officially declares that the participant has met the qualification criteria for the event. Qualification may be determined through any officially recognized path: league standings or points accumulation, tournament progression, direct selection or invitation by the governing body, or performance-based criteria. ### What if a team is rebranded, relocated, or renamed? Futures placed before the change remain valid and follow the team under its new name or location, as long as the governing body continues to recognize the team as the same competitive entity. ### How do elimination and qualification work? * **Futures.** If a team is eliminated from a championship (e.g. knocked out of the playoffs), the Contract settles at \$0.00. * **Qualifiers.** If a team cannot qualify for the specified event or round (e.g. eliminated during the tournament stage and cannot make the playoffs), the Contract settles at \$0.00. ### What if a qualification is reversed before expiration? If a participant initially qualifies or is eliminated and that determination is later reversed, vacated, or reassigned before the Contract’s expiration date, the Contract settles based on the final official determination as of the expiration date. Reversals announced after expiration do not affect settlement. *** ## Sport-Specific FAQs All game-level rules in *General Rules* apply to both pregame and in-game trading unless explicitly stated otherwise in a sport-specific section. *** ## Tennis (WTA, ATP, & ITF) ### When does a match officially begin? A tennis match officially begins when the first serve is struck. Anything happening before the first serve (cancellation, walkover, withdrawal) is treated as a pre-event scenario and Contracts settle at last fair market prices as of the official announcement. Except in the case of ITF Men's and Women's matches, cancellation, walkover, or withdrawal will resolve at \$0.50 per Contract. ### Mid-match retirement, default, or disqualification If a player retires after the first serve, defaults for a code violation, or is disqualified, the market resolves on the official result declared by the governing body. Whoever is awarded the win settles at \$1.00, regardless of how many games or sets were completed. ### Untraditional format If a match format differs from traditional WTA, ATP, or ITF format, or if formats are updated mid-tournament, all markets stand and resolve on the official governing-body result. ### Venue or surface change If a match is moved to a different venue, court type, or surface but continues to be played before the expiration date between the same players, the market remains open and trading continues. *** ## Baseball (MLB) ### Game threshold and universal settlement rule If MLB declares an official result, that result governs settlement for all markets (Winner, Spread, Total) regardless of how many innings were played. The official score at the time of the MLB ruling governs all market settlement. Example: a game is called due to weather and MLB awards an official result with a score of 1-0. The team leading 1-0 wins the Winner market, +1.5 on the spread settles at \$1.00, and the Totals market settles based on a score of 1-0. ### Doubleheaders Each game in a doubleheader is an independent Contract. If one game of a scheduled doubleheader is not played on the original date and is not rescheduled before the Contract’s expiration date, that game’s Contracts settle at LFMP. The completed game settles per the official MLB result. For non-standard doubleheader formats (e.g. seven-inning doubleheaders), the official game threshold is per MLB’s rules for that format. ### Player doesn’t play If the player doesn’t participate at all, the Contract settles at LFMP. ### Player leaves the game early Stats accumulated up to that point count per the official box score. If the threshold was already crossed, the Contract settles at \$1.00. If it can no longer be met, it settles at \$0.00. If the outcome is unclear, the final official stats determine settlement. ### Settlement source for awards Awards settle on the official announcement by the relevant governing body. For BBWAA awards (MVP, Cy Young, Rookie of the Year), the official BBWAA vote result governs settlement. ### Player traded between leagues If a player is traded between the AL and NL, they are no longer eligible for the league-specific award tied to the league they left. Their Contract for that award (e.g. AL MVP) settles at \$0.00. Applies to MVP, Cy Young, and Rookie of the Year. *** ## Soccer ### Result is regulation time All markets are based on the result at the end of 90 minutes plus stoppage, unless explicitly stated otherwise. Extra time, golden goals, and penalty shootouts are not included unless the Contract Terms specify otherwise. For any market in which a draw is a listed outcome (league or group-stage matches), the market refers to regulation time only. ### Postponement with home/away flip If a postponed match is rescheduled with the home and away team designations reversed, the original Contract settles at LFMP because the nature of the match has fundamentally changed. ### Soccer Futures Unless explicitly designated as a regular-season winner market, Soccer Futures settle on the final competition winner including playoff rounds. *** ## Golf ### How tournament winners are settled Playoffs are included: if two or more players are tied at the end of regulation and a playoff is conducted, the playoff winner is the tournament winner for settlement purposes. ### Dead heat rules If two or more players are declared co-winners without a playoff, each winner’s Contract settles at \$1.00 divided by the number of winners, rounded down to the nearest tick. All other player Contracts settle at \$0.00. ### Tournament shortened or canceled If the governing body declares an official winner, that result governs regardless of how many holes or rounds were completed. If the tournament does not reach the minimum threshold required by the governing body to declare an official winner, all Contracts settle at LFMP. *** ## UFC ### Draws and technical draws A draw occurs when the judges’ scorecards are equal at the end of the bout. A technical draw occurs when a fight is stopped before the scheduled distance due to an accidental foul and the scorecards are used to determine the result. Both are treated identically for settlement: all Contracts settle at \$0.50. ### Disqualification or retirement between rounds If a fighter is disqualified during a bout, the opponent is declared the winner and the opponent’s Contract settles at \$1.00. If a fighter retires between rounds and does not answer the bell for the next round, the bout is deemed to have ended at the conclusion of the previous round and the opponent wins. ### Mid-fight no contest If a bout is declared a no contest during the fight (e.g. due to an accidental foul or headbutt), all Contracts settle at LFMP regardless of how many rounds were completed. ### Substitutions and bout changes * **Fighter substitution.** Orders on the original contest settle at LFMP and a new market is created for the new fight. * **Round count change** (3 ↔ 5). All existing Winner Contracts remain active and settle on the official UFC result. * **Weight miss or catchweight.** All existing Contracts remain active and settle on the official UFC result. *** ## NHL ### Overtime and shootouts * **Regular season.** If tied after 60 minutes of regulation, a 5-minute 3-on-3 sudden-death overtime is played. If still tied, a shootout determines the winner. All Winner, Spread, and Total markets include overtime and shootout results unless explicitly stated otherwise. The shootout winner is credited with one goal in the official NHL recorded score, and that score governs settlement for all markets. * **Playoffs.** Full 5-on-5 sudden-death overtime periods continue until a goal is scored. There is no shootout. All overtime periods count toward Winner, Spread, and Total markets. *** ## Basketball (NBA / NCAA) ### Overtime All Winner, Spread, and Total markets include overtime unless explicitly stated. Overtime periods continue until a winner is determined; there are no ties. The official final score after all overtime periods governs settlement. *** ## Cricket ### Tie, draw, or no-result If a match ends in a tie, draw, no result (NR), abandonment, or cancellation and no official winner is declared, all markets settle at \$0.50. ### Forfeit, disqualification, or concession * **Before the match begins.** All markets settle at \$0.50. * **After the match has begun.** If the governing body declares an official winner, the market settles based on that result. ### Insufficient play If play begins but insufficient play occurs to determine an official result, all markets settle at \$0.50. # Weather FAQs Source: https://docs.polymarket.us/faqs/weather-faqs Frequently asked questions about trading Weather Contracts on Polymarket US ## General Rules ### What kinds of Weather Contracts are offered? Polymarket US offers Temperature Contracts -- Event Contracts that resolve based on whether the temperature in a specified location during a specified period satisfies a specified condition relative to a specified value. ### How are Weather Contracts settled? Settlement is determined by the official NWS Daily Climate Report (CLI) published by the local Weather Forecast Office. The CLI is an official government record that reports observed high, low, and average temperatures for a given location and date. The settlement source for each currently offered city is: | City | Station | CLI Source | | ------------- | ------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------- | | New York City | KNYC (Central Park) | [CLINYC](https://forecast.weather.gov/product.php?site=NWS\&issuedby=NYC\&product=CLI\&format=CI\&version=1\&glossary=1\&highlight=off) | | San Francisco | KSFO (San Francisco International Airport) | [CLISFO](https://forecast.weather.gov/product.php?site=NWS\&issuedby=SFO\&product=CLI\&format=CI\&version=1\&glossary=1\&highlight=off) | | Miami | KMIA (Miami International Airport) | [CLIMIA](https://forecast.weather.gov/product.php?site=NWS\&issuedby=MIA\&product=CLI\&format=CI\&version=1\&glossary=1\&highlight=off) | | Chicago | KMDW (Chicago Midway Airport) | [CLIMDW](https://forecast.weather.gov/product.php?site=NWS\&issuedby=MDW\&product=CLI\&format=CI\&version=1\&glossary=1\&highlight=off) | | Los Angeles | KLAX (Los Angeles International Airport) | [CLILAX](https://forecast.weather.gov/product.php?site=NWS\&issuedby=LAX\&product=CLI\&format=CI\&version=1\&glossary=1\&highlight=off) | ### When does settlement occur? Settlement occurs at 8:00 AM ET on the day following the Contract's specified date. If the CLI reading is inconsistent with the 24-hour METAR observation for the same location, settlement may be delayed until 11:00 AM ET for review. If no data is published within one week of the scheduled release, the Contract settles at last fair market prices. # Fee Schedule Source: https://docs.polymarket.us/fees Trading fee schedule, rebates, and examples Effective exchange-wide from 12 AM ET, Wednesday July 1, 2026. ## Trading Fees Fees are computed using a symmetric formula that scales with price uncertainty: ``` Fee = Θ × C × p × (1 - p) ``` Where: * **C** is the number of contracts * **p** is the trade price (\$0.01 to \$0.99) * **Θ** (theta) is the fee coefficient | | Theta | Max (p = \$0.50) | | ---------------- | ------- | ---------------- | | **Taker Fee** | 0.06 | \$1.50 | | **Maker Rebate** | -0.0125 | -\$0.31 | * **Maker rebate** is applied at the point of trade. * **Taker rebate**: Participants who trade over \$250,000 in taker volume during the prior calendar month receive rebates according to the following schedule. A Participant's tier for a given month is determined by their notional taker volume in the immediately preceding calendar month. Rebates are paid out weekly. | Prior calendar-month taker volume | % Taker fee rebate | | --------------------------------- | ------------------ | | \$250,000 - \$999,999 | 10% | | \$1,000,000 - \$9,999,999 | 25% | | \$10,000,000+ | 50% | **Accelerated Tier Placement**: A Participant may provide verifiable proof of their trailing-30-day notional trading volume on another prediction market and be assigned to the rebate tier corresponding to that volume. API integrators: `C` is the number of **contracts** and `p` the **decimal** price. Execution reports carry these as fixed-point integers, and the collected fee in scaled notional units — see [Fees on execution reports](/partners/orders/data-model#fees-on-execution-reports) for how to decode `commission_notional_collected` with `price_scale` and `fractional_quantity_scale`. ### Fee Schedule by Price | Price | Trade Value (100-lot) | Taker Pays (100-lot) | Maker Receives (100-lot) | | ------ | --------------------- | -------------------- | ------------------------ | | \$0.01 | \$1 | \$0.06 | \$0.01 | | \$0.02 | \$2 | \$0.12 | \$0.02 | | \$0.03 | \$3 | \$0.17 | \$0.04 | | \$0.04 | \$4 | \$0.23 | \$0.05 | | \$0.05 | \$5 | \$0.28 | \$0.06 | | \$0.06 | \$6 | \$0.34 | \$0.07 | | \$0.07 | \$7 | \$0.39 | \$0.08 | | \$0.08 | \$8 | \$0.44 | \$0.09 | | \$0.09 | \$9 | \$0.49 | \$0.10 | | \$0.10 | \$10 | \$0.54 | \$0.11 | | \$0.11 | \$11 | \$0.59 | \$0.12 | | \$0.12 | \$12 | \$0.63 | \$0.13 | | \$0.13 | \$13 | \$0.68 | \$0.14 | | \$0.14 | \$14 | \$0.72 | \$0.15 | | \$0.15 | \$15 | \$0.76 | \$0.16 | | \$0.16 | \$16 | \$0.81 | \$0.17 | | \$0.17 | \$17 | \$0.85 | \$0.18 | | \$0.18 | \$18 | \$0.89 | \$0.18 | | \$0.19 | \$19 | \$0.92 | \$0.19 | | \$0.20 | \$20 | \$0.96 | \$0.20 | | \$0.21 | \$21 | \$1.00 | \$0.21 | | \$0.22 | \$22 | \$1.03 | \$0.21 | | \$0.23 | \$23 | \$1.06 | \$0.22 | | \$0.24 | \$24 | \$1.09 | \$0.23 | | \$0.25 | \$25 | \$1.12 | \$0.23 | | \$0.26 | \$26 | \$1.15 | \$0.24 | | \$0.27 | \$27 | \$1.18 | \$0.25 | | \$0.28 | \$28 | \$1.21 | \$0.25 | | \$0.29 | \$29 | \$1.24 | \$0.26 | | \$0.30 | \$30 | \$1.26 | \$0.26 | | \$0.31 | \$31 | \$1.28 | \$0.27 | | \$0.32 | \$32 | \$1.31 | \$0.27 | | \$0.33 | \$33 | \$1.33 | \$0.28 | | \$0.34 | \$34 | \$1.35 | \$0.28 | | \$0.35 | \$35 | \$1.36 | \$0.28 | | \$0.36 | \$36 | \$1.38 | \$0.29 | | \$0.37 | \$37 | \$1.40 | \$0.29 | | \$0.38 | \$38 | \$1.41 | \$0.29 | | \$0.39 | \$39 | \$1.43 | \$0.30 | | \$0.40 | \$40 | \$1.44 | \$0.30 | | \$0.41 | \$41 | \$1.45 | \$0.30 | | \$0.42 | \$42 | \$1.46 | \$0.30 | | \$0.43 | \$43 | \$1.47 | \$0.31 | | \$0.44 | \$44 | \$1.48 | \$0.31 | | \$0.45 | \$45 | \$1.48 | \$0.31 | | \$0.46 | \$46 | \$1.49 | \$0.31 | | \$0.47 | \$47 | \$1.49 | \$0.31 | | \$0.48 | \$48 | \$1.50 | \$0.31 | | \$0.49 | \$49 | \$1.50 | \$0.31 | | \$0.50 | \$50 | \$1.50 | \$0.31 | | \$0.51 | \$51 | \$1.50 | \$0.31 | | \$0.52 | \$52 | \$1.50 | \$0.31 | | \$0.53 | \$53 | \$1.49 | \$0.31 | | \$0.54 | \$54 | \$1.49 | \$0.31 | | \$0.55 | \$55 | \$1.48 | \$0.31 | | \$0.56 | \$56 | \$1.48 | \$0.31 | | \$0.57 | \$57 | \$1.47 | \$0.31 | | \$0.58 | \$58 | \$1.46 | \$0.30 | | \$0.59 | \$59 | \$1.45 | \$0.30 | | \$0.60 | \$60 | \$1.44 | \$0.30 | | \$0.61 | \$61 | \$1.43 | \$0.30 | | \$0.62 | \$62 | \$1.41 | \$0.29 | | \$0.63 | \$63 | \$1.40 | \$0.29 | | \$0.64 | \$64 | \$1.38 | \$0.29 | | \$0.65 | \$65 | \$1.36 | \$0.28 | | \$0.66 | \$66 | \$1.35 | \$0.28 | | \$0.67 | \$67 | \$1.33 | \$0.28 | | \$0.68 | \$68 | \$1.31 | \$0.27 | | \$0.69 | \$69 | \$1.28 | \$0.27 | | \$0.70 | \$70 | \$1.26 | \$0.26 | | \$0.71 | \$71 | \$1.24 | \$0.26 | | \$0.72 | \$72 | \$1.21 | \$0.25 | | \$0.73 | \$73 | \$1.18 | \$0.25 | | \$0.74 | \$74 | \$1.15 | \$0.24 | | \$0.75 | \$75 | \$1.12 | \$0.23 | | \$0.76 | \$76 | \$1.09 | \$0.23 | | \$0.77 | \$77 | \$1.06 | \$0.22 | | \$0.78 | \$78 | \$1.03 | \$0.21 | | \$0.79 | \$79 | \$1.00 | \$0.21 | | \$0.80 | \$80 | \$0.96 | \$0.20 | | \$0.81 | \$81 | \$0.92 | \$0.19 | | \$0.82 | \$82 | \$0.89 | \$0.18 | | \$0.83 | \$83 | \$0.85 | \$0.18 | | \$0.84 | \$84 | \$0.81 | \$0.17 | | \$0.85 | \$85 | \$0.76 | \$0.16 | | \$0.86 | \$86 | \$0.72 | \$0.15 | | \$0.87 | \$87 | \$0.68 | \$0.14 | | \$0.88 | \$88 | \$0.63 | \$0.13 | | \$0.89 | \$89 | \$0.59 | \$0.12 | | \$0.90 | \$90 | \$0.54 | \$0.11 | | \$0.91 | \$91 | \$0.49 | \$0.10 | | \$0.92 | \$92 | \$0.44 | \$0.09 | | \$0.93 | \$93 | \$0.39 | \$0.08 | | \$0.94 | \$94 | \$0.34 | \$0.07 | | \$0.95 | \$95 | \$0.28 | \$0.06 | | \$0.96 | \$96 | \$0.23 | \$0.05 | | \$0.97 | \$97 | \$0.17 | \$0.04 | | \$0.98 | \$98 | \$0.12 | \$0.02 | | \$0.99 | \$99 | \$0.06 | \$0.01 | ### Fee Rules * Fees are symmetric around p = 0.50 and lowest near the extremes (0 and 1). * All fees and rebates are rounded to the nearest \$0.01 using banker's rounding (round half to even). * When an aggressive order fills against multiple resting orders, each fill is charged its banker's-rounded fee, adjusted so that the total commission collected across the order's fills never exceeds the banker's rounding of the cumulative exact fee. The adjustment can only reduce a fill's charge, never increase it. Maker rebates are computed per fill, independently. ## Examples #### Example 1: Buy 1,000 contracts at \$0.10 — cheap contract Buying a long shot. The fee scales with price uncertainty: p × (1 − p) = 0.10 × 0.90 = 0.09. * **Buyer (taker):** 0.06 × 1,000 × 0.10 × 0.90 = **−\$5.40** * **Seller (maker):** 0.0125 × 1,000 × 0.10 × 0.90 = **+\$1.12** *** #### Example 2: Buy 1,000 contracts at \$0.65 — expensive contract Buying a likely outcome. Higher price but lower p × (1 − p) than midpoint. * **Buyer (taker):** 0.06 × 1,000 × 0.65 × 0.35 = **−\$13.65** * **Seller (maker):** 0.0125 × 1,000 × 0.65 × 0.35 = **+\$2.84** *** #### Example 3: Sell 1,000 contracts at \$0.30 — sell low probability The seller is the aggressor. Both sides pay based on the same p × (1 − p) factor. * **Seller (taker):** 0.06 × 1,000 × 0.30 × 0.70 = **−\$12.60** * **Buyer (maker):** 0.0125 × 1,000 × 0.30 × 0.70 = **+\$2.62** *** #### Example 4: Sell 1,000 contracts at \$0.90 — sell high probability When the price is close to \$1.00, p × (1 − p) is small and fees are minimal. * **Seller (taker):** 0.06 × 1,000 × 0.90 × 0.10 = **−\$5.40** * **Buyer (maker):** 0.0125 × 1,000 × 0.90 × 0.10 = **+\$1.12** *** #### Example 5: Buy 1,000 contracts at \$0.50 — coin flip market A 50/50 market. This is where the fee is highest per contract because p × (1 − p) = 0.25. * **Buyer (taker):** 0.06 × 1,000 × 0.50 × 0.50 = **−\$15.00** * **Seller (maker):** 0.0125 × 1,000 × 0.50 × 0.50 = **+\$3.12** ## FAQ ### Are fees deducted from my balance automatically? Yes. Taker fees are deducted from your balance at the time of the trade. Maker rebates are credited to your balance at the time of the fill. ### Can fees ever be zero? Yes. Fees are rounded to the nearest cent. On small trades (low quantity or prices near \$0.00 or \$1.00), the fee can round down to \$0.00. ### Do I pay fees when my order is canceled or expires? No. Fees are only charged when a trade executes. If your order is canceled, expires, or is rejected, no fee is charged. ### What is banker's rounding? Fees are rounded to the nearest cent using banker's rounding (round half to even). For example, \$0.025 rounds to \$0.02 (down to even), while \$0.035 rounds to \$0.04 (up to even). # Glossary Source: https://docs.polymarket.us/getting-started/glossary Glossary of common Polymarket terms ## Market Structure & Hierarchy | Term | Definition | | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | **Category** | Broadest classification level organizing instruments by event type (e.g., Sports, Politics, Crypto, Culture). | | **Series** | More specific classification within a category, such as a sports league or topic (e.g., NFL, NBA, US Presidential, Bitcoin). | | **Event** | A specific occurrence with multiple possible outcomes. Each event consists of one or more instruments representing the complete set of tradable outcomes. | | **Product** | The type of contract structure used to format instruments (e.g., Athletic Event Contract, Election Winner Contract, Total Score Contract). | | **Instrument** | The tradable symbol representing a specific outcome for an event. Follows consistent naming conventions for easy identification. Also called a market. | | **Market** | Same as an instrument. A tradable outcome with defined resolution criteria and sources. | | **Contract** | An instance of an instrument. You trade contracts in quantities (e.g., buying 100 contracts). Each contract settles at \$1.00 if the outcome occurs and \$0.00 if it does not. | ## Trading Basics | Term | Definition | | ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Long position** | Buying contracts of an instrument, trading on the outcome occurring. You pay the current price per contract and receive \$1.00 per contract if the outcome happens. | | **Short position** | Selling (shorting) contracts of an instrument, trading on the outcome not occurring. You receive the current price per contract but must cover potential losses if the outcome happens. | | **Order** | A request to buy or sell contracts at a chosen price. | | **Order book** | List of bids and asks showing available prices and size at each level. | | **Bid** | Highest price buyers are willing to pay. | | **Ask** | Lowest price sellers are willing to accept. | | **BBO (Best Bid and Offer)** | The best bid and ask prices currently available in the order book - the tightest spread at the top of the book. | | **Spread** | Gap between the bid and the ask. | | **Size** | Number of contracts available to buy or sell at a given price. | | **Liquidity** | How much size is available to trade at listed prices. | ## Order Execution | Term | Definition | | -------------------------- | ----------------------------------------------------------------------- | | **Fill** | When your order completes, fully or partially. | | **Partial fill** | When only part of your order fills. | | **Execution price** | The price at which your order fills. | | **Fill price** | Average price you receive when your order fills across multiple levels. | | **Marketable limit order** | An order that fills at the best available price shown on your screen. | | **Price impact** | How much your fill price changes because of limited liquidity. | | **Slippage** | Difference between expected and actual execution price. | | **Maker** | Trader who adds liquidity by placing an order on the book. | | **Taker** | Trader who removes liquidity by filling an existing order. | ## Positions & Account | Term | Definition | | ------------------------ | ---------------------------------------------------------------------------------------------------------- | | **Open position** | Contracts you currently hold. | | **Closed position** | A position you no longer hold because you sold it or the market resolved. | | **Position value** | Real-time dollar value of your open positions. | | **Cash balance** | Funds in your account not tied to open positions. | | **Buying power** | Cash available to open new positions after margin is applied. | | **Instant buying power** | Part of your deposit that becomes available to trade immediately. | | **Margin** | Funds locked to cover the maximum possible loss of a short position. Equal to \$1.00 per contract shorted. | | **Max gain** | The most you can earn on a position. | | **Max loss** | The most you can lose on a position. | ## Market Lifecycle | Term | Definition | | ----------------- | ----------------------------------------------------------------------------------------------- | | **Clarification** | Extra context added to explain how rules should be understood. | | **Resolution** | When Polymarket determines the final outcome of a market using the sources listed in the rules. | | **Settlement** | Final payout of \$1.00 for winning contracts and \$0.00 for losing contracts. | ## Fees & Display | Term | Definition | | ---------------- | ---------------------------------------------------------------------- | | **Fees** | Trading or platform fees applied to executed orders. | | **Odds display** | How prices are shown, such as price, percent chance, or American odds. | | **Open order** | An order waiting on the book to be filled. | | **Order status** | Whether an order is open, filled, or partially filled. | | **History** | Section showing your past filled orders and closed positions. | # Quickstart Source: https://docs.polymarket.us/getting-started/quickstart Make your first API request in minutes. ## Step 1: Get your API keys 1. **Download the app** - Get the [Polymarket US app](https://apps.apple.com/us/app/polymarket/id6648798962) and create an account. 2. **Complete identity verification** - You'll be asked to verify your identity before you can trade or access the API. Once approved, you'll see a confirmation in the app. Approved to Start Trading 3. **Go to the developer portal** - Visit [polymarket.us/developer](https://polymarket.us/developer) and sign in with the same method you used in the app (Apple, Google, or email). Developer Portal 4. **Create an API key** - Click to create a new key. You'll get a **Key ID** and a **Secret Key**. Create API Key Your secret key is shown **only once**. Copy it somewhere safe before closing the dialog. If you need help getting set up or need an invite code to access the app, email [support@polymarket.us](mailto:support@polymarket.us). *** ## Step 2: Install the SDK ```bash TypeScript theme={null} npm install polymarket-us ``` ```bash Python theme={null} pip install polymarket-us ``` TypeScript requires Node.js 18+. Python requires 3.10+. *** ## Step 3: Configure the client ```typescript TypeScript theme={null} import { PolymarketUS } from 'polymarket-us'; const client = new PolymarketUS({ keyId: process.env.POLYMARKET_KEY_ID, secretKey: process.env.POLYMARKET_SECRET_KEY, }); ``` ```python Python theme={null} import os from polymarket_us import PolymarketUS client = PolymarketUS( key_id=os.environ["POLYMARKET_KEY_ID"], secret_key=os.environ["POLYMARKET_SECRET_KEY"], ) ``` *** ## Step 4: Fetch market data No authentication required for public endpoints. ```typescript TypeScript theme={null} const client = new PolymarketUS(); const events = await client.events.list({ limit: 10, active: true }); const market = await client.markets.retrieveBySlug('chiefs-super-bowl'); const book = await client.markets.book('chiefs-super-bowl'); ``` ```python Python theme={null} client = PolymarketUS() events = client.events.list({"limit": 10, "active": True}) market = client.markets.retrieve_by_slug("chiefs-super-bowl") book = client.markets.book("chiefs-super-bowl") ``` *** ## Step 5: Place an order ```typescript TypeScript theme={null} const order = await client.orders.create({ marketSlug: 'chiefs-super-bowl', intent: 'ORDER_INTENT_BUY_LONG', type: 'ORDER_TYPE_LIMIT', price: { value: '0.55', currency: 'USD' }, quantity: 100, tif: 'TIME_IN_FORCE_GOOD_TILL_CANCEL', }); ``` ```python Python theme={null} order = client.orders.create({ "marketSlug": "chiefs-super-bowl", "intent": "ORDER_INTENT_BUY_LONG", "type": "ORDER_TYPE_LIMIT", "price": {"value": "0.55", "currency": "USD"}, "quantity": 100, "tif": "TIME_IN_FORCE_GOOD_TILL_CANCEL", }) ``` *** ## Check your account ```typescript TypeScript theme={null} const balances = await client.account.balances(); const positions = await client.portfolio.positions(); const openOrders = await client.orders.list(); ``` ```python Python theme={null} balances = client.account.balances() positions = client.portfolio.positions() open_orders = client.orders.list() ``` *** ## Error handling ```typescript TypeScript theme={null} import { AuthenticationError, BadRequestError, NotFoundError, RateLimitError, } from 'polymarket-us'; try { const order = await client.orders.create({ marketSlug: '...' }); } catch (error) { if (error instanceof AuthenticationError) { console.error('Invalid credentials'); } else if (error instanceof BadRequestError) { console.error('Invalid parameters:', error.message); } else if (error instanceof RateLimitError) { console.error('Rate limited'); } else if (error instanceof NotFoundError) { console.error('Not found'); } } ``` ```python Python theme={null} from polymarket_us import ( AuthenticationError, BadRequestError, NotFoundError, RateLimitError, APITimeoutError, APIConnectionError, ) try: order = client.orders.create({"marketSlug": "..."}) except AuthenticationError as e: print(f"Invalid credentials: {e.message}") except BadRequestError as e: print(f"Invalid parameters: {e.message}") except RateLimitError as e: print(f"Rate limited: {e.message}") except NotFoundError as e: print(f"Not found: {e.message}") ``` | Error | Description | | --------------------- | ------------------------------ | | `AuthenticationError` | Invalid or missing credentials | | `BadRequestError` | Invalid request parameters | | `NotFoundError` | Resource not found | | `RateLimitError` | Rate limit exceeded | | `APITimeoutError` | Request timed out | | `APIConnectionError` | Network connection error | *** ## Next steps Full SDK reference for TypeScript. Full SDK reference for Python. Explore all REST endpoints. Stream live market data. # Welcome Source: https://docs.polymarket.us/getting-started/welcome Polymarket US
Looking for Polymarket International documentation? Visit International Docs →
# Polymarket US US flag Documentation
Build on the world's largest prediction market.
## Developer Quickstart

Make your first API request in minutes. Learn the basics of the Polymarket US platform, fetch market data, place orders, and redeem winning positions.

```typescript TypeScript theme={null} import { PolymarketUS } from 'polymarket-us'; const client = new PolymarketUS({ keyId: process.env.POLYMARKET_KEY_ID, secretKey: process.env.POLYMARKET_SECRET_KEY, }); const order = await client.orders.create({ marketSlug: 'chiefs-super-bowl-lx', intent: 'ORDER_INTENT_BUY_LONG', type: 'ORDER_TYPE_LIMIT', price: { value: '0.55', currency: 'USD' }, quantity: 100, tif: 'TIME_IN_FORCE_GOOD_TILL_CANCEL', }); ``` ```python Python theme={null} import os from polymarket_us import PolymarketUS client = PolymarketUS( key_id=os.environ["POLYMARKET_KEY_ID"], secret_key=os.environ["POLYMARKET_SECRET_KEY"], ) order = client.orders.create({ "marketSlug": "chiefs-super-bowl-lx", "intent": "ORDER_INTENT_BUY_LONG", "type": "ORDER_TYPE_LIMIT", "price": {"value": "0.55", "currency": "USD"}, "quantity": 100, "tif": "TIME_IN_FORCE_GOOD_TILL_CANCEL", }) ```
## Get Familiar with Polymarket US

Learn the fundamentals, explore our APIs, and start building on the world's largest prediction market.

Set up your environment and make your first API call in minutes. Understand markets, events, and how trading works. Explore REST endpoints, WebSocket streams, and authentication. Official Python and TypeScript libraries for faster development.
# What is Polymarket US? Source: https://docs.polymarket.us/getting-started/what-is-polymarket-us Polymarket US is a CFTC-regulated exchange for trading event contracts on real-world outcomes. Each market asks a clear yes/no question about something that might happen, and prices reflect how likely traders think the outcome is. ## Polymarket vs Polymarket US **Polymarket** is our international, crypto-based product that operates on blockchain technology. **Polymarket US** is a fiat-based, US-regulated platform operating as a designated contract market (DCM) and derivatives clearing organization (DCO) under CFTC oversight. All trading is conducted in US dollars with full regulatory compliance. Crypto-based product on blockchain technology. Fiat-based, CFTC-regulated exchange. Trades in USD. Built for US residents. *** ## What is a prediction market? A prediction market is a place where people trade on the odds of future events. Prices reflect how the crowd views the likelihood of an outcome happening. * A contract priced at **62¢** means the market thinks there is roughly a **62% chance** the event occurs * Contracts settle at **\$1** if the outcome happens, or **\$0** if it does not * You can buy or sell at any point before settlement - you don't have to hold to the end Because prices are set entirely by real trades, they tend to track real-world probabilities closely. *** ## How contracts work Each market on Polymarket US is a yes/no question about something that will happen in the future. **Example**: *Will the Kansas City Chiefs win Super Bowl LX?* | Scenario | You buy YES at | Outcome | Contract settles at | Your profit | | ----------- | -------------- | ------- | ------------------- | ----------------- | | Chiefs win | 55¢ | YES | \$1.00 | +45¢ per contract | | Chiefs lose | 55¢ | NO | \$0.00 | -55¢ per contract | You can also sell your position at any time before the outcome is known. If the odds move in your favor, you can lock in a profit early without waiting for settlement. *** ## What can you trade? Polymarket US currently offers markets on: * NFL * NBA * NHL * MLB * MLS * CBB * Tennis, golf, and more Politics, culture, finance, and economics coming soon. *** ## How is Polymarket US different from a sportsbook? Polymarket US operates a central limit order book. Your order is matched against another user's order. The platform does not take the other side of your trades and does not set prices. Unlike a sportsbook where your trade is locked in, you can sell your contracts before the event resolves. If sentiment shifts and prices move in your favor, you can close out early and take profits. Odds on Polymarket US come from real trades. As new information becomes available, traders adjust their orders and prices update in real time - just like a financial market. Polymarket US operates under CFTC oversight as a designated contract market. Event contracts are a regulated financial instrument. # gRPC API Overview Source: https://docs.polymarket.us/grpc-api/overview Guide to using gRPC unary (request/response) methods on Polymarket Exchange The Polymarket Exchange exposes all API functionality via **gRPC** using Protocol Buffers. This section covers **unary (request/response) methods** - for streaming services, see [gRPC Streaming](/streaming-endpoints/grpc-overview). ## Why Use gRPC? gRPC offers several advantages over REST: * **Performance**: Binary protocol with smaller payloads and faster serialization * **Type Safety**: Strongly-typed messages defined in Protocol Buffers * **Streaming**: Native support for bidirectional streaming (covered in gRPC Streaming tab) * **Code Generation**: Auto-generate client libraries in any language Get the complete Protocol Buffer definitions to generate client libraries in any language ## Server Endpoints ### Pre-Production Environment ``` grpc-api.preprod.polymarketexchange.com:443 ``` ### Production Environment ``` grpc-api.prod.polymarketexchange.com:443 ``` All gRPC connections use **TLS/SSL**. Ensure your client is configured for secure connections. ## Authentication All gRPC calls require an access token passed in the `authorization` metadata header: ```python theme={null} import grpc # Obtain access token token = get_access_token() # Create authenticated channel credentials = grpc.ssl_channel_credentials() channel = grpc.secure_channel("grpc-api.prod.polymarketexchange.com:443", credentials) # Add authorization metadata to calls metadata = [("authorization", f"Bearer {token}")] response = stub.GetWhoAmI(request, metadata=metadata) ``` See [Authentication](/streaming-endpoints/authentication) for details on obtaining access tokens. ## Available Services ### Trading Services | Service | Description | | ----------------- | -------------------------------------------------------- | | **OrderEntryAPI** | Insert, cancel, and preview orders | | **ComboAPI** | Create and read combo instruments | | **RFQAPI** | Read and manage combo RFQs and quotes; stream RFQ events | | **ReportAPI** | Search orders, trades, and executions | | **PositionAPI** | Query account balances and positions | ### Market Data Services | Service | Description | | ---------------- | --------------------------------------- | | **RefDataAPI** | List instruments, symbols, and metadata | | **OrderBookAPI** | Get order book snapshots and BBO | ### Account Services | Service | Description | | --------------- | ---------------------------- | | **AccountsAPI** | Get user info, list accounts | | **HealthAPI** | Health check endpoint | ### Funding Services | Service | Description | | --------------- | ------------------------------------------ | | **KYCAPI** | KYC verification status and referral codes | | **AeropayAPI** | ACH bank linking via Aeropay | | **CheckoutAPI** | Card payments via Checkout.com | | **FundingAPI** | Funding accounts and transactions | ### Execution Feed | Service | Description | | --------------- | -------------------------------- | | **DropCopyAPI** | Trade capture and execution feed | *** ## Drop Copy & Trade Capture gRPC Streaming The `DropCopyAPI` service provides real-time streaming endpoints for execution reports, trade capture, and position changes. These are **gRPC streaming only** - there are no REST equivalents. For complete documentation including code examples, see [DropCopy & Trade Capture Streaming](/streaming-endpoints/dropcopy-stream). ### Available Streaming Endpoints ```protobuf theme={null} service DropCopyAPI { // Real-time execution reports (fills, cancels, rejects) rpc CreateDropCopySubscription(CreateDropCopySubscriptionRequest) returns (stream CreateDropCopySubscriptionResponse); // Completed trade records for reconciliation rpc CreateTradeCaptureReportSubscription(CreateTradeCaptureReportSubscriptionRequest) returns (stream CreateTradeCaptureReportSubscriptionResponse); // Market state changes (halts, opens, closes) rpc CreateInstrumentStateChangeSubscription(CreateInstrumentStateChangeSubscriptionRequest) returns (stream CreateInstrumentStateChangeSubscriptionResponse); // Real-time position updates rpc CreatePositionChangeSubscription(CreatePositionChangeSubscriptionRequest) returns (stream CreatePositionChangeSubscriptionResponse); } ``` ### Drop Copy Subscription Stream execution reports as they occur for your firm. Use this for: * Real-time order execution monitoring * Fill notifications across all firm accounts * Cancel and reject tracking **Key parameters:** * `symbols` - Filter by specific symbols (empty = all) * `resume_token` - Resume from previous position after disconnect ### Trade Capture Report Subscription Stream completed trades for reconciliation and compliance. Each trade contains: * `aggressor` - The incoming (taker) execution * `passive` - The resting (maker) execution * `trade_type` - Type of trade (REGULAR, CROSS, etc.) * `state` - Trade state (NEW, CLEARED, BUSTED, ...); see [Trade States](/streaming-endpoints/dropcopy-stream#trade-states) ### Quick Example ```python theme={null} import grpc from polymarket.v1 import dropcopy_pb2, dropcopy_pb2_grpc # Connect channel = grpc.secure_channel( "grpc-api.prod.polymarketexchange.com:443", grpc.ssl_channel_credentials() ) stub = dropcopy_pb2_grpc.DropCopyAPIStub(channel) # Subscribe to executions request = dropcopy_pb2.CreateDropCopySubscriptionRequest( symbols=["tec-nfl-sbw-2026-02-08-kc"] # or empty for all ) metadata = [("authorization", f"Bearer {token}")] for response in stub.CreateDropCopySubscription(request, metadata=metadata): for execution in response.executions: print(f"Execution: {execution.id} - {execution.type}") ``` **When to Use Each Stream** | Stream | Use Case | | --------------------------- | ------------------------------------------------ | | **DropCopy** | Real-time execution monitoring, trading systems | | **Trade Capture Report** | Back-office reconciliation, compliance reporting | | **Instrument State Change** | Trading halts, market open/close notifications | | **Position Change** | Real-time P\&L, risk monitoring | *** ## Package Structure All services are in the `polymarket.v1` package: ```protobuf theme={null} package polymarket.v1; service OrderEntryAPI { rpc InsertOrder(InsertOrderRequest) returns (InsertOrderResponse); rpc CancelOrder(CancelOrderRequest) returns (CancelOrderResponse); rpc PreviewOrder(PreviewOrderRequest) returns (PreviewOrderResponse); rpc GetOpenOrders(GetOpenOrdersRequest) returns (GetOpenOrdersResponse); } ``` ## Quick Start ### 1. Install gRPC Libraries ```bash theme={null} # Python pip install grpcio grpcio-tools # Go go get google.golang.org/grpc # Node.js npm install @grpc/grpc-js @grpc/proto-loader ``` ### 2. Obtain Proto Files Download the proto files directly: [Polymarket - Proto Files.zip](https://drive.google.com/uc?export=download\&id=1oT9gaeBEn0vukHD9GOoj_YvzPnR3otng) Use the downloaded definitions as the client contract. Do not make client startup depend on server reflection, whose availability can differ by environment. ### 3. Generate Client Code ```bash theme={null} # Python example python -m grpc_tools.protoc \ --proto_path=./protos \ --python_out=./gen \ --grpc_python_out=./gen \ ./protos/polymarket/v1/*.proto ``` ### 4. Make Your First Call ```python theme={null} import grpc from gen.polymarket.v1 import accounts_pb2, accounts_pb2_grpc # Connect channel = grpc.secure_channel( "grpc-api.prod.polymarketexchange.com:443", grpc.ssl_channel_credentials() ) stub = accounts_pb2_grpc.AccountsAPIStub(channel) # Authenticate and call metadata = [("authorization", f"Bearer {token}")] response = stub.GetWhoAmI(accounts_pb2.GetWhoAmIRequest(), metadata=metadata) print(f"User ID: {response.user_id}") ``` ## REST vs gRPC Both REST and gRPC access the same underlying services. Choose based on your needs: | Aspect | REST | gRPC | | --------------- | --------------- | ------------------ | | Protocol | HTTP/JSON | HTTP/2 + Protobuf | | Performance | Good | Better | | Type Safety | Schema optional | Built-in | | Browser Support | Native | Requires proxy | | Tooling | curl, Postman | grpcurl, Bloom RPC | Most integrations use **REST for simplicity** and **gRPC for performance-critical paths** like order entry. ## Next Steps Real-time market data and order subscriptions Access token setup for gRPC Equivalent REST endpoints Proto message definitions # Liquidity Incentive Program Source: https://docs.polymarket.us/incentives/liquidity Earn rewards for placing resting orders close to the best price.
**The live rewards schedule is published at polymarket.us/rewards.** Search and filter rewards on every market — current reward pools, discount factors, target sizes, and eligible markets, always up to date.
This program rewards traders for placing resting limit orders. The closer your orders are to the best price and the larger they are, the more you earn. Every second, the Exchange scores each trader's resting orders based on price and size, and rewards are split proportionally. | Term | Definition | | ---------------------- | ------------------------------------------------------------------------------------------------------------- | | **Time Period** | A window during the event lifecycle (e.g., pre-event, event day, mid-event) with its own reward pool | | **Discount Factor** | How much orders further from the best price are penalized; closer orders score higher | | **Target Size** | The minimum number of contracts that must exist on each side of the book for that side to qualify for rewards | | **Time Period Reward** | The total reward pool for each time period | ## FAQ ### How does scoring work? Every second, a random snapshot of the order book is taken. Your score for each resting order is: `Score = Discount Factor ^ (ticks from best price) × Order Size` For example, with a Discount Factor of 0.30: * 1,000 contracts at best price: 0.30^0 × 1,000 = **1,000** * 1,000 contracts 1 tick away: 0.30^1 × 1,000 = **300** * 1,000 contracts 2 ticks away: 0.30^2 × 1,000 = **90** * 1,000 contracts 3 ticks away: 0.30^3 × 1,000 = **27** If these are the only orders on this side, the trader at the best price earns 1,000 / 1,417 = **70.6%** of that snapshot's score on that side, and the trader 3 ticks away earns 27 / 1,417 = **1.9%**. Each snapshot is equally weighted; see "How are snapshots weighted?" below. With a higher Discount Factor like 0.90 (MLB futures), the penalty is gentler: 1,000 at best price scores **1,000**, one tick away scores **900**, two ticks away scores **810**. Each side of the book is scored independently; the spread between your bid and offer doesn't matter. The formula works the same regardless of tick size (1c or 0.1c); a "tick" is always one minimum price increment. ### How are snapshots weighted? Each snapshot is normalized; the bid side and ask side are each independently normalized to 1.0 per snapshot, provided Target Size is met on that side. This means every second of the time period is equally weighted regardless of how much liquidity is on the book. A second during halftime with 1M contracts counts the same as a second during live play with 2K contracts, so long as Target Size is satisfied. ### How does Target Size work? Target Size is the minimum aggregate size of resting orders (across all participants) needed on a side of the book for that side to qualify. It uses raw size, not discounted size. The exchange walks from the best price outward, accumulating orders until Target Size is reached. All orders within that range score; orders beyond it do not. If Target Size is reached before your price level, your order will not score, regardless of how close it is to the best price. For example, if Target Size is 20,000 and there are 25,000 contracts resting at the best price, orders at the second-best price receive zero score. Target Size isn't a per-person cap. Once the threshold is met, all qualifying orders score proportionally. Bigger orders earn more. There's no ceiling on how much size you can have scored. ### Can size compensate for the Discount Factor? Yes; a larger order further from the best price can still earn meaningful rewards. However, the discount compounds with each tick, so orders closer to the best price are significantly more capital-efficient. For example, with a Discount Factor of 0.30, an order one tick away needs roughly 3× the size to earn the same score as an order at the best price. ### Can the parameters change? Yes. Target Size, Discount Factor, and Time Period Reward may be adjusted between time periods as liquidity conditions change. The current schedule for every market is published live at [polymarket.us/rewards](https://polymarket.us/rewards). ### Is there a cap on how much one person can earn? No. Your payout is purely proportional to your share of the total score. If you're the only one providing liquidity and you meet the Target Size, you earn the entire reward pool. ### When are rewards paid out? Rewards are calculated within 5 business days of each time period ending and credited to your account within 2 business days of the end of that calculation. ### What do the time periods mean? * **Early / Pre-game (pre-day):** From market listing until 6 hours before the event * **Day-of / Pre-game:** From 6 hours before until the event starts * **Live:** From event start until settlement * **Daily (per event):** A per-day pool on an event without a fixed start (e.g. futures), running midnight to midnight Eastern Time (ET) ### Is there a minimum payout? Yes. Rewards under \$1.00 are not paid out. ### What about canceled or postponed games? No rewards are distributed for canceled or postponed games. # Market Maker Program Source: https://docs.polymarket.us/incentives/market-maker Apply to provide stable liquidity across a wide range of contracts. This program rewards approved market makers for providing liquidity across a wide range of contracts in given categories. To learn more or apply, contact [institutional@qcex.com](mailto:institutional@qcex.com). # Overview Source: https://docs.polymarket.us/incentives/overview Polymarket operates a variety of incentive programs for all traders. Polymarket US offers incentive programs that pay you for trading activity and providing liquidity. Some programs are open to everyone, others require approval.
Program Access Description
User Incentive Programs
Deposit Incentive Program Open Earn incentive credits for making a qualifying deposit
Refer-A-Friend Incentive Program Open Earn incentive credits for referring your friends
Daily Trading Incentive Program Coming Soon Earn incentive credits for daily trading activity
Deposit and Trading Incentive Program Coming Soon Earn incentive credits for qualifying deposit and trading activity
Institutional Incentive Programs
Volume Incentive Program Open Rewards for taker trading volume
Liquidity Incentive Program Open Rewards for placing resting orders, whether they fill or not — see the live rewards schedule
Market Maker Program Application Strong incentives for providing stable liquidity across a variety of markets
Affiliate Incentive Programs
Referral Incentive Program Application Rewards for referring new traders to Polymarket US
**Open** programs are live right now. No signup or application needed. **Application** programs are formal arrangements with contractual obligations and require approval before you can participate. # Referral Incentive Program Source: https://docs.polymarket.us/incentives/referral Earn rewards for referring new traders to Polymarket US. This program rewards approved affiliates for referring new Participants to Polymarket US. To learn more, contact [affiliate@polymarket.com](mailto:affiliate@polymarket.com). # User Incentive Programs Source: https://docs.polymarket.us/incentives/user-programs Deposit, Refer-A-Friend, Daily Trading, and Deposit and Trading Incentive Programs for Polymarket US Participants. Polymarket US may offer incentive programs designed to increase market participation, liquidity, and engagement on Polymarket US. Each program has its own eligibility requirements, payment conditions, limits, and controls. Current incentive programs may include: | Program | Applies to | Requirement | | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------- | ------------------------------------------------------------------------------------------- | | **[Deposit Incentive Program](https://polymarketexchange.com/files/notices/Exchange%20Notice%20-%20Amended%20Deposit%20Incentive%20Program%20\(2026.08.17\).pdf)** | Deposit-based credits | Make the applicable qualifying deposit | | **[Refer-A-Friend Incentive Program](https://polymarketexchange.com/files/notices/Exchange%20Notice%20-%20Amended%20Refer-A-Friend%20Incentive%20Program%20\(2026.08.26\).pdf)** | Fixed referral credits | Referred friend onboards and makes a qualifying deposit; both Participants receive a credit | | **[Daily Trading Incentive Program](https://polymarketexchange.com/files/notices/Exchange%20Notice%20-%20Amended%20Daily%20Trading%20Incentive%20Program%20\(2026.07.29\).pdf)** | Daily activity credits | Deposit and satisfy daily trading requirements | | **[Deposit and Trading Incentive Program](https://polymarketexchange.com/files/notices/Exchange%20Notice%20-%20Deposit%20and%20Trading%20Incentive%20Program%20\(2026.08.24\).pdf)** | Credits for qualifying deposit and trading activity | Make a qualifying deposit and/or satisfy trading activity requirements | Polymarket US may withhold, cancel, or reverse incentives in cases of suspected fraud, abuse, manipulation, self-dealing, self-referral, coordinated activity, or other activity inconsistent with the applicable program terms or Polymarket US rules. ## Promotional Trading Credits Incentives are credited to the qualifying Participant's Polymarket US account. Incentives are promotional trading credits that may be used to open positions and pay applicable fees, but may not be withdrawn as cash. A Participant's existing collateral will be used before the Credit. Account balance in excess of the Credit is normal collateral that may be withdrawn. ## Program Changes Polymarket US may modify, suspend, or terminate any incentive program or campaign on a prospective basis. Changes will be posted on the Polymarket US website. ## Deposit Incentive Program Eligible Participants may receive an incentive credit after making a qualifying deposit under an applicable Deposit Incentive Program campaign. A Participant qualifies only for the campaign presented to them, via an onboarding code or link, before the qualifying deposit. Each Participant may only receive one (1) incentive credit under the Deposit Incentive Program. | Campaign | Qualifying Deposit | Incentive Credit | | ---------- | --------------------- | ---------------- | | Campaign A | Deposit at least \$10 | Receive \$50 | Incentive credits are applied once Polymarket US confirms the Participant's deposit satisfies the applicable campaign requirements. Participants receive only the incentives they qualify for under the applicable campaign terms. ## Refer-A-Friend Incentive Program Participants may earn incentive credits for referring new users to Polymarket US. To participate, a Participant shares their unique referral code (their username) with an individual who does not have an existing Polymarket US account. Upon that individual creating an account using the code and making the qualifying deposit, the referring Participant and the referred Participant each receive the incentive credit under the applicable campaign in their respective Polymarket US accounts. | Campaign | Qualifying Deposit | Incentive Credit | | ---------- | --------------------- | ---------------- | | Campaign A | Deposit at least \$10 | Receive \$50 | Each Participant is limited to 50 friend referrals. Incentive credits are applied once Polymarket US confirms the applicable campaign requirements have been satisfied. Participants receive only the incentives they qualify for under the applicable campaign terms. ## Daily Trading Incentive Program Details for the Daily Trading Incentive Program will be published here once the program is live. ## Deposit and Trading Incentive Program Details for the Deposit and Trading Incentive Program will be published here once the program is live. # Volume Incentive Program Source: https://docs.polymarket.us/incentives/volume Earn rewards based on your share of trading volume on eligible contracts. This program rewards traders based on their share of trading volume on eligible contracts. The more you trade, the more you earn. Reward amounts for each contract will be published here. | Term | Definition | | --------------------- | ------------------------------------------------------------------------------------ | | **Eligible Term** | How long the reward runs — usually the full lifetime of the contract | | **Eligible Volume** | Taker-side notional only. Only trades executed between 3c and 97c (inclusive) count. | | **Volume Reward** | The total reward amount for each contract | | **Volume Multiplier** | A bonus applied during certain time windows to encourage trading | ### Example A contract has a \$100,000 reward pool. If total eligible taker notional volume in the market is \$1M, and your share is \$100k, you receive \$10,000 (10%) of the pool. ### NBA Playoffs Moneyline Volume Rewards Every NBA Playoffs Moneyline market has a **\$100,000 rewards pool for in-game trading** (live as of 8:00pm ET, Thursday May 21). * Only trades executed between \$0.03 and \$0.97 (inclusive) are eligible * Rewards are paid based on your share of total eligible taker notional volume * Minimum **\$500 notional** required to be eligible for payouts # Accounts API Overview Source: https://docs.polymarket.us/institutional/accounts/overview Query the authenticated identity, tradable accounts, and users you may act on behalf of # Accounts API The Accounts API is the public identity and account **query** surface. Market makers and ISV/IB partners use it to learn who they are trading as and which accounts they can use. Account **creation** is not a public REST call. ISV/IB trading accounts are provisioned when KYC is approved — see [Accounts](/partners/onboarding/accounts). Market-maker accounts are provisioned during onboarding. ## Endpoints | Method | Endpoint | Description | | ------ | -------------- | -------------------------------------------- | | `GET` | `/v1/whoami` | Get current user and firm info | | `GET` | `/v1/accounts` | List accounts the caller may use to trade | | `GET` | `/v1/users` | List users the caller may trade on behalf of | ## Account Hierarchy ``` Firm └── Users └── Accounts └── Positions & Orders ``` * **Firm**: Your organization * **Users**: People or participant identities that can trade * **Accounts**: Trading accounts belonging to those users ## User vs Account | Entity | Description | | ----------- | --------------------------------------------- | | **User** | A person with identity (KYC verified) | | **Account** | A trading account with balances and positions | A user can have multiple accounts (e.g., for different strategies or purposes). ## Common Use Cases 1. **Identity** - Call `GET /v1/whoami` to confirm the authenticated user and firm 2. **Account lookup** - Call `GET /v1/accounts` to list trading accounts and display names 3. **Act on behalf of a user** - Call `GET /v1/users` for the participant names you may trade as # Combos API Overview Source: https://docs.polymarket.us/institutional/combos/overview Create and read combo instruments A combo is a user-defined instrument with 2–10 legs. Each leg identifies an existing market symbol and whether the combo buys or sells that leg. Combo instruments can trade through normal order entry and can be used as the symbol of an [RFQ](/institutional/rfqs/overview). The public `polymarket.v1.ComboAPI` gRPC service creates and reads combo instruments. Each RPC also has a REST endpoint. REST JSON uses lower camel case; protobuf fields use snake case. ## Endpoints | Method | Endpoint | Scope | Per-firm limit | Description | | ------ | ---------------------------- | -------------- | -------------- | -------------------------------------------- | | `GET` | `/v1/combos?symbol={symbol}` | `read:orders` | 100 req/sec | Get a combo by exact symbol | | `POST` | `/v1/combos` | `write:orders` | 1 req/sec | Create or retrieve a combo for a set of legs | All calls require bearer-token authentication and an acting participant, supplied through `x-participant-id` or the token's `participant_id` claim. Each method has a separate per-firm rate-limit bucket with one second of burst capacity. See [Rate Limits](/trader-guide/rate-limits#combos-endpoints). Separately, combo creation has a participant-wide service quota of 1,000 new instruments per week across all accounts and both Retail and Institutional APIs. The quota resets Monday at 00:00 UTC; returning an existing canonical combo does not consume it. ## Create a Combo `POST /v1/combos` accepts a list of 2–10 unique legs: ```json theme={null} { "legs": [ { "symbol": "aec-mlb-nyy-bos-2026-07-29-nyy", "side": "SIDE_BUY" }, { "symbol": "tsc-nba-bos-lal-2026-07-29-207pt5", "side": "SIDE_SELL" } ] } ``` The component symbols must be open, tradable, supported instruments. The service rejects duplicate symbols and invalid combinations. A canonical set of legs always maps to the same `caoc-...` combo symbol; if that combo already exists, `CreateCombo` returns it. ```json theme={null} { "combo": { "id": "caoc-...", "legs": [ { "symbol": "aec-mlb-nyy-bos-2026-07-29-nyy", "side": "SIDE_BUY" }, { "symbol": "tsc-nba-bos-lal-2026-07-29-207pt5", "side": "SIDE_SELL" } ], "state": "INSTRUMENT_STATE_OPEN", "createdTime": "2026-07-29T14:00:00Z", "tickSize": 0.001 } } ``` ## Get a Combo `GET /v1/combos?symbol=caoc-...` requires the exact combo symbol and returns: ```json theme={null} { "combos": [ { "id": "caoc-...", "legs": [ { "symbol": "aec-mlb-nyy-bos-2026-07-29-nyy", "side": "SIDE_BUY" }, { "symbol": "tsc-nba-bos-lal-2026-07-29-207pt5", "side": "SIDE_SELL" } ], "state": "INSTRUMENT_STATE_OPEN", "createdTime": "2026-07-29T14:00:00Z", "tickSize": 0.001 } ] } ``` An unknown symbol returns an empty `combos` array. This endpoint does not paginate. ## See Also Create and manage combo RFQs and quotes Maker workflow and quote rules OAuth metadata and required scopes Endpoint-level request limits # FIX API Reference Data Source: https://docs.polymarket.us/institutional/fix-api/fix-api-reference-data Participants may download a list of the instruments available (or reference data about them) to trade on Polymarket US by submitting a SecurityListRequest \[x] message. ## Table 23: SecurityListRequest (x) message
Tag Name Req Type Description
\< Standard Header >Y35 = x
320SecurityReqIDYStringUnique ID associated with this request.
336TradingSessionIDNStringUsed to filter for instruments by market state (CLOSED, OPEN, PREOPEN, SUSPENDED, EXPIRED, TERMINATED, HALTED, MATCH\_AND\_CLOSE\_AUCTION)
559SecurityListRequestTypeYintThe type of request being made (0=Individual symbol, 4=All Securities)
55SymbolCStringRequired if SecurityListRequestType (559) = 0 (individual security)
\< Standard Trailer >Y
**Example 21: Request for information on all securities** ``` 8=FIXT.1.1 | 9=75 | 35=x | 49=SENDER | 56=TARGET | 34=12 | 52=20240516-14:28:38 | 320=REF-DATA-001 | 559=4 | 10=048 | ``` Upon receipt, Polymarket US will respond with a SecurityList \[y] message containing the requested information. Note that the content provided by the SecurityList \[y] message does not differ depending on whether an individual symbol was listed versus all securities. ## Table 24: SecurityList (y) message
Tag Name Req Type Description
\< Standard Header >Y35 = y
320SecurityReqIDYStringThe unique SecurityReqID (320) sent on the request.
322SecurityResponseIDYStringUnique ID for this response. Typically a 13 character alphanumeric string.
560SecurityRequestResultYintThe status of the request (0=Valid, 1=Invalid/unsupported, 3=Not authorized)
146NoRelatedSymCNumInGroupNumber of instruments to be returned. Only present for valid requests.
→ 55SymbolYStringInstrument symbol
→ 48SecurityIDYStringWill always equal Symbol (55)
→ 22SecurityIDSourceYint8 = Exchange symbol
→ 167SecurityTypeNStringEVENT=Event contract. Currently, Polymarket only offers EVENT instruments.
→ 231ContractMultiplierNfloatThe ratio or multiplier to convert from "nominal" units (e.g. contracts) to total units (e.g. shares). Applicable for Fixed Income, Derivatives, etc.
→ 864NoEventsYNumInGroupNumber of repeating EventType entries. Will always be 1.
→→ 865EventTypeYintCode to represent the type of event (5=Activation)
→→ 866EventDateYLocalMktDateDate that the instrument first started (or will start) trading on the exchange in YYYYMMDD format.
→→ 868EventTextYStringEvent string. Always 'StartDate'.
→ 969MinPriceIncrementYfloatMinimum price increment (tick size)
→ 1151SecurityGroupYStringThe name of the group of securities to which this instrument belongs.
→ 562MinTradeVolYQtyThe minimum quantity allowed on an order. This field can be a decimal, indicating the ability to trade fractional shares of this instrument.
→ 15CurrencyYCurrencyCurrency for the instrument.
\< Standard Trailer >Y
**Example 22: SecurityList \[y] indicating two securities (color-coded)** ``` 8=FIXT.1.1 | 9=336 | 35=y | 34=9 | 49=TARGET | 52=20240516-14:28:38.864954771 | 56=SENDER | 146=2 | 55=GC-Dec-2030 | 48=GC-Dec-2030 | 22=8 | 167=NONE | 231=1 | 864=1 | 865=5 | 866=19700101 | 868=StartDate | 969=0.01 | 1151=GC | 562=1 | 15=USD | 55=GOOG | 48=GOOG | 22=8 | 167=NONE | 231=1 | 864=1 | 865=5 | 866=19700101 | 868=StartDate | 969=0.01 | 1151=Equities | 562=1 | 15=USD | 320=2007026312 | 322=1HPT7F2AA6404 | 560=0 | 10=007 | ``` ### Figure 16: Request form security reference data ![](https://files.readme.io/e1f144a-security_list.png) # FIX Data Types Source: https://docs.polymarket.us/institutional/fix-api/fix-appendix-a-fix-data-types | Type Name | Data Type | Description | | ----------------- | --------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | int | Signed integer. Zeros permitted. | Sequence of digits without commas or decimals and optional sign character (ASCII characters - and 0 - 9 ). The sign character utilizes one byte (i.e. positive int is 99999 while negative int is -99999). Note that int values may contain leading zeros (e.g. 00023 = 23). | | Length | Strictly positive integer. | int field representing the length in bytes. | | SeqNum | Strictly positive integer. | int field representing a message sequence number. | | NumInGroup | Strictly positive integer. | int field representing the number of entries in a repeating group. | | float | Signed float, with optional decimal point. | The absence of the decimal point within the string will be interpreted as the float representation of an integer value. All float fields must accommodate up to fifteen significant digits. Float values may contain leading zeros (e.g. 00023.23 = 23.23) and may contain or omit trailing zeros after the decimal point (e.g. 23.0 = 23.0000 = 23 = 23.). | | Qty | Positive float. | float field capable of storing either a whole number (no decimal places) of shares (securities denominated in whole units) or a decimal value containing decimal places for non-share quantity asset classes (securities denominated in fractional units). | | Price | Signed float price. | float field representing a price with a varying number of decimal places. For certain asset classes prices may be negative values. For example, prices for options strategies can be negative under certain market conditions. | | Amt | Signed float | float field typically representing a Price times a Qty. | | char | Case-sensitive, single alphanumeric character | Can include any alphanumeric character or punctuation except the delimiter (SOH). All char fields are case sensitive (i.e. m != M). | | Boolean | 'Y' = True/Yes
'N' = False/No | char field containing one of two possible values. | | String | Case-sensitive, alphanumeric string. | Can include any character or punctuation except the delimiter. All String fields are case sensitive (i.e. morstatt != Morstatt). | | MultipleCharValue | Space-delimited sequence of single character values | string field containing one or more space-delimited values (e.g. \| 18=G c \| ). | | Currency | Three character string. | string field representing a currency type using ISO 4217 Currency code (3 character) values. | | UTCTimestamp | Formatted UTC timestamp (date and time) | string field representing time/date combination represented in UTC in either:
YYYYMMDD-HH:MM:SS (whole seconds) or
YYYYMMDD-HH:MM:SS.sss\_format
Where:
SS can be 60 seconds in the rare case of UTC leap second, and
sss\_ represents fractions of seconds, either
3 digits to represent milliseconds,
6 digits to convey microseconds,
9 digits to convey nanoseconds, or
12 digits to convey picoseconds. | | UTCTimeOnly | Formatted UTC timestamp (time only) | string field representing a time represented in UTC in either:
HH:MM:SS (whole seconds) or
HH:MM:SS.sss\* format | | UTCDateOnly | Formatted UTC timestamp (date only) | string field representing Date represented in UTC in YYYYMMDD format. | | LocalMktDate | Date in the timezone local to the sender | string field representing Date represented in sender's timezone in YYYYMMDD format. | ## Additional Time/Date Field Details **UTCTimeOnly** * Formatted UTC timestamp (time only) * string field representing a time represented in UTC in either: * HH:MM:SS (whole seconds) or * HH:MM:SS.sss\* format **UTCDateOnly** * Formatted UTC timestamp (date only) * string field representing Date represented in UTC in YYYYMMDD format. **LocalMktDate** * Date in the timezone local to the sender * string field representing Date represented in sender's timezone in YYYYMMDD format. # FIX Components Source: https://docs.polymarket.us/institutional/fix-api/fix-component-definitions ## Standard Header & Trailer Each FIX message sent to and received from the Polymarket US must start and end with a message header and trailer components. ## Standard Header Component
Tag Name Req Type Description
8BeginStringYStringFIXT.1.1
9BodyLengthYLengthStandard FIX message body length
35MsgTypeYStringThe message type. See relevant section.
49SenderCompIDYStringSender identity as agreed with the exchange operator.
56TargetCompIDYStringIntended target identity as agreed with the exchange operator.
50SenderSubIDCStringSender sub-identifier representing an individual user (as previously agreed with the exchange operator). Required on all application messages related to the entry or management of orders.
57TargetSubIDNStringTarget sub-identifier representing an individual user (as previously agreed with the exchange operator)
34MsgSeqNumYSeqNumFIX Message sequence number
43PossDupFlagNBooleanAlways required for retransmitted messages as the result of a resend request
52SendingTimeYUTCTimeSending time in UTC
## Standard Trailer Component
Tag Name Req Type Description
10ChecksumYStringStandard FIX checksum
# FIX Connection Setup Source: https://docs.polymarket.us/institutional/fix-api/fix-connection-setup FIX connectivity to Polymarket US is provisioned via AWS VPC PrivateLink. After your [onboarding](/trader-guide/onboarding) is complete, you will receive your connection details in the FIX Connectivity form: Template form for FIX session configuration and connection details *** ## AWS VPC Connection Setup Connecting to Polymarket Exchange FIX services from your AWS VPC involves adding VPC Endpoints to your VPC's private subnets. You will need to have at least one (preferably 3) subnets in the Availability Zone IDs that are supported by the Polymarket environment you are connecting to. AWS randomly maps Availability Zones to physical Zone IDs per account. Please verify that the Zone IDs match. [Learn more about Availability Zones](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-regions-availability-zones.html#concepts-availability-zones). ### Supported AWS Availability Zone IDs | Environment | Zone IDs | | ------------------ | ---------------------------- | | **Production** | use1-az1, use1-az2, use1-az6 | | **Pre-Production** | use1-az1, use1-az2, use1-az4 | We strongly recommend using separate AWS accounts to clearly distinguish between your production and non-production sessions. ### Creating a VPC Endpoint In your AWS Console, create a new VPC Endpoint using the VPC Service Name provided to you based on the specific environment you want to connect to. #### 1. Navigate to the VPC Console 1. Open the Amazon VPC console in the AWS Management Console 2. In the navigation pane, choose **Endpoints** 3. Choose **Create endpoint** #### 2. Configure the Endpoint Settings * (Optional) For **Name tag**, enter a descriptive name for your endpoint * For **Service category**, select **Endpoint services that use NLBs and GWLBs** * For **Service name**, enter the exact service name provided to you and choose **Verify service** * For **VPC**, select the VPC from which you will access the service * For **IP address type**, choose **IPv4** #### 3. Configure Subnets and Security * For **Subnets**, select one subnet per Availability Zone where you want to create an endpoint network interface. For high availability, select at least two AZs * For **Security Group**, select a security group that allows inbound traffic from the resources in your VPC that need to access the service on the required ports #### 4. Connection Approval Once you've created the VPC Endpoint, a connection request will be sent to Polymarket. Our DevOps team will review and accept the request, and we will notify you once it has been approved. You will not be able to proceed with DNS configuration or FIX connection until we've accepted the connection on our end. #### 5. Enable Private DNS After connecting to the endpoint, enable Private DNS for the endpoint. When Private DNS is enabled, the DNS names provided to you will resolve to private IP addresses assigned to your VPC Endpoint, and all traffic will route over the private AWS network. *** ## Retrieving Your VPC Endpoint DNS Name It is recommended to use the DNS name provided to you and not the AWS-generated DNS names assigned to your VPC Endpoint. To connect to the VPC Endpoint Service, you can use the AWS-assigned DNS name generated within your account, or the Private DNS described above. If you choose the AWS-assigned DNS name, it will continue to work independent of the Private DNS. ### Option 1: Using the AWS Management Console 1. Log in to your AWS account and navigate to the [VPC Console](https://console.aws.amazon.com/vpc/) 2. In the left navigation pane, select **Endpoints** 3. Select the Interface Endpoint you created for this service (search by the Service Name if needed) 4. In the **Details** tab at the bottom of the screen, locate the **DNS names** section 5. Copy the first entry listed (the Regional DNS Name) Do not use the zonal DNS names (the ones containing availability zone letters like .us-east-1a.) unless you are specifically targeting a single zone. ### Option 2: Using the AWS CLI If you have the AWS CLI configured, run the following command to retrieve the DNS names directly: ```bash theme={null} aws ec2 describe-vpc-endpoints \ --filters Name=service-name,Values=[VPC_SERVICE_NAME] \ --query "VpcEndpoints[0].DnsEntries[*].DnsName" \ --output text ``` Replace `[VPC_SERVICE_NAME]` with the VPC Service name that was provided to you for the initial creation of the VPC Endpoints. You can also run this in CloudShell from the AWS Web Console. *** ## FIX Session Configuration ### Session Identifiers Your FIX engine will initiate the TCP connection sessions to Polymarket Exchange using FIXT.1.1 transport with FIX 5.0 SP2 application messages. Identifiers can include ASCII printable characters excluding SOH (Start of Header) characters only. **SenderCompID (Tag 49):** The firm ID assigned by Polymarket to your firm's FIX session (e.g., `YOURFIRM_PMX_OE`, `YOURFIRM_PMX_MD`, `YOURFIRM_PMX_DC`) **TargetCompID (Tag 56):** The exchange ID assigned by Polymarket to your firm's FIX session (e.g., `PMX_YOURFIRM_OE`, `PMX_YOURFIRM_MD`, `PMX_YOURFIRM_DC`) **SenderSubID (Tag 50):** The trader identifier assigned based on the user we create at Polymarket (e.g., `20251118-yourfirm-api-user-1`), sent on order and order-management messages **Account (Tag 1):** The account identifier assigned based on the account we create at Polymarket (e.g., `20251118-yourfirm-api-account-1`), sent on orders to attribute trading activity ### Session Types Polymarket Exchange provides three types of FIX sessions: | Session Type | Purpose | Port | | -------------------- | ------------------------------ | ------------------------------ | | **Order Entry (OE)** | Submit and manage orders | Provided in connection details | | **Market Data (MD)** | Subscribe to market data feeds | Provided in connection details | | **Drop Copy (DC)** | Receive execution reports | Provided in connection details | ### Sequence Number Reset Each participant's FIX session can have a different sequence reset time (e.g., 00:00:00 UTC, 00:00:00 ET). Your configured reset time will be provided in your connection details. *** ## Testing Your Connection Once your VPC Endpoint is approved and configured: 1. Test connectivity to each port (Order Entry, Market Data, Drop Copy) 2. Initiate FIX sessions using the identifiers provided 3. Verify sequence number behavior and message flow 4. Test order submission and market data subscriptions For detailed FIX protocol specifications, see: * [FIX Session Management](/institutional/fix-api/fix-session-management) * [FIX Order Entry](/institutional/fix-api/fix-order-entry-overview) * [FIX Market Data](/institutional/fix-api/fix-market-data-subscription) # Drop Copy Configurations Source: https://docs.polymarket.us/institutional/fix-api/fix-drop-copy-configurations ## Fills Only Mode For participants who only need fill notifications (not the complete order lifecycle), the drop copy session can be configured in "fills only" mode. When enabled: * Only ExecutionReport messages with **ExecType = F (Trade)** are sent * Order acknowledgements, cancellations, modifications, and rejections are not included * Reduces message volume for participants focused solely on trade reconciliation To enable fills only mode, request this configuration when setting up your drop copy session with the exchange operator. *** ## Comparing FIX Drop Copy vs gRPC DropCopy Polymarket Exchange offers drop copy feeds via both FIX and gRPC protocols: | Feature | FIX Drop Copy | gRPC DropCopy | | ---------------------- | ---------------------------- | ----------------------- | | **Protocol** | FIX 5.0 SP2 | gRPC streaming | | **Message Format** | FIX tag-value | Protobuf | | **Session Management** | FIX logon/logout | gRPC connection | | **Reconnection** | FIX resend | Resume tokens | | **Use Case** | Traditional FIX integrations | Modern API integrations | Choose FIX Drop Copy if you have existing FIX infrastructure. Choose gRPC if you prefer modern streaming APIs with protobuf messages. For more information on gRPC DropCopy streaming, see the [DropCopy Stream](/streaming-endpoints/dropcopy-stream) documentation. # ExecutionReport Message Source: https://docs.polymarket.us/institutional/fix-api/fix-drop-copy-execution-report The Drop Copy feed supports the delivery of Execution Reports for order updates, fills, and cancels. The ExecutionReport message structure is identical to that sent over order-entry sessions. ## ExecutionReport \[8] Message ### Core Fields | Tag | Field | Req | Type | Description | | --- | ------------ | --- | ----- | ------------------------------------------------- | | 35 | MsgType | Y | str | 8 = ExecutionReport | | 1 | Account | N | str | Account reference if indicated on order | | 6 | AvgPx | Y | price | Volume-weighted average price of all trades | | 11 | ClOrdID | Y | str | Participant-assigned order ID | | 14 | CumQty | Y | qty | Cumulative filled quantity | | 17 | ExecID | Y | str | Unique execution report ID (13-char alphanumeric) | | 31 | LastPx | Y | price | Price of this fill (zero if not a trade) | | 32 | LastQty | Y | qty | Quantity of this fill (zero if not a trade) | | 37 | OrderID | Y | str | Exchange-assigned order ID (13-char alphanumeric) | | 38 | OrderQty | Y | qty | Total order quantity | | 44 | Price | Y | price | Order limit price | | 54 | Side | Y | char | 1=Buy, 2=Sell | | 55 | Symbol | Y | str | Instrument symbol | | 60 | TransactTime | Y | time | Transaction timestamp | | 99 | StopPx | Y | price | Stop price (zero for non-stop orders) | | 151 | LeavesQty | Y | qty | Remaining unexecuted quantity | ### Order Status | Tag | Field | Req | Type | Values | | --- | --------- | --- | ---- | ------------------------------------------------------------------------------------------------------------------------------------------ | | 39 | OrdStatus | Y | char | 0=New, 1=Partially filled, 2=Filled, 3=Done For Day, 4=Canceled, 6=Pending Cancel, 8=Rejected, A=Pending New, C=Expired, E=Pending Replace | | 150 | ExecType | Y | char | 0=New, 3=Done For Day, 4=Canceled, 5=Replaced, 8=Rejected, C=Expired, F=Trade | ### Order Type & Time in Force | Tag | Field | Req | Type | Values | | --- | ----------- | --- | ---- | -------------------------------------------------------------- | | 40 | OrdType | Y | char | 2=Limit, 3=Stop, 4=Stop Limit, K=Market with leftover as limit | | 59 | TimeInForce | Y | char | 0=Day, 1=GTC, 3=IOC, 4=FOK, 6=GTD | | 126 | ExpireTime | N | time | Order expiry timestamp | ### Security Identification | Tag | Field | Req | Type | Description | | --- | ---------------- | --- | ---- | --------------------------------------------------------------------------------------------------- | | 22 | SecurityIDSource | Y | str | Security ID source (8=Exchange symbol) | | 48 | SecurityID | Y | str | Security identifier (matches Symbol) | | 167 | SecurityType | N | str | EVENT=Event contract. Currently, Polymarket only offers EVENT instruments. | | 762 | SecuritySubType | N | str | Sub-type qualification of SecurityType/CFICode | | 460 | Product | N | int | Product type: 1=AGENCY, 2=COMMODITY, 3=CORPORATE, 4=CURRENCY, 5=EQUITY, 6=GOVERNMENT, 7=INDEX, etc. | ### Trade-Specific Fields | Tag | Field | Req | Type | Description | | ---- | ------------------ | --- | ---- | --------------------------------------------------------------- | | 12 | Commission | N | amt | Commission amount charged on this fill, trades only | | 13 | CommType | N | char | Commission calculation method. 3=Absolute (total dollar amount) | | 119 | SettlCurrAmt | N | amt | Amount of this fill (LastPx × LastQty), trades only | | 381 | GrossTradeAmt | N | amt | Total traded amount (AvgPx × CumQty) | | 828 | TrdType | N | int | Trade type: 0=Regular trade | | 880 | TrdMatchID | C | str | Unique trade ID (13-char, same for buyer/seller) | | 1057 | AggressorIndicator | C | bool | Whether this order was the aggressor | ### Order Modification | Tag | Field | Req | Type | Description | | --- | --------------------- | --- | ---- | ------------------------------------------------------------------- | | 41 | OrigClOrdID | N | str | Original ClOrdID being amended/canceled | | 378 | ExecRestatementReason | N | int | Unsolicited cancel reason: 8=Market/exchange option, 99=Other (SMP) | ### Rejection | Tag | Field | Req | Type | Description | | --- | ------------ | --- | ---- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | 103 | OrdRejReason | N | int | 0=Broker/Exchange, 1=Unknown symbol, 2=Closed, 3=Price limit, 5=Unknown order, 6=Duplicate ClOrdID, 11=Unsupported, 12=Surveillance, 13=Bad quantity, 15=Unknown account, 16=Price band, 18=Bad tick size, 99=Other | ### Account Classification | Tag | Field | Req | Type | Values | | --- | ----------------- | --- | ---- | ----------------------------------------------------------------------------------------------------------------------------------------- | | 581 | AccountType | N | int | 1=CUSTOMER, 2=NON\_CUSTOMER, 3=HOUSE\_TRADER, 4=FLOOR\_TRADER, 14=LIQUIDITY\_PROVIDER, 17=FUTURES\_MARKET\_MAKER (see spec for full list) | | 582 | CustOrderCapacity | N | int | 1=OWN\_ACCOUNT, 2=PROPRIETARY, 3=FINANCIAL\_ADVISOR, 4=ALL\_OTHER, 5=RETAIL\_CUSTOMER | ### Party Identification | Tag | Field | Req | Type | Description | | ----- | ------------- | --- | ---- | ----------------------------------------------------- | | 453 | NoPartyIDs | N | int | Number of party entries | | → 448 | PartyID | N | str | Party identifier/code | | → 447 | PartyIDSource | N | char | D=Proprietary | | → 452 | PartyRole | N | int | 1=EXECUTING\_FIRM, 3=CLIENT\_ID, 24=CUSTOMER\_ACCOUNT | ### Advanced Features | Tag | Field | Req | Type | Description | | ---- | ---------------------- | --- | ---- | ---------------------------------------------- | | 110 | MinQty | N | qty | Minimum execution quantity | | 1028 | ManualOrderIndicator | N | bool | Order received manually vs electronically | | 6127 | ConditionTriggerMethod | N | int | Stop trigger: 2=Last price, 5=Settlement price | ### Self-Match Prevention | Tag | Field | Req | Type | Description | | ---- | ------------------------------ | --- | ---- | ----------------------------------------------------------------------------------------------- | | 7928 | SelfMatchPreventionID | N | str | Unique identifier for the self-match prevention instruction | | 8000 | SelfMatchPreventionInstruction | N | str | Self-match instruction. O = Cancel oldest (resting) order, N = Cancel newest (aggressive) order | *** ## Example ExecutionReport for a Fill ``` | Field | Value | Description | |--------------------|------------|--------------------------------| | ClOrdID | "ABCD" | Client order ID | | OrdStatus | "2" | Order fully filled | | ExecType | "F" | Trade execution | | Side | "1" | Buy order | | Account | "account1" | Trading account | | Price | 0.525 | Order limit price | | OrderQty | 100 | Original order quantity | | LastQty | 100 | Quantity of this fill | | LastPx | 0.525 | Price of this fill | | AvgPx | 0.525 | Average fill price | | LeavesQty | 0 | Remaining unfilled quantity | | CumQty | 100 | Total filled quantity | | GrossTradeAmt | 52.50 | Total trade amount | | AggressorIndicator | false | Passive side of trade | | TrdMatchID | "trade123" | Unique trade identifier | ``` # Drop Copy Overview Source: https://docs.polymarket.us/institutional/fix-api/fix-drop-copy-overview The Polymarket Exchange provides optional drop-copy sessions which deliver real-time copies of execution reports and order state changes to authorized participants. ## What is Drop Copy? Drop-copy sessions are read-only FIX sessions that cannot be used to enter, modify, or cancel orders. Instead, they provide a separate feed of execution activity, allowing participants to monitor their trading activity through an independent connection. The Drop Copy FIX feed provides execution reports to exchange participants and third-party service providers. The service can be configured to provide data for a specific participant, clearing member, or all participants. Drop copies provided via this service will include all execution reports regardless if the orders or quotes were sent via a FIX session or the REST API. ## Common Use Cases Drop-copy sessions are commonly used for: * **Regulatory compliance and audit trails** - Maintain a complete record of all order activity * **Real-time risk management** - Monitor fills and positions without exposing order-entry capabilities * **Back-office reconciliation** - Feed execution data to separate systems for settlement and accounting * **Clearing firm oversight** - Allow clearing firms to monitor client execution activity ## Session Configuration Drop-copy sessions use the same FIXT 1.1 / FIX 5.0 SP2 protocol as order-entry sessions, but with different credentials and typically connect to a different endpoint. Contact the exchange operator to provision drop-copy session access. ## Messages Received Drop-copy sessions receive unsolicited ExecutionReport messages for all order activity, including: * Order acknowledgements * Order fills * Order cancellations * Order modifications * Order rejections * Order expirations For detailed field specifications of the ExecutionReport message, see [ExecutionReport Message](/institutional/fix-api/fix-drop-copy-execution-report). # ExecutionReport Source: https://docs.polymarket.us/institutional/fix-api/fix-execution-report
The ExecutionReport \[8] message is used to acknowledge various order lifecycle events including the acceptance, rejection, and expiry of orders, as well as providing details of matches (fills) against orders.
All orders entering the Platform are first either accepted or rejected using an Execution Report \[8]. The order may then receive further Execution Reports \[8] as necessary given their marketability and TimeInForce (59) constraints. For example: 1. A badly-formatted order which fails validation upon entry will receive an ExecutionReport \[8] with OrdStatus (39) = 8 (Rejected). 2. A good for day order which is not immediately executable will receive an ExecutionReport \[8] with OrdStatus (39) = 0 (New). Should it receive executions during the course of the day, then these will be notified in subsequent Execution Report \[8] messages with OrdStatus (39) = 1 (Partially Filled) and/or 2 (Fully Filled). 3. An order with TimeInForce (59) = 6 (good till time) will receive an ExecutionReport \[8] with OrdStatus (39) = 0 (New) upon entry. If it remains unexecuted, it will expire at the indicated ExpireTime (126) with an Execution Report \[8] message with OrdStatus (39) = C (Expired). 4. A Fill Or Kill order with TimeInForce (59) = 4 which can not be fully executed will first receive an ExecutionReport \[8] with OrdStatus (39) = 0 (New) and then an immediate Execution Report \[8] message with OrdStatus (39) = C (Expired). 5. An order with TimeInForce (59) = 3 (immediate or cancel) which can be partially filled upon entry will receive at least three ExecutionReport \[8] messages; the first will indicate OrdStatus (39) = 0 (New), the next messages will detail the fill(s), and the final Execution Report \[8] message will expire the remainder with OrdStatus (39) = C (Expired). ## Table 16: ExecutionReport (8) message
Tag Name Req Type Description
\< Standard Header >Y35 = 8
1AccountNStringAccount reference if indicated on the original order
6AvgPxYPriceVolume-weighted average price of all trades against this order. May be zero for unexecuted orders.
11ClOrdIDYStringThe participant-assigned ClOrdID value as sent on the last order action message form the Participant (new order, amendment or cancel).
12CommissionNAmtCommission amount charged on this fill. Present on trades only.
13CommTypeNcharCommission calculation method. 3=Absolute (total dollar amount)
14CumQtyYQtyCumulative quantity so far for this order. May be zero for unexecuted orders.
17ExecIDYStringUnique identifier for the Execution Report as assigned by Polymarket US. Typically a 13-character alphanumeric string.
22SecurityIDSourceYStringIdentifies source of SecurityID (48) value. 8=Exchange symbol
31LastPxYPricePrice of this last fill. Will be zero for messages not relating to a trade.
32LastQtyYQtyQuantity traded on this last fill. Will be zero for messages not relating to a trade.
37OrderIDYStringUnique identifier for Order as assigned by Polymarket US. Typically a 13-character alphanumeric string.
38OrderQtyYQtyTotal order quantity (amended as necessary)
39OrdStatusYcharThe latest status of the order after any changes have been applied. 0=New, 1=Partially filled, 2=Fully filled, 4=Canceled, 8=Rejected, C=Expired
40OrdTypeYcharThe type of order. 2=Limit, 3=Stop, 4=Stop Limit, K=Market with left over as limit
41OrigClOrdIDNStringSent in the case of order amendment or cancellation. References the prior ClOrdID (11) value that the action amended/canceled.
44PriceYPriceOrder limit price (amended as necessary)
48SecurityIDYStringSecurity identifier; will always match Symbol (55).
54SideYchar1=Buy, 2=Sell
55SymbolYStringInstrument symbol
460ProductYIntIndicates the type of product the security is associated with. 1=AGENCY, 2=COMMODITY, 3=CORPORATE, 4=CURRENCY, 5=EQUITY, 6=GOVERNMENT, 7=INDEX, 8=LOAN, 9=MONEYMARKET, 10=MORTGAGE, 11=MUNICIPAL, 12=OTHER, 13=FINANCING, 14=ENERGY
59TimeInForceYcharEchoed from New Order Single. 0=Good for day, 1=Good till cancel, 3=Immediate or cancel, 4=Fill or kill, 6=Good till date
60TransactTimeYUTCTimeTimestamp when the business transaction represented by the message occurred.
99StopPxYPriceOrder stop price (amended as necessary). Will be zero for non stop orders.
103OrdRejReasonNintRejection reason (where OrdStatus = Rejected). 0=Broker/Exchange Option, 1=Unknown symbol, 2=Exchange closed (maintenance), 3=Order exceeds limit (price validation), 5=Unknown order, 6=Duplicate order (ClOrdID), 11=Unsupported order characteristic, 12=Surveillance option, 13=Incorrect quantity (lot size), 15=Unknown account (tag 1), 16=Price exceeds current price band, 18=Invalid price increment (tick size), 99=Other
119SettlCurrAmtNAmtPresent on trades only. Total amount of this last fill. Equal to LastPx (31) x LastQty (32)
126ExpireTimeNUTCTimeOrder expiry date (amended as necessary).
150ExecTypeYcharThe reason that the Polymarket US sent this Execution Report. 0=New, 4=Canceled, 5=Replaced, 8=Rejected, C=Expired, F=Trade
151LeavesQtyYQtyRemaining, unexecuted quantity left on the order. May be zero for fully filled orders.
381GrossTradeAmtNAmtPresent on orders which have been filled. Total amount traded across all fills for this order. Equal to AvgPx (6) x CumQty (38).
581AccountTypeNInt1=CUSTOMER, 2=NON\_CUSTOMER, 3=HOUSE\_TRADER, 4=FLOOR\_TRADER, 6=NON\_CUSTOMER\_CROSS\_MARGINED, 7=HOUSE\_TRADER\_CROSS\_MARGINED, 8=JOINT\_BACK\_OFFICE, 9=EQUITIES\_SPECIALIST, 10=OPTIONS\_MARKET\_MAKER, 11=OPTIONS\_FIRM\_ACCOUNT, 12=AGGREGATED\_CUSTOMER\_AND\_NON\_CUSTOMER, 13=AGGREGATED\_MULTIPLE\_CUSTOMERS, 14=LIQUIDITY\_PROVIDER, 15=OPERATING, 16=CLEARING\_FUND, 17=FUTURES\_MARKET\_MAKER
582CustOrderCapacityNInt1=OWN\_ACCOUNT, 2=PROPRIETARY\_ACCOUNT, 3=FINANCIAL\_ADVISOR, 4=ALL\_OTHER, 5=RETAIL\_CUSTOMER
453NoPartyIDsNIntNumber of PartyID (448), PartyIDSource (447), and PartyRole (452) entries
→ 448PartyIDNStringParty identifier/code
→ 447PartyIDSourceNcharD=Proprietary
→ 452PartyRoleNInt1=EXECUTING\_FIRM, 3=CLIENT\_ID, 24=CUSTOMER\_ACCOUNT
828TrdTypeNintPresent on trades only. 0=Regular trade
880TrdMatchIDCStringAlways populated for trades. Note that buyer and seller will receive the same value. Will match the TradeID (1003) value on market data updates. Typically a 13-character alphanumeric string.
1028ManualOrderIndicatorNBooleanIndicates if the order was initially received manually (as opposed to electronically)
1057AggressorIndicatorCBooleanAlways populated for trades. Identifies whether this order was the aggressor in the trade.
378ExecRestatementReasonNintIndicates that the resting order has been canceled as a result of self-match prevention. 99=Self-match prevention
110MinQtyNQtyMinimum required execution quantity for the order (if specified)
6127ConditionTriggerMethodNintThe reference price used for triggering the stop order. 2=Last price, 5=Settlement price
7928SelfMatchPreventionIDNStringUnique identifier for the self-match prevention instruction.
8000SelfMatchPreventionInstructionNStringSelf-match instruction. O=Cancel oldest (resting) order, N=Cancel newest (aggressive) order
\< Standard Trailer >Y
## Figure 5: Simple limit order for 150 is acknowledged, partially filled for 100, and fully filled ![](https://files.readme.io/df29571-ExecutionReport.png)
**Example 6: Acknowledgement of new limit order to buy 1,000 shares at 50.00** ``` 8=FIXT.1.1 | 9=269 | 35=8 | 34=14 | 49=TARGET | 52=20240517-19:00:28 | 56=SENDER | 57=SENDERSUB | 1=ACCT | 6=0.00 | 11=1182560819 | 14=0 | 17=1HPT7DPFMC5KW | 22=8 | 31=0.00 | 32=0 | 37=1HQ4A5T0EDM00 | 38=1000 | 39=0 | 40=2 | 44=50.00 | 48=GOOG | 54=1 | 55=GOOG | 59=0 | 60=20240517-19:00:28.678960817 | 99=0.00 | 150=0 | 151=1000 | 581=3 | 582=1 | 10=088 | ``` **Example 7: Execution of 500 shares at a price of 50.00** ``` 8=FIXT.1.1 | 9=338 | 35=8 | 34=33 | 49=TARGET | 52=20240517-19:06:47.985808615 | 56=SENDER | 57=SENDERSUB | 1=ACCT | 6=50.00 | 11=1182560826 | 14=500 | 17=1HPT7DPFMC5M5 | 22=8 | 31=50.00 | 32=500 | 37=1HQ4A5T0EDM07 | 38=500 | 39=2 | 40=2 | 44=50.00 | 48=GOOG | 54=2 | 55=GOOG | 59=0 | 60=20240517-19:06:47.977567695 | 99=0.00 | 119=25000.00 | 150=F | 151=0 | 381=25000.00 | 581=3 | 582=1 | 828=0 | 880=1HPT7DPFMC5M4 | 1057=Y | 10=116 | ``` **Example 8: Expiry of FOK order (without execution)** ``` 8=FIXT.1.1 | 9=275 | 35=8 | 34=43 | 49=TARGET | 52=20240517-19:09:23.494862156 | 56=SENDER | 57=SENDERSUB | 1=ACCT | 6=0.00 | 11=1182560830 | 14=0 | 17=1HPT7DPFMC5MB | 22=8 | 31=0.00 | 32=0 | 37=1HQ4A5T0EDM0A | 38=500 | 39=C | 40=2 | 44=50.01 | 48=GOOG | 54=2 | 55=GOOG | 59=4 | 60=20240517-19:09:23.491276593 | 99=0.00 | 150=C | 151=0 | 581=3 | 582=1 | 10=201 | ``` # FIX FAQs Source: https://docs.polymarket.us/institutional/fix-api/fix-faqs ## Getting FIX Access **What do I need to provide to get FIX access?** If you want FIX access, indicate it and include your AWS account ID on your application. Polymarket will provide: * SenderCompID and TargetCompID * User identifiers (SenderSubID) * Account identifiers (Account) * Connection ports and DNS endpoints **What is the CompID format?** CompIDs follow the format: * SenderCompID: `PMX_{FIRMNAME}` * TargetCompID: `{FIRMNAME}_PMX` These are PMX-assigned and case-sensitive. Clients must not generate or modify CompIDs. *** ## FIX Session Configuration **What is the FIX protocol version used?** FIXT.1.1 with DefaultApplVerID=9 (FIX50SP2) **What are the session connection details?** Connection details are provided during onboarding and vary by client. Typical parameters include: * BeginString: FIXT.1.1 * SenderCompID: \[Your assigned CompID] * TargetCompID: \[Polymarket assigned CompID] * Host: \[Provided during onboarding] * Port: \[Varies by service - Order Entry, Drop Copy, or Market Data] * Heartbeat Interval: 30 seconds (configurable) * Encryption: None (98=0) **What ports are used for different FIX services?** There are three services: Order Entry, Drop Copy, and Market Data. Ports are provided during onboarding and are fixed for all clients. **What is the connection model?** FIX sessions are client-initiated. PMX acts as the acceptor. Transport is FIXT.1.1 with application messages using FIX 5.0 SP2. Example QuickFIX-style configuration: ``` [DEFAULT] ConnectionType=initiator BeginString=FIXT.1.1 DefaultApplVerID=9 SocketConnectHost= SocketConnectPort= [SESSION] SenderCompID= TargetCompID= ``` **Is the FIX connection SSL-enabled?** No, encryption method is None (tag 98=0). FIX connectivity runs over plain TCP via AWS PrivateLink, with access controlled by network-level access (PrivateLink) and FIX session identifiers (SenderCompID / TargetCompID). No client certificates or private keys are required. **What is the DNS hostname format for FIX connections?** FIX connections use the format: `-fix..privatelink..polymarketexchange.com` We do not expose fixed outbound IPs for FIX; the FIX gateways run behind AWS where IP addresses are not guaranteed to be static. Clients should connect using the provided FIX DNS entry, which resolves via DNS to the active endpoints. **Is Private DNS required immediately?** No. Private DNS may not be immediately enabled, but this is not a blocker. Clients can connect using the default VPC endpoint-specific DNS and switch to the friendly hostname once Private DNS is enabled. **Does FIX Logon require username and password?** No. FIX Logon (35=A) does not require username/password (tags 553/554). Session authentication is based on the agreed SenderCompID / TargetCompID and network access via AWS PrivateLink. Password-based Logon authentication can be supported in the future but is not required for current integrations. *** **What are the identifier rules?** All identifiers (SenderCompID, TargetCompID, SenderSubID, Account) are: * PMX-assigned during onboarding * Case-sensitive * Printable alphanumeric characters only * Must not be generated or modified by clients **How are instruments identified?** Instruments use Symbol (55) only. No SecurityID or other identification fields are required. *** ## Sequence Number Management **When does the FIX session sequence number reset?** Sequence numbers are session-scoped and only reset when a new FIX session is established using ResetSeqNumFlag (141=Y) on Logon. Sending Logon without 141=Y resumes the existing session and allows gap recovery. Any regular reset cadence (daily, weekly, etc.) is an operational convention and must be explicitly coordinated; it is not automatic or protocol-defined. **Can the sequence number reset time be changed?** Yes. Contact the Polymarket team to configure your preferred reset time (e.g., 00:00:00 UTC, 22:00:00 UTC, etc.). **Are there limits on ResendRequest (35=2)?** No limit on the number of messages returned in response to a ResendRequest. **What happens if early messages are unavailable when a ResendRequest starts from sequence 1?** This should not happen, but if it does, you would receive a SequenceReset (MsgType=4) to advance to the available sequence number. **Why might the first sequence number in FIX logon be 2 instead of 1?** This can occur after a logout/logon cycle due to session persistence from a previous connection or timing misalignment between session schedules. Your FIX client application may send a resend request in this case, but no impact is expected. FIX logon messages should start with sequence number 34=1 when using ResetSeqNumFlag (tag 141=Y). **What happens when the FIX sequence number reaches its maximum value (2^31 - 1)?** The FIX protocol defines MsgSeqNum (tag 34) as type SEQNUM (positive integer). When approaching practical limits, the session should be reset using one of these methods: * Scheduled sequence number reset at the configured daily reset time * Manual session restart with ResetSeqNumFlag (tag 141=Y) in the Logon message * Administrative SequenceReset (MsgType=4) to restart numbering In practice, sequence numbers rarely approach this limit due to daily resets. If you anticipate high message volumes that could approach this limit between resets, contact Polymarket to adjust your reset schedule. *** ## Trade Messages **What message type is used for FIX drop-copy - TCR (35=AE) or ER (35=8)?** Execution Reports (35=8) are currently used for drop-copy and include all order lifecycle events (new, fills, cancels, amendments, rejections). Trade Capture Reports (TCR, 35=AE) are available in streaming mode for clients who only need fill notifications. When using TCR: * Client establishes Drop Copy FIX session and sends Logon (35=A) * On successful logon, the exchange automatically streams TradeCaptureReport (35=AE) for all new trades going forward * TCRs only include fills, not cancels, replaces, or rejects * No TradeCaptureReportRequest (35=AD) messages are required beyond normal FIX session management (heartbeats/resend) Request-based TCRs (where you send TradeCaptureReportRequest to get historical snapshots) are not currently supported. **For trades, do we only need to process 35=8 with 150=F?** Yes, correct. To process trade executions, filter for: * Message type 35=8 (ExecutionReport) * AND ExecType 150=F (Trade) Other ExecType values indicate different order lifecycle events (0=New, 4=Cancellation, 5=Amendment, 8=Rejection, C=Expiration). Only 150=F indicates an actual trade execution. **Can TrdMatchID (tag 880) be used as a unique trade identifier?** Yes, you can use TrdMatchID (tag 880) as a unique trade identifier. Key characteristics: * Both buyer and seller receive the SAME TrdMatchID value - it identifies the trade itself, not each side * The value is a 13-character alphanumeric string * It matches the TradeID (1003) value in market data updates * Each trade receives a unique TrdMatchID **When can a value in TrdMatchID (tag 880) be reused?** TrdMatchID values are unique per trade and should not be reused across different trades, days, or instruments. **Is it possible to receive more than one Execution Report (35=8) for a trade?** You will receive one ExecutionReport (35=8) with ExecType=F per fill event. However, a single order can receive multiple fills, which means you'll receive multiple ERs with 150=F for that order. Example: * Order for 1000 contracts is submitted * First fill: 400 contracts → ER with 150=F, LastQty=400, CumQty=400, OrdStatus=1 (Partially Filled) * Second fill: 600 contracts → ER with 150=F, LastQty=600, CumQty=1000, OrdStatus=2 (Filled) So you get multiple ERs with 150=F for one order (one per fill), but only one ER per individual fill/trade event. **Is the quantity provided in LastPx (tag 31) absolute/total quantity or nominal?** Note: Tag 31 is LastPx (last price), not quantity. Tag 32 (LastQty) contains the quantity. Tag 32 (LastQty): This is the absolute/total quantity traded in this last fill, not a nominal quantity requiring a multiplier. Related tags: * Tag 31 (LastPx): Price of the last fill * Tag 32 (LastQty): Quantity of the last fill * Tag 119 (SettlCurrAmt): Total amount = LastPx × LastQty There is no multiplier - the quantity is the actual traded amount. **Do you support amendment/cancellation of already executed trades?** We do not support amending or canceling already-executed trades via the FIX API. Our FIX protocol supports: * Order amendments (OrderCancelReplaceRequest \[G]) - for open orders only * Order cancellations (OrderCancelRequest \[F]) - for open orders only Once a trade is executed (you receive an ER with 150=F), it is considered final and cannot be modified or canceled through FIX messages. If a trade correction is required due to an error, contact our operations team directly. *** ## Matching Engine Behavior **When an order is partially filled through multiple executions within a single match, do you send one consolidated trade notification or multiple trade messages?** Each individual fill generates a separate ExecutionReport (35=8) with ExecType=F (150=F). Example: If a buy order for quantity 30 is matched against two sell orders of 10 and 20: * You receive TWO separate ExecutionReports with 150=F * First ER: LastQty=10, CumQty=10, OrdStatus=1 (Partially Filled) * Second ER: LastQty=20, CumQty=30, OrdStatus=2 (Filled) * Each fill will have a DIFFERENT TrdMatchID (tag 880) because they represent separate trades with different counterparties **Can a buy/sell order be matched against multiple opposite-side orders at different prices?** Yes, this is possible depending on the matching algorithm. Each match at a different price generates a separate trade with its own ExecutionReport. Example: Buy order for qty=30 at limit price=0.99 matched against: * Sell order: qty=10 at price=0.98 → First ER with LastQty=10, LastPx=0.98, unique TrdMatchID * Sell order: qty=20 at price=0.99 → Second ER with LastQty=20, LastPx=0.99, different TrdMatchID Each fill is reported as a separate trade with different TrdMatchIDs because they are distinct trade events at different prices. *** ## Party IDs & Roles **What is the business meaning of Party IDs for PartyRoles 1, 3, and 24?** * **PartyRole 1 (EXECUTING\_FIRM)**: The clearing member or broker executing the trade on behalf of the client * **PartyRole 3 (CLIENT\_ID)**: The end client for whom the trade is being executed * **PartyRole 24 (CUSTOMER\_ACCOUNT)**: The specific trading account within the customer's structure **How are Party IDs determined? Are they configurable?** Party IDs use PartyIDSource = D (Proprietary/Custom code), which means each organization establishes its own proprietary identifier scheme independently. For Polymarket: * **Who assigns them**: Polymarket assigns Party ID values during client onboarding * **Format**: Proprietary format following Polymarket's internal naming conventions (e.g., "20251118-ion-test-api-clearing-member") * **Configurable**: The values themselves are assigned by Polymarket, not client-configurable. However, they are specific to your account and determined during onboarding * **Usage**: * **PartyRole 1 (EXECUTING\_FIRM)**: Identifies the clearing member or broker executing trades * **PartyRole 3 (CLIENT\_ID)**: Identifies the end client * **PartyRole 24 (CUSTOMER\_ACCOUNT)**: Identifies the specific trading account These Party IDs are optional fields in FIX messages but help identify the various parties involved in a trade. Contact the Polymarket onboarding team to confirm your assigned Party ID values for each role. *** ## FIX Market Data **Does MarketDataIncrementalRefresh (35=X) include TradingSessionID only for trades, or will we receive updates when trading status changes?** Yes, you will receive updates when trading status changes. Trading status changes are communicated via MarketDataIncrementalRefresh (35=X) messages using the TradingSessionID tag (336). When an instrument's state changes, you'll receive a MarketDataIncrementalRefresh with TradingSessionID set to one of: * CLOSED * OPEN * PREOPEN * SUSPENDED * EXPIRED * TERMINATED * HALTED * MATCH\_AND\_CLOSE\_AUCTION **Since SecurityListUpdate (35=d) is not supported, when a new instrument is added after receiving the initial SecurityList (35=y), how do we get that new instrument?** Currently, SecurityListUpdate (35=d) is not supported. To discover new instruments, you must send another SecurityListRequest (35=x). SecurityListUpdate will soon be added to proactively notify clients when new instruments are added. **Are new instruments added to your system only once per day? When?** New instruments are added throughout the day as they become available. *** ## Historical Trades **How can historical trades be downloaded?** **Within a session**: If a client crashes or disconnects, FIX automatically redelivers missed messages via gap fill. Client reconnects and resumes processing using ResendRequest (35=2). This is the primary mechanism for intraday recovery. **Beyond a session**: FIX replay is limited to messages retained for the active session. Once a session is reset with ResetSeqNumFlag (141=Y), prior messages cannot be replayed. FIX is not intended for bulk or multi-session historical backfill. Prior-day or long-range history requires a separate reporting/export mechanism. **Important**: Trade persistence is independent of FIX sequencing. Trades are stored regardless of session state. *** ## Session Behavior **What are the Cancel on Disconnect/Logout settings?** Cancel on Disconnect = Yes, Cancel on Logout = Yes **What are the session times?** Default session time is 17:01:00 - 17:01:00 America/New\_York (configurable per client) **Are CompIDs different between environments?** No. CompIDs, SenderSubID, and Account values are the same in both preprod and prod environments. This simplifies configuration management across environments. *** ## Common Connection Issues **Why might a connection be immediately reset after sending a FIX Logon?** The most common cause is having SenderCompID and TargetCompID reversed. The server cannot find a matching session if the CompIDs don't match any configured session. **Solution:** Verify your SenderCompID and TargetCompID match the values provided during onboarding. The CompID convention is that you send AS your assigned PMX-side CompID TO Polymarket's assigned CompID. **What is the correct CompID configuration?** Use the SenderCompID and TargetCompID values provided during your onboarding. Example format: * SenderCompID = PMX\_\[CLIENT]*DC (for Drop Copy) or PMX*\[CLIENT]\_OE (for Order Entry) * TargetCompID = \[CLIENT]\_PMX\_DC (for Drop Copy) or \[CLIENT]\_PMX\_OE (for Order Entry) Contact the Polymarket onboarding team if you need clarification on your assigned CompIDs. *** ## Testing & Environment **How can I test the FIX connection?** Automated trading and liquidity is simulated in the testing environment. Contact Polymarket's onboarding team to coordinate additional test trades if needed for your session testing purposes. **Can you provide bulk test data for high-volume recovery testing?** Contact the Polymarket onboarding team to request bulk test trade data for testing FIX message recovery scenarios and capacity planning. *** ## FIX Data Format **What delimiter is used?** FIX uses SOH (Start of Header, `\x01`) as the field delimiter. **Do prices and quantities support decimals?** Yes. Prices and quantities support decimal values. **What timestamp format is used?** UTC timestamps, which may include sub-second precision. # Incremental Market Data Source: https://docs.polymarket.us/institutional/fix-api/fix-market-data-incremental If the participants subscribed to ongoing updates for the instrument(s), the platform will then start sending unsolicited MarketDataIncrementalRefresh \[X] messages which contain a mixture of trade and/or order book update messages. These incremental market data messages may contain a repeating group of updates in a single message. For example, an incoming sell order which enters the order book, executes against a resting buy order with the remainder written to the order book will trigger a MarketDataIncrementalRefresh \[X] message containing the following updates: * The deletion of the previous best bid (as a result of the immediate fill) * The addition of a new best bid (next-best bid price) * The deletion of the best offer (new order has a better price) * The addition of a new best offer (representing the balance of the incoming sell order) * A record relating to the trade, and * A record relating to updated overall market volume ## Table 22: MarketDataIncrementalRefresh (X) message
Tag Name Req Type Description
\< Standard Header >Y35 = X
262MDReqIDYStringThe ID of the request as indicated on the request
268NoMDEntriesYNumInGroup
→ 279MDUpdateActionYcharThe type of action conveyed by this block (0=New, 1=Change, 2=Delete)
→ 269MDEntryTypeCcharType of entry. Set where MDUpdateAction (279) = (0) New or 1 (Change). 0=Bid, 1=Offer, 2=Trade, 4=Opening Price, 5=Closing Price, 6=Settlement Price, 7=Trading Session High Price, 8=Trading Session Low Price, B=Trade Volume, g=Trading Reference Price
→ 278MDEntryIDNStringUnique reference for this entry. Typically a 13-character alphanumeric string.
→ 55SymbolYStringInstrument symbol
→ 22SecurityIDSourceYint8 = Exchange symbol
→ 48SecurityIDYStringWill always equal Symbol (55)
→ 167SecurityTypeNStringEVENT=Event contract. Currently, Polymarket only offers EVENT instruments.
→ 1151SecurityGroupNStringSecurity sub-type. For example "Equities"
→ 270MDEntryPxNPriceOrder level price where MDEntryType (269) = 0 (Bid) or 1 (Offer). Traded price where MDEntryType (269) = 2 (Trade). Total value traded where MDEntryType (269) = B (Trade Volume)
→ 271MDEntrySizeNQtyRemaining order size where MDEntryType (269) = 0 (Bid) or 1 (Offer). Will be zero where MDUpdateAction (279) = 2 (Delete). Trade size where MDEntryType (269) = 2 (Trade). Total quantity traded where MDEntryType (269) = B (Trade Volume)
→ 272MDEntryDateNUTCDateOnlyThe date on which the price level or trade occurred
→ 273MDEntryTimeNUTCTimeOnlyThe time at which the price level updated or trade occurred (in UTC)
→ 59TimeInForceNcharTime in force for this order (0=Good for day, 1=Good till cancel, 6=Good till date)
→ 126ExpireTimeNUTCTimestampPopulated where TimeInForce (59) = 6 (Good Till Date)
→ 37OrderIDNStringOnly sent for price level updates. Will match OrderID (37) in the ExecutionReport \[8], allowing Participants to identify their own orders within market data.
→ 40OrdTypeNchar2 = Limit order, K = Market-to-limit order
→ 828TrdTypeNintOnly sent for trades. 0 = Regular trade
→ 1003TradeIDNStringOnly sent for trade updates. Will match the ExecID (17) in the ExecutionReport fill, allowing Participants to identify their own trades within market data.
→ 2446AggressorSideNcharOnly sent for trades. Indicates which side was the aggressor in a trade (1=Buy, 2=Sell)
→ 336TradingSessionIDCStringSent for trades and trading status changes. Possible values: CLOSED, OPEN, PREOPEN, SUSPENDED, EXPIRED, TERMINATED, HALTED, MATCH\_AND\_CLOSE\_AUCTION
\< Standard Trailer >Y
**Example 20:** Market Data incremental containing multiple updates (repeating groups color-coded) ``` 8=FIXT.1.1 | 9=987 | 35=X | 34=87 | 49=TARGET | 52=20240521-09:52:30.013930670 | 56=SENDER | 262=1552371733 | 268=6 | 279=0 | 269=0 | 278=1HQ4A5T0EDM1T | 55=GOOG | 48=GOOG | 22=8 | 167=NONE | 1151=Equities | 270=0.03 | 271=1500 | 272=20240521 | 273=09:52:30.004561670 | 59=0 | 37=1HQ4A5T0EDM1T | 40=2 | 279=0 | 269=1 | 278=1HQ4A5T0EDM1W | 55=GOOG | 48=GOOG | 22=8 | 167=NONE | 1151=Equities | 270=0.03 | 271=15 | 272=20240521 | 273=09:52:30.004561670 | 59=0 | 37=1HQ4A5T0EDM1W | 40=2 | 279=2 | 269=1 | 278=1HQ4A5T0EDM1W | 55=GOOG | 48=GOOG | 22=8 | 167=NONE | 1151=Equities | 270=0.03 | 271=0 | 272=20240521 | 273=09:52:30.004561670 | 59=0 | 37=1HQ4A5T0EDM1W | 40=2 | 279=2 | 269=0 | 278=1HQ4A5T0EDM1V | 55=GOOG | 48=GOOG | 22=8 | 167=NONE | 1151=Equities | 270=0.03 | 271=0 | 272=20240521 | 273=09:52:30.004561670 | 59=0 | 37=1HQ4A5T0EDM1V | 40=2 | 279=0 | 269=2 | 278=1HPT7DQ1GC4DS | 55=GOOG | 48=GOOG | 22=8 | 167=NONE | 1151=Equities | 270=0.03 | 271=15 | 272=20240521 | 273=09:52:30.004561670 | 59=0 | 40=2 | 828=0 | 1003=1HPT7DQ1GC4DS | 2446=2 | 279=0 | 269=B | 55=GOOG | 48=GOOG | 22=8 | 167=NONE | 1151=Equities | 270=93544.85 | 271=23660 | 272=20240521 | 273=09:52:30.004561670 | 336=OPEN | 10=156 | ``` *** ## Settlement Outcomes ### Implementation FIX tag 58 (Text) has been added to the MDIncGrp repeating group to communicate final settlement outcomes. When an instrument resolves with final settlement, the MarketDataIncrementalRefresh (35=X) or MarketDataSnapshotFullRefresh (35=W) message includes the textual outcome. ### Message Structure When an instrument settles, the message includes: ``` NoMDEntries/0/MDEntryType | "6" # Settlement price (269=6) NoMDEntries/0/MDEntryPx | # Settlement price value NoMDEntries/0/Text | # NEW: Textual outcome (tag 58) NoMDEntries/0/TradingSessionID | "EXPIRED" # Market status (336=EXPIRED) ``` ### Usage **Final Settlement:** Tag 58 is present with the outcome text describing the resolution (e.g., "Pistons", "Bulls", "Over", "Yes"). **Trade Day Roll:** Tag 58 is absent, even when `269=6` is present with `336=CLOSED` or `336=EXPIRED`. Tag 58 only populates when the settlement price and open interest update occur at final settlement, not during initial state transition to EXPIRED. ### Technical Notes * No data dictionary modifications required - tag 58 already exists in FIX 5.0+ spec for MDIncGrp * This implementation keeps FIX vanilla/standard-compliant * Tag 58 is equivalent to the TIER1 field in gRPC settlement\_tiers **Example:** NBA game settlement where Detroit Pistons win: ``` 269=6 # Settlement price entry type 270=1.000 # Settlement price (winning outcome pays $1.00) 58=Pistons # Textual outcome 336=EXPIRED # Trading session status ``` # Market Data Subscription Source: https://docs.polymarket.us/institutional/fix-api/fix-market-data-subscription The market data sessions for the Polymarket US are available by a second, separate FIX gateway; they are not accessible via the order-entry session. When displaying the order book, the Polymarket US provides a Market-by-Order view; i.e each order is displayed individually with a corresponding timestamp (used to determine time priority within a price level). Each order also carries the unique OrderID reference, which Participants can use to identify their own orders in market data. ## Subscribing to Market Data Participants can subscribe to market data for a given symbol using a MarketDataRequest \[V] message. ## Table 20: MarketDataRequest (V) message
Tag Name Req Type Description
\< Standard Header >Y35 = V
262MDReqIDYStringUnique ID for this request
263SubscriptionRequestTypeYcharType of subscription requested (0=Snapshot, 1=Snapshot plus Updates, 2=Delete previous request/unsubscribe)
264MarketDepthYintDepth requested, maximum 25 levels (0=Full book depth, 1=Top of book/best prices only, 2+=Number of levels requested)
267NoMDEntryTypesNNumInGroup
→269MDEntryTypeNcharA repeating group of MD Entry Types requested (0=Bid, 1=Offer, 2=Trade, 4=Opening Price, 5=Closing Price, 6=Settlement Price, 7=Trading Session High Price, 8=Trading Session Low Price, B=Trade Volume, g=Trading Reference Price)
146NoRelatedSymYNumInGroupNumber of symbols requested
→55SymbolYStringInstrument symbol.
\< Standard Trailer >Y
If the MarketDataRequest \[V] message is valid, Polymarket US will respond with a single MarketDataSnapshotFullRefresh \[W] message for each requested Instrument, providing details of all orders in the order book (all levels as a repeating group within a single message). Note that the (repeating) MDEntryType (269) field can be specified if required to tailor the elements returned. **Example 18:** Request a snapshot of all market data elements using MarketDataRequest \[V] message ``` 8=FIXT.1.1 | 9=92 | 35=V | 49=SENDER | 56=TARGET | 34=4 | 52=20240517-19:05:47 | 262=MD-REQ-001 | 263=1 | 264=3 | 146=1 | 55=GOOG | 10=075 | ``` Note that since the Polymarket US returns market data split by order, requesting only the best price using MarketDepth (264) = 1 (Top of book) may still return multiple bid and offer entries if there is more than one order at this price. ## Table 21: MarketDataSnapshotFullRefresh (W) message
Tag Name Req Type Description
\< Standard Header >Y35 = W
22SecurityIDSourceYint8 = Exchange symbol
48SecurityIDYStringWill always equal Symbol (55)
55SymbolYStringInstrument symbol
167SecurityTypeNStringEVENT=Event contract. Currently, Polymarket only offers EVENT instruments.
262MDReqIDYStringThe ID of the request as indicated on the request.
292Corporate ActionCcharProvided if the instrument is currently subject to a Corporate Action (A-W codes)
268NoMDEntriesYNumInGroupThe number of market data levels returned. Might be zero if the symbol is valid but there are currently no bids/offers in this symbol.
→ 269MDEntryTypeYcharType of entry (0=Bid, 1=Offer, 2=Trade, 4=Opening Price, 5=Closing Price, 6=Settlement Price, 7=Trading Session High Price, 8=Trading Session Low Price, B=Trading Session Volume, g=Trading Reference Price)
→ 270MDEntryPxYPricePrice level
→ 271MDEntrySizeCQtyQuantity of the individual order or trade, or the aggregate quantity where MDEntryType (269) = B (Trading Session Volume). Not sent for session open/high/low.
→ 272MDEntryDateYUTCDateOnlyTime priority (date) of the order.
→ 273MDEntryTimeYUTCTimeOnlyTime priority (time) of the order.
→ 336TradingSessionIDCStringSent for entries other than MDEntryType (269) = 0 (Bid) or 1 (Offer): CLOSED, OPEN, PREOPEN, SUSPENDED, EXPIRED, TERMINATED, HALTED, MATCH\_AND\_CLOSE\_AUCTION
→ 1151SecurityGroupCStringThe name of the group of related securities to which this instrument belongs.
→ 1070MDQuoteTypeCintIdentifies market data quote type. Only sent for MDEntryType (269) = 4 (Opening Price). 0 = Indicative
→ 59TimeInForceCcharSent for MDEntryType (269) = 0 (Bid) or 1 (Offer). The time in force for this order (0=Good for day, 1=Good till cancel, 6=Good till date)
→ 37OrderIDCStringSent for MDEntryType (269) = 0 (Bid) or 1 (Offer). Matches the order ID in the ExecutionReport \[8] acknowledgement, allowing Participants to identify their own orders within market data. Typically a 13-character alphanumeric string.
→ 278MDEntryIDCStringSent for MDEntryType (269) = 0 (Bid) or 1 (Offer). Unique reference for the entry. Typically 13-character alphanumeric string.
→ 40OrdTypeCcharSent for MDEntryType (269) = 0 (Bid) or 1 (Offer).
→ 126ExpireTimeNUTCTimestampSent for MDEntryType (269) = 0 (Bid) or 1 (Offer) where the order has ExpiryTime (126) set.
\< Standard Trailer >Y
**Example 19:** Initial Market Data Snapshot (five repeating groups color-coded) ``` 8=FIXT.1.1 | 9=458 | 35=W | 34=79 | 49=TARGET | 52=20240521-09:45:49.860198821 | 56=SENDER | 22=8 | 48=GOOG | 55=GOOG | 167=NONE | 262=1552371733 | 268=5 | 269=2 | 270=0.00 | 271=1499 | 272=20240521 | 273=09:06:39.324891684 | 336=OPEN | 269=4 | 270=3.00 | 272=20240515 | 273=21:24:03.898604733 | 336=OPEN | 1070=1 | 269=7 | 270=50.00 | 272=20240517 | 273=19:06:47.977567695 | 336=OPEN | 269=8 | 270=0.00 | 272=20240521 | 273=09:06:39.324891684 | 336=OPEN | 269=B | 270=93544.40 | 271=23645 | 272=20240521 | 273=09:06:39.324891684 | 336=OPEN | 1151=Equities | 10=199 | ``` Note the response will contain ONLY a snapshot of the current order book; it does not contain information about historic trades that have occurred on the platform. ### Figure 14: Successful market data subscription with snapshot and incremental updates ![](https://files.readme.io/9536423-market_refresh.png) # Unsubscribing from Market Data Source: https://docs.polymarket.us/institutional/fix-api/fix-market-data-unsubscribe Participants can unsubscribe from a piece of market data by entering a second MarketDataRequest \[V] message, referencing the original MDReqID (262) reference and setting SubscriptionRequestType (263) = 2 (unsubscribe). Note that there is no explicit response to requests to unsubscribe from the Polymarket US; a successful unsubscription will simply prevent further MarketDataIncrementalRefresh \[X] messages. ## Figure 15: Successful market data subscription and unsubscription ![](https://files.readme.io/9b2dd8d-unsubscribe.png) # Mass Quote Protection Source: https://docs.polymarket.us/institutional/fix-api/fix-mass-quote-protection Mass Quote Protection (MQP) is a risk control that automatically cancels an account's remaining eligible resting orders when that account trades too much quantity within a short, rolling time window. MQP is intended to limit the impact of runaway quoting or unexpected market conditions by quickly removing outstanding liquidity once an execution threshold is reached. ## Triggers MQP is configured per account with two parameters: * **Interval (Y)**: duration of the rolling time window (e.g., 3s) * **Traded Quantity (X)**: quantity that may be executed within the interval before MQP triggers (e.g., 10) MQP triggers when the sum of executed quantity for a bucket within a rolling interval Y reaches or exceeds the traded quantity threshold X. ## Rolling Interval Semantics The interval is rolling, not fixed to wall-clock boundaries. At any moment, the system considers executions in the trailing Y seconds when computing the bucket's traded quantity. As a result: * Executions separated by more than Y seconds do not accumulate toward the same trigger * Executions clustered within Y seconds do accumulate ## Bucketing and Scope MQP tracking is done per bucket, where a bucket is defined as: ``` (account, clOrdLinkId) ``` Orders are associated to a bucket using the order's `clord_link_id` value. Orders submitted without a `clord_link_id` (blank / missing) are still subject to MQP. They are tracked in their own bucket: ``` (account, "") ``` ## What MQP Cancels When MQP triggers for a given bucket, the matching engine cancels all remaining eligible resting orders in that same bucket: * Same account * Same `clord_link_id` (or blank bucket) * Regardless of instrument * Regardless of side (buy/sell) Orders in other buckets are not affected, including: * Same account but different `clord_link_id` * Same `clord_link_id` but different account ## Execution and Cancel Timing MQP is applied as part of the matching process: 1. The execution that causes the bucket's traded quantity to reach or exceed the threshold is processed normally. 2. Once MQP triggers, the matching engine cancels remaining eligible resting orders in that bucket. MQP does not prevent an aggressing order from matching against all eligible resting orders it can reach during that matching event. This means the traded quantity may exceed the configured threshold during a single aggressing order's sweep. ## Reset Behavior After MQP triggers and cancels orders in a bucket, the bucket's rolling interval tracking is reset immediately. The account may reuse the same `clord_link_id` and continue trading without automatic cancellation unless and until the threshold is reached again in a new interval. # NewOrderSingle Source: https://docs.polymarket.us/institutional/fix-api/fix-new-order-single Participants may place orders into the Polymarket US order book to buy or sell securities using a NewOrderSingle \[D] message. ## Table 15: NewOrderSingle (D) message
Tag Name Req Type Description
\< Standard Header >Y35 = D
11ClOrdIDYStringUnique, participant-created identifier for this Order. Uniqueness must be guaranteed across a session (i.e. between logon and logout), which may span multiple days
1AccountNStringAccount reference as previously advised to the exchange operator
18ExecInstNMultipleCharInstructions for order handling. Note that Price Validity Checks can only be ignored (c) for market-to-limit orders to sell. G=All or None, c=Ignore Price Validity Checks, 6=Participate Don't Initiate
110MinQtyNQtyMinimum order quantity that must be executed upon entry (or else the whole order is immediately canceled).
55SymbolYStringInstrument symbol
460ProductNIntIndicates the type of product the security is associated with. All current products on Polymarket are Product=12 (OTHER).
54SideYchar1=Buy, 2=Sell
60TransactTimeNUTCTimeTimestamp of order entry in UTC.
38OrderQtyYQtyOrder quantity. Can be a decimal.
40OrdTypeYCharOrder type (2=Limit, 3=Stop, 4=Stop limit, K=Market with left-over as limit)
44PriceNPricePrice per share/unit. Required where OrdType (40) = 2 (Limit) or 4 (Stop Limit).
99StopPxNPriceStop price at which to trigger the stop order. Required for OrdType (40) = 3 (Stop) or 4 (Stop Limit). Must be greater than or equal to Price (44) for buy order, or less than or equal to Price (44) for sell orders.
581AccountTypeNIntAccount type codes (1-17)
582CustOrderCapacityNIntCustomer order capacity codes (1-5)
453NoPartyIDsNNumingGroupNumber of PartyID (448), PartyIDSource (447), and PartyRole (452) entries
→ 448PartyIDNStringParty identifier/code
→ 447PartyIDSourceNcharD=Proprietary
→ 452PartyRoleNInt1=EXECUTING\_FIRM, 3=CLIENT\_ID, 24=CUSTOMER\_ACCOUNT
59TimeInForceNchar0=Good for day \[Default], 1=Good till cancel, 3=Immediate or cancel, 4=Fill or kill, 6=Good till date
126ExpireTimeNUTCTimeOrder expiry date and time for orders where TimeInForce = Good Till Date
1028ManualOrderIndicatorNBooleanIndicates if the order was initially received manually (as opposed to electronically)
6127ConditionTriggerMethodNintThe reference price used to trigger the stop order. Applicable to Stop orders only. 2=Last price \[Default], 5=Settlement price. Settlement price may be the preferred trigger method for markets where settlement price is updated frequently from a price oracle.
7928SelfMatchPreventionIDNStringUnique identifier to be returned in the case of a self-match prevention cancellation. The same ID must be present on all orders where self-match prevention is desired.
8000SelfMatchPreventionInstructionCStringSelf-match instruction. O=Cancel oldest (resting) order, N=Cancel newest (aggressive) order
\< Standard Trailer >Y
**Example 5: Entry of a new limit order to buy 1,000 shares at 50.00** ``` 8=FIXT.1.1 | 9=123 | 35=D | 49=SENDER | 56=TARGET | 34=16 | 50=SENDERSUB | 52=20240517-19:00:28 | 11=1182560819 | 21=1 | 55=GOOG | 54=1 | 40=2 | 44=50 | 38=1000 | 1=ACCT | 10=166 | ``` # Order Cancellation Source: https://docs.polymarket.us/institutional/fix-api/fix-order-cancellation Participants may request to cancel any open order using the OrderCancelRequest \[F] message. As with order amend requests, the order to be canceled is identified using the last-entered ClOrdID (11) value relating to the order into the OrigClOrdID (41) field. If the order has been canceled, the Polymarket US will respond with an ExecutionReport \[8] message which echoes many of the key (updated) order attributes, and with ExecType (150) = 4 (canceled). ## Table 18: OrderCancelRequest (F) message
Tag Name Req Type Description
\< Standard Header >Y35 = F
11ClOrdIDYStringFresh, participant-generated, unique reference for this OrderCancelRequest. Must be different from the original ClOrdID (11) for the order.
41OrigClOrdIDYStringThe last (participant-generated) ClOrdID (11) reference for the order. This might be from the original NewOrderSingle or prior amends.
55SymbolYStringInstrument symbol as indicated on the order.
\< Standard Trailer >Y

If the request to cancel the order passed validation, then the Polymarket US will acknowledge the order cancellation with an ExecutionReport \[8] message indicating OrdStatus (39) = 4 (canceled), and LeavesQty (151) = 0. ## Figure 12: Successful cancelation of existing order ![](https://files.readme.io/4964694-order_cancel.png) **Example 16: Example of order cancel message** ``` 8=FIXT.1.1 | 9=107 | 35=F | 49=SENDER | 56=TARGET | 34=96 | 52=20240517-19:29:50 | 50=SENDERSUB | 41=1182560834 | 11=1182560840 | 55=GOOG | 54=1 | 10=244 | ``` Rejected attempts to cancel orders for any reason (for example order is no longer alive) are rejected using the OrderCancelReject \[9] message, which identifies the reason for the rejection. ## Table 19: OrderCancelReject (9) message
Tag Name Req Type Description
\< Standard Header >Y35 = 9
11ClOrdIDYStringEchoed from the OrderCancelRequest
37OrderIDYStringWill be 'NONE' for unknown orders
39OrdStatusYcharStatus of the cancellation request. 8=Rejected
41OrigClOrdIDYStringEchoed from the OrderCancelRequest
58TextYStringFree text string providing additional rejection reason
102CxlRejReasonYintReason for the rejection. 0=Too late to cancel, 1=Unknown order, 6=Duplicate ClOrdID, 18=Invalid price increment (tick size), 99=Other
434CxlRejResponseToYchar1=Order cancel request, 2=Order amend request
\< Standard Trailer >Y

## Figure 13: Unsuccessful attempt to cancel existing order ![](https://files.readme.io/401042e-unsuccessful_cancel.png)
**Example 17: Rejection of an unsuccessful cancel attempt** ``` 8=FIXT.1.1 | 9=145 | 35=9 | 34=91 | 49=TARGET | 52=20240521-09:26:30.378549737 | 56=SENDER | 57=SENDERSUB | 11=1886428723 | 37=NONE | 39=8 | 41=1886428676 | 58=Unknown order | 102=1 | 434=1 | 10=198 | ``` # Order Entry Overview Source: https://docs.polymarket.us/institutional/fix-api/fix-order-entry-overview ## Overview The Exchange operates as a continuous central limit order book to match orders within the system according to price-time priority: * Higher-priced orders to buy have priority over lower-priced bids, * Lower-priced offers to sell buy have priority over higher-priced sells, * Within a price level, older orders have priority over newer orders, * Should an existing order increase its quantity (at the same price), it is assigned a new timestamp and therefore loses time priority, but quantity decreases retain time priority. ## Tick Sizes / Minimum Quantity Increments Each instrument on the Polymarket US has a minimum price increment ("tick size") and minimum quantity increment ("lot size"). These instrument-level settings are indicated in the SecurityList \[y] message in the MinPriceIncrement (969) and MinTradeVol (562) fields respectively. Order with a price and/or quantity which do not comply with these increments will be rejected using BusinessMessageReject \[j] with reason 13 = Incorrect quantity or 18 = Invalid price increment (tick size). ## Order Types, Time In Force & Execution Instructions The intended behavior of orders entering the Polymarket US is controlled using a combination of OrderType (40), TimeInForce (59) and ExecInst (18) fields. These are explained in turn below. ### Table 12: OrdType (40) definitions | Order Type | Value | Definition | | --------------- | ----- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | LIMIT | 2 | A priced order that can only trade at a price equal to or better than the price specified. | | MARKET TO LIMIT | K | An order that fills as far as possible at the best price(s) in the market. Should the order volume exceed that available in the order book, then the remaining order quantity is converted into a Limit order with the price equal to the last fill price (subject to Time In Force instructions).\
In the case where there is no market, the order will be rejected with reason "No liquidity for market order". | | STOP | 3 | An order which initially enters the system as hidden, but which activates (converts to active) once the price indicated by StopPx (99) is triggered in the market. At this point the order is converted into a Market to Limit order with the indicated quantity. | | STOP LIMIT | 4 | An order similar to STOP, but where the converted order is a Limit order with the indicated Price (44). | Looking for a "classic" Market Order? Simply enter a Market-to-Limit order with IOC Time in Force condition. ### Table 13: TimeInForce (59) conditions | Time In Force | Value | Definition | | :------------------------ | :---- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | DAY | 0 | The order remains open for the current trading day only. | | GOOD TILL CANCEL (GTC) | 1 | The order remains available for execution until fully executed or canceled. | | IMMEDIATE OR CANCEL (IOC) | 3 | The order is immediately executed as far as possible upon entry, with all unfilled quantity expired. Can be used in conjunction with MinQty (110) to specify a minimum quantity which must be executed immediately. | | FILL OR KILL (FOK) | 4 | If the entire order quantity can not be satisfied immediately, then the order is canceled in full. | | GOOD TILL DATE (GTD) | 6 | The trade is active until a specific date and time (expressed in UTC) as indicated in ExpireTime (126). | In addition to order-level Time In force conditions, participants can also specify a minimum order quantity using MinQty (110), which requires that an order receives at least a minimum execution quantity upon entry (possibly in multiple fills). When used in combination with Time In Force, this field offers fine-grained control over the conditions under which an order can participate. For example: * An order marked as IOC with MinQty (110) of 200 must receive an immediate, minimum trade quantity of 200. If this is not possible, then the entire order immediately expires. * In the case of orders marked with a FOK TimeInForce (59), MinQty (110) can be included but has no material effect, as the FOK requires a full fill upon entry. * A Good Till Cancel order with a MinQty (110) specified must trade at least the specified quantity upon first entry into the order book, otherwise the order expires. Note that MinQty (110) only limits order behavior as it enters the order book; it does not prevent smaller trades occurring against the order once it is resting on the order book. ### Table 14: ExecInst (18) definitions | Exec Instruction | Value | Definition | | :----------------------------------------- | :---- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ALL OR NONE | G | Require that either (a) all of the order quantity is filled immediately, or (b) none of it should trade even partially. Marking an order with this ExecInst instruction is functionally-equivalent to setting Time In Force to FOK. | | IGNORE PRICE VALIDITY CHECKS | c | Exempt this order from absolute price limits, relative price limits, order size limits, and total notional limits. Only allowed for market-to-limit orders to sell. This flag exists to support quickly liquidating a position. | | PARTICIPATE DON'T INITIATE | 6 | Require that the order is only accepted if it would NOT immediately match. This guarantees that the order will always be the passive side in any trades. An order that would result in a match would be rejected with reason "Order may participate but not initiate in the market". Should not be used in combination with MinQty (110) or conditions which require immediate executions - ExecInst (18) = G (All or None), or TimeInForce (59) = 3 or 4 (IOC or FOK). | | SINGLE EXECUTION REQUESTED FOR BLOCK TRADE | j | A flag for submitting block trades to Polymarket US. | | BEST LIMIT | R | A flag that if set indicates that the price of a limit order shall be set to the price at the top of the book on the same side as this order. | | IMMEDIATELY EXECUTABLE LIMIT | T | A flag that if set indicates that the price of a limit order shall be set to the price at the top of the book on the opposing side as this order, thus able to immediately match. |
# Order Modification Source: https://docs.polymarket.us/institutional/fix-api/fix-order-modification
Participants may request to amend any open order using the OrderCancelReplaceRequest \[G] message. In this case, the order to be amended is identified using the last-entered ClOrdID (11) value relating to the order into the OrigClOrdID (41) field. Following FIX convention, this modification message will also contain a fresh ClOrdID (11) value which can then be used by the Participant to further modify the order if required. Note that the Polymarket US does not require the entry of the (platform-generated) OrderID (37) in order to amend the order. ## Table 17: OrderCancelReplaceRequest (G) message If the order has been successfully modified, the Polymarket US will respond with an ExecutionReport \[8] message which echoes many of the key (updated) order attributes, and with ExecType (150) = 5 (replaced). Note that a modification which increases the remaining order quantity will lose its time priority at a price level. Any modifications to reduce order quantity (or other non-price modifications which do not change quantity) will not cause the order to lose their order book priority. ## Figure 10: Successful amend of existing order to adjust OrderQty \[38]
![](https://files.readme.io/b69c215-order_replace.png)
**Example 15: Request to replace an existing order** ``` 8=FIXT.1.1 | 9=127 | 35=G | 49=SENDER | 56=TARGET| 34=66 | 52=20240517-19:17:08 | 50=SENDERSUB | 41=1182560827 | 11=1182560836 | 55=GOOG | 54=2 | 40=2 | 38=500 | 44=1000 | 10=061 | ``` If the order can not be modified for any reason (for example if the requested price / order size are not acceptable, or the order has already expired), then the Polymarket US will respond with an OrderCancelReject \[9] message indicating the reason. **Where the amendment has been rejected, the existing order remains working with prior attributes.** ## Figure 11: Unsuccessful amend of existing order to adjust OrderQty \[38] ![](https://files.readme.io/b95417e-order_replace2.png) # FIX API Overview Source: https://docs.polymarket.us/institutional/fix-api/fix-overview The purpose of this document is to outline the trading functionality available via a FIX trading protocol. Download FIX 5.0 SP2 and FIXT 1.1 XML specifications (ZIP) ## Rate Limits The FIX API enforces a rate limit of **150 messages per second per session**. This limit applies to all inbound messages from the client to the exchange, across all participants. ## Firm, User and Account Identifiers The Polymarket US exposes two FIX gateways to participants; an order management gateway, and a second gateway to receive market data. Additional drop-copy sessions can be provided upon request. Note that should the Exchange offer direct market access (DMA) to their underlying customer, then each DMA customer should have their own dedicated pair of FIX gateways. An example FIX session configuration is shown below. Note the network details and Sender/TargetCompID (in red) will be provided by the exchange operator. ### Example QuickFIX session configuration `[DEFAULT] ConnectionType=initiator SocketConnectHost=12.12.12.12 SocketConnectPort=13001 BeginString=FIXT.1.1 DefaultApplVerID=9 SenderCompID=SENDERCOMP1 [SESSION] TargetCompID=EXCHANGECOMPID` The Polymarket US also validates individual users (traders) using SenderSubID (50), and customer accounts using Account (1) which are validated on order entry. Please contact the exchange operator to allocate these codes. ## Symbology Polymarket US uses a simple string identifier to instruments trading on the platform, which is required to identify instruments in the API using Symbol (55). There is currently no support to identify instruments using any other common identifiers such as CUSIP, ISIN or Bloomberg code. A list of instruments on the platform can be retrieved using the SecurityListRequest \[x] message. ## Instrument States Instruments follow the primary lifecycle: PENDING → OPEN → CLOSED → EXPIRED → TERMINATED. Instruments may also be SUSPENDED or HALTED during their lifecycle.
### Primary State Flow | State                                               | Description | | --------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `PENDING` | Initial state for a newly created instrument which has not yet begun trading. Clients will receive a PENDING → OPEN state change notification but will not see PENDING in the order book. | | `OPEN` | In this state, the instrument is open for continuous order entry and matching. | | `CLOSED` | In this state, orders can not be entered, modified, or canceled, and no matching occurs. Any existing Day orders will be expired. | | `EXPIRED` | An instrument moves to this state when its Expiration Date/Time is reached. In this state, any resting orders are expired and no new orders can be entered. | | `TERMINATED` | When an instrument's Termination Date is reached, the order book is removed from the matching engine, orders are canceled, and positions are closed. Historical data will still remain in Polymarket US ledgers. | ### Exception States | State                                               | Description | | --------------------------------------------------- | --------------------------------------------------------------------------------------------- | | `SUSPENDED` | Orders can be canceled but no matching occurs, and no order entry or modification is allowed. | | `HALTED` | This state is similar to SUSPENDED, with the exception that orders cannot be canceled. | ### Other Possible States | State                                               | Description | | --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `PREOPEN` | Orders can be entered and modified, but no matching occurs. When the instrument transitions to an OPEN state, the orders entered during PREOPEN will match at a single opening price that is automatically determined by an algorithm that is designed to maximize the volume traded at the open. | | `MATCH_AND_CLOSE_AUCTION` | This state is similar to PREOPEN, with the exception that matching will occur upon the transition of this state to any other state. This state is useful if you want matching to occur at the end of the state, but you don't want the instrument to be open after. | ## FIX Notation Please note the following presentation notes which apply to message definitions and FIX examples throughout this document. * FIX tag/value pairs are delimited within a TCP connection using the SOH (ascii character 1) character. Since this is a non-printable character, in this document we use the | character instead, and pad each pair with spaces to make them easier to use. * Components are blocks of FIX tags which appear frequently in the specification (e.g. header and footers which appear on every FIX message). They are defined centrally for convenience and then referenced throughout the document using \<> notation. * Repeating groups of FIX tags appear in various messages. The depth of a repeating group is indicated using the → marker in FIX message definitions. * References to individual FIX fields (or “tags”) are presented in italic font, with the tag number following the tag name. For example HeartBtInt (108). # FIX Reject Reasons Source: https://docs.polymarket.us/institutional/fix-api/fix-reject-reasons Error codes and reject messages across FIX sessions ## Application Layer Messages ### Execution Reports (35=8) and Order Cancel Reject (35=9) For Execution Reports (i.e. when a NewOrderSingle is rejected), the reason code is provided in **OrdRejReason (Tag 103)**. For Order Cancel Reject messages (i.e. when a cancel or cancel/replace is rejected), the reason code is provided in **CxlRejReason (Tag 102)**. Note that the FIX protocol specifies different enumerations for OrdRejReason vs CxlRejReason. As such, the exact same error condition may produce a different error code in an execution report versus an order cancel reject message. | Description | Example Error String (Tag 58) | OrdRejReason (Tag 103) | CxlRejReason (Tag 102) | | ------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------- | ---------------------- | | Notional lower than minimum | "Low total notional size limit is 1, given 0.944" | Exchange option (0) | Exchange option (2) | | Notional larger than maximum | "High total notional size limit is 500000, given 500025" | Exchange option (0) | Exchange option (2) | | Price lower than minimum | "Low price limit is 0.5, given 0.49" | Exchange option (0) | Exchange option (2) | | Price higher than maximum | "High price limit is 1.5, given 1.51" | Exchange option (0) | Exchange option (2) | | Quantity lower than minimum | "Low order size limit is 100, given 98" | Exchange option (0) | Exchange option (2) | | Quantity higher than maximum | "High order size limit is 500, given 600" | Exchange option (0) | Exchange option (2) | | Minimum trade quantity | "Minimum trade quantity is 100, given 99" | Exchange option (0) | Exchange option (2) | | Insufficient buying power | "1000 USD available, requested 1000.098" | Exchange option (0) | Exchange option (2) | | Self Match Prevention | "Self Match Prevention" | Exchange option (0) | Exchange option (2) | | Participate Don't Initiate | "Order may participate but not initiate in the market" | Exchange option (0) | Exchange option (2) | | BEST\_LIMIT Exec Instruction with no orders on same side | "No liquidity to derive limit price" | Exchange option (0) | Exchange option (2) | | IMMEDIATELY\_EXECUTABLE\_LIMIT Exec Instruction with no orders on opposite side | "No liquidity to derive limit price" | Exchange option (0) | Exchange option (2) | | Re-use of ClOrdID in Session | "ClOrdID already in use by an open order" | Exchange option (0) | Exchange option (2) | | Account Type Validation | "Requested Account Type (FLOOR\_TRADER) does not match account configuration" | Exchange option (0) | Exchange option (2) | | Customer Order Capacity validation | "Requested Customer Order Capacity (PROPRIETARY\_ACCOUNT) does not match user/account configuration" | Exchange option (0) | Exchange option (2) | | No Reference Price for Market to Limit Order | "failed risk check: rpc error: code = FailedPrecondition desc = No last trade when open, trading reference price, settlement price, or previous close is available to evaluate reference price" | Exchange option (0) | n/a | | No Qty Filled on CashOrderQty Order | "There is no liquidity to immediately fill any portion of this CashOrderQty order, and the order cannot rest as a limit order because the order qty would be 0 at the current fallback price" | Exchange option (0) | n/a | | Invalid Attributes | "Order has attributes that cannot be placed in MATCH\_AND\_CLOSE\_AUCTION" | Exchange closed (2) | Exchange option (2) | | Invalid Attributes | "Order has attributes that cannot be placed in PRE\_OPEN" | Exchange closed (2) | Exchange option (2) | | Market Closed | "Book is CLOSED" | Exchange closed (2) | Exchange option (2) | | Market Suspended by Exchange | "Book is SUSPENDED" | Exchange closed (2) | Exchange option (2) | | Market Halted by Exchange | "Book is HALTED" | Exchange closed (2) | Exchange option (2) | | Attempt to Cancel/Replace a Filled Order | "Unknown order" | n/a | Unknown order (1) | **`Global Rate Limit Exceeded` is a latency stopgap, not a rate limit.** During periods of increased latency, an order that has been received but not processed within 5 seconds is rejected with the text `Global Rate Limit Exceeded` to protect you from a bad fill at a stale price. This is **not** an actual rate limit — do **not** throttle your traffic in response. It applies to new orders and cancel/replace modifications, but **not** to pure cancels. You can always cancel an order before it has been acknowledged or processed. See [Rate Limits](/trader-guide/rate-limits#latency-stopgap-on-orders). ### Market Data Request Reject (35=Y) There are no cases where the FIX Market Data Gateway will send this message. Rejections on the FIX MD Gateway will be either 35=3 or 35=j messages. ## Session Layer Messages ### New Message Reject (35=3) A session level reject is sent when the FIX session cannot process an incoming message. RefSeqNum(45) will contain the MsgSeqNum(34) of the message that triggered the reject. SessionRejectReason(373) and Text(58) will contain an error code and description, respectively. | Error Code (Tag 373) | Example Error String (Tag 58) | | -------------------- | ------------------------------------------------ | | 0 | "Invalid tag number" | | 1 | "Required tag missing" | | 1 | "Account type must be provided" | | 1 | "Customer order capacity must be provided" | | 2 | "Tag not defined for this message type" | | 4 | "Tag specified without a value" | | 5 | "Value is incorrect (out of range) for this tag" | | 6 | "Incorrect data format for value" | | 7 | "Decryption problem" | | 8 | "Signature problem" | | 9 | "CompID problem" | | 10 | "SendingTime accuracy problem" | | 11 | "Invalid MsgType" | | 13 | "Tag appears more than once" | | 14 | "Tag specified out of required order" | | 15 | "Repeating group fields out of order" | | 16 | "Incorrect NumInGroup count for repeating group" | | 99 | "Other" | ### Business Message Reject (35=j) Below is a list of Business Message Reject (35=j) error strings produced by the exchange FIX gateways. | Reason | Example Error String (Tag 58) | BusinessRejectReason (Tag 380) | | ------------------------------------------------------------- | ------------------------------------------------------------ | ---------------------------------------- | | Self-Match Override Prohibited | "Session-level self match prevention ID already provided" | Other (0) | | Rate Limits on Session | "Message rate limit throttled for session" | Other (0) | | Unknown Account Type | "Account type unknown" | Other (0) | | Unknown Customer Order Capacity | "Customer order capacity unknown" | Other (0) | | Unsupported Commission Type | "Unsupported CommType" | Other (0) | | MD session not eligible to have commission spread overwritten | "Cannot supply MDFeedType on this session" | Other (0) | | Unknown account property in MDFeedType | "Unknown MDFeedType" | Other (0) | | Unknown Firm | "Legal Entity Unavailable: %s" | Unknown ID (1) | | Unknown Security | "Instrument Unavailable: %s" | Unknown security (2) | | Non Tradable Instrument | "Instrument is not tradable" | Unknown security (2) | | Unsupported Message | "Unsupported Message Type" | Unsupported message type (3) | | Quotes Disabled | "quotes are not available" | Application not available (4) | | Cash Order Quantity Validation | "For cash\_order\_qty order\_type must be MARKET\_TO\_LIMIT" | Conditionally required field missing (5) | | Conditionally Required Field Missing | "Conditionally Required Field Missing (%d)" | Conditionally required field missing (5) | | Unsupported Message Type for Session | "message is not supported for this session type" | Not authorized (6) | *** ## Common Connection and Order Issues ### 1. Logon Rejected Immediately **Cause**: SenderSubID (50) sent on Logon message **Fix**: Do not send tag 50 on Logon (35=A). Only include SenderSubID on application messages (order entry, cancel, etc.), not on session management messages. ### 2. No Connection / TCP Reset **Cause**: Source IP not allowlisted, or connecting to wrong environment (preprod vs prod) **Fix**: Confirm your static egress IP is whitelisted and verify you're connecting to the correct environment with Polymarket. ### 3. Order Rejected: Unknown Account **Cause**: Missing or invalid Account (1) **Fix**: Use account IDs explicitly provided by Polymarket during onboarding. Verify the Account tag value matches your assigned account. ### 4. Order Rejected: Not Authorized **Cause**: Trader (SenderSubID) not permissioned for the specified account **Fix**: Confirm trader-to-account mapping with Polymarket. Ensure the SenderSubID has permissions for the Account you're trading on. ### 5. Order Rejected: Invalid Price Increment **Cause**: Price violates tick size for the instrument **Fix**: Fetch MinPriceIncrement (969) from SecurityList (35=y) and ensure your order price respects the tick size increment. ### 6. Order Rejected: Incorrect Quantity **Cause**: Quantity violates lot size or minimum/maximum quantity rules **Fix**: Fetch MinTradeVol (562) from SecurityList (35=y). Ensure quantity respects the lot size increment and falls within min/max limits. ### 7. Post-Only Order Rejected **Cause**: ExecInst=6 (post-only) would immediately match against existing orders **Fix**: Adjust your limit price so it does not cross the current market, or remove the post-only instruction. ### 8. Order Accepted Then Immediately Canceled **Cause**: Self-match prevention triggered **Fix**: Check your SelfMatchPreventionID (7928) and verify the self-match instruction (8000=O or N). Orders from the same SMP ID that would match are canceled. ### 9. Market Data Not Received **Cause**: Attempted to subscribe on order-entry session, or no MarketDataRequest (35=V) sent **Fix**: Use the dedicated market data FIX session and send a valid MarketDataRequest subscription message. ### 10. Sequence Number / Resend Loop **Cause**: MsgSeqNum mismatch after restart, creating continuous resend requests **Fix**: Restart your session with ResetSeqNumFlag (141=Y) on Logon to reset sequence numbers to 1, or manually resync sequence numbers with your FIX engine. # Self-Match Prevention Source: https://docs.polymarket.us/institutional/fix-api/fix-self-match-prevention The Polymarket US offers self-match prevention logic which can be enabled either at the FIX session level (automatically applied to all orders entered through a session), or on a per-order basis using the SelfMatchPreventionID (7928) and SelfMatchPreventionInstruction (8000) tags. This instruction will automatically cancel one or both orders (without execution) which would potentially be involved in a self-match. The options are: 1. The aggressor (new) order will be canceled. 2. The passive (existing) order is canceled. Contact the exchange operator to enable the setting at the FIX session level. ## How Self-Match Prevention Works Orders may have self-match prevention enabled at either the FIX session level, or at an order-level basis using tags SelfMatchPreventionID (7928) and SelfMatchPreventionInstruction (8000). Where specified, two otherwise-executable orders from the same participant and which carry the same SelfMatchPreventionID (7928) will be prevented from matching by expiring one of the orders. Whether the resting or aggressive order is canceled is governed by SelfMatchPreventionInstruction (8000) of the incoming order. ### Figure 8: Self-match prevention expires the oldest order to prevent self-trading
![](https://files.readme.io/ff5ba39-selfmatch.png)
Note that resting order(s) will expire just after the incoming order has been accepted by the Platform, and before any trades have taken place. The incoming order is therefore permitted to trade against other orders in the order book as it entered the market. **Example 13: ExecutionReport indicating resting order expiry as a result of self-match prevention** ``` 8=FIXT.1.1 | 9=341 | 35=8 | 34=69 | 49=TARGET | 52=20240521-11:22:26.078209896 | 56=SENDER | 57=SENDERSUB | 1=ACCT | 6=0.00 | 11=1886428747 | 14=0 | 17=1HPT7DQ1GC4JA | 22=8 | 31=0.00 | 32=0 | 37=1HQ4A5T0EDM26 | 38=1 | 39=4 | 40=2 | 41=1886428747 | 44=0.02 | 48=GOOG | 54=1 | 55=GOOG | 58=Self Match Prevention | 59=0 | 60=20240521-11:22:26.075068980 | 99=0.00 | 150=4 | 151=0 | 378=99 | 581=3 | 582=1 | 7928=111 | 8000=O | 10=039 | ``` If the SelfMatchPreventionInstruction (8000) is N (newest), then it is the incoming order which is rejected to prevent the execution, as shown below. This is the default behavior where SelfMatchPreventionID (7928) is present but SelfMatchPreventionInstruction (8000) is not specified. Note that the incoming order is fully canceled in the case it would potentially match against other orders with the same SelfMatchPreventionID (7928); partial execution against other orders is not permitted. ### Figure 9: Self-match prevention causes newest order to be rejected
![](https://files.readme.io/f39184350c03a8ce8bca9bc6aa9799e5a75e95bf388b74e00311fa475f083dad-figure9.jpeg)
**Example 14: ExecutionReport immediately rejecting incoming order as a result of self-match prevention** ``` 8=FIXT.1.1 | 9=314 | 35=8 | 34=109 | 49=TARGET | 52=20240521-11:55:30.495116582 | 56=SENDER | 57=SENDERSUB | 1=ACCT | 6=0.00 | 11=1886428755 | 14=0 | 17=1HPT7DQ1GC4JT | 22=8 | 31=0.00 | 32=0 | 37=1HQ4A5T0EDM2E | 38=10 | 39=8 | 40=2 | 44=0.02 | 48=GOOG | 54=1 | 55=GOOG | 58=Self Match Prevention | 59=0 | 60=20240521-11:55:30.487845738 | 99=0.00 | 103=0 | 150=8 | 151=0 | 581=3 | 582=1 | 7928=222 | 10=165 | ```
# FIX Session Management Source: https://docs.polymarket.us/institutional/fix-api/fix-session-management ## Establishing and Maintaining a FIX Session The Logon \[A] message initiates a connection to the Polymarket US. FIX sessions are typically initiated by customers (as opposed to the exchange platform), and can be made any time. **Important: Logon \[A] messages must NOT specify a SenderSubID \[50] in the header. Sending this tag will cause the logon attempt to be rejected.** ### Table 4: Logon (A) message
Tag Name Req Type Description
\< Standard Header >Y35 = A
98EncryptMethodYintEncrypted messages are not supported. 0=None
108HeartBtIntYintA 30-second interval is recommended
141ResetSeqNumFlagYBooleanIndicates both sides of a FIX session should reset sequence numbers back to 1 during a normal end of session (and not due to an unintended disconnect). Recommended
1137DefaultApplVerIDYStringSpecifies the FIX 5.0 service pack release being applied. 9=FIX50SP2
\< Standard Trailer >Y

**Example 1: Logon message** ``` 8=FIXT.1.1 | 9=76 | 35=A | 49=SENDER | 56=TARGET | 34=1 | 52=20240516-14:14:53 | 98=0 | 108=30 | 1137=9 | 141=Y | 10=132 | ```
### Figure 1: Successful Logon ![](https://files.readme.io/688c178-logon.png) Under normal circumstances, both FIX engines on either side of the connection will regularly exchange Heartbeat messages. The frequency of such a message exchange is determined by the HeartBtInt (108) value indicated in the Logon \[A] message. Regular application messages qualify as a heartbeat, i.e both sides of the connection are behaving nominally if a message is received during the HeartBtInt (108). If no application messages are received during the interval, the Heartbeat \[0] message is used as described below.
### Table 5: Heartbeat (0) message
Tag Name Req Type Description
\< Standard Header >Y35 = 0 (zero)
112TestReqIDNStringRequired when the heartbeat is the result of a TestRequest \[1] message
\< Standard Trailer >Y
At any point either counterparty can force the opposing FIX engine to send a Heartbeat \[0] message by submitting a TestRequest \[1] message containing a TestReqID (112) value, which should be echoed in the Heartbeat \[0] response. ### Table 6: TestRequest (1) message
Tag Name Req Type Description
\< Standard Header >Y35 = 1
112TestReqIDYStringID to be returned in the resulting Heartbeat \[0] response
\< Standard Trailer >Y
### Figure 2: Using TestRequest \[1] to request Heartbeat \[0] from other side ![](https://files.readme.io/6dc3db8-test_request.png) ## Cancel on Disconnect Cancel on Disconnect is an optional feature which can act as an automatic risk control for participants in the event that application connectivity is lost for any reason, from network error to graceful logout. In these circumstances, the exchange will automatically cancel all open (unexecuted) DAY orders for the Participant while GTC and GTD orders continue to rest. Note that a network-level disconnect instantly triggers this Cancel on Disconnect functionality in the FIX gateway. After reconnecting, missed messages will be replayed including execution reports for any canceled orders or in-flight fills. It is the FIX client's responsibility to re-enter any orders cancelled by COD feature, if they choose to based on market conditions when connectivity is re-established. Cancel on Disconnect is disabled by default, and enabled on a session-by-session basis in the FIX configurations. The below example shows two sessions, one with CancelOnDisconnect=Y and one with CancelOnDisconnect=N which puts the participant at more risk of in-flight fills. ```yaml theme={null} fixConf: |- [DEFAULT] ConnectionType=acceptor SocketAcceptPort=13001 BeginString=FIXT.1.1 DefaultApplVerID=9 SenderCompID=EP3 StartDay=Sunday StartTime=16:45:01 EndDay=Sunday EndTime=16:45:00 TimeZone=America/Chicago [SESSION] ClearingMemberFirm=firms/Clearing-Member-1 Firm=firms/Trading-Firm-A TargetCompID=TFA CancelOnDisconnect=Y ResetOnLogon=Y [SESSION] ClearingMemberFirm=firms/Clearing-Member-1 Firm=firms/Trading-Firm-B TargetCompID=TFB CancelOnDisconnect=N ResetOnLogon=Y ``` ## Cancel on Logout Cancel on Logout is an optional feature that cancels working DAY orders on a FIX session logout. This functionality is similar to Cancel on Disconnect in that it cancels working DAY orders, but differs in that it triggers due to a logout for any reason. Note this feature cancels working orders when a session disconnects for any reason, including unsolicited disconnects as well as clean logouts. Cancel on Disconnect will only trigger on a network disconnection (with no preceding FIX logout message) whereas Cancel on Logout will trigger for that case in addition to the graceful FIX logout flow as initiated by either the acceptor or initiator. If you only want to cancel when an unexpected connection interruption is detected, instead use Cancel on Disconnect. The example below shows how CancelOnLogout can be enabled while CancelOnDisconnect is disabled. In this example, orders for TargetCompID=TFA will cancel only on a network error (CancelOnDisconnect) whereas TargetCompID=TFB will cancel on any session logout event (CancelOnLogout). ```yaml theme={null} fixConf: |- [DEFAULT] ConnectionType=acceptor SocketAcceptPort=13001 BeginString=FIXT.1.1 DefaultApplVerID=9 SenderCompID=EP3 StartDay=Sunday StartTime=16:45:01 EndDay=Sunday EndTime=16:45:00 TimeZone=America/Chicago [SESSION] ClearingMemberFirm=firms/Clearing-Member-1 Firm=firms/Trading-Firm-A TargetCompID=TFA CancelOnDisconnect=Y ResetOnLogon=Y [SESSION] ClearingMemberFirm=firms/Clearing-Member-1 Firm=firms/Trading-Firm-B TargetCompID=TFB CancelOnDisconnect=N CancelOnLogout=Y ResetOnLogon=Y ``` ## Sequence Number Tracking, Recovery, and Reset The FIX protocol uses simple, incrementing MsgSeqNum (34) values (carried in the StandardHeader of each message) to both detect and request retransmission of missed messages. Each FIX engine will maintain a simple message count of outbound and inbound messages, values which should increment by one for each message sent or received per FIX session. Should a message be received that does not match the expected sequence number given, then it is possible that one or more messages were lost. In that situation, a ResendRequest \[2] message may be sent to request retransmission of a specified range of messages identified by their MsgSeqNum (34) value. ### Table 7: ResendRequest (2) message
Tag Name Req Type Description
\< Standard Header >Y35 = 2
7BeginSeqNoYSeqNumMsgSeqNum (34) of first message in the range to be resent (inclusive)
16EndSeqNoYSeqNumMsgSeqNum (34) of the last message in the range to be resent (inclusive), or "0" to request resend all messages after the indicated BeginSeqNo (7)
\< Standard Trailer >Y
The expected response to a ResendRequest \[2] message is a stream of application messages with PossDupFlag (43) set to “Y” (yes) in the header to indicate that this is a potentially duplicative message. It may be appropriate in some situations for the opposite FIX engine to refuse to replay the message. This is typically the case for administrative messages which either have limited value (e.g., Heartbeat messages) or might cause confusion (e.g., Logon messages). In such circumstances, the opposite FIX engine may respond with a SequenceReset \[4] message (below) with GapFillFlag (123) set to “Y”. The SequenceReset \[4] message can be used in two distinct situations:\ To request that the counterparty adjusts their internal sequence number to the indicated NewSeqNo (36) value. In this case, GapFillFlag (123) is either not present or set to “N” (no). In the message replay situation described above, to replace a FIX message which will not be re-sent. In this case, GapFillFlag (123) should be set to “Y” and the NewSeqNo (36) field will contain the next sequence number to be sent. ### Table 8: SequenceReset (4) message
Tag Name Req Type Description
\< Standard Header >Y35 = 4
123GapFillFlagNBooleanIndicates that the Sequence Reset message is replacing administrative or application messages which will not be resent.
36NewSeqNoYSeqNumNew sequence number
\< Standard Trailer >Y
### Figure 3: ResendRequest \[2] message triggers re-transmission of missed messages ![](https://files.readme.io/1e36f8b-resend_request.png) ## Terminating a FIX Session The Logout \[5] message initiates or confirms the termination of a FIX session. Customers may log out of their FIX sessions at any time. ### Table 9: Logout (5) message
Tag Name Req Type Description
\< Standard Header >Y35 = 5
58TextNStringFree format text string
\< Standard Trailer >Y
The expected response to a successful Logout \[5] message is a reciprocal Logout \[5] message, after which the TCP connection should be dropped. **Example 2: Logout message** ``` 8=FIXT.1.1 | 9=51 | 35=5 | 34=2 | 49=SENDER | 52=20240516-14:17:38 | 56=TARGET | 10=237 | ``` ### Figure 4: Graceful Logout sequence ![](https://files.readme.io/d4287de-logout.png) Participants are recommended to schedule a graceful logout before the start of a scheduled maintenance window. If this is not received, then the Polymarket US will initiate the logout process. **IMPORTANT: If Cancel on Disconnect is enabled, any open (unexecuted) DAY orders in the Polymarket US are automatically canceled when a FIX session terminates for any reason, including a graceful logout. GTC and GTD orders will continue to rest.** ## General Error Handling Most transactional messages in FIX are rejected using an application-level message (as described in various sections which follow). In the event that one of these messages can not be used to reject a message, however, then the Polymarket US may return either a Reject \[3] message, or a BusinessMessageReject \[j] message as described below.\ Reject \[3] messages are commonly used to reject messages where the message type is known, but there is something syntactically wrong with its content (e.g. incorrect repeating group). BusinessMessageReject \[j] is a more generic message used to reject errors such as unsupported message type or malformed messages. ### Table 10: Reject (3) message
Tag Name Req Type Description
\< Standard Header >Y35 = 3
45RefSeqNumYSeqNumThe message sequence number being rejected
371RefTagIDNintThe tag (field) number being rejected
373SessionRejectReasonNintReason for the rejection. 0=Invalid tag number, 1=Required tag missing, 2=Tag not defined for this message type, 3=Undefined tag, 4=Tag specified without value, 5=Value is incorrect, 6=Incorrect data format for value, 9=CompID problem, 10=SendingTime accuracy problem, 11=Invalid MsgType, 13=Tag appears more than once, 14=Tag specified out of required order, 15=Repeating group fields out of order, 16=Incorrect NumInGroup count, 99=Other
58TextNStringOptional string to further describe the error
\< Standard Trailer >Y

**Example 3: Reject \[3] example due to bad limit price** ``` 8=FIXT.1.1 | 9=127 | 35=3 | 34=39 | 49=TARGET | 52=20240517-19:08:47 | 56=SENDER | 45=42 | 58=Value is incorrect (out of range) for this tag | 371=44 | 372=D | 373=5 | 10=237 | ```
### Table 11: BusinessMessageReject (j) message
Tag Name Req Type Description
\< Standard Header >Y35 = j
45RefSeqNumNSeqNumThe message sequence number being rejected
58TextNStringString to further describe the error
372RefMsgTypeYStringThe MsgType being rejected
379BusinessRejectRefIDNStringWhen a FIX gateway rejects a message with a BusinessMessageReject, it provides tag 379 (BusinessRejectRefID) on the BusinessMessageReject and populates it with the ClOrdId or MDReqID to allow FIX clients to quickly determine which message was rejected.
380BusinessRejectReasonYintThe reason for the rejection. 0=Other, 1=Unknown ID, 2=Unknown Security, 3=Unsupported Message Type, 4=Application Not Available (downtime), 5=Conditionally required field missing, 6=Not authorized, 18=Invalid price increment (tick size)
\< Standard Trailer >Y

**Example 4: BusinessMessageReject \[j] example due to bad role permissions** ``` 8=FIXT.1.1 | 9=116 | 35=j | 34=5 | 49=TARGET | 52=20240516-14:19:40 | 56=SENDER | 45=6 | 58=Supervising firms cannot perform this action | 372=x | 380=6 | 10=082 | ``` # Stop Orders Source: https://docs.polymarket.us/institutional/fix-api/fix-stop-orders ## Triggering Stop Orders Stop Orders which have been accepted by the Platform are immediately acknowledged with an ExecutionReport \[8] message indicating OrdStatus (39) = 0 (New). They remain outside the central limit order book until the StopPx (99) has been observed, triggering the release of either a Limit or Market-to-Limit order into the order book. At this point, the Platform will send a second, unsolicited Execution Report \[8] with a matching OrderID (37) value, and the OrdType (40) of either 2 (Limit) or K (market-to-limit). The OrdStatus (39) of this triggered order will be 0 (New). ### Figure 6: Triggering of Stop Limit order ![](https://files.readme.io/4350afd-StopLimit.png)
**Example 9: Entry of a Stop Limit order** ``` 8=FIXT.1.1 | 9=160 | 35=D | 49=SENDER | 56=TARGET | 34=119 | 52=20240521-09:45:21 | 11=1886428727 | 21=1 | 55=GOOG | 54=1 | 60=20240521-09:45:21 | 40=4 | 44=0.03 | 38=1500 | 50=SENDERSUB | 1=ACCT | 59=0 | 99=0.03 | 10=163 | ``` **Example 10: Initial acknowledgement of Stop Limit order** ``` 8=FIXT.1.1 | 9=279 | 35=8 | 34=112 | 49=TARGET | 52=20240521-09:45:21.252049258 | 56=SENDER | 57=SENDERSUB | 1=ACCT | 6=0.00 | 11=1886428727 | 14=0 | 17=1HPT7DQ1GC4C4 | 22=8 | 31=0.00 | 32=0 | 37=1HQ4A5T0EDM1T | 38=1500 | 39=0 | 40=4 | 44=0.03 | 48=GOOG | 54=1 | 55=GOOG | 59=0 | 60=20240521-09:45:21.246689976 | 99=0.03 | 150=0 | 151=1500 | 581=3 | 582=1 | 10=079 | ``` **Example 11: ExecutionReport indicating conversion of Stop Limit into Limit order** ``` 8=FIXT.1.1 | 9=293 | 35=8 | 34=126 | 49=TARGET | 52=20240521-09:52:30.011976583 | 56=SENDER | 57=SENDERSUB | 1=ACCT | 6=0.00 | 11=1886428732 | 14=0 | 17=1HPT7DQ1GC4ET | 22=8 | 31=0.00 | 32=0 | 37=1HQ4A5T0EDM1T | 38=1500 | 39=0 | 40=2 | 41=1886428727 | 44=0.03 | 48=GOOG | 54=1 | 55=GOOG | 59=0 | 60=20240521-09:52:30.004561670 | 99=0.02 | 150=0 | 151=1500 | 581=3 | 582=1 | 10=253 | ``` ## Market-To-Limit Order Behavior Market-to-limit orders (OrderType (39) = K) are unpriced orders which are designed to execute against any existing orders in the order book upon arrival, with any remaining order balance automatically converted into a Limit order at the last trade or "worst fill" price. To illustrate this behavior, consider the below order book: ![](https://files.readme.io/c36a61b-orderbook1.png) When a new Market-to-Limit order selling 1,800 hits the order book, then the order will immediately trade 1,000 shares at 10.03, and a further 500 shares at 10.02. Since there are no further bids, the Platform will place the remaining 300 shares into the order book at a price equal to the last fill price of 10.02. ![](https://files.readme.io/1be3696-orderbook2.png) ### Figure 7: Handling of Market-to-Limit order (see example)
![](https://files.readme.io/10ea2fc-mkt_to_limit.png)
Note that unlike Stop Orders, Market-to-Limit orders retain the same OrdType (40) of K (Market-to-limit) throughout their life; the initial acknowledgement contains the limit Price (44) of the resting order.
**Example 12: Initial acknowledgment of Market-to-Limit order indicating end Limit Price (44)** ``` 8=FIXT.1.1 | 9=278 | 35=8 | 34=11 | 49=TARGET | 52=20240521-10:40:28.546598320 | 56=SENDER | 57=SENDERSUB | 1=ACCT | 6=0.00 | 11=1886428739 | 14=0 | 17=1HPT7DQ1GC4GH | 22=8 | 31=0.00 | 32=0 | 37=1HQ4A5T0EDM20 | 38=1000 | 39=0 | 40=K | 44=0.02 | 48=GOOG | 54=2 | 55=GOOG | 59=0 | 60=20240521-10:40:28.542693638 | 99=0.00 | 150=0 | 151=1000 | 581=3 | 582=1 | 10=012 | ```
# REST/gRPC vs FIX Source: https://docs.polymarket.us/institutional/fix-api/fix-vs-rest Understanding the differences between REST/gRPC and FIX APIs ## Architecture ### REST/gRPC is Internet-Native * Public API accessible over HTTPS * Authentication is Private Key JWT (RSA key signatures → access token) * Secured cryptographically (private key signatures), not by network location * No VPC, PrivateLink, or IP allowlisting required * Designed for stateless, elastic, internet-style clients ### FIX is Exchange-Native * Traffic terminates on dedicated FIX gateways via AWS PrivateLink * Access is gated by AWS account allowlisting * Authentication is FIX session identity (SenderCompID / TargetCompID / SenderSubID) * Designed for long-lived, stateful connections with known counterparties REST/gRPC relies on cryptographic identity (RSA signatures and JWTs). FIX relies on network-level trust (AWS account allowlisting via PrivateLink) + session semantics. They run on different infrastructure layers and intentionally use different trust models. *** ## Concept Mapping This table shows how the same concepts are represented in REST/gRPC vs FIX: | Concept | REST/gRPC | FIX | | ------------------ | -------------------- | ----------------- | | Firm / participant | Implicit via login | SenderCompID (49) | | Exchange | REST base URL | TargetCompID (56) | | User / trader | Logged-in user | SenderSubID (50) | | Trading account | Implicit | Account (1) | | Instrument | Symbol | Symbol (55) | | Client order ID | Client-generated ID | ClOrdID (11) | | Exchange order ID | Returned in response | OrderID (37) | | Side | buy / sell | Side (54) | | Order type | JSON field | OrdType (40) | | Quantity | JSON field | OrderQty (38) | | Price | JSON field | Price (44) | | Time in force | JSON field | TimeInForce (59) | *** ## Authentication ### REST/gRPC Authentication is cryptographic and passwordless. User and account context is implicit once authenticated. Users authenticate by: 1. Generating an RSA key pair during onboarding 2. Signing a JWT with their private key 3. Exchanging the signed JWT for an access token from Auth0 4. Using the access token in API requests ### FIX User and account context is explicit per order. FIX does not use JWTs or passwords. Authentication is based on: 1. AWS account allowlisting via PrivateLink 2. FIX session identity (SenderCompID, TargetCompID) *** ## Identity Model **REST/gRPC** infers user + account from login. **FIX** requires them to be sent on every order. | Concept | REST/gRPC | FIX | | --------------- | ------------------ | ----------------- | | Firm identity | Implicit via login | SenderCompID (49) | | User / trader | Login user | SenderSubID (50) | | Trading account | Implicit | Account (1) | | Auth scope | Session token | FIX session | In FIX, the session itself (SenderCompID / TargetCompID) uniquely identifies the participant firm/clearing member, so symbols, accounts, and trader IDs do not need globally unique, fully qualified names the way REST resources do. **REST/gRPC**: Uses globally unique, fully qualified resource names (e.g., `firms/{id}/accounts/{id}`) **FIX**: Fully qualified names are not required; participant identity is scoped by the FIX session (CompIDs), and identifiers only need to be unique within that session context *** ## Connectivity ### REST/gRPC HTTPS (REST) / HTTP streaming (gRPC). No static IP requirement. No VPC or network allowlisting. Public internet access. Single endpoint per environment. ### FIX TCP FIXT.1.1. Static IP allowlisting required. Separate sessions for: * Order Management * Drop Copy * Market Data *** ## State & Reliability | Area | REST/gRPC | FIX | | ---------- | ---------------- | ----------------------- | | Transport | Request/response | Persistent session | | Sequencing | Not required | Mandatory (MsgSeqNum) | | Recovery | Client retries | ResendRequest / GapFill | | Heartbeats | Not applicable | Required | *** ## Permissions & Validation ### REST/gRPC Permissions enforced at login. Invalid actions rejected at API layer. ### FIX Permissions enforced at: 1. Session level 2. User level (SenderSubID) 3. Account level (Account) Invalid values cause order-level rejects. *** ## Onboarding ### REST/gRPC Self-service after initial onboarding, no network infrastructure required, cryptographically secured (RSA signatures), public internet access. 1. Generate RSA key pairs for each environment 2. Submit onboarding request via Google Drive to [onboarding@polymarket.us](mailto:onboarding@polymarket.us) with public keys and signed Individual or Entity Participant Agreement 3. Receive Client ID credentials from Polymarket 4. Sign JWTs with private key to obtain access tokens 5. Fund account via wire transfer (production only) ### FIX Requires AWS account and VPC setup, coordinated provisioning with exchange, uses AWS PrivateLink for private network connectivity, suited for automated trading systems and high-throughput integrations. 1. Download and complete Individual or Entity Participant Agreement 2. If you want FIX access, indicate it and include your AWS account ID on your application 3. Receive connection details from Polymarket (VPC Service Names, FIX session identifiers, user/account IDs, connection ports and DNS endpoints) 4. Create AWS VPC Endpoint and wait for Polymarket to accept connection request 5. Configure Private DNS and test FIX sessions (Logon, Orders, Market Data, Drop Copy) *** ## Operational Differences | Area | REST/gRPC | FIX | | --------------- | ----------- | ------------------------- | | Who can onboard | Any user | Exchange + client | | Network setup | None | Required | | Identity setup | Automatic | Manual | | Failure modes | HTTP errors | Session / sequence errors | # Funding Overview Source: https://docs.polymarket.us/institutional/funding/overview Cash balance changes (deposits, withdrawals, fills, fees, adjustments) The balance ledger records every change to an account's cash balance, with both `before_balance` / `after_balance` and a typed `entry_type` describing why the balance moved. Use it for cash reconciliation, audit trails, and regulatory reporting. For real-time push of these same entries, see the [Balance Ledger Stream](/streaming-endpoints/balance-ledger-stream). ## Endpoints | Method | Endpoint | Required Scope | Description | | ------ | ------------------------------------- | ---------------- | ----------------------------------------------- | | `GET` | `/v1/funding/balance-ledger` | `read:positions` | Paginated query of balance ledger entries | | `GET` | `/v1/funding/balance-ledger/download` | `read:positions` | Streamed CSV download of balance ledger entries | Balance ledger endpoints are scoped under `read:positions` (not `read:funding`) to stay consistent with the existing balance-query endpoints (`GetAccountBalance`, `ListAccountBalances`). Calls without `read:positions` fail with `403 Forbidden` (REST) / `PERMISSION_DENIED` (gRPC). ## Historical Floor | Setting | Value | | ----------------------- | ---------------------- | | Earliest queryable date | `2026-05-01T00:00:00Z` | The ledger has a hard historical floor of **May 1, 2026 (UTC)**. Enforcement is defense-in-depth: 1. `start_time` is **clamped upstream** to the floor when the caller asks for earlier data. 2. Entries with `update_time` before the floor are **post-filtered** from JSON responses. Pre-floor entries are not retrievable through this endpoint. ## Cross-Firm Access The account in `account=firms/{firm}/accounts/{id}` must belong to the caller's firm (extracted from the JWT `firm_id` claim). | Condition | Error Code | | ----------------------------------------- | -------------------- | | Account belongs to a different firm | `PermissionDenied` | | JWT missing or `firm_id` absent | `Unauthenticated` | | ISV credentials not configured at gateway | `FailedPrecondition` | | Upstream exchange service unavailable | `Unavailable` | ## LedgerEntryType The balance ledger uses a strict **allowlist** of entry types. Internal exchange types are suppressed and never reach clients. ### Allowed Types | Wire Value | Name | Description | | ---------- | ----------------------------- | ------------------------------------- | | `1` | `DEPOSIT` | Funds deposited into the account | | `2` | `WITHDRAWAL` | Funds withdrawn from the account | | `3` | `ORDER_EXECUTION` | Cash impact of a trade execution | | `4` | `CORRECTION` | Manual correction | | `6` | `RESOLUTION` | Market resolution / settlement payout | | `7` | `MANUAL_ADJUSTMENT` | Admin adjustment | | `10` | `ACCOUNT_PROPERTY_ADJUSTMENT` | Account property change | | `11` | `COMMISSION` | Trading fee | | `16` | `WITHDRAWAL_REJECTION` | Failed withdrawal returned to balance | | `17` | `MANUAL_TRANSFER` | Internal transfer between accounts | | `22` | `PENDING_WITHDRAWAL_CREATION` | Withdrawal initiated (funds reserved) | ### Suppressed Types These Internal types are blocked at the gateway and are never returned to clients. | Wire Value | Name | Reason | | ---------- | ----------------------------- | -------- | | `5` | `NETTING` | Internal | | `8` | `SECURITY_BALANCE_ADJUSTMENT` | Internal | | `9` | `SECURITY_MARK_TO_MARKET` | Internal | | `12` | `CONTRACT_EXPIRATION` | Internal | | `13` | `PENDING_CREDIT_ADJUSTMENT` | Internal | | `14` | `BEGINNING_OF_DAY` | Internal | | `15` | `SECURITY_WITHDRAWAL` | Internal | | `18` | `AVERAGE_PRICE_TRANSFER` | Internal | | `19` | `GIVE_UP` | Internal | | `20` | `SYNCHRONIZATION` | Internal | | `21` | `INTEREST` | Internal | | `23` | `SETTLEMENT_FEE` | Internal | ### Enforcement | Surface | Behavior | | ------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Request validation** (any endpoint) | Requesting a suppressed type in `entry_types` returns `Aborted` (HTTP `409`). | | **JSON responses** (`GET /v1/funding/balance-ledger`) | Suppressed types in upstream responses are silently dropped. | | **CSV downloads** (`GET /v1/funding/balance-ledger/download`) | When `entry_types` is empty, the gateway substitutes the **full allowlist** before forwarding upstream so suppressed types do not leak in opaque CSV bytes. | ## Get Balance Ledger ```bash theme={null} GET /v1/funding/balance-ledger?account=firms/ISV-Alice/accounts/alice-trading¤cy=USD&start_time=2026-05-01T00:00:00Z&page_size=100 ``` ### Query Parameters | Parameter | Type | Required | Description | | -------------- | --------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------ | | `account` | string | Yes | Fully qualified account name. | | `currency` | string | No | ISO currency code (e.g., `USD`). Omit for all currencies on the account. | | `start_time` | RFC3339 | No | Inclusive lower bound on `update_time`. Clamped to `2026-05-01T00:00:00Z`. | | `end_time` | RFC3339 | No | Inclusive upper bound on `update_time`. | | `entry_types` | string\[] | No | Filter by one or more allowlist types. Suppressed types return `Aborted` (409). | | `symbol` | string | No | Filter by instrument symbol. | | `description` | string | No | Substring filter on the entry `description`. **Maximum 200 Unicode characters (not bytes)**; longer values return `InvalidArgument`. | | `newest_first` | boolean | No | If `true`, descending `update_time` order. Default `false`. | | `page_size` | integer | No | Maximum entries per page. **Max 1000**; values above 1000 return `InvalidArgument`. | | `page_token` | string | No | Pagination token from a previous response's `nextPageToken`. | ### Sample Response ```json theme={null} { "entries": [ { "id": "bl_01HXYZ...", "account": "firms/ISV-Alice/accounts/alice-trading", "currency": "USD", "beforeBalance": "10000.00", "afterBalance": "9474.03", "description": "Buy 100 @ 525 + commission", "updateTime": "2026-05-02T14:30:15.123Z", "modifiedSecurityId": "sec_8129", "entryType": "ORDER_EXECUTION", "symbol": "tec-nfl-sbw-2026-02-08-kc", "updateBusinessDate": "2026-05-02" }, { "id": "bl_01HXYW...", "account": "firms/ISV-Alice/accounts/alice-trading", "currency": "USD", "beforeBalance": "9474.03", "afterBalance": "9472.53", "description": "Maker fee", "updateTime": "2026-05-02T14:30:15.123Z", "entryType": "COMMISSION", "symbol": "tec-nfl-sbw-2026-02-08-kc", "updateBusinessDate": "2026-05-02" } ], "nextPageToken": "eyJvZmZzZXQiOjEwMH0=", "eof": false } ``` ### BalanceLedgerEntry Fields | Field | Type | Description | | -------------------- | ----------------- | ----------------------------------------------------- | | `id` | string | Unique entry identifier. | | `account` | string | Account this entry belongs to. | | `currency` | string | ISO currency code. | | `beforeBalance` | string | Balance immediately before this change (decimal). | | `afterBalance` | string | Balance immediately after this change (decimal). | | `description` | string | Human-readable reason for the change. | | `updateTime` | RFC3339 | Timestamp of the change. | | `modifiedSecurityId` | string | Security ID associated with the change, if any. | | `entryType` | `LedgerEntryType` | One of the allowlisted entry types. | | `symbol` | string | Instrument symbol associated with the change, if any. | | `updateBusinessDate` | string | Business date in `YYYY-MM-DD`. | ## Download Balance Ledger ```bash theme={null} GET /v1/funding/balance-ledger/download?account=firms/ISV-Alice/accounts/alice-trading¤cy=USD&start_time=2026-05-01T00:00:00Z ``` The download endpoint streams **raw CSV bytes** from the upstream ledger as a passthrough. **CSV passthrough caveat.** Because the body is opaque to the gateway, individual rows cannot be post-filtered. To prevent suppressed types from leaking in CSV bytes, the gateway substitutes the **full allowlist** for `entry_types` upstream when the caller leaves it empty. Empty result sets return a single empty chunk followed by EOF (HTTP 200 with an empty body). ## Rate Limits (per firm) | Endpoint | Rate | Burst | Effective | | ----------------------------------------- | -------------- | ----- | ------------ | | `GET /v1/funding/balance-ledger` | 0.5 req/sec | 5 | \~30 req/min | | `GET /v1/funding/balance-ledger/download` | 0.0833 req/sec | 1 | \~5 req/min | Exceeding these limits returns `ResourceExhausted` (`429 Too Many Requests`). ## Error Codes | Error | Cause | | -------------------- | ----------------------------------------------------------------------- | | `InvalidArgument` | Missing `account`, or `description` longer than 200 Unicode characters. | | `Aborted` (409) | Requested a suppressed `entry_types` value. | | `PermissionDenied` | Account belongs to a different firm. | | `Unauthenticated` | Missing JWT or `firm_id` claim. | | `FailedPrecondition` | ISV credentials not configured. | | `Unavailable` | Upstream exchange service not connected. | | `ResourceExhausted` | Per-firm rate limit exceeded. | ## See Also Real-time gRPC subscription for balance ledger entries Position changes (quantity, cost, realized P\&L) Deposit / withdrawal state changes Required scopes and OAuth flow # Health API Overview Source: https://docs.polymarket.us/institutional/health/overview Service health check endpoint ## Endpoints | Method | Endpoint | Description | | ------ | ------------ | --------------------------- | | `GET` | `/v1/health` | Check service health status | **No Authentication Required** The health check endpoint is publicly accessible and does not require authentication. This allows monitoring systems to verify service availability without credentials. ## When to Use | Use Case | Description | | ------------------------------- | ------------------------------------------------------------- | | **Service Monitoring** | Check if the API is online and responding | | **Pre-flight Checks** | Verify connectivity before starting trading operations | | **Load Balancer Health Checks** | Configure external load balancers to monitor API availability | | **Uptime Monitoring** | Set up automated alerts for service outages | ## Response Format The health check returns a simple JSON response indicating the service status: ```json theme={null} { "status": "ok" } ``` | Field | Description | | -------- | ---------------------------------- | | `status` | Service status (`ok` when healthy) | ## Example Usage ### Check Service Health ```bash theme={null} curl https://api.preprod.polymarketexchange.com/v1/health ``` ### Production Health Check ```bash theme={null} curl https://api.prod.polymarketexchange.com/v1/health ``` **Quick Links** Test the health endpoint for each environment: * [Preprod Health Check](https://api.preprod.polymarketexchange.com/v1/health) * [Production Health Check](https://api.prod.polymarketexchange.com/v1/health) ## Integration Examples ### Monitoring Script ```python theme={null} import requests import time def check_health(base_url): try: response = requests.get(f"{base_url}/v1/health", timeout=5) if response.status_code == 200: data = response.json() print(f"✓ Service is {data['status']}") return True else: print(f"✗ Unexpected status code: {response.status_code}") return False except Exception as e: print(f"✗ Health check failed: {e}") return False # Check preprod environment check_health("https://api.preprod.polymarketexchange.com") ``` ### Kubernetes Liveness Probe ```yaml theme={null} livenessProbe: httpGet: path: /v1/health port: 443 scheme: HTTPS initialDelaySeconds: 30 periodSeconds: 10 timeoutSeconds: 5 failureThreshold: 3 ``` ### Docker Health Check ```dockerfile theme={null} HEALTHCHECK --interval=30s --timeout=5s --start-period=30s --retries=3 \ CMD curl -f https://api.preprod.polymarketexchange.com/v1/health || exit 1 ``` # Incentives API Overview Source: https://docs.polymarket.us/institutional/incentives/overview View incentive programs and your earnings # Incentives API The Incentives API provides access to active incentive programs and your earned rewards. For details on how incentive programs work, see the [Incentive Programs overview](/incentives/overview). ## Endpoints | Method | Endpoint | Auth | Description | | ------ | ------------------------- | -------- | --------------------------- | | `GET` | `/v1/incentives` | None | Get incentive programs | | `GET` | `/v1/incentives/earnings` | Required | Get your incentive earnings | **Authentication Required for Earnings** The `/v1/incentives/earnings` endpoint requires API key authentication. See the [Authentication guide](/trader-guide/authentication) for details. The `/v1/incentives` endpoint is public and requires no authentication. ## Get Incentive Programs Returns incentive programs for each market. ```bash theme={null} GET /v1/incentives?pageSize=10&symbols=aec-nba-bos-nyk-2026-04-01 ``` ### Query Parameters Parameter names accept both camelCase (`pageSize`) and snake\_case (`page_size`) forms. | Parameter | Type | Required | Description | | ---------------- | --------- | -------- | ----------------------------------------------------------- | | `pageSize` | integer | No | Number of markets per page | | `pageToken` | string | No | Pagination token from a previous response's `nextPageToken` | | `symbols` | string\[] | No | Filter by market symbols | | `orderBy` | string | No | Sort field: `created_at` (default) | | `orderDirection` | string | No | Sort direction: `desc` (default) or `asc` | | `statuses` | string\[] | No | Filter by status: `active`, `closed`, `pending` | ### Response ```json theme={null} { "programs": [ { "marketSlug": "aec-nba-bos-nyk-2026-04-01", "timePeriods": [ { "programId": "nba_t1_ml_early", "programType": "liquidityProgram", "start": "2026-03-28T04:00:00Z", "end": "2026-04-01T21:00:00Z", "rewardPool": 3000.0, "status": "closed", "discountFactor": 0.40, "targetSize": 20000, "period": "early", "createdAt": "2026-03-28T01:00:00Z" }, { "programId": "nba_t1_ml_live", "programType": "liquidityProgram", "start": "2026-04-01T21:00:00Z", "rewardPool": 3000.0, "status": "active", "discountFactor": 0.35, "targetSize": 20000, "period": "live", "createdAt": "2026-03-28T01:00:00Z" } ] } ], "nextPageToken": "abc123" } ``` `end` is omitted when the program's final end time is not known yet, such as an in-progress live game. ### IncentiveProgram Fields | Field | Type | Description | | ------------- | ------------- | --------------------------------- | | `marketSlug` | string | Market identifier | | `timePeriods` | TimePeriod\[] | Incentive periods for this market | ### TimePeriod Fields | Field | Type | Description | | ---------------- | ------- | ------------------------------------------------------------------ | | `programId` | string | Unique program period identifier | | `programType` | string | Program type (e.g., `liquidityProgram`) | | `start` | string | ISO 8601 start timestamp | | `end` | string | ISO 8601 end timestamp. Omitted when the end time is not known yet | | `rewardPool` | number | Total reward pool for this period in USD | | `status` | string | `active`, `closed`, or `pending` | | `discountFactor` | number | Discount factor for scoring (optional) | | `targetSize` | integer | Minimum book size to qualify (optional) | | `period` | string | Reward period type: `early`, `day_of`, `live`, etc. | | `createdAt` | string | ISO 8601 timestamp when the program was created | ## Get Incentive Earnings Returns incentive earnings for the authenticated user. ```bash theme={null} GET /v1/incentives/earnings?startDate=2026-03-21&marketSlug=aec-nba-bos-nyk-2026-04-01 ``` ### Query Parameters Parameter names accept both camelCase (`startDate`) and snake\_case (`start_date`) forms. | Parameter | Type | Required | Description | | ------------- | ------ | -------- | ---------------------------------------------------------- | | `startDate` | string | No | Start date filter (`YYYY-MM-DD`). Defaults to `2026-03-21` | | `endDate` | string | No | End date filter (`YYYY-MM-DD`) | | `marketSlug` | string | No | Filter by market | | `programType` | string | No | Filter by program type (e.g., `liquidityProgram`) | ### Response ```json theme={null} { "rewards": [ { "reward": 1828.62, "programType": "liquidityProgram", "marketSlug": "tsc-nba-ny-okc-2026-03-29-223pt5", "date": "2026-03-30", "status": "PAID" }, { "reward": 142.50, "programType": "liquidityProgram", "marketSlug": "tsc-nba-ny-okc-2026-03-29-223pt5", "date": "2026-03-30", "status": "PENDING" }, { "reward": 325.97, "programType": "liquidityProgram", "marketSlug": "aec-cbb-cabap-kan-2026-03-20", "date": "2026-03-29", "status": "PAID" } ] } ``` Each day represents rewards earned midnight to midnight ET. A single market on a single date may return multiple rows — one per payout `status` (`PAID`, `PENDING`, `SKIPPED`). Sum across statuses (or filter to one) when aggregating per market and date. ### UserReward Fields | Field | Type | Description | | ------------- | ------ | --------------------------------------------------------------------------------------------------------------- | | `reward` | number | Reward amount in USD (sum of payouts for this market and date with this status) | | `programType` | string | Program type (e.g., `liquidityProgram`) | | `marketSlug` | string | Market identifier | | `date` | string | Reward date in Eastern Time (`YYYY-MM-DD`) | | `status` | string | Payout disposition: `PAID`, `PENDING`, or `SKIPPED`. A single `marketSlug` + `date` may appear once per status. | ## Rate Limits | Endpoint | Rate Limit | | ----------------------------- | --------------------- | | `GET /v1/incentives` | 5 requests per second | | `GET /v1/incentives/earnings` | 5 requests per second | # REST API Overview Source: https://docs.polymarket.us/institutional/introduction Complete guide to the Polymarket US REST API The Polymarket US REST API provides programmatic access to trading, account management, market data, and funding operations. This documentation is specific to Polymarket US. The international version can be found [here](https://docs.polymarket.com/). ## Streaming First Architecture **Use gRPC Streaming for Real-Time Data** For production applications requiring continuous data updates, use the [gRPC Streaming APIs](/streaming-endpoints/grpc-overview) instead of polling REST endpoints. Streaming provides: * **Real-time updates** as they happen * **No rate limiting** concerns * **Lower latency** than polling * **Reduced infrastructure load** REST APIs are subject to rate limits and are best suited for one-time queries, historical data, and administrative operations. | Use Case | Recommended API | | ----------------------------- | ------------------------------------------------------------------ | | Real-time market data | [gRPC Market Data Stream](/streaming-endpoints/market-data-stream) | | Real-time order/trade updates | [gRPC Order Stream](/streaming-endpoints/order-stream) | | Order entry/cancellation | REST Trading API | | Historical data queries | REST Report API | | Account management | REST Accounts API | | KYC and payments | REST Partner APIs | ## Base URLs | Environment | Base URL | | -------------- | -------------------------------------------- | | Pre-production | `https://api.preprod.polymarketexchange.com` | | Production | `https://api.prod.polymarketexchange.com` | All endpoints use the `/v1/` path prefix. ## Authentication All API requests require an access token in the `Authorization` header: ```bash theme={null} curl -X GET "https://api.preprod.polymarketexchange.com/v1/whoami" \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -H "x-participant-id: firms/YourFirm/users/your-user" ``` Access tokens expire every **3 minutes**. Implement automatic token refresh in your application. See the [Authentication Setup Guide](/trader-guide/authentication) for complete authentication setup. ## API Groups ### Trading APIs For direct trading operations. Used by all partners. | Endpoint Group | Description | Streaming Alternative | | ------------------ | --------------------------------------------- | ------------------------------------------------------------- | | **Trading** | Insert, cancel, and replace orders | - | | **Combos** | Create and retrieve combo instruments | - | | **RFQs** | Create, quote, accept, and confirm combo RFQs | [RFQ Events Stream](/streaming-endpoints/rfq-events-stream) | | **Report** | Search orders and trades, download history | [Order Stream](/streaming-endpoints/order-stream) | | **Positions** | Query account balances and positions | - | | **Reference Data** | List instruments, symbols, and metadata | - | | **Order Book** | Get order book depth and best bid/offer | [Market Data Stream](/streaming-endpoints/market-data-stream) | | **Drop Copy** | Execution feed and trade capture | [Order Stream](/streaming-endpoints/order-stream) | ### Combos and RFQ APIs Use the [Combos API](/institutional/combos/overview) to create and retrieve combo instruments. Use the [RFQ API](/institutional/rfqs/overview) to create RFQs, quote RFQs, delete quotes, accept quotes, and confirm accepted quotes during last look. Use the [RFQ Events Stream](/streaming-endpoints/rfq-events-stream) for real-time RFQ and quote lifecycle events instead of polling `GetRFQs` and `GetQuotes`. ### Partner APIs For partners building retail trading platforms with end-user onboarding, KYC, and payments. Complete guide for partners including Accounts, KYC, and Payments APIs **Which APIs do I need?** * **Direct trading partners**: Use the Trading APIs documented in this section * **Retail partners**: Use Trading APIs plus the [Partner Guide](/partners/overview) APIs ## Request Format ### Headers | Header | Required | Description | | ------------------ | ----------- | --------------------------------------------------------------------------- | | `Authorization` | Yes | `Bearer {access_token}` | | `Content-Type` | Yes | `application/json` | | `x-participant-id` | Conditional | Your participant ID (required for trading, positions, and report endpoints) | **When is `x-participant-id` required?** * **Required** for all account-scoped endpoints: trading, positions, reports, and account operations * **Not required** for market data, order book, reference data, and instrument state endpoints Your participant ID is given to you at onboarding, or returned as `participantId` on KYC approval for an end user you onboard — send it exactly as provided rather than assembling it from another response. `GET /v1/users` lists your firm's users once you already hold a valid participant ID, but is account-scoped itself and so can't be used to find your first one. See [Finding Your Participant ID](/trader-guide/accounts-identity#finding-your-participant-id) for details. ### Request Body POST requests accept JSON bodies: ```json theme={null} { "symbol": "tec-nfl-sbw-2026-02-08-kc", "side": "SIDE_BUY", "order_qty": 100, "price": 550, "type": "ORDER_TYPE_LIMIT", "time_in_force": "TIME_IN_FORCE_GOOD_TILL_CANCEL" } ``` ## Response Format ### Success Response ```json theme={null} { "order": { "id": "ord_abc123", "symbol": "tec-nfl-sbw-2026-02-08-kc", "state": "ORDER_STATE_NEW", "order_qty": 100, "leaves_qty": 100, "cum_qty": 0 } } ``` ### Error Response ```json theme={null} { "code": 3, "message": "invalid order quantity", "details": [] } ``` | HTTP Status | Meaning | | ----------- | --------------------------------------- | | `200` | Success | | `400` | Bad Request - Invalid parameters | | `401` | Unauthorized - Invalid or expired token | | `403` | Forbidden - Insufficient permissions | | `404` | Not Found - Resource doesn't exist | | `429` | Too Many Requests - Rate limited | | `500` | Internal Server Error | ## Price Representation All prices are represented as `int64` values. Divide by the instrument's `price_scale` to get the decimal price: ```python theme={null} # Get price_scale from instrument metadata instrument = get_instrument("tec-nfl-sbw-2026-02-08-kc") price_scale = instrument.price_scale # e.g., 1000 # Convert API price to decimal decimal_price = api_price / price_scale # 550 / 1000 = 0.55 ($0.55) ``` Query price\_scale from the Reference Data API and cache it for each instrument. ## Rate Limits Trading endpoints are rate-limited at **100 requests per second per firm**, averaged over a 1-minute window (short bursts above this rate are allowed). Query endpoints have lower per-endpoint limits. Public (unauthenticated) endpoints are limited to **20 requests per second per IP**. See [Rate Limits](/trader-guide/rate-limits) for the full breakdown by endpoint and protocol. ## Support For REST API questions or issues, contact [onboarding@qcex.com](mailto:onboarding@qcex.com). ## Next Steps Place your first order in 5 minutes Set up Private Key JWT authentication Real-time market data and order updates API endpoints for dev, preprod, and prod # Order Book API Overview Source: https://docs.polymarket.us/institutional/orderbook/overview Access L2 order book snapshots and best bid/offer (BBO) data **Prefer Streaming for Production Use** This polling API is subject to rate limits. For production applications that need continuous market data, use the [gRPC Market Data Stream](/streaming-endpoints/market-data-stream) instead. The streaming API provides real-time updates with lower latency and no rate limit concerns. ## Endpoints | Method | Endpoint | Description | | ------ | ---------------------------- | -------------------------- | | `GET` | `/v1/orderbook/{symbol}` | Get L2 order book snapshot | | `GET` | `/v1/orderbook/{symbol}/bbo` | Get best bid/offer | **No Participant ID Required** These endpoints only require Auth0 JWT authentication with `read:marketdata` scope. You do not need to provide the `x-participant-id` header or complete KYC onboarding to access order book data. ## L2 Order Book The L2 (Level 2) order book provides aggregated price levels showing the total quantity available at each price point. This is useful for: * Understanding market depth at different price levels * Analyzing liquidity distribution * Building trading strategies based on order book imbalance ### Request Parameters | Parameter | Type | Required | Description | | --------- | ------- | -------- | ------------------------------------------------------ | | `symbol` | string | Yes | Instrument symbol (e.g., "tec-nfl-sbw-2026-02-08-kc") | | `depth` | integer | No | Number of price levels to return (default: 3, max: 10) | ### Response Fields | Field | Type | Description | | -------------- | -------- | -------------------------------------------------------- | | `symbol` | string | Instrument symbol | | `bids` | array | Bid side of order book (sorted by price descending) | | `offers` | array | Offer/ask side of order book (sorted by price ascending) | | `state` | string | Current trading state of the instrument (optional) | | `stats` | object | Market statistics (last trade, OHLC, etc.) | | `transactTime` | datetime | Server timestamp of the data | ### Book Entry Structure Each entry in the `bids` and `offers` arrays contains: | Field | Type | Description | | ----- | ------ | ---------------------------- | | `px` | string | Price level | | `qty` | string | Total quantity at this price | ## Best Bid/Offer (BBO) The BBO endpoint returns only the top of book - the best (highest) bid and best (lowest) offer. This is the most efficient way to get current market prices. ### Response Fields | Field | Type | Description | | -------------- | -------- | ----------------------------------------------------- | | `symbol` | string | Instrument symbol | | `bestBid` | object | Best bid (highest buy price) | | `bestOffer` | object | Best offer (lowest sell price) | | `spread` | string | Spread in price units (best\_offer.px - best\_bid.px) | | `midPrice` | string | Mid price ((best\_bid.px + best\_offer.px) / 2) | | `state` | string | Current trading state (optional) | | `transactTime` | datetime | Server timestamp | **Instrument State Tracking:** The `state` field in order book and BBO responses is optional. The preferred approach is to use `ListInstruments` to get and cache the initial state, then subscribe to the instrument state change subscription for real-time state updates. ## Instrument States Instruments follow the primary lifecycle: PENDING → OPEN → CLOSED → EXPIRED → TERMINATED. Instruments may also be SUSPENDED or HALTED during their lifecycle. ### Primary State Flow | State                                               | Description | | --------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `PENDING` | Initial state for a newly created instrument which has not yet begun trading. | | `OPEN` | In this state, the instrument is open for continuous order entry and matching. | | `CLOSED` | In this state, orders can not be entered, modified, or canceled, and no matching occurs. Any existing Day orders will be expired. | | `EXPIRED` | An instrument moves to this state when its Expiration Date/Time is reached. In this state, any resting orders are expired and no new orders can be entered. | | `TERMINATED` | When an instrument's Termination Date is reached, the order book is removed from the matching engine, orders are canceled, and positions are closed. Historical data will still remain in Polymarket US ledgers. | ### Exception States | State                                               | Description | | --------------------------------------------------- | --------------------------------------------------------------------------------------------- | | `SUSPENDED` | Orders can be canceled but no matching occurs, and no order entry or modification is allowed. | | `HALTED` | This state is similar to SUSPENDED, with the exception that orders cannot be canceled. | ### Other Possible States | State                                               | Description | | --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `PREOPEN` | Orders can be entered and modified, but no matching occurs. When the instrument transitions to an OPEN state, the orders entered during PREOPEN will match at a single opening price that is automatically determined by an algorithm that is designed to maximize the volume traded at the open. | | `MATCH_AND_CLOSE_AUCTION` | This state is similar to PREOPEN, with the exception that matching will occur upon the transition of this state to any other state. This state is useful if you want matching to occur at the end of the state, but you don't want the instrument to be open after. | ## Example Usage ### Get L2 Order Book ```bash theme={null} curl -X GET "https://api.preprod.polymarketexchange.com/v1/orderbook/tec-nfl-sbw-2026-02-08-kc?depth=5" \ -H "Authorization: Bearer YOUR_TOKEN" ``` ### Get BBO ```bash theme={null} curl -X GET "https://api.preprod.polymarketexchange.com/v1/orderbook/tec-nfl-sbw-2026-02-08-kc/bbo" \ -H "Authorization: Bearer YOUR_TOKEN" ``` ## When to Use | Use Case | Recommended API | | ----------------------------- | ------------------------------------------------------------------ | | Continuous market data feed | [gRPC Market Data Stream](/streaming-endpoints/market-data-stream) | | One-time snapshot for display | REST Order Book (this API) | | Building a trading UI | [gRPC Market Data Stream](/streaming-endpoints/market-data-stream) | | Quick price check | REST BBO endpoint | **Streaming First Architecture** For any use case requiring more than occasional snapshots, use the streaming API. It provides: * Real-time updates as they happen * No rate limiting concerns * Lower latency than polling * Reduced API calls and infrastructure load # Aeropay Integration Source: https://docs.polymarket.us/institutional/payments/aeropay-guide Bank account linking and ACH transfers **Work in Progress** — This page is currently being updated and is not yet available. Please check back soon. # Checkout Integration Source: https://docs.polymarket.us/institutional/payments/checkout-guide Card payment processing via Checkout.com **Work in Progress** — This page is currently being updated and is not yet available. Please check back soon. # Funding Management Source: https://docs.polymarket.us/institutional/payments/funding-management Manage funding sources, accounts, and transactions **BETA - SUBJECT TO CHANGE** - This API is in beta and may change without notice. # Funding Management The Funding API provides read access to funding sources, accounts, and transaction history. ## Authentication **Authentication Required** - All Funding endpoints require authentication. Include your access token in the `Authorization` header. ```bash theme={null} Authorization: Bearer YOUR_ACCESS_TOKEN ``` See [Authentication Setup](/trader-guide/authentication) for instructions on obtaining tokens. ## Endpoints | Method | Endpoint | Description | | ------- | -------------------------------------- | ---------------------------- | | `GET` | `/v1/funding/sources` | List funding sources | | `GET` | `/v1/funding/accounts` | List funding accounts | | `PATCH` | `/v1/funding/accounts/{id}` | Update funding account | | `GET` | `/v1/funding/transactions` | List transactions | | `GET` | `/v1/funding/transaction-requirements` | Get transaction requirements | ## List Funding Sources Retrieve all funding sources (payment methods) available to the user. ### Request ```bash theme={null} GET /v1/funding/sources?fundingSourceType=FUNDING_SOURCE_TYPE_AEROPAY_BANK_ACCOUNT ``` ### Query Parameters | Parameter | Type | Description | | ------------------- | ------- | -------------------- | | `pageSize` | integer | Results per page | | `pageToken` | string | Pagination token | | `fundingSourceIds` | array | Filter by source IDs | | `fundingSourceType` | enum | Filter by type | ### Funding Source Types | Type | Description | | ------------------------------------------ | ------------------------ | | `FUNDING_SOURCE_TYPE_BANK_ACCOUNT` | Traditional bank account | | `FUNDING_SOURCE_TYPE_AEROPAY_BANK_ACCOUNT` | Aeropay-linked bank | | `FUNDING_SOURCE_TYPE_CHECKOUT_CARD` | Payment card | | `FUNDING_SOURCE_TYPE_APPLE_PAY` | Apple Pay | ### Response ```json theme={null} { "fundingSources": [ { "id": "fs_abc123", "type": "FUNDING_SOURCE_TYPE_AEROPAY_BANK_ACCOUNT", "aeropayBankAccountDetails": { "userId": "aero_user_123", "bankAccountId": "ba_123", "name": "John's Checking", "bankName": "Chase Bank", "accountLast4": "4567", "accountType": "checking" }, "transactionLimits": { "currencyLimits": [ { "currency": "USD", "maxDepositRequestAmount": {"value": "10000.00"}, "maxDepositDailyAmount": {"value": "25000.00"}, "maxDeposit60dayAmount": {"value": "100000.00"} } ] }, "disabled": false } ], "nextPageToken": "", "eof": true } ``` ## List Funding Accounts Retrieve funding accounts associated with the user. ### Request ```bash theme={null} GET /v1/funding/accounts?accountType=ACCOUNT_TYPE_CLEARING ``` ### Query Parameters | Parameter | Type | Description | | ------------------ | ------- | ---------------------------- | | `pageSize` | integer | Results per page | | `pageToken` | string | Pagination token | | `accountIds` | array | Filter by account IDs | | `fundingSourceIds` | array | Filter by funding source IDs | | `accountType` | enum | Filter by account type | ### Account Types | Type | Description | | ----------------------- | -------------------- | | `ACCOUNT_TYPE_CLEARING` | Main trading account | | `ACCOUNT_TYPE_REVENUE` | Revenue account | | `ACCOUNT_TYPE_HOLDING` | Holding account | | `ACCOUNT_TYPE_ADVANCE` | Advance account | ### Response ```json theme={null} { "accounts": [ { "id": "fa_123", "name": "Main Trading Account", "accountType": "ACCOUNT_TYPE_CLEARING", "associatedFundingSources": ["fs_abc123", "fs_def456"], "riskAccountId": "risk_123", "creationTime": "2024-01-15T10:00:00Z" } ], "nextPageToken": "", "eof": true } ``` ## List Transactions Retrieve transaction history with filtering options. ### Request ```bash theme={null} GET /v1/funding/transactions?accountId=fa_123&transactionTypes=TRANSACTION_TYPE_DEPOSIT&newestFirst=true ``` ### Query Parameters | Parameter | Type | Description | | ------------------- | -------- | -------------------------- | | `pageSize` | integer | Results per page | | `pageToken` | string | Pagination token | | `accountId` | string | Filter by account | | `currency` | string | Filter by currency | | `transactionTypes` | array | Filter by transaction type | | `transactionStates` | array | Filter by state | | `startTime` | datetime | Start of date range | | `endTime` | datetime | End of date range | | `newestFirst` | boolean | Sort order | ### Transaction Types | Type | Description | | ------------------------------------ | ---------------------- | | `TRANSACTION_TYPE_DEPOSIT` | Deposit transaction | | `TRANSACTION_TYPE_WITHDRAWAL` | Withdrawal transaction | | `TRANSACTION_TYPE_TRANSFER` | Internal transfer | | `TRANSACTION_TYPE_EXECUTION_FEE` | Trading fee | | `TRANSACTION_TYPE_SETTLEMENT_FEE` | Settlement fee | | `TRANSACTION_TYPE_MANUAL_ADJUSTMENT` | Manual adjustment | ### Transaction States | State | Description | | -------------------------------- | ----------- | | `TRANSACTION_STATE_ACKNOWLEDGED` | Received | | `TRANSACTION_STATE_PROCESSING` | In progress | | `TRANSACTION_STATE_COMPLETED` | Completed | | `TRANSACTION_STATE_CANCELLED` | Cancelled | | `TRANSACTION_STATE_REFUNDED` | Refunded | ### Response ```json theme={null} { "transactions": [ { "transactionId": "txn_123", "fundingTransactionId": "ft_deposit_789", "amount": "100.00", "currency": "USD", "description": "Account funding", "ledgerEntryType": "LEDGER_ENTRY_TYPE_CREDIT", "transactionType": "TRANSACTION_TYPE_DEPOSIT", "transactionState": "TRANSACTION_STATE_COMPLETED", "transactTime": "2024-01-15T10:30:00Z", "accountId": "fa_123", "fundingSourceId": "fs_abc123", "fundingSourceType": "FUNDING_SOURCE_TYPE_AEROPAY_BANK_ACCOUNT", "beforeBalance": "0.00", "afterBalance": "100.00" } ], "nextPageToken": "", "eof": true } ``` ## Get Transaction Requirements Check requirements and limits before initiating transactions. This is especially important for **withdrawal requirements**. ### Request ```bash theme={null} GET /v1/funding/transaction-requirements?accountIds=fa_123 ``` ### Query Parameters | Parameter | Type | Description | | ------------ | ------- | --------------------- | | `pageSize` | integer | Results per page | | `pageToken` | string | Pagination token | | `accountIds` | array | Filter by account IDs | ### Response ```json theme={null} { "transactionRequirements": [ { "id": "req_123", "accountId": "fa_123", "transactionType": "TRANSACTION_TYPE_WITHDRAWAL", "fundingSourceId": "fs_bank_b", "amount": "500.00", "currency": "USD", "description": "Must withdraw to original deposit source", "triggers": ["withdrawal_requirement"] }, { "id": "req_124", "accountId": "fa_123", "transactionType": "TRANSACTION_TYPE_WITHDRAWAL", "fundingSourceId": "fs_bank_a", "amount": "1000.00", "currency": "USD", "description": "Must withdraw to original deposit source", "triggers": ["withdrawal_requirement"] } ], "nextPageToken": "", "eof": true } ``` *** ## Update Funding Account Update account settings (limited fields). ### Request ```bash theme={null} PATCH /v1/funding/accounts/fa_123 ``` ```json theme={null} { "account": { "name": "Updated Account Name", "aliases": { "display_name": "Trading Account" } } } ``` ### Response ```json theme={null} { "account": { "id": "fa_123", "name": "Updated Account Name", ... } } ``` ## Pagination All list endpoints support cursor-based pagination: 1. Make initial request without `pageToken` 2. Check `eof` field - if `false`, more results exist 3. Use `nextPageToken` for subsequent requests 4. Continue until `eof` is `true` ```python theme={null} page_token = None while True: params = {"pageSize": 100} if page_token: params["pageToken"] = page_token response = get_transactions(params) process(response["transactions"]) if response["eof"]: break page_token = response["nextPageToken"] ``` # Payments Overview Source: https://docs.polymarket.us/institutional/payments/overview Funding options for deposits and withdrawals **BETA - SUBJECT TO CHANGE** - This API is in beta and may change without notice. # Payments API The Payments API provides multiple funding options for deposits and withdrawals. Users can fund their accounts via bank transfers (ACH) through Aeropay or card payments through Checkout.com. **Authentication Required** - All payment endpoints require a valid access token. See [Authentication Setup](/trader-guide/authentication) for setup instructions. ## Payment Methods | Method | Provider | Type | Speed | | ------------------- | ------------ | -------------------- | ----------------- | | Bank Transfer (ACH) | Aeropay | Bank account linking | 1-3 business days | | Debit/Credit Card | Checkout.com | Card payments | Instant | | Apple Pay | Checkout.com | Mobile wallet | Instant | ## APIs ### Aeropay API Bank account linking and ACH transfers. **Orchestrated Endpoints (Recommended):** | Method | Endpoint | Description | | ------ | -------------------------- | ---------------------------------------------------------------- | | `POST` | `/v1/aeropay/initialize` | Start bank linking - handles user creation and MFA automatically | | `POST` | `/v1/aeropay/validate-mfa` | Submit MFA code if required | | `GET` | `/v1/aeropay/methods` | List payment methods with limits | | `POST` | `/v1/aeropay/deposits` | Create ACH deposit | | `POST` | `/v1/aeropay/withdrawals` | Create ACH withdrawal | [Aeropay Integration Guide](/api-reference/payments/aeropay-guide) ### Checkout API Card payment processing: | Method | Endpoint | Description | | ------ | ------------------------------- | --------------------------------------------- | | `POST` | `/v1/checkout/payment-sessions` | Request payment session for card tokenization | | `POST` | `/v1/checkout/instruments` | Save tokenized card as instrument | | `POST` | `/v1/checkout/deposits` | Process card or Apple Pay deposit | | `POST` | `/v1/checkout/withdrawals` | Process card or Apple Pay withdrawal | [Checkout Integration Guide](/api-reference/payments/checkout-guide) ### Funding API Manage funding sources and transactions: | Method | Endpoint | Description | | ------- | -------------------------------------- | ----------------------------- | | `GET` | `/v1/funding/sources` | List funding sources | | `GET` | `/v1/funding/accounts` | List funding accounts | | `PATCH` | `/v1/funding/accounts/{id}` | Update funding account | | `GET` | `/v1/funding/transactions` | View transaction history | | `GET` | `/v1/funding/transaction-requirements` | Check withdrawal requirements | [Funding Management](/api-reference/payments/funding-management) ## Typical Integration Flow ### New User Onboarding ```mermaid theme={null} graph TD A[User Completes KYC] --> B{Choose Payment Method} B -->|Bank Account| C[Aeropay Flow] B -->|Card| D[Checkout Flow] C --> E[POST /v1/aeropay/initialize] E --> F{Response Type} F -->|mfa_required| G[POST /v1/aeropay/validate-mfa] F -->|sdk_credentials| H[Link Bank via SDK] F -->|accounts_linked| I[Ready for Deposits] G --> H H --> I D --> J[POST /v1/checkout/payment-sessions] J --> K[Tokenize Card] K --> L[POST /v1/checkout/instruments] L --> M[Ready for Deposits] I --> N[Create Deposit] M --> N N --> O[Funds Available] ``` ### Deposit Flow 1. **Select funding source** - User chooses linked bank/card 2. **Check limits** - Validate amount against transaction limits 3. **Enter amount** - Collect deposit amount 4. **Confirm** - Process the deposit 5. **Track status** - Monitor transaction completion ### Withdrawal Flow 1. **Check requirements** - Get withdrawal requirements from funding source 2. **Select destination** - User chooses where to send funds 3. **Enter amount** - Validate against available balance and requirements 4. **Confirm** - Process the withdrawal 5. **Track status** - Monitor transaction completion ## Funding Source Types The `FundingSourceType` enum identifies the payment method: | Type | Description | | ------------------------------------------ | --------------------------- | | `FUNDING_SOURCE_TYPE_BANK_ACCOUNT` | Traditional bank account | | `FUNDING_SOURCE_TYPE_AEROPAY_BANK_ACCOUNT` | Aeropay-linked bank account | | `FUNDING_SOURCE_TYPE_CHECKOUT_CARD` | Tokenized payment card | | `FUNDING_SOURCE_TYPE_APPLE_PAY` | Apple Pay | ## Transaction States | State | Description | | -------------------------------------- | ---------------------- | | `TRANSACTION_STATE_ACKNOWLEDGED` | Transaction received | | `TRANSACTION_STATE_PROCESSING` | Being processed | | `TRANSACTION_STATE_COMPLETED` | Successfully completed | | `TRANSACTION_STATE_CANCELLED` | Cancelled | | `TRANSACTION_STATE_ALLOCATED` | Funds allocated | | `TRANSACTION_STATE_REFUNDED` | Fully refunded | | `TRANSACTION_STATE_PARTIALLY_REFUNDED` | Partially refunded | ## Transaction Types | Type | Description | | --------------------------------- | ----------------- | | `TRANSACTION_TYPE_DEPOSIT` | Funds deposited | | `TRANSACTION_TYPE_WITHDRAWAL` | Funds withdrawn | | `TRANSACTION_TYPE_TRANSFER` | Internal transfer | | `TRANSACTION_TYPE_EXECUTION_FEE` | Trading fee | | `TRANSACTION_TYPE_SETTLEMENT_FEE` | Settlement fee | ## Best Practices 1. **Use orchestrated endpoints** - For Aeropay, use `/v1/aeropay/initialize` instead of primitive endpoints 2. **Check limits before transactions** - Use the Aeropay methods or funding transaction requirements APIs 3. **Check withdrawal requirements** - Always check withdrawal requirements before processing 4. **Handle all states** - Implement UI for pending, success, and failure 5. **Implement webhooks** - Don't rely solely on polling for status updates 6. **Validate amounts client-side** - Reduce failed API calls 7. **Store payment method IDs** - Cache linked payment methods for faster checkout # Positions API Overview Source: https://docs.polymarket.us/institutional/positions/overview Query account balances and positions ## Endpoints | Method | Endpoint | Description | | ------ | ------------------------------- | --------------------------------------------------------------------------- | | `GET` | `/v1/positions` | Get current positions | | `POST` | `/v1/positions/balance` | Get single account balance | | `POST` | `/v1/positions/balances` | Get multiple account balances | | `GET` | `/v1/positions/ledger` | Query historical position changes. See [Position Ledger](#position-ledger). | | `GET` | `/v1/positions/ledger/download` | Download position ledger as CSV. See [Position Ledger](#position-ledger). | ## Position Data Each position includes: | Field | Description | | ------------- | ------------------------------------- | | `symbol` | Trading instrument | | `account` | Trading account | | `netPosition` | Current net position quantity | | `qtyBought` | Total quantity bought | | `qtySold` | Total quantity sold | | `cost` | Total cost basis (scaled integer) | | `realized` | Realized profit/loss (scaled integer) | | `bodPosition` | Beginning-of-day position | | `updateTime` | Last update timestamp | ## Balance Data Account balances include: | Field | Description | | -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `balance` | Current cash balance | | `buyingPower` | Available buying power for trading | | `capitalRequirement` | Required capital to maintain positions | | `excessCapital` | Capital above requirements | | `marginRequirement` | Margin required for open positions | | `unsettledFunds` | Funds pending settlement | | `openOrders` | Worst-case value of open orders (see [Open Orders and Order Collateralization](/market-structure/collateral-and-margin#open-orders-and-order-collateralization) for how open orders are risk-checked) | | `updateTime` | Last update timestamp | ## Historical Position Queries Query positions as they existed at a specific point in time. Useful for regulatory reporting, reconciliation, or end-of-day snapshots. ### Parameters | Parameter | Type | Description | | ------------ | ----------------- | -------------------------------------------------- | | `as_of_time` | RFC3339 timestamp | Exact point-in-time (e.g., `2026-01-02T17:00:00Z`) | | `as_of_date` | Date object | End-of-trading-day snapshot (year, month, day) | **Mutually Exclusive**: Use `as_of_time` OR `as_of_date`, not both. The `as_of_time` parameter already contains date information. ### Examples **Query by timestamp (exact point in time):** ```bash theme={null} GET /v1/positions?name=firms/ISV-Alice/accounts/trading&as_of_time=2026-01-02T17:00:00Z ``` **Query by trade date (end of trading day):** ```bash theme={null} GET /v1/positions?name=firms/ISV-Alice/accounts/trading&as_of_date.year=2026&as_of_date.month=1&as_of_date.day=2 ``` ### Use Cases * **End-of-Day Reports**: Query positions at market close for daily P\&L calculations * **Regulatory Snapshots**: Capture position state at specific regulatory timestamps * **Reconciliation**: Compare historical positions against external records * **Audit Trail**: Review position history for compliance or dispute resolution ## Usage Notes **Position Updates After Trades** Position data is updated after each trade execution. For real-time trade notifications that affect positions, use the [gRPC Order Stream](/streaming-endpoints/order-stream). * Positions are aggregated by symbol and account * Balances reflect current available and reserved amounts * Use the Order Stream for real-time execution updates that affect positions * Historical queries return the position state as of the specified time *** ## Position Ledger The position ledger records every change to a position as a single entry, with both the **delta** (`quantityChange`, `costChange`, `realizedChange`) and the **cumulative state immediately after the change** (`netPosition`, `cost`, `realized`). Use it for reconciliation, point-in-time replay, and end-of-day reporting. ### Ledger Endpoints | Method | Endpoint | gRPC | Required Scope | Description | | ------ | ------------------------------- | ------------------------------------ | ---------------- | ------------------------------------------------ | | `GET` | `/v1/positions/ledger` | `PositionAPI.GetPositionLedger` | `read:positions` | Paginated query of position ledger entries | | `GET` | `/v1/positions/ledger/download` | `PositionAPI.DownloadPositionLedger` | `read:positions` | Streamed CSV download of position ledger entries | Both endpoints are scoped under `read:positions` (the same scope used for `GetAccountBalance`, `ListAccountBalances`, and the position queries on this section). Calls without `read:positions` fail with `403 Forbidden` (REST) / `PERMISSION_DENIED` (gRPC). ### Historical Floor | Setting | Value | | ----------------------- | ---------------------- | | Earliest queryable date | `2026-05-01T00:00:00Z` | The ledger has a hard historical floor of **May 1, 2026 (UTC)**. Enforcement is defense-in-depth: 1. `start_time` is **clamped upstream** to the floor when the caller asks for earlier data. 2. Entries with `update_time` before the floor are **post-filtered** from responses. Pre-floor entries are not retrievable through this endpoint. ### Delta Computation Each ledger entry is computed from the exchange's before/after position snapshots: ``` quantityChange = after.netPosition - before.netPosition costChange = after.cost - before.cost realizedChange = after.realized - before.realized ``` The cumulative fields (`netPosition`, `cost`, `realized`) on each entry are the state **immediately after** the change. To reconstruct point-in-time position state, replay entries in `update_time` order. ### Cross-Firm Access The account in `account=firms/{firm}/accounts/{id}` must belong to the caller's firm (extracted from the JWT `firm_id` claim). Cross-firm access is blocked at the gateway: | Condition | Error Code | | ----------------------------------------- | -------------------- | | Account belongs to a different firm | `PermissionDenied` | | JWT missing or `firm_id` absent | `Unauthenticated` | | ISV credentials not configured at gateway | `FailedPrecondition` | | Upstream exchange service unavailable | `Unavailable` | ### Get Position Ledger ```bash theme={null} GET /v1/positions/ledger?account=firms/ISV-Alice/accounts/alice-trading&start_time=2026-05-01T00:00:00Z&page_size=100 ``` #### Query Parameters | Parameter | Type | Required | Description | | -------------- | ------- | -------- | ----------------------------------------------------------------------------------- | | `account` | string | Yes | Fully qualified account name. Example: `firms/ISV-Alice/accounts/alice-trading`. | | `symbol` | string | No | Filter by instrument symbol. | | `start_time` | RFC3339 | No | Inclusive lower bound on `update_time`. Clamped to `2026-05-01T00:00:00Z`. | | `end_time` | RFC3339 | No | Inclusive upper bound on `update_time`. | | `page_size` | integer | No | Maximum entries per page. **Max 1000**; values above 1000 return `InvalidArgument`. | | `page_token` | string | No | Pagination token from a previous response's `nextPageToken`. | | `newest_first` | boolean | No | If `true`, descending `update_time` order. Default `false`. | #### Sample Response ```json theme={null} { "entries": [ { "id": "pl_01HXYZ...", "account": "firms/ISV-Alice/accounts/alice-trading", "symbol": "tec-nfl-sbw-2026-02-08-kc", "quantityChange": "50", "costChange": "26000", "realizedChange": "0", "netPosition": "150", "cost": "78000", "realized": "0", "updateTime": "2026-05-02T14:30:15.123Z", "updateBusinessDate": "2026-05-02", "description": "Trade fill" } ], "nextPageToken": "eyJvZmZzZXQiOjEwMH0=", "eof": false } ``` #### PositionLedgerEntry Fields | Field | Type | Description | | -------------------- | ------- | ------------------------------------------------------------------------ | | `id` | string | Unique entry identifier. | | `account` | string | Account this entry belongs to. | | `symbol` | string | Instrument symbol. | | `quantityChange` | int64 | Delta from previous position (`after.netPosition - before.netPosition`). | | `costChange` | int64 | Delta in cost basis. | | `realizedChange` | int64 | Delta in realized P\&L. | | `netPosition` | int64 | Net position **after** this change. | | `cost` | int64 | Cost basis **after** this change. | | `realized` | int64 | Cumulative realized P\&L **after** this change. | | `updateTime` | RFC3339 | Timestamp of the position change. | | `updateBusinessDate` | string | Business date in `YYYY-MM-DD`. | | `description` | string | Human-readable reason (e.g., trade fill, expiry, correction). | **int64 fields are serialized as strings in JSON** (e.g., `"50"` not `50`) per the protobuf JSON mapping spec. Parse them as strings to avoid precision loss in languages with 53-bit integer limits. #### Response Wrapper Fields | Field | Type | Description | | --------------- | ------- | ------------------------------------------------------------------------------ | | `entries` | array | Position ledger entries for the requested account / time window. | | `nextPageToken` | string | Pagination token to fetch the next page. Empty when there are no more results. | | `eof` | boolean | `true` when this response contains the final page of results. | ### Download Position Ledger ```bash theme={null} GET /v1/positions/ledger/download?account=firms/ISV-Alice/accounts/alice-trading&start_time=2026-05-01T00:00:00Z ``` The download endpoint accepts the **same query parameters** as `GET /v1/positions/ledger` (`account`, `symbol`, `start_time`, `end_time`, `page_size`, `page_token`, `newest_first`). It streams **raw CSV bytes from the upstream ledger** as a passthrough; the gateway does not parse individual rows. **CSV passthrough caveat.** Because the body is opaque to the gateway, ledger entries cannot be post-filtered row-by-row. The historical floor is still enforced upstream via `start_time` clamping, but no per-row filtering is performed on the CSV stream. Empty result sets return a single empty chunk followed by EOF (HTTP 200 with an empty body). The gateway translates an upstream "stream returned a nil response" condition into an empty result. ### Ledger Rate Limits (per firm) | Endpoint | Rate | Burst | Effective | | ----------------------------------- | -------------- | ----- | ------------ | | `GET /v1/positions/ledger` | 0.5 req/sec | 5 | \~30 req/min | | `GET /v1/positions/ledger/download` | 0.0833 req/sec | 1 | \~5 req/min | Exceeding these limits returns `ResourceExhausted` (`429 Too Many Requests`). ### Ledger Error Codes | Error | Cause | | -------------------- | ---------------------------------------- | | `InvalidArgument` | Missing `account`. | | `PermissionDenied` | Account belongs to a different firm. | | `Unauthenticated` | Missing JWT or `firm_id` claim. | | `FailedPrecondition` | ISV credentials not configured. | | `Unavailable` | Upstream exchange service not connected. | | `ResourceExhausted` | Per-firm rate limit exceeded. | ## See Also Cash balance changes (deposits, withdrawals, fills, fees) Real-time balance ledger via gRPC Real-time execution updates that affect positions Required scopes and OAuth flow # Reference Data API Overview Source: https://docs.polymarket.us/institutional/refdata/overview Instruments, symbols, and market metadata ## Endpoints ### Instruments & Symbols | Method | Endpoint | Description | | ------ | ------------------------- | -------------------------------------------- | | `POST` | `/v1/refdata/symbols` | List all symbols | | `POST` | `/v1/refdata/instruments` | List instruments (filter by symbols in body) | | `POST` | `/v1/refdata/metadata` | Get instrument metadata | ### Sports Data | Method | Endpoint | Description | | ------ | ----------------------------------- | ---------------------------------- | | `GET` | `/v1/refdata/sports` | List all sports with metadata | | `GET` | `/v1/refdata/sports/teams` | List teams with optional filtering | | `GET` | `/v1/refdata/sports/teams/provider` | Get teams by provider-specific IDs | See [Sports Reference Data](/api-reference/refdata/sports) for detailed documentation. **No Participant ID Required** These endpoints only require Auth0 JWT authentication with `read:instruments` scope. You do not need to provide the `x-participant-id` header or complete KYC onboarding to access reference data. **No GET endpoint for single instrument** To get a single instrument, use `POST /v1/refdata/instruments` with `{"symbols": ["SYMBOL-NAME"]}` in the body. *** ## Filtering Instruments The `/v1/refdata/instruments` endpoint supports configurable query parameters to filter and paginate results. ### Request Parameters | Parameter | Type | Description | | ---------------- | --------- | ------------------------------------------------------------------------------------ | | `pageSize` | int32 | Results per page (default: 50, max: 1000) | | `pageToken` | string | Pagination cursor from previous response | | `symbols` | string\[] | Filter by exact instrument symbols | | `productId` | string | Filter by exact product ID | | `tradableFilter` | enum | `TRADABLE_FILTER_TRADABLE`, `TRADABLE_FILTER_NON_TRADABLE`, or `TRADABLE_FILTER_ALL` | | `states` | enum\[] | Filter by instrument states (multiple values = OR logic) | | `eventSeries` | string | Filter by event series (e.g., `cbb`, `nfl`, `nba`) | | `eventCategory` | string | Filter by event category (e.g., `SPR`, `POL`, `CUL`) | | `clearingSym` | string | Filter by clearing symbol (e.g., `AEC-NFL`, `AEC-BASKETBALL`) | | `startTimeGte` | string | Instruments starting on or after date (format: `YYYY-MM-DD`) | | `startTimeLte` | string | Instruments starting on or before date | | `endTimeGte` | string | Instruments expiring on or after date | | `endTimeLte` | string | Instruments expiring on or before date | All parameter names accept both camelCase and snake\_case (e.g., `eventSeries` and `event_series` are equivalent). This is standard protobuf JSON serialization behavior. **Protobuf Enum Caution** The `states` and `tradableFilter` parameters use protobuf enums. Unrecognized values (typos, made-up states, etc.) **silently map to the default enum value** and return misleading results - they do not error or return empty. Only use the exact values documented here. ### Response Fields | Field | Type | Description | | --------------- | ------- | ---------------------------------------------- | | `instruments` | array | List of matching instruments | | `nextPageToken` | string | Token for next page (empty if no more results) | | `eof` | boolean | True when no more results | ### Pagination Use `pageSize` and `pageToken` to paginate through large result sets. ```bash cURL theme={null} # First page curl -X POST "https://api.preprod.polymarketexchange.com/v1/refdata/instruments" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"pageSize": 100}' # Next page (use nextPageToken from previous response) curl -X POST "https://api.preprod.polymarketexchange.com/v1/refdata/instruments" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"pageSize": 100, "pageToken": "eyJvIjoxMDB9"}' ``` ```python Python theme={null} page_token = None all_instruments = [] while True: body = {"pageSize": 100} if page_token: body["pageToken"] = page_token response = requests.post( "https://api.preprod.polymarketexchange.com/v1/refdata/instruments", headers={"Authorization": f"Bearer {token}"}, json=body ) data = response.json() all_instruments.extend(data["instruments"]) if data.get("eof", False): break page_token = data.get("nextPageToken") if not page_token: break print(f"Total instruments: {len(all_instruments)}") ``` ```go Go theme={null} var allInstruments []Instrument pageToken := "" for { resp, err := client.ListInstrumentsWithPagination(rest.ListInstrumentsRequest{ PageSize: 100, PageToken: pageToken, }) if err != nil { return err } allInstruments = append(allInstruments, resp.Instruments...) if resp.Eof || resp.NextPageToken == "" { break } pageToken = resp.NextPageToken } ``` ### Filtering by State Filter instruments by their current trading state using the `states` parameter. ```bash theme={null} curl -X POST "https://api.preprod.polymarketexchange.com/v1/refdata/instruments" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "states": ["INSTRUMENT_STATE_OPEN"], "pageSize": 100 }' ``` You can specify multiple states to match any of them: ```json theme={null} { "states": ["INSTRUMENT_STATE_OPEN", "INSTRUMENT_STATE_SUSPENDED"], "pageSize": 100 } ``` ### Filtering by Series Filter instruments by series (e.g., NFL, NBA, US Presidential): ```bash theme={null} curl -X POST "https://api.preprod.polymarketexchange.com/v1/refdata/instruments" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "event_series": "nba", "states": ["INSTRUMENT_STATE_OPEN"], "pageSize": 1000 }' ``` ### Filtering by Category Filter instruments by category (e.g., SPR for sports, POL for politics): ```bash theme={null} curl -X POST "https://api.preprod.polymarketexchange.com/v1/refdata/instruments" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "event_category": "SPR", "states": ["INSTRUMENT_STATE_OPEN"], "pageSize": 1000 }' ``` ### Combining Filters You can combine multiple filters: ```bash theme={null} curl -X POST "https://api.preprod.polymarketexchange.com/v1/refdata/instruments" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "eventSeries": "nfl", "eventCategory": "SPR", "clearingSym": "AEC-NFL", "states": ["INSTRUMENT_STATE_OPEN"], "pageSize": 1000 }' ``` ### Advanced Filtering The `filter` parameter supports two approaches that can be used independently or combined. When both are provided, results must match both conditions (AND). **Option A: `whereClause`** - A SQL-like string expression: ```bash theme={null} curl -X POST "https://api.preprod.polymarketexchange.com/v1/refdata/instruments" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "filter": { "whereClause": "symbol LIKE '\''aec-nfl-%'\'' AND state = '\''INSTRUMENT_STATE_OPEN'\''" } }' ``` Supported operators: `=`, `LIKE` (with `%` wildcard), `AND` Supported columns: | Column | Notes | | ---------------- | ---------------------------- | | `state` | Instrument state enum values | | `symbol` | Exact match or LIKE pattern | | `event_series` | e.g., `'cbb'`, `'nfl'` | | `event_category` | e.g., `'SPR'` | | `clearing_sym` | e.g., `'AEC-BASKETBALL'` | | `clearing_house` | e.g., `'QCC'` | | `product_id` | Product identifier | Do **not** use `instrument_product`, `outcome_type`, or `event_subcategory` in `whereClause` - these cause HTTP 500 errors. The column `event_id` is accepted but returns 0 results for known values. **Option B: `fieldFilters`** - Structured, type-safe filters: ```json theme={null} { "filter": { "fieldFilters": [ { "field": "event_series", "operator": "FILTER_OPERATOR_EQ", "stringValue": "nfl" }, { "field": "state", "operator": "FILTER_OPERATOR_IN", "stringList": { "values": ["INSTRUMENT_STATE_OPEN", "INSTRUMENT_STATE_PREOPEN"] } } ] } } ``` Each field filter has a `field` name, an `operator`, and a typed value (`stringValue` or `stringList`). Multiple entries are ANDed together. The same columns supported in `whereClause` work in `fieldFilters`. **Available operators:** | Operator | Description | Value field | | ---------------------- | ------------------------------- | ---------------------------------- | | `FILTER_OPERATOR_EQ` | Exact match | `stringValue` | | `FILTER_OPERATOR_IN` | Match any in list | `stringList` (`{"values": [...]}`) | | `FILTER_OPERATOR_LIKE` | SQL LIKE pattern (`%` wildcard) | `stringValue` | *** ## Instrument Data Each instrument includes: | Field | Type | Description | | -------------------------- | ---------------------- | ------------------------------------------------------------------- | | `symbol` | string | Unique trading symbol | | `tickSize` | double | Minimum price increment | | `baseCurrency` | string | Base currency (e.g., `"USD"`) | | `multiplier` | double | Contract multiplier | | `minimumTradeQty` | string (int64) | Minimum order quantity | | `startDate` | Date | Market start date (`{year, month, day}`) | | `expirationDate` | Date | Expiration date | | `terminationDate` | Date or null | Termination date (null when not set) | | `tradingSchedule` | TradingHours\[] | Trading schedule segments (empty array when none) | | `description` | string | Human-readable instrument description | | `clearingHouse` | string | Clearing house code (e.g., `"QCC"`) | | `minimumUnaffiliatedFirms` | string (int64) | Minimum unaffiliated firms requirement | | `nonTradable` | boolean | Whether instrument is non-tradable | | `jsonAttributes` | string | Additional JSON attributes (empty when unused) | | `productId` | string | Product identifier | | `priceLimit` | PriceLimit | Price limits (`{low, high, lowSet, highSet, ...}`) | | `orderSizeLimit` | OrderSizeLimit or null | Order size limits (null when not set) | | `expirationTime` | TimeOfDay | Expiration time of day (`{hours, minutes, seconds}`) | | `tradeSettlementPeriod` | string (int64) | Settlement period | | `state` | string (enum) | Current instrument state (e.g., `"INSTRUMENT_STATE_OPEN"`) | | `priceScale` | string (int64) | Price scale divisor for converting integer prices to decimals | | `fractionalQtyScale` | string (int64) | Fractional quantity scale | | `settlementCurrency` | string | Reserved for future use | | `settlementPriceScale` | string (int64) | Reserved for future use | | `metadata` | map\ | Key-value metadata (sports league, market category, game IDs, etc.) | | `eventAttributes` | EventAttributes | Event resolution details (`{question, payoutValue, ...}`) | | `createTime` | string (Timestamp) | Instrument creation time (RFC 3339) | | `updateTime` | string (Timestamp) | Last update time (RFC 3339) | **Integer Fields Encoded as Strings in JSON** Fields typed as `int64` in the protocol buffer definition (such as `minimumTradeQty`, `priceScale`, `fractionalQtyScale`, `priceLimit.low`, `priceLimit.high`) are serialized as **strings** in JSON responses per the proto3 JSON specification. Parse these values as numbers in your client code. For example, `"priceScale": "100"` is the string `"100"`, not the number `100`. ## Instrument States ### Primary State Flow | State                                               | Description | | --------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `PENDING` | Initial state for a newly created instrument which has not yet begun trading. | | `OPEN` | In this state, the instrument is open for continuous order entry and matching. | | `CLOSED` | In this state, orders can not be entered, modified, or canceled, and no matching occurs. Any existing Day orders will be expired. | | `EXPIRED` | An instrument moves to this state when its Expiration Date/Time is reached. In this state, any resting orders are expired and no new orders can be entered. | | `TERMINATED` | When an instrument's Termination Date is reached, the order book is removed from the matching engine, orders are canceled, and positions are closed. Historical data will still remain in Polymarket US ledgers. | ### Exception States | State                                               | Description | | --------------------------------------------------- | --------------------------------------------------------------------------------------------- | | `SUSPENDED` | Orders can be canceled but no matching occurs, and no order entry or modification is allowed. | | `HALTED` | This state is similar to SUSPENDED, with the exception that orders cannot be canceled. | ### Other Possible States | State                                               | Description | | --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `PREOPEN` | Orders can be entered and modified, but no matching occurs. When the instrument transitions to an OPEN state, the orders entered during PREOPEN will match at a single opening price that is automatically determined by an algorithm that is designed to maximize the volume traded at the open. | | `MATCH_AND_CLOSE_AUCTION` | This state is similar to PREOPEN, with the exception that matching will occur upon the transition of this state to any other state. This state is useful if you want matching to occur at the end of the state, but you don't want the instrument to be open after. | ## Price Scale Prices in the API are represented as integers. Divide by `priceScale` to get decimal prices: ```python theme={null} decimal_price = int_price / instrument.price_scale # Example: 50 / 100 = $0.50 ``` **Cache Reference Data** Instrument metadata changes infrequently. Cache reference data locally and refresh periodically (e.g., daily or on startup) rather than fetching for every request. ## Usage Notes * Reference data is static during a trading session * Use `/v1/refdata/instruments` to get `priceScale` for price conversions * Instrument states change based on market schedule # Report API Overview Source: https://docs.polymarket.us/institutional/report/overview Search and export orders, trades, and executions **Prefer Streaming for Real-Time Updates** This polling API is subject to rate limits. For production applications that need continuous order and trade updates, use the [gRPC Order Stream](/streaming-endpoints/order-stream) instead. The streaming API provides real-time execution reports as they happen. ## Endpoints | Method | Endpoint | Description | | ------ | ------------------------------ | ------------------------------- | | `POST` | `/v1/report/orders/search` | Search orders with filters | | `POST` | `/v1/report/trades/search` | Search trades with filters | | `POST` | `/v1/report/executions/search` | Search executions with filters | | `POST` | `/v1/report/orders/csv` | Export orders to CSV | | `POST` | `/v1/report/trades/csv` | Export trades to CSV | | `POST` | `/v1/report/executions/csv` | Export executions to CSV | | `POST` | `/v1/report/trades/stats` | Get aggregated trade statistics | **No Participant ID Required for Trade Stats** The `/v1/report/trades/stats` endpoint only requires Auth0 JWT authentication with `read:reports` scope. You do not need to provide the `x-participant-id` header or complete KYC onboarding to access aggregated trade statistics. Note: Other report endpoints (orders search, trades search, etc.) still require participant\_id. ## When to Use | Use Case | Recommended API | | ------------------------------ | ------------------------------------------------------ | | Real-time order status updates | [gRPC Order Stream](/streaming-endpoints/order-stream) | | Real-time fill notifications | [gRPC Order Stream](/streaming-endpoints/order-stream) | | Historical order lookup | REST Orders Search (this API) | | End-of-day reconciliation | REST Reports Export | | Audit and compliance reports | REST Reports Export | **Streaming First Architecture** For any use case requiring real-time order or trade updates, use the streaming API. Use the REST Report API only for: * Historical data queries * One-time lookups * Batch exports for reporting ## Search vs Export * **Search endpoints** (`/v1/report/orders/search`, `/v1/report/trades/search`, `/v1/report/executions/search`): Return paginated JSON results for programmatic access * **Export endpoints** (`/v1/report/orders/csv`, `/v1/report/trades/csv`, `/v1/report/executions/csv`): Return CSV file streams for spreadsheet analysis and reporting ## Common Filters | Filter | Description | | ----------------------- | ------------------------------------------ | | `symbols` | Filter by trading symbols | | `accounts` | Filter by trading accounts | | `startTime` / `endTime` | Date range filter | | `states` | Order states (NEW, FILLED, CANCELED, etc.) | | `sides` | BUY or SELL | See the individual endpoint documentation for complete filter options. # RFQ API Overview Source: https://docs.polymarket.us/institutional/rfqs/overview Create and manage combo RFQs and quotes Market makers should read the [Combos guide](/trader-guide/combos) before integrating. It explains quote construction, visibility, last look, and recovery. The public `polymarket.v1.RFQAPI` gRPC service creates, reads, and manages combo RFQs and quotes. An RFQ references the exact symbol of a [combo instrument](/institutional/combos/overview). The service also exposes the gRPC-only RFQ event stream. Every REST endpoint below has an equivalent unary gRPC RPC. REST JSON uses lower camel case; protobuf fields use snake case. ## Endpoints | Method | Endpoint | Scope | Per-firm limit | Description | | -------- | ------------------------------------------- | -------------- | ------------------------- | ------------------------------------------ | | `GET` | `/v1/rfqs/user-id` | `read:orders` | 1 req/sec | Get your pseudonymous RFQ user ID | | `GET` | `/v1/rfqs` | `read:orders` | 10 req/sec | Query RFQs | | `POST` | `/v1/rfqs` | `write:orders` | 1 req/sec | Create an RFQ | | `DELETE` | `/v1/rfqs/{rfqId}` | `write:orders` | 100 req/sec | Close an open RFQ | | `GET` | `/v1/rfqs/quotes` | `read:orders` | 10 req/sec | Query visible quotes | | `POST` | `/v1/rfqs/quotes` | `write:orders` | 400–2,000 req/sec by tier | Create or replace your quote for an RFQ | | `DELETE` | `/v1/rfqs/{rfqId}/quotes/{quoteId}` | `write:orders` | 400–2,000 req/sec by tier | Delete your quote | | `PUT` | `/v1/rfqs/{rfqId}/quotes/{quoteId}/accept` | `write:orders` | 100 req/sec | Accept one side of a quote | | `PUT` | `/v1/rfqs/{rfqId}/quotes/{quoteId}/confirm` | `write:orders` | 100 req/sec | Confirm an accepted quote during last look | All calls require bearer-token authentication and an acting participant, supplied through `x-participant-id` or the token's `participant_id` claim. Each unary method has a separate per-firm rate-limit bucket with one second of burst capacity, shared across REST and gRPC requests. Your RFQ tier determines the `CreateQuote` and `DeleteQuote` limits. See [RFQ rate-limit tiers](/trader-guide/rate-limits#rfq-rate-limit-tiers) for rates and eligibility requirements. Opening `StreamRFQEvents` is limited to one new stream per second per firm. See [Rate Limits](/trader-guide/rate-limits#rfq-endpoints). ## RFQ Lifecycle ```mermaid theme={null} sequenceDiagram autonumber participant R as Requester participant API as RFQAPI participant M as Maker R->>API: CreateRFQ(symbol, sizing, account) API-->>M: rfq_created M->>API: CreateQuote(buyPrice, sellPrice, account) API-->>R: quote_created API-->>M: quote_created R->>API: AcceptQuote(acceptedSide) API-->>R: rfq_closed API-->>M: rfq_closed API-->>M: quote_accepted + confirmationDeadline M->>API: ConfirmQuote API-->>R: quote_confirmed + executionDeadline API-->>M: quote_confirmed + executionDeadline API-->>R: quote_executed + durable execution state API-->>M: quote_executed + durable execution state ``` Successful `AcceptQuote` produces both `rfq_closed` and `quote_accepted`. A client may receive the public `rfq_closed` event first. Stop creating or replacing quotes for that RFQ, but keep existing quote state until the private quote event arrives or `GetQuotes` confirms its current status. ## Create an RFQ `POST /v1/rfqs` ```json theme={null} { "cashOrderQty": "10.0000", "symbol": "caoc-...", "restRemainder": false, "account": "firm/account" } ``` | Field | Required | Description | | --------------- | ---------------- | ---------------------------------------------------------------------------------------------- | | `qtyDecimal` | One sizing field | Exact contract quantity. Mutually exclusive with `cashOrderQty`. | | `cashOrderQty` | One sizing field | Positive cash notional with at most four decimal places. Mutually exclusive with `qtyDecimal`. | | `symbol` | Yes | Existing open and tradable combo symbol. | | `restRemainder` | Yes | Whether an unfilled requester remainder may rest after paired order submission. | | `account` | Yes | Requester's fully qualified trading account. | The response contains the new `rfqId`. A created RFQ starts in `RFQ_STATUS_OPEN`. ## Query RFQs `GET /v1/rfqs?limit=10&status=RFQ_STATUS_OPEN` | Parameter | Description | | ------------ | ------------------------------------------------------------ | | `limit` | Results per page. Default 100; valid range 1–100. | | `cursor` | Opaque cursor returned by the preceding page. | | `rfqId` | Exact RFQ ID. Do not combine an exact-ID read with `cursor`. | | `symbol` | Exact combo symbol. | | `status` | `RFQ_STATUS_OPEN` or `RFQ_STATUS_CLOSED`. | | `userFilter` | `USER_FILTER_SELF` returns RFQs created by the caller. | The response has `rfqs` and an opaque `cursor`. An exact RFQ ID that is absent or not visible returns an empty `rfqs` array. Each RFQ includes the combo's ordered leg snapshot: ```json theme={null} { "id": "rfq_...", "symbol": "caoc-...", "status": "RFQ_STATUS_OPEN", "comboLegs": [ { "symbol": "market-a", "side": "SIDE_BUY", "settlementPrice": "0.4" }, { "symbol": "market-b", "side": "SIDE_SELL", "settlementPrice": "0" } ] } ``` | Leg field | Description | | ----------------- | ------------------------------------------------------- | | `symbol` | Component instrument symbol. | | `side` | Component side in the combo: `SIDE_BUY` or `SIDE_SELL`. | | `settlementPrice` | Optional raw YES/LONG settlement normalized to `[0,1]`. | `settlementPrice` is a canonical decimal string such as `"0"`, `"0.4"`, or `"1"`. It is not inverted for `SIDE_SELL` legs. An absent field means no valid settlement is currently available; it is distinct from a present `"0"`. Treat absence as unavailable, not proof that the leg is unresolved. Leg order and sides are fixed when the RFQ is created. Settlement prices are hydrated when the RFQ is returned, so later exact and list reads can expose settlements that were unavailable at creation. Historical RFQs created before leg snapshots were introduced can have an empty `comboLegs` array. ## Close an RFQ `DELETE /v1/rfqs/{rfqId}` closes an open RFQ. Only its requester can close it. The response is `{}`. ## Quotes A quote can offer both requester sides: * `buyPrice` is the price for a requester buy; the maker sells. * `sellPrice` is the price for a requester sell; the maker buys. Set an unavailable side to `"0"`. At least one side must be positive. Nonzero prices must be within the instrument's price limits and land on its tick size. ### Create or Replace a Quote `POST /v1/rfqs/quotes` ```json theme={null} { "rfqId": "rfq_...", "buyPrice": "0.615", "sellPrice": "0.585", "restRemainder": false, "postOnly": true, "account": "firm/account" } ``` | Field | Required | Description | | --------------- | -------- | --------------------------------------------------------------------------------- | | `rfqId` | Yes | Open RFQ to quote. | | `buyPrice` | Yes | Requester-buy price, or `"0"` if unavailable. | | `sellPrice` | Yes | Requester-sell price, or `"0"` if unavailable. | | `restRemainder` | Yes | Whether the maker order may rest after paired order submission. | | `postOnly` | No | If true, submit the maker order as participate-don't-initiate. Defaults to false. | | `account` | Yes | Maker's fully qualified trading account. | The service derives `buyQtyDecimal` and `sellQtyDecimal` from the RFQ: * A quantity RFQ uses its `qtyDecimal` for every offered side. * A cash RFQ derives each side independently from `cashOrderQty / price`, rounded down to the instrument's fractional quantity scale. Each maker has one deterministic quote ID per RFQ. Calling `CreateQuote` again replaces that maker's quote in place, resets it to `QUOTE_STATUS_ACTIVE`, and returns the same `quoteId`. ### Query Quotes `GET /v1/rfqs/quotes?rfqId=rfq_...` | Parameter | Description | | --------------- | ------------------------------------------------------------------------------------- | | `limit` | Results per page. Default 100; valid range 1–100. | | `cursor` | Opaque cursor returned by the preceding page. | | `quoteId` | Exact quote ID. Requires `rfqId`; do not combine with `cursor`. | | `rfqId` | Exact RFQ ID. The requester sees all quotes; another participant sees only its quote. | | `status` | One current `QuoteStatus` value. | | `userFilter` | `USER_FILTER_SELF` returns quotes created by the caller. | | `rfqUserFilter` | `USER_FILTER_SELF` returns quotes on RFQs created by the caller. | Without `rfqId`, provide exactly one of `userFilter=USER_FILTER_SELF` or `rfqUserFilter=USER_FILTER_SELF`. The response has `quotes` and an opaque `cursor`. Each visible `Quote` carries durable execution state once available: | REST JSON field | Description | | ------------------- | --------------------------------------- | | `executionDeadline` | Scheduled paired-order submission time. | | `executedTime` | Durable execution-state timestamp. | | `rfqCreatorOrderId` | Optional requester exchange order ID. | | `creatorOrderId` | Optional quoter exchange order ID. | The requester and quoter can both see both exchange order IDs. A `Quote` does not expose client order IDs. `GetQuotes` is the durable recovery path when a stream event is missed. For compatibility, stream events retain their existing recipient-specific wrapper fields. The embedded `Quote` contains the durable fields above. Cursors are query-, participant-, and path-bound. Treat them as opaque and reuse them only with the same filters and authenticated participant. ### Accept a Quote `PUT /v1/rfqs/{rfqId}/quotes/{quoteId}/accept` ```json theme={null} { "acceptedSide": "SIDE_BUY" } ``` Only the requester can accept an active quote. `SIDE_BUY` selects `buyPrice`; `SIDE_SELL` selects `sellPrice`. The selected price must be positive. Acceptance closes the RFQ, emits public `rfq_closed`, changes the quote to `QUOTE_STATUS_ACCEPTED`, emits participant-private `quote_accepted`, and starts last look. ### Delete a Quote `DELETE /v1/rfqs/{rfqId}/quotes/{quoteId}` deletes the caller's active quote while the RFQ is open. The selected maker can also delete its accepted quote before the confirmation deadline to decline during last look. The response is `{}`. ### Confirm a Quote `PUT /v1/rfqs/{rfqId}/quotes/{quoteId}/confirm` The selected maker must confirm before `confirmationDeadline`. Confirmation changes the quote to `QUOTE_STATUS_CONFIRMED` and schedules paired order submission. The response is `{}`. ## Statuses | Type | Status | Meaning | | ----- | ------------------------ | ------------------------------------------------------------------------------------- | | RFQ | `RFQ_STATUS_OPEN` | Can receive and accept quotes. | | RFQ | `RFQ_STATUS_CLOSED` | Closed by the requester or by quote acceptance. | | Quote | `QUOTE_STATUS_ACTIVE` | Can be accepted while its RFQ is open. | | Quote | `QUOTE_STATUS_ACCEPTED` | Selected; maker last look is active. | | Quote | `QUOTE_STATUS_CONFIRMED` | Maker confirmed; paired order submission is scheduled or needs reconciliation. | | Quote | `QUOTE_STATUS_DELETED` | Maker deleted or declined the quote. | | Quote | `QUOTE_STATUS_EXECUTED` | Both exchange orders were accepted for submission. Reconcile fills through Drop Copy. | ## Events and Recovery `RFQAPI.StreamRFQEvents` is a live, best-effort gRPC stream. Public RFQ events are visible to participants; quote events are private to the requester and relevant maker. The stream has no replay, gap-free handoff, ordering, or deduplication guarantee. Open the stream for low-latency changes. On startup, reconnect, or after a suspected missed event, reconcile durable state with `GetRFQs` and `GetQuotes`. See [RFQ Events Stream](/streaming-endpoints/rfq-events-stream). ## See Also Create and read combo instruments Maker workflow and quote rules Current event payloads and recovery behavior OAuth metadata and required scopes # Trading API Overview Source: https://docs.polymarket.us/institutional/trading/overview Insert, cancel, and manage orders ## Endpoints ### Order Entry | Method | Endpoint | Description | | ------ | ---------------------------- | ------------------------------- | | `POST` | `/v1/trading/orders` | Insert a single order | | `POST` | `/v1/trading/orders/list` | Insert multiple orders (batch) | | `POST` | `/v1/trading/orders/preview` | Preview order before submission | ### Order Modification | Method | Endpoint | Description | | ------ | --------------------------------- | -------------------------------------- | | `POST` | `/v1/trading/orders/replace` | Replace/modify a single order | | `POST` | `/v1/trading/orders/replace/list` | Replace/modify multiple orders (batch) | ### Order Cancellation | Method | Endpoint | Description | | ------ | -------------------------------- | ------------------------------ | | `POST` | `/v1/trading/orders/cancel` | Cancel a single order | | `POST` | `/v1/trading/orders/cancel/list` | Cancel multiple orders (batch) | ### Order Query | Method | Endpoint | Description | | ------ | ------------------------- | --------------- | | `GET` | `/v1/trading/orders/open` | Get open orders | ## Order Types | Type | Description | | ----------------- | ----------------------------------- | | `LIMIT` | Limit order at specified price | | `MARKET_TO_LIMIT` | Market order that converts to limit | | `STOP` | Stop order | | `STOP_LIMIT` | Stop-limit order | ## Order Lifecycle Orders progress through these states: ``` NEW → PARTIALLY_FILLED → FILLED ↓ CANCELED ``` **Real-Time Order Updates** After submitting orders via REST, use the [gRPC Order Stream](/streaming-endpoints/order-stream) to receive real-time updates on order status, fills, and cancellations. This is more efficient than polling for order status. ## Time in Force | TIF | Description | | ----- | ------------------------ | | `DAY` | Good for the trading day | | `GTC` | Good till canceled | | `IOC` | Immediate or cancel | | `FOK` | Fill or kill | ## Best Practices 1. **Use Order Stream for updates** - Don't poll for order status; use streaming 2. **Include client order ID** - Use `clOrdId` for your own order tracking 3. **Preview before submit** - Use the preview endpoint for order validation 4. **Handle rejects** - Implement proper error handling for rejected orders # Protect Your Account Source: https://docs.polymarket.us/learn/account/protect Keep your password, login, and personal access secure ## Keep your Google account or Apple ID secure Your Polymarket login is linked to your Google account or Apple ID. Make sure your primary login remains secure. ## Never share verification codes Do not share Polymarket verification codes with anyone, including support. ## Avoid fake apps and login screens Only sign in through the official Polymarket app. Avoid fake Google or Apple login screens. Need help? Contact support through the **in-app chat**. # Liquidity Source: https://docs.polymarket.us/learn/advanced/liquidity Learn how posted size across price levels affects where your order fills Liquidity is the size available at each price level on the order book. Where your order fills depends on how much liquidity is available at or near the best price. ## Depth and Posted Size Liquidity comes from resting orders that other users have placed and are waiting to be matched. Each price level shows how many shares are available to buy or sell. The more liquidity near the current price, the easier it is to trade without moving the price. When liquidity is low or disappears, even smaller trades can move through multiple price levels. ### Example At 30¢ there are **120 shares** available, at 31¢ there are **300 shares**, and at 32¢ there are **600 shares**. Those amounts show how much can trade at each price before orders begin filling at the next level. | Price | Shares | | --------------- | ------ | | 30¢ | 120 | | 31¢ | 300 | | 32¢ | 600 | | Total up to 32¢ | 1,020 | ## Order Interaction With Liquidity Orders fill against available liquidity starting at the best available price. If there isn't enough available at that price to fill your full order, the remaining shares fill at the next price levels. ### Example You place a buy for **500 shares**. * 120 shares fill at 30¢ * 300 shares fill at 31¢ * 80 shares fill at 32¢ | Price | Shares available | Total up to | | ----- | ---------------- | ----------- | | 30¢ | 120 | 120 | | 31¢ | 300 | 420 | | 32¢ | 600 | 1,020 | As a result, your average fill price ends up higher than **30¢** because there wasn't enough liquidity available at the best price. ## Thin Liquidity Conditions Liquidity can become thin—or even disappear entirely—when traders pull their orders. This is especially common in **live sports**, including: * Late in the 4th quarter or final minutes * Overtime * Right after a major play (turnover, touchdown, penalty, injury) * Right before the match ends, when the outcome looks nearly certain In these moments, orders can be added or removed quickly, and prices can move sharply. **Why this matters**: Thin liquidity can cause **slippage**, which means your order fills at worse prices than expected. In fast-moving markets, this can quickly lead to paying more or receiving less than expected. ### Example At 75¢, there are only **40 shares** available. If you place a buy for 120 shares, part of your order will fill at 76¢ or higher. | Price | Shares | | --------------- | ------ | | 75¢ | 40 | | 76¢ | 60 | | 77¢ | 80 | | Total up to 77¢ | 180 | ## Reducing Liquidity Risk Polymarket US is a **peer-to-peer market**. Prices and available liquidity change as other traders place or cancel orders. Orders execute against available liquidity at the best available price. You cannot set a custom limit price, so the price you receive depends on what is available at the moment your order is submitted. Any unfilled portion of your order rests on the book at your executed price until it fills or is canceled. Treat the displayed price as dynamic, especially in live sports. ### Practical ways to protect yourself * **Pay close attention to the price right before you submit**. In fast-moving markets, prices can change in seconds. * **Consider placing smaller orders instead of one large order** to reduce the chance your order sweeps through multiple price levels. * **Be extra cautious near the end of games or matches**, where liquidity often thins out or disappears. * **If the price is moving quickly, consider pausing briefly** and re-checking the current price before submitting. ### Example You want to buy **\$100** worth of shares at 50¢ (**200 shares**). | Price | Shares | | --------------- | ------ | | 50¢ | 50 | | 51¢ | 80 | | 52¢ | 70 | | Total up to 52¢ | 200 | If liquidity is thin, submitting a buy for **200 shares** at once may result in part of your order filling at **51¢**, **52¢**, **or higher**, instead of 50¢. Instead, try submitting **4 smaller trades of \$25** (**50 shares each**). This can reduce price impact during periods of thin liquidity. ## Evaluating Available Liquidity Before placing a larger order—especially in live sports—compare the size you want to trade to what's available near your expected price, as larger orders may fill at worse levels. ### Example You want to buy around **75¢**. Available shares near that price: | Price | Shares | | --------------- | ------ | | 75¢ | 10 | | 76¢ | 40 | | 77¢ | 60 | | Total up to 77¢ | 110 | A buy for **150 shares** would result in at least **40 shares** filling at **78¢ or higher**, raising your average purchase price. ### Simple rule of thumb If your order size is close to or larger than the liquidity available near your expected price—especially late in a game—consider placing **smaller orders** and pay close attention to the price right before you submit to reduce slippage. # Price Impact Source: https://docs.polymarket.us/learn/advanced/price-impact Learn how order size moves prices and affects your execution Price impact is the change in execution price caused by the size of your order. If your order is larger than the size posted at the best price, it fills at higher prices, raising your average fill price. ## Depth Structure The order book lists posted size at each price. If the best price cannot fill your entire order, the remaining size fills at the next available price levels. The distribution of size across those price levels determines where your order fills. **Example:** Best ask is 15¢ with 9,150 shares posted. A 50,000-share buy clears 15¢, sweeps 16¢, and finishes partway through 17¢. Price impact increases when available liquidity is thin at the best price. ## Order Size and Fill Behavior Small orders typically fill at a single price level. Larger orders fill at higher prices because each level has limited posted size. What counts as small or large depends entirely on current depth. **Example:** 9,150 shares fill at 15¢. Any remaining size fills at 16¢ and then higher prices if needed. ## Thin Depth Conditions Posted size often drops during quiet periods, before major announcements, and near deadlines when traders cancel resting orders. When depth is low at the best price, even relatively small trades can fill at higher prices. **Example:** At 99¢ only 2,249 shares are posted. Any buy large enough to clear that size fills at the next available price levels. ## Estimating Impact Check posted size across the first few price levels and compare it with the size you plan to trade. If your order is larger than the combined size across those levels, it fills at higher prices and increases your average fill price. **Example:** At 15¢, 16¢, and 17¢ there are about 92,000 shares in total. Any order larger than that fills at 18¢ and above. ## Reducing Execution Cost Break large orders into smaller clips so each one interacts with fewer price levels and gives new liquidity time to post between trades. **Example:** If depth is thin around 15–17¢ and you plan to buy \$2,000, splitting the order into several smaller clips can prevent it from filling at 18¢. # Market Rules Source: https://docs.polymarket.us/learn/advanced/rule-structure Learn the structure of market rules and how each part affects resolution Market rules follow a consistent structure. Each part contributes to how the final outcome is determined. ## Resolution Criteria Market rules begin with the **resolution criteria**. The criteria describe the conditions for the market to resolve to **Yes** or **No**. Any **alternative settlement terms** also appear here, including situations where the market settles at 0.50. **Examples** * This market will resolve to Yes if the candidate wins the election. * If Team A wins, the market will resolve to "Team A". * If the game is canceled entirely with no make-up game, this market will settle 50-50. ## Qualifying Requirements If additional clarity is needed, the rules may include **qualifying requirements**, **non-examples**, and **edge-case scenarios** to show how resolution applies in specific situations. **Examples** * **Qualifying requirement**: The handshake must be clearly visible on video from start to finish. * **Non-example**: Any handshake too unclear to measure. * **Edge case**: If the measured duration falls exactly on the boundary between two duration brackets, this market will resolve to the higher bracket. ## Resolution Timeframe Rules specify **when** the outcome is evaluated. **Examples** * The market resolves based on results available at 11:59 PM ET on the end date. * The market resolves once official certification is published. * If the event does not occur by 11:59 PM ET, this market will resolve to "No". ## Resolution Sources Rules list the **official sources** used to confirm the outcome. Unlisted sources have **no effect** on resolution. **Examples** * The resolution source for this market will be information from the **governing league**. * The resolution source for this market will be information from an **official data provider**. * The resolution source for this market will be confirmed once **all listed news sources** report the same outcome. # Bank Transfer (ACH) Source: https://docs.polymarket.us/learn/deposits/deposit-methods/bank-transfer Deposit using a bank transfer (ACH) via Aeropay In Funding Methods, select **Bank Transfer**. Securely link your bank account via **Aeropay**. Enter your deposit amount up to **\$50,000 per day**. Review your transaction details, then tap **Confirm**. Start trading with instant buying power. The rest of your funds become available once the deposit fully clears. Bank transfer (ACH) deposits are limited to **\$25,000**/transaction, **\$50,000**/day, and **\$2,000,000** over a rolling 60-day period. *** Need help? See [Troubleshooting](/learn/deposits/deposit-methods/troubleshooting) for common issues. # Debit Card Source: https://docs.polymarket.us/learn/deposits/deposit-methods/debit-card Deposit instantly using a debit card In Funding Methods, select **Debit Card**. Enter your card information in the required fields. Enter your deposit amount up to **\$50,000 per day**. Review your transaction details, then tap **Confirm**. Start trading with instant buying power. The rest of your funds become available once the deposit fully clears. Debit card deposits are limited to **\$25,000**/transaction, **\$50,000**/day, and **\$2,000,000** over a rolling 60-day period. *** Need help? See [Troubleshooting](/learn/deposits/deposit-methods/troubleshooting) for common issues. # Overview Source: https://docs.polymarket.us/learn/deposits/deposit-methods/overview Deposit with debit card, bank transfer (ACH), or wire transfer You can fund your Polymarket account using: * Debit card * Bank transfer (ACH) * Wire transfer Here's how each method compares: | Funding Method | Daily Limit | Processing Time | Best For | | ------------------- | ----------------------- | ----------------- | --------------- | | Debit Card | \$50,000 | 3–4 business days | Small deposits | | Bank Transfer (ACH) | \$50,000 | 3–4 business days | Medium deposits | | Wire Transfer | \$1,000 minimum, no max | 1 business day | Large deposits | Debit card and bank transfer (ACH) deposits may be credited with instant buying power while the deposit is processing. Withdrawals are available once the deposit has fully cleared. *** ## How to Deposit From the home screen, tap the **Profile** icon at the bottom center. Tap **Deposit** on your Profile page. Tap **Pay With**, then select **debit card**, **bank transfer (ACH)**, or **wire transfer**. All deposits must come from accounts in **your own name** and may be subject to verification checks. # Troubleshooting Source: https://docs.polymarket.us/learn/deposits/deposit-methods/troubleshooting Resolve common deposit issues and contact support if needed ## Debit Card | Issue | Likely Cause | Fix | | ------------------- | --------------------------------------------- | --------------------------------------------- | | Deposit not showing | Card transaction still pending | Wait a few minutes, then refresh your balance | | Deposit declined | Incorrect card details or insufficient funds | Verify card information and available balance | | Deposit delayed | Payment processor still completing settlement | Wait a few minutes and check again | ## Bank Transfer (ACH) | Issue | Likely Cause | Fix | | ----------------- | ------------------------------------ | ------------------------------------------------------ | | Deposit pending | Transfer is still clearing | Allow 3–4 business days for the deposit to fully clear | | Transfer failed | Incorrect bank account details | Re-link your bank account and retry | | Duplicate deposit | Bank submitted multiple ACH requests | Contact support with your transfer receipt | ## Wire Transfer | Issue | Likely Cause | Fix | | ------------------ | ----------------------------------- | ------------------------------------------- | | Wire rejected | Incorrect routing or account number | Double-check wire details before resending | | Funds not received | Missing memo or reference code | Contact support with your wire confirmation | Always include your **full name and phone number** in the memo or message field. Missing details can cause delays. ## Compliance Note All deposits are subject to **Know Your Customer (KYC)** and **Anti-Money-Laundering (AML)** checks. These reviews can temporarily delay fund availability until verification is complete. **Still need help?** Contact [support@polymarket.us](mailto:support@polymarket.us) or use the **in-app chat** if you encounter errors. # Wire Transfer Source: https://docs.polymarket.us/learn/deposits/deposit-methods/wire-transfer Fund your account by wire transfer In Funding Methods, select **Wire Transfer**. Tap **View Wire Instructions** to see your unique details. From your bank, enter the wire details **exactly as shown**, including the beneficiary, routing number, and account information. Include the **full name and phone number linked to your Polymarket US account** in the memo or message field, then send the wire. Funds are credited once received and verified, typically within **1 business day**. Double-check every field, including the **memo**, in your wire instructions. Any missing or incorrect details can cause **delays** or **rejection**. ## Wire Transfer Details | Field | Information | | --------------------- | ------------------------------------------------------------------------------------------------------------ | | **Recipient Name** | QC Clearing LLC | | **Recipient Address** | 7251 W Palmetto Park Rd Ste 102, Boca Raton, FL 33433 | | **Bank Name** | Merchants Bank of Indiana | | **Bank Address** | 3737 East 96th Street, Indianapolis, IN 46240 | | **Routing Number** | 074909153 | | **Account Number** | 4946110 | | **Bank Country** | United States | | **Minimum Amount** | \$1,000 USD | | **Memo / FFC / FBO** | Include your full name and phone number linked to your Polymarket US account. Example: John Doe - 9175551234 | You must include your **full name and phone number** linked to your Polymarket US account in your bank's **Message to Recipient**, **Memo**, **FFC**, or **FBO field**. Missing or incorrect details will delay processing. ## Processing Time Most wires are processed within **1 business day** after being received and verified. Wires sent late in the day or missing required details may take longer. You will receive **in-app confirmation** once the deposit is applied. ## Avoid Rejected Wire Transfers * The bank account name must match your Polymarket US account * Wire transfers must come from an account in your own name * Third-party or joint-account wires will be rejected * Incorrect or missing **Memo**, **FBO**, or **FFC** details require manual review * Returned wires may take several business days depending on your bank * All wires are reviewed under **anti-money-laundering (AML)** and **Know Your Customer (KYC)** rules ## Quick Checklist Before Sending * Sent from a **U.S. bank in USD** * Minimum **\$1,000** amount * Recipient: **QC Clearing LLC** * Memo/FBO/FFC field includes **Full Name + Phone Number linked to your Polymarket US account** * Bank account name matches your Polymarket US account * Saved your confirmation or receipt If you experience any delays or need to confirm receipt, contact [support@polymarket.us](mailto:support@polymarket.us) or reach out through the **in-app chat** with your wire confirmation details. # Overview Source: https://docs.polymarket.us/learn/deposits/withdraw-funds/overview Withdraw your cash balance securely and for free You can withdraw your cash balance using: * Debit card * Bank transfer (ACH) * Wire transfer Here's how each method compares: | Withdrawal Method | Typical Arrival | Best For | Notes | | ------------------- | ----------------- | ------------------ | ------------------------------------------------------ | | Debit Card | 3–4 business days | Small withdrawals | Funds return to the same card used for deposit | | Bank Transfer (ACH) | 3–4 business days | Medium withdrawals | Funds return to the same bank account used for deposit | | Wire Transfer | 1 business day | Large withdrawals | Contact support if needed | Withdrawals are only available for deposits that have **fully cleared**, which typically takes 3–4 business days. This includes instant buying power and any proceeds from trading activity tied to that deposit. *** ## How to Withdraw From the home screen, tap the **Profile** icon at the bottom center. Tap **Withdraw** on your Profile page. Tap **Withdraw To** to open available payout methods. Choose your withdrawal method: **debit card**, **bank transfer (ACH)**, or **wire transfer**. Withdrawals must return to the **original funding source** used for deposit. This ensures accurate fund routing. # Withdrawal Rules Source: https://docs.polymarket.us/learn/deposits/withdraw-funds/rules Withdrawals follow strict rules to meet compliance requirements ## Funds in Flight Deposits may appear in your balance before they fully clear. These are called **funds in flight** and cannot be withdrawn until they are fully cleared. You can still trade while funds are in flight. Here's how long each funding method typically takes to fully clear: | Funding Method | Processing Time | Notes | | ------------------- | ----------------- | ---------------------------------------- | | Debit Card | 3–4 business days | Funds must fully clear before withdrawal | | Bank Transfer (ACH) | 3–4 business days | Funds must fully clear before withdrawal | | Wire Transfer | 1 business day | Withdrawals available after confirmation | ## Original Funding Source All withdrawals must return to the **same payment method** used for deposit. Funds cannot be redirected to **new or third-party accounts**. If your deposit method is no longer active, contact **Polymarket US Support**. ## First-In-First-Out (FIFO) Withdrawals are processed in the **order deposits were made**. **Example**: If you deposit \$100 by debit card and later \$200 by bank transfer (ACH), your first \$100 withdrawn will return to your debit card. This rule ensures compliance with **anti-money-laundering (AML) regulations**. ## Summary * Deposits must fully clear before they can be withdrawn * Withdrawals return only to the original funding source * First-In-First-Out (FIFO) applies to every withdrawal Need help? See [Troubleshooting](/learn/deposits/withdraw-funds/troubleshooting) for common issues. # Troubleshooting Source: https://docs.polymarket.us/learn/deposits/withdraw-funds/troubleshooting Resolve common withdrawal issues and contact support if needed ## Common Errors | Issue | Likely Cause | Fix | | ----------------------- | -------------------------------------------------------- | ----------------------------------- | | Withdrawal unavailable | Deposit has not fully cleared | Wait for the deposit to fully clear | | Wrong withdrawal method | Withdrawal method does not match original funding source | Use the same method as your deposit | | ACH rejected | Incorrect or outdated bank details | Re-link your correct bank account | ## Anti-Money-Laundering (AML) Compliance All withdrawals are reviewed under AML regulations. These checks ensure funds return to verified accounts and may temporarily delay processing until verification is complete. *** **Still need help?** Contact [support@polymarket.us](mailto:support@polymarket.us) or use the **in-app chat** if you encounter any errors. Include your **exact error code**. # Does Polymarket have an API? Source: https://docs.polymarket.us/learn/faq/api Yes. Polymarket US provides a Retail API with documentation for market data, orders, portfolio, and related endpoints, plus Python and TypeScript SDKs. See the [API Reference](/api-reference/introduction) on this site to get started. # How do I contact support? Source: https://docs.polymarket.us/learn/faq/contact-support How to reach Polymarket US support ## In-app chat You can contact support through the in-app chat in your account settings. ## Email support You can reach us at [**support@polymarket.us**](mailto:support@polymarket.us) ## What to include For faster responses, include: * What you were trying to do * Error messages * Reference IDs * Screenshots or screen recordings # Is my money safe? Source: https://docs.polymarket.us/learn/faq/money-safety Yes. Your funds are kept in a dedicated customer account that is separate from Polymarket US's operating funds. Polymarket US cannot access or use your money. Keep your account credentials secure, because if you lose them or someone else gains access, you can lose access to your funds. # How are Polymarket odds determined? Source: https://docs.polymarket.us/learn/faq/odds Polymarket odds come entirely from user trades. Prices update whenever a buy or sell order is matched in the central order book, and those trades reflect what users believe right now. As new information appears, traders adjust their orders, and the odds move in real time to capture the current market sentiment. Polymarket US does not set prices or take the other side of your trades. # How does Polymarket compare to polling? Source: https://docs.polymarket.us/learn/faq/polling Polymarket odds update in real time as traders react to new information, while traditional polls capture opinions at one moment and often lag by days. Because money is at stake, the prices tend to reflect more informed decisions and show public sentiment more accurately than traditional polls. # What is a prediction market? Source: https://docs.polymarket.us/learn/faq/prediction-market A prediction market is a place where people trade on the odds of future events. Prices reflect how the market currently views the odds of an outcome. Traders take positions based on their beliefs, and accurate predictions pay out. # How does Polymarket drive social good? Source: https://docs.polymarket.us/learn/faq/social-good Polymarket aggregates the wisdom of the crowd into valuable predictions on major events and global issues. These predictions can help individuals and institutions make more informed decisions, relying on real-time, data-driven forecasts rather than outdated, limited, or unreliable information. # Why does my position look down right after I buy? Source: https://docs.polymarket.us/learn/faq/whole-contracts When you enter a dollar amount to buy, you might not spend the full amount because Polymarket US only supports whole contracts. Any leftover funds go straight back to your cash balance. Your portfolio then shows only the value of the contracts you received, which can make the position look slightly down at first even though you did not lose anything. # Why can't I withdraw my funds? Source: https://docs.polymarket.us/learn/faq/withdraw-funds In most cases, your deposit is still processing. Deposits must fully clear before they can be withdrawn, which typically takes **3–4 business days**. Business days do not include weekends or holidays. When you deposit, we credit you up to \$50,000 in instant buying power so you can trade right away. To withdraw the original deposit or any proceeds from instant buying power, the funds must fully clear. If your deposit is still pending after **5 business days**, contact support. # Why can't I withdraw my promotional credit? Source: https://docs.polymarket.us/learn/faq/withdraw-promo Promo credits are **trading credits, not cash**. They cannot be withdrawn, including after they are used in trading or after the related position settles or is liquidated. To make trading proceeds generated with a promo credit eligible for withdrawal, the full promo credit must first be used as trading collateral and the related positions must settle or be liquidated. Only the resulting proceeds — not the original promo credit — may become withdrawable. For example, if you receive a \$20 promo credit and use it in trading, the \$20 credit itself remains non-withdrawable. If that trading produces \$10 in eligible proceeds after settlement, up to \$10 may become withdrawable, subject to the applicable [withdrawal requirements](/learn/deposits/withdraw-funds/rules). To withdraw eligible proceeds, you must: 1. Link a payment method 2. Make an initial deposit Once the deposit fully clears, typically within **2–3 business days**, eligible funds become available to withdraw to the same linked payment method. Your deposited cash remains your money and may be withdrawn when it is available to withdraw, subject to normal withdrawal requirements and any funds committed to open orders or positions. For full program terms, see [User Incentive Programs](/incentives/user-programs). # Why is my account under review? Source: https://docs.polymarket.us/learn/get-started/account-under-review Understand why your account might be under review and what happens next Your account may be under review for the following reasons: * Information mismatch or missing details * Similar information found on another account * Additional identity checks required for **Know Your Customer (KYC)** or **anti-money-laundering (AML)** compliance Once verification is complete, your account activates automatically. You will be notified **in-app** and by **email**. # Browse Markets Source: https://docs.polymarket.us/learn/get-started/browse Learn how to browse active market categories on Polymarket Polymarket US currently offers professional football, professional basketball, professional hockey, and college football markets. Additional categories will open as they complete testing and regulatory review. ## How to Browse Find active markets on your home screen. Scroll across the top section to view the available categories. Each market shows: * The **market question** * Current **Yes and No prices** * The **resolution date** # Check Your Balance Source: https://docs.polymarket.us/learn/get-started/cash Learn how your cash balance works, including deposits, withdrawals, and available funds Your **cash balance** is the money not tied to any open positions. You can use it to trade or withdraw back to your original deposit source. ## How It Works * Deposits appear as cash * Proceeds from sales or resolved markets return automatically to cash * Withdrawals can be made only from your cash balance ## Regulatory Requirement Withdrawals must return to the same account or payment method used for the deposit, as required under anti-money-laundering (AML) rules. # Fund Your Account Source: https://docs.polymarket.us/learn/get-started/fund-account Learn how to fund your Polymarket US account with debit card, bank transfer (ACH), or wire transfer You can fund your Polymarket account using: * Debit card * Bank transfer (ACH) * Wire transfer Here's how each method compares: | Funding Method | Daily Limit | Processing Time | Best For | | ------------------- | ----------------------- | ----------------- | --------------- | | Debit Card | \$50,000 | 3–4 business days | Small deposits | | Bank Transfer (ACH) | \$50,000 | 3–4 business days | Medium deposits | | Wire Transfer | \$1,000 minimum, no max | 1 business day | Large deposits | Debit card and bank transfer (ACH) deposits may be credited with instant buying power while the deposit is processing. Withdrawals are available once the deposit has fully cleared. *** ## How to Deposit From the home screen, tap the **Profile** icon at the bottom center. Tap **Deposit** on your Profile page. Tap **Pay With**, then select **debit card**, **bank transfer (ACH)**, or **wire transfer**. All deposits must come from accounts in **your own name** and may be subject to verification checks. # Place a Trade Source: https://docs.polymarket.us/learn/get-started/place-order Learn how to place your first trade on Polymarket From the home screen, browse the list of available markets. Tap any market to open it. Select **Yes** if you think the event will happen or **No** if you think it will not. Enter the **dollar amount** you want to trade or tap Max to use your available balance. Review your order details, then swipe up to confirm. Open the **Portfolio** tab to track your position and market value. Positions update in real time as market prices change. You can **cash out** anytime while the market is open or **hold** until it resolves. # Monitor Your Positions Source: https://docs.polymarket.us/learn/get-started/portfolio Learn how to view your portfolio and track open positions Your Polymarket portfolio shows your **cash balance**, **open positions**, and **total account value**. It updates in real time as markets move. ## What's in Your Portfolio * **Cash balance**: Funds available to trade or withdraw * **Open positions**: Contracts you hold in active markets * **Total value**: Combined value of cash and positions You can also see: * Contracts held in each market * Your average entry price * The current market price * Potential payout # Create an Account Source: https://docs.polymarket.us/learn/get-started/signup Learn how to create your Polymarket US account, verify your identity, and complete Know Your Customer (KYC) securely ## Sign-Up and Verification Process Sign up with **Google** or **Apple ID** to create your Polymarket US account. Choose a **username** that will be visible on your profile and linked to your account activity. Provide your **full name**, **date of birth**, and **residential address** for verification. Most users are verified automatically. If additional verification is needed, you may be asked to upload a **government-issued ID** and complete a **selfie check.** Once verified, you can **deposit funds** and start trading. Most verifications are completed instantly. If manual review is required, processing can take up to 3–5 business days. You will be notified **in-app** and by **email** once verification is complete. ## Why KYC Is Required KYC verification is required to comply with federal identity and anti-money-laundering (AML) rules. These checks confirm user identity and help maintain a secure and compliant trading environment. You must complete KYC before you can deposit funds or trade. ## Compliance Notice Polymarket US operates in partnership with a **Commodity Futures Trading Commission (CFTC)-regulated exchange**. All onboarding and identity verification processes follow applicable U.S. federal compliance standards, including identity verification and AML requirements. # Polymarket US Source: https://docs.polymarket.us/learn/home Overview of Polymarket US, a CFTC-regulated exchange for trading event contracts Polymarket US is a CFTC-regulated exchange for trading event contracts on real-world outcomes. Each market asks a clear yes/no question about something that might happen, and prices reflect how likely traders think the outcome is. ## Polymarket vs Polymarket US **Polymarket** is our international, crypto-based product that operates on blockchain technology. **Polymarket US** is a fiat-based, US-regulated platform operating as a designated contract market (DCM) and derivatives clearing organization (DCO) under CFTC oversight. All trading is conducted in US dollars with full regulatory compliance. ## How Event Contracts Work Event contracts are yes/no trades on whether something will happen. Each contract settles at \$1 if the event happens and \$0 if it does not. **Example**: *Will the Rams win Super Bowl 2027?* * You can **buy or sell contracts** based on what you think will happen * If you buy yes at 12¢ and they win, the contract settles at \$1 * If they lose, it settles at \$0 * You can trade until the outcome is known ## Trade on everything Trade on professional football, professional basketball, professional hockey, and college football, with more markets coming soon. # Market Clarification Source: https://docs.polymarket.us/learn/markets/clarifications Learn how Polymarket US uses clarifications to explain market rules Markets resolve based on the rules shown on the market page. Sometimes the rules need extra clarity. When that happens, Polymarket US may clarify how the rules should be understood. Clarifications provide additional context when rule wording can be interpreted in more than one way. They specify how the existing rules should be understood, resolve ambiguous language, and ensure markets resolve according to the intended criteria. ## When Clarifications Are Added Clarifications are added when: * A rule could be read in more than one way * A detail in the description needs to be specified * An event develops in a way that raises a question about what counts * Timing or wording could be interpreted more than one way * It needs to be stated whether a specific event satisfies the rules * Resolution sources need to be defined for the market ## Where Clarifications Appear Clarifications appear **at the top of the rules section** as an **Additional context** message. ## Effect on Orderbooks Before a clarification is posted: * Liquidity may be reduced * Spreads may widen * Volatility can increase when there is uncertainty When a clarification is posted, Polymarket US may cancel resting orders so they are not executed under clarified rules. ## Effect on Contracts After a clarification is posted: * It removes ambiguity about what counts * It can confirm whether the rule conditions have been met * It can clarify whether the market should continue trading or is eligible for resolution ## Examples ### Timing Interpretation * **Rule ambiguity**: The rules require an event to occur "by the end of the quarter." * **Clarification**: Only events occurring on or before a stated cut-off time count. * **Effect**: Defines the exact timing threshold. ### Event Qualification * **Rule ambiguity**: The rules require "an official announcement" but do not specify the communication channel. * **Clarification**: Only announcements published through the designated official channel count. * **Effect**: Specifies which announcements qualify as the event. ### Source Specification * **Rule ambiguity**: The rules require confirmation "from official data." * **Clarification**: Only data published by the designated official source counts. * **Effect**: Identifies the authoritative source used for resolution. ## Key Points * Clarifications add context to explain the rules * They do not change the market question * They appear at the top of the rules section as **Additional context** * Polymarket US may cancel resting orders when a clarification is posted * Clarifications can confirm whether rule conditions have been met * Always read the rules and any clarifications before trading # Market Settlement Source: https://docs.polymarket.us/learn/markets/contract-settlement Learn how Polymarket US and Polymarket Clearing settle and finalize event contracts after resolution Settlement begins once a market's outcome is finalized. Polymarket Clearing processes the settlement and updates balances automatically in your Portfolio. ## After Resolution When a market resolves: * Winning contracts settle at **\$1.00** * Losing contracts settle at **\$0.00** * Your Portfolio updates automatically when settlement completes ## Alternative Settlement Some markets include predefined settlement terms that differ from the standard \$1/\$0 structure. When alternative settlement applies, Polymarket Clearing follows the instructions in the market's **Settlement Description**. These terms determine how all positions are processed for that specific market. ## Finality of Settlement All settlements are final in accordance with the [Polymarket US Exchange Rulebook](https://polymarketexchange.com/regulatory.html). # Market Creation Source: https://docs.polymarket.us/learn/markets/how-markets-are-created Learn how Polymarket creates and lists event-based markets across politics, sports, and more Polymarket lists markets tied to **real-world events** with **clear, verifiable outcomes**. Markets cover major categories such as politics, sports, and news events. The Markets Team evaluates trending topics, reliable data sources, and user suggestions to decide which events to list. When selecting new markets, Polymarket prioritizes: * **Relevance**: Events with meaningful public interest * **Clarity**: Outcomes that can be verified using trusted public sources * **Demand**: Topics users are actively searching or trading * **Integrity**: Alignment with U.S. compliance and operational standards All markets undergo review before launch to ensure they can be resolved using transparent and publicly verifiable information. # Market Resolution Source: https://docs.polymarket.us/learn/markets/market-resolution Learn how Polymarket US determines outcomes for event contracts Markets stay open until the event is over. Once the outcome becomes publicly known, the Exchange determines the result using publicly verifiable information. ## How Market Outcomes Are Determined Each market asks a clear question about a real-world event. When the outcome becomes publicly known, the Exchange confirms it using the criteria defined in the market's description and rules. Markets resolve using the **specific sources listed in the rules**. These sources provide the authoritative outcome. Unlisted sources have no effect on market resolution. All determinations follow the event-contract framework used across Polymarket US. ## Types of Resolution Sources Resolution sources fall into three main categories. Each category has clear standards for what counts as official. ### Government Government providers supply final, authoritative data for political, economic, and administrative outcomes (e.g., Congress, Bureau of Labor Statistics, National Weather Service). ### Sports Sports markets resolve using results published by the governing league or competition organizer (e.g., professional football leagues, professional basketball leagues, professional baseball leagues). ### News Some markets use news outlets as resolution sources. When the rules list specific outlets, resolution comes from **those exact outlets**. Some markets rely on a single outlet, while others require **agreement** between several named outlets (e.g., major news outlets, wire services, national newsrooms). ## Timing of Resolution Resolution occurs after the event outcome is publicly confirmed. * **Sports markets**: Often resolve shortly after the official result is posted * **Political or news markets**: May take longer if certification or official publication is required * **Complex events**: May require additional verification before resolution Resolution timing varies by event and depends on when reliable information becomes available. ## After Resolution When a market resolves: * Winning shares settle at **\$1.00** * Losing shares settle at **\$0.00** * Your Portfolio updates automatically when settlement completes ## Finality of Resolution All resolutions are final in accordance with the [**Polymarket US Exchange Rulebook**](https://polymarketexchange.com/regulatory.html). # Trading Hours Source: https://docs.polymarket.us/learn/trading/access-and-limits/trading-hours Learn about Polymarket US trading availability, reporting schedule, and maintenance policy Polymarket US operates nearly 24/7, with a recurring weekly maintenance window every **Thursday from 2:00–6:00 AM ET**. Trading may also be temporarily suspended if technical issues, emergency maintenance, or other operational needs require action to maintain security, integrity, or orderly market operation. ## Emergency Maintenance Trading may be paused without prior notice to protect market participants or ensure system stability. Service resumes once operational integrity is restored. ## Reporting Schedule Daily market and operational reporting occurs as of **5:00 PM Eastern Time (ET)** each business day. This timestamp is used for performance metrics, reconciliations, and regulatory recordkeeping. ## Compliance Notice All trading activity is governed by the [**Polymarket US Exchange Rulebook**](https://polymarketexchange.com/regulatory.html), applicable U.S. regulations, and internal operational procedures. Trading availability may be adjusted when necessary to maintain compliance, transparency, and market integrity. # Trading Limits Source: https://docs.polymarket.us/learn/trading/access-and-limits/trading-limits Understand Polymarket US trading limits Polymarket US does not set trading size limits. You can place any order size, but it will only fill if there are **matching buy or sell orders** at that price. Large orders may result in partial fills or fill at different prices if there are not enough matching orders at one level. Before placing a large order, review the order book to see available prices and sizes. # Trading Restrictions Source: https://docs.polymarket.us/learn/trading/access-and-limits/trading-restrictions Guidelines for trading on Polymarket US under compliance and eligibility rules Polymarket US event contracts are regulated event-based derivatives. They reflect outcomes of real-world events, not company performance or securities. ## Who Can Trade Employees at **sell-side institutions** (banks, broker-dealers, advisory firms) and **buy-side institutions** (hedge funds, family offices, private equity firms) are generally permitted to trade on Polymarket US, unless their firm's internal policies state otherwise. ## When You Should Not Trade You should not trade if: * Your firm interacts with event contracts as part of its business, or * You possess **material nonpublic information (MNPI)** related to the event. ## Compliance Notice Review your firm's compliance manual and confirm with your compliance department before trading on Polymarket US. # Market Structure Source: https://docs.polymarket.us/learn/trading/basics/buying-yes-vs-selling-no Understand how Polymarket US markets use a single instrument per outcome Each market on Polymarket US has **one instrument** representing a specific outcome. You take positions by buying or selling (shorting) that single instrument. ## How It Works Every binary market has only **one instrument**: * **Buying the instrument** = taking the YES side of that outcome * **Selling (shorting) the instrument** = taking the NO side of that outcome **Example**: NFL game between Team A and Team B * There is one instrument: "Team A wins" * **Go long** (buy) = you think Team A will win * **Go short** (sell) = you think Team A will lose (Team B wins) ## Buying vs Shorting **Buying (Going Long)** * You pay the current price (e.g., \$0.70) * If the outcome happens, you receive \$1.00 * If the outcome doesn't happen, you receive \$0.00 * Maximum profit: \$1.00 minus purchase price * Maximum loss: your purchase price **Shorting (Going Short)** * You receive the current price (e.g., \$0.70) * If the outcome happens, you pay \$1.00 * If the outcome doesn't happen, you pay \$0.00 * Maximum profit: sale price * Maximum loss: \$1.00 minus sale price For more details on shorting mechanics, see [Collateral and Margin](/market-structure/collateral-and-margin#shorting-mechanics). ## Synthetic No Position There are no separate "No" shares to trade. Instead: * To take the NO side of an outcome, you **short** the instrument * Shorting creates a synthetic NO position * The profit/loss works exactly as if you owned a NO share ## Key Points * Each market has **one instrument** per outcome * **Buy** to take the YES side, **short** to take the NO side * There are no separate YES and NO tokens * Prices reflect the market's implied probability of the outcome # Fractional Contracts Source: https://docs.polymarket.us/learn/trading/basics/fractional-shares Learn why Polymarket US only supports whole contracts and does not offer fractional contracts Polymarket US does not support fractional contracts. All trades are executed in **whole event contracts**. ## How It Works When you buy using a dollar amount, the system purchases as many whole contracts as that amount can buy at the current market price. Any remaining amount that is not enough to buy a full contract returns to your cash balance. ## Example You want to spend 100 dollars to buy Yes contracts priced at **\$0.65**: * Each contract costs **\$0.65** * 100 ÷ 0.65 = **153.84 contracts** * You receive **153 whole contracts** for \$99.45 * The remaining **\$0.55** returns instantly to your cash balance Only whole contracts are purchased. No fractional amounts are created. ## Key Points * Polymarket US only supports **whole contracts** * When buying with a dollar amount, you receive the maximum number of whole contracts available at the current price * Any unused amount that cannot buy a full contract returns immediately to your cash balance # Order Types Source: https://docs.polymarket.us/learn/trading/basics/order-types Learn how marketable limit orders execute and how fills work On Polymarket US, all orders are processed as **marketable limit orders**. ## How It Works 1. When you place an order, it executes at the best available price shown on the screen. 2. If enough size is available at that price, your entire order is filled. 3. If there isn't enough size, you receive a **partial fill**. 4. The unfilled portion stays on the order book as an **open order** until it is filled or canceled. Execution is always based on available liquidity and standard time-and-price priority within the central limit order book (CLOB). ## Example You buy 1,000 Yes contracts when the best ask is \$0.52: * If at least 1,000 contracts are available at \$0.52 or better, the entire order is filled immediately. * If only 600 contracts are available at \$0.52, you receive a partial fill for 600 contracts. * The remaining 400 stay on the order book at \$0.52 as an open order until they are filled or canceled. * Your execution price reflects only the portion that filled. ## Key Points * **Price protection**: Your order will never be filled at a worse price than the one you set. * **Partial fills**: Only available size is filled. Any remaining portion stays on the book as an open order. * **Dynamic markets**: Prices and available size may change while your order is being filled, but it will only be filled at your set price. * **Priority**: Orders match using standard time-and-price priority within the CLOB. * **Compliance**: All orders are handled in accordance with applicable law and Polymarket US platform rules to ensure fair and orderly trading. # Order Placement Source: https://docs.polymarket.us/learn/trading/basics/place-order Learn how to place an order on Polymarket From the home screen, browse the list of available markets. Tap any market to open it. Select **Yes** if you think the event will happen or **No** if you think it will not. Enter the **dollar amount** you want to trade or tap Max to use your available balance. Review your order details, then swipe up to confirm. Open the **Portfolio** tab to track your position and market value. Positions update in real time as market prices change. You can **cash out** anytime while the market is open or **hold** until it resolves. # Can I sell early? Source: https://docs.polymarket.us/learn/trading/basics/sell-early Cash out your positions at any time You can close your position at any time. You do not need to wait for a market to resolve. When you sell, the proceeds are added immediately to your cash balance. As a market gets closer to resolution, prices can change quickly. This can affect the price you receive if you sell before the outcome is known. # Spreads Source: https://docs.polymarket.us/learn/trading/basics/spread Learn how the gap between bid and ask affects your execution price Every market has two prices: * The **bid** is the highest price buyers are willing to pay * The **ask** is the lowest price sellers are willing to accept The **spread** is the gap between these two prices. A wider gap means you may pay more when buying or receive less when selling. ## How It Works 1. When you buy, you pay the **ask** price. 2. When you sell, you receive the **bid** price. 3. The difference between them is the **spread**. 4. Tighter spreads mean better execution. 5. Wider spreads mean higher trading cost. **Tight spread:** A tight spread means the bid and ask are close together, common in more liquid markets. **Wide spread:** A wide spread means the bid and ask are far apart, common in less liquid markets. ## Key Points * You buy at the **ask** and sell at the **bid** * The spread is the gap between these two prices * Wider spreads increase your trading cost * More liquid markets usually have tighter spreads * Your execution price depends on the bid, the ask, and the available size # Odds Display Source: https://docs.polymarket.us/learn/trading/prices-and-fees/odds-display Learn how to switch between percent and price display in the app From the home screen, tap the **Odds** icon at the top right. Select **price** (¢) or **percent chance** (%) from the odds menu. Your selected display format applies across all markets. *** ## Example In a live Washington vs. Kansas City market: | Display Type | Example | | -------------- | ---------------- | | Price | WAS 17¢ / KC 86¢ | | Percent chance | WAS 17% / KC 86% | All display formats show the same market information — only the way it's shown changes. # Price Slippage Source: https://docs.polymarket.us/learn/trading/prices-and-fees/price-slippage Learn how price slippage occurs and how to reduce its impact Price slippage occurs when the price you expect to sell at is different from the price your order actually receives. This usually happens when liquidity is low or when an outcome appears nearly decided. ## Why It Happens 1. **Markets stay open until settlement**\ Event contracts on Polymarket US remain open until the final outcome is confirmed, even if the result looks certain. 2. **Liquidity thins on the side that is already priced as the likely winner**\ As a result becomes clear, demand often drops on the side priced as the likely winner. If you try to exit a winning position early, there may not be enough buyers at the expected price. 3. **Prices can move while you sell**\ With limited liquidity, your sell order may fill at lower levels than the displayed price. For example, if a contract is trading near 97¢ close to resolution, limited demand could cause your order to fill at 94¢ instead of the expected 97¢. ## Best Practices * **Consider holding until settlement**\ When liquidity is thin, holding through resolution may provide better value, since winning contracts settle at full price. * **Always check before selling**\ Compare your position value and the current trading price to understand what you may receive if you exit early. If you would like to review a recent trade, contact **Polymarket US Support** — we can walk you through it. # Collateral and Margin Source: https://docs.polymarket.us/market-structure/collateral-and-margin Learn how collateral and margin work for long and short positions Polymarket Exchange operates with **fully-collateralized contracts**, meaning sufficient funds are locked to cover the maximum possible payout at the time the trade is executed. No additional funds are required afterward. ## How Collateral Works When a trade executes at a given price: **Buyers (Long Positions)** * Pay the contract price * No additional margin required * Maximum loss: amount paid * Maximum gain: \$1.00 – price paid **Sellers (Short Positions)** * Receive the contract price as proceeds * Post \$1.00 margin per contract (full payout value) * Fiat balance increases by sale proceeds * Buying power decreases by (Payout Value – Sale Price) * Maximum loss: \$1.00 – sale price * Maximum gain: sale price **Example: Trade at \$0.40** | Participant | Cash Flow | Margin Required | Buying Power Change | | ----------- | --------- | --------------- | ------------------- | | Buyer | –\$0.40 | \$0 | –\$0.40 | | Seller | +\$0.40 | \$1.00 | –\$0.60 | At settlement, **Polymarket Clearing** holds the seller's \$1.00 margin to guarantee payout. The buyer's \$0.40 payment becomes the seller's proceeds. ## Maximum Gain and Loss Once a trade is executed, maximum gain and loss are fixed and do not change regardless of subsequent price movements. **For a contract trading at \$0.40:** | Position | Max Loss | Max Gain | | ------------ | -------- | -------- | | Buy (Long) | \$0.40 | \$0.60 | | Sell (Short) | \$0.60 | \$0.40 | ## Shorting Mechanics Shorting lets you take the opposite side of a market by **selling a yes contract without owning it**. You receive the sale price immediately, and margin equal to the full payout value (\$1.00 per contract) is locked to cover your potential obligations at settlement. Shorts are created by selling yes contracts and posting \$1.00 margin per contract. The collateral requirement is the full payout value (\$1.00), not max loss. There is no collateral release from offsets or favorable price moves. ### Trading Examples **Buying yes at \$0.60 (Long Position)** * **At trade:** You pay \$0.60 per contract. Fiat balance decreases by \$0.60, and buying power decreases by \$0.60 because cash has been converted into a position. * **Position value:** \$0.60 (quantity × last price) * **If yes wins:** You receive \$1.00 (P/L = +\$0.40) * **If yes loses:** You receive \$0 (P/L = –\$0.60) **Selling yes at \$0.60 (Short Position)** * **At trade:** You receive \$0.60 in sale proceeds. Fiat balance increases by \$0.60. You must post margin equal to the payout value: \$1.00 per contract. The net effect on buying power: +\$0.60 (proceeds) – \$1.00 (margin) = **–\$0.40**. * **Position value:** \$0.40 (quantity × \[\$1.00 – \$0.60]) * **If yes wins:** Loss is the full \$1.00 payout minus the \$0.60 proceeds (P/L = –\$0.40) * **If yes loses:** You keep the \$0.60 proceeds and margin is released (P/L = +\$0.60) ### P/L Summary Table | Action | Outcome | P/L | | ---------------- | --------- | ------- | | Buy yes @\$0.60 | yes wins | +\$0.40 | | Buy yes @\$0.60 | yes loses | –\$0.60 | | Sell yes @\$0.60 | yes wins | –\$0.40 | | Sell yes @\$0.60 | yes loses | +\$0.60 | ## Short Position Details In a "Did X happen?" market, you can express a view by either buying yes (trading on the event happening) or selling yes (trading on the event not happening). When you take a short position, you are selling yes without owning it; i.e., expressing the view that the event will not occur. All shorts must be fully collateralized through a margin requirement equal to the payout value (\$1.00 per contract), ensuring you can cover potential losses if the market moves against you. For example, if yes is trading at \$0.60 and you short 100 contracts, you receive \$60 in proceeds from the buyer, but you must also post \$100 in margin. Your fiat balance increases by \$60, but your buying power decreases by \$40 (\$60 proceeds – \$100 margin locked). **Cash flow by position type:** * **Long yes:** Fiat balance decreases by the purchase cost, buying power decreases by the same amount (cash converted to position). * **Short yes:** Fiat balance increases by sale proceeds, but buying power decreases by (Payout Value – Sale Price) due to the full \$1.00 margin locked per contract. If you attempt a trade that would cause your buying power to fall below zero, the trade will fail automatically. ## Open Orders and Order Collateralization Open orders consume buying power before they fill, and the risk check is **scoped per instrument**: when you submit an order, its worst-case loss is checked against your buying power counting your open orders in that same instrument only, not across your whole account. Open orders in other instruments do not reduce the buying power available to a new order. Worked example, with \$10 of buying power: | Order | Result | | ----------------------------------------------------------------- | ------------------------------------------------------------ | | One order with \$11 of worst-case loss | **Rejected** - exceeds buying power on its own | | Two \$10 orders on the **same** instrument, same side | **Rejected** - orders in one instrument aggregate | | One \$10 order on instrument A and one \$10 order on instrument B | **Both accepted** - each instrument is checked independently | This scoping is deliberate: it lets liquidity providers quote across many markets without fully funding every resting quote simultaneously. Within a single instrument, open orders are assessed at the worst case across your resting interest: the greater of the loss if all your bids fill or the loss if all your offers fill, with offsets for any existing position the orders would close. Quoting both sides of one order book therefore consumes only the riskier side, not the sum. ### Automatic cancellation of unfunded orders Because open-order exposure across instruments can exceed your balance, a fill can consume buying power that resting orders in other instruments were relying on. When that happens, the exchange re-checks your open orders per instrument and **automatically cancels** resting orders that are no longer fully funded, until your remaining open orders are supported. These arrive as unsolicited cancels on the order status stream - handle them in your order-state tracking. Executed trades are never unwound for collateral reasons: a fill consumes real balance, and the automatic cancellation above is what reconciles open-order exposure back inside your deposited collateral. ## Portfolio Value Calculation Portfolio value accounts for both buying power and position values: **For long positions:** Portfolio Value = Buying Power + (Quantity × Last Price) **For short positions:** Portfolio Value = Buying Power + (Quantity × \[Payout Value – Last Price]) Example: You have \$5 starting balance and sell 1 yes contract @ \$0.70: * Fiat Balance: \$5.70 (received \$0.70 proceeds) * Margin Requirement: \$1.00 (locked) * Buying Power: \$4.70 (\$5.70 – \$1.00) * Position value: 1 × (\$1.00 – \$0.70) = \$0.30 * Portfolio Value: \$4.70 + \$0.30 = \$5.00 ✓ ## Settlement and Payout At settlement, **Polymarket Clearing** releases funds automatically: * Winners receive **\$1.00 per contract** * Losers receive **\$0** * No margin calls, reconciliations, or additional obligations **Settlement Example: Seller at \$0.40** If the seller wins (event does not occur): * Seller keeps: \$0.40 proceeds * Margin released: \$1.00 * Total return: \$1.40 * Net profit: \$0.40 If the buyer wins (event occurs): * Seller's \$1.00 margin → paid to buyer * Seller keeps: \$0.40 proceeds * Net loss: \$0.60 ## Collateral Management Collateral and margin are managed at the clearing level: 1. You submit a withdrawal (e.g., \$100) via Aeropay. 2. The DCO (Derivatives Clearing Organization) reviews it. 3. The DCM (Designated Contract Market) denies the request if it would leave insufficient buying power to meet margin obligations. The same logic applies to open orders: unmatched longs or shorts cannot remain if they would breach margin limits. When a withdrawal is risk-checked, buying power is reduced by the largest open-order reservation among your instruments - for example, with \$100 in buying power, \$20 of open orders in instrument A and \$30 in instrument B, you may withdraw up to \$70. ## Portfolio Margin Polymarket Exchange applies portfolio-level margining to **positions**: margin requirements consider your entire set of open positions rather than treating each market in isolation, and mutually exclusive or directional events can reduce the requirement further (see [Mutually Exclusive Collateral Return](/market-structure/mutually-exclusive-collateral-return) and [Directional Collateral Return](/market-structure/directional-collateral-return)). Open **orders** are assessed per instrument, as described in [Open Orders and Order Collateralization](#open-orders-and-order-collateralization). ## Reducing or Closing a Short You can reduce or close a short at any time by buying back the yes contracts you sold. **Examples:** 1. **Short 1 yes, buy back at a lower price** - Your exposure is closed and margin is released. 2. **Short 10 yes, buy back 4** - Your remaining exposure decreases, and proportional margin is unlocked. ## Key Points * Buyers post the purchase price; sellers post \$1.00 margin per contract. * Maximum gain and loss are **fixed at the time of the trade**. * Sellers receive sale proceeds but must post full payout value as margin. * **Polymarket Clearing guarantees payout** from the seller's locked margin. * There are no margin calls. * Shorting lets you express a bearish view on the yes outcome and provides liquidity to the market. # Directional Collateral Return Source: https://docs.polymarket.us/market-structure/directional-collateral-return How portfolio margin optimization works for directional event outcomes Directional collateral return is a portfolio margin optimization that reduces your margin requirement when you hold offsetting positions in instruments from the same directional event. ## What Are Directional Events? Directional events have multiple instruments with ordered strike levels where outcomes are logically linked. If a higher threshold is true, all lower thresholds must also be true. Examples include: * **Point spreads**: Will the team win by more than 3.5? More than 6.5? More than 10.5? * **Totals**: Will the combined score exceed 40.5? Exceed 47.5? Exceed 50.5? * **Price levels**: Will Bitcoin exceed 50K? 75K? 100K? Each instrument has an ordinal rank (rank 1 = lowest threshold). If rank 3 resolves Yes, then ranks 1 and 2 must also resolve Yes. ## How Collateral Return Works When collateral return is enabled on your account and you hold a lower-ranked long position that offsets a higher-ranked short position in the same directional event, your margin requirement is reduced. Consider a Bitcoin price hit event with three instruments: | Instrument | Question | | ------------------------------------ | ------------------------------------- | | `cphc-btc-hit-2026-03-31-120000pt00` | What price will Bitcoin hit in March? | | `cphc-btc-hit-2026-03-31-130000pt00` | What price will Bitcoin hit in March? | | `cphc-btc-hit-2026-03-31-140000pt00` | What price will Bitcoin hit in March? | If Bitcoin hits 140,000, it must have also hit 130,000 and 120,000. This directional relationship is what enables collateral return. **Without Collateral Return:** * long 3 contracts of `cphc-btc-hit-2026-03-31-120000pt00` * short 2 contracts of `cphc-btc-hit-2026-03-31-130000pt00` * Margin requirement: 2 (full short position) * Buying power reduced by 2 **With Collateral Return:** * long 3 contracts of `cphc-btc-hit-2026-03-31-120000pt00` * short 2 contracts of `cphc-btc-hit-2026-03-31-130000pt00` * Margin requirement: 0 (short fully offset by lower-ranked long) * Buying power: no reduction ## Why This Matters Your long position at the lower strike guarantees a payout in any scenario where your short position at the higher strike loses. If Bitcoin hits 130,000 (your short loses), it must have also hit 120,000 (your long wins), so the long payout covers the short's loss. **Maximum loss calculation:** * Bitcoin below 120,000: Long loses, short wins = net depends on entry prices * Bitcoin between 120,000 and 130,000: Both positions win * Bitcoin above 130,000: Long wins, short loses - but long payout offsets short loss The exchange recognizes this natural hedge and reduces your margin accordingly. ## Directionality Matters Only a lower-ranked long can offset a higher-ranked short. The reverse does not work. * long `cphc-btc-hit-2026-03-31-120000pt00`, short `cphc-btc-hit-2026-03-31-130000pt00` - collateral return applies * long `cphc-btc-hit-2026-03-31-130000pt00`, short `cphc-btc-hit-2026-03-31-120000pt00` - NO collateral return This is because Bitcoin hitting 130,000 guarantees it also hit 120,000, but Bitcoin failing to hit 120,000 tells you nothing useful - 130,000 already failed too, so there's no offset. ## Multiple Offsets A single lower-ranked long position can offset multiple higher-ranked short positions. Using the Bitcoin example with more strike levels: * long 10 contracts of `cphc-btc-hit-2026-03-31-120000pt00` * short 2 contracts of `cphc-btc-hit-2026-03-31-130000pt00` * short 3 contracts of `cphc-btc-hit-2026-03-31-140000pt00` * short 6 contracts of `cphc-btc-hit-2026-03-31-150000pt00` * Total short: 11, Collateral return: 10 (2 + 3 + 5 from remaining long) * Margin requirement: 1 Multiple long/short pairs across different ranks can offset simultaneously. The exchange matches the highest-ranking short with the highest available long first, working down. ## Using Freed-Up Buying Power Freed-up buying power can be deployed into other markets (different events). This allows for more efficient capital utilization across your entire portfolio. However, you cannot use this freed-up buying power to increase your position in the same directional event that generated the collateral return. ## Closing Offsetting Positions When you close one of the offsetting positions, you must "return" the collateral that was freed up. **Example:** * You are long 3 contracts of `cphc-btc-hit-2026-03-31-120000pt00`, short 2 contracts of `cphc-btc-hit-2026-03-31-130000pt00` (collateral return of 2) * You use that freed buying power to trade in a different market * If you try to sell your long position, you must return the collateral * If that buying power is already deployed elsewhere, the order will be rejected Hypothetical collateral return from new orders is not factored into buying power checks. The exchange only considers your current positions when calculating collateral return. ## Key Points * Collateral return applies to directional events where lower thresholds must be true if higher ones are * A lower-ranked long offsets a higher-ranked short (not the reverse) * One long position can offset multiple short positions across higher ranks * Freed-up buying power can be used in other markets, not the same event * Closing offsetting positions requires returning the freed collateral * This is a portfolio margin optimization, not a reduction in actual risk # Mutually Exclusive Collateral Return Source: https://docs.polymarket.us/market-structure/mutually-exclusive-collateral-return How portfolio margin optimization works for mutually exclusive event outcomes Mutually exclusive collateral return is a portfolio margin optimization that reduces your margin requirement when you hold offsetting short positions in instruments from the same event where only one outcome can occur. ## What Are Mutually Exclusive Events? Mutually exclusive events have multiple possible outcomes, but only one can happen. Examples include: * **Elections**: Only one candidate can win * **Championships**: Only one team can win the title * **Award winners**: Only one nominee can win the award When you're short on multiple instruments from the same mutually exclusive event, the exchange recognizes that your maximum loss is capped because only one outcome can occur. ## How Collateral Return Works When collateral return is enabled on your account and you hold short positions across multiple instruments in the same mutually exclusive event, your margin requirement is reduced by the smaller position size. **Without Collateral Return:** * Short 9,000 contracts of Candidate A * Short 1,000 contracts of Candidate B (same election) * Margin requirement: 10,000 (9,000 + 1,000) * Buying power: 0 (if you started with 10,000 balance) **With Collateral Return:** * Short 9,000 contracts of Candidate A * Short 1,000 contracts of Candidate B (same election) * Margin requirement: 9,000 (reduced by the 1,000 offsetting position) * Buying power: 1,000 (freed up capital) ## Why This Matters Because only one candidate can win, your worst-case scenario is having the larger short position lose. The smaller offsetting position guarantees you'll win that amount, effectively reducing your net exposure. **Maximum loss calculation:** * If Candidate A wins: You lose 9,000 but gain 1,000 = Net loss of 8,000 * If Candidate B wins: You lose 1,000 but gain 9,000 = Net gain of 8,000 Your true maximum loss is 8,000, not 10,000. Collateral return recognizes this and only requires 9,000 in margin instead of 10,000. ## Using Freed-Up Buying Power The 1,000 in freed-up buying power can be deployed into other markets (different events). This allows for more efficient capital utilization across your entire portfolio. However, you cannot use this freed-up buying power to increase your position in the same mutually exclusive event that generated the collateral return. ## Closing Offsetting Positions When you close one of the offsetting positions, you must "return" the collateral that was freed up. **Example:** * You have 9,000 short Candidate A, 1,000 short Candidate B * Collateral return gives you 1,000 buying power * You use that 1,000 to trade in a different market * If you try to buy back the 1,000 contracts of Candidate B, you must return the 1,000 in collateral * If that 1,000 is already deployed elsewhere, the order to close will be rejected ## Portfolio Margin Optimization Collateral return is part of Polymarket US's portfolio-level margining system. The exchange calculates margin requirements across your entire portfolio, recognizing natural hedges and offsetting positions to maximize capital efficiency. This differs from position-by-position margining where each position is treated in isolation, requiring full margin regardless of offsets. ## Key Points * Collateral return only applies to mutually exclusive events where one outcome must occur * Your margin requirement is reduced by the smaller offsetting position size * Freed-up buying power can be used in other markets, not the same event * Closing offsetting positions requires returning the freed collateral * This is a portfolio margin optimization, not a reduction in actual risk # Deposits & Withdrawals Source: https://docs.polymarket.us/partners/funding/deposits-withdrawals Mirroring wallet allocations into on-platform buying power with deposit and withdrawal transfers. **BETA — SUBJECT TO CHANGE.** This capability is in beta and may change without notice. A participant's buying power at Polymarket US is the cash in their trading account. Your funding entity manages that cash with two [transfer](/partners/funding/transfers) reasons — **`DEPOSIT`** (partner funding account → participant account) and **`WITHDRAWAL`** (participant account → funding account) — mirroring what the participant does in their wallet: ```mermaid theme={null} sequenceDiagram participant RP as Retail Participant participant FE as Your Funding Entity participant PM as Polymarket US RP->>FE: "Allocate $500 to trading" (wallet, off-platform) FE->>PM: DEPOSIT transfer — $500, funding account → participant account PM-->>FE: CONFIRMED Note over PM: Participant can trade $500 within seconds RP->>FE: "Withdraw $200" (wallet, off-platform) FE->>PM: WITHDRAWAL transfer — $200, participant account → funding account PM-->>FE: CONFIRMED Note over FE: Funding entity settles the $200
to the participant per their wallet terms ``` **Why this beats bank rails:** the participant's fiat already sits with your funding entity. Allocating it to trading is one platform ledger transfer — seconds — instead of a bank transfer to the clearinghouse — hours to days. The time between *"I want to trade with these funds"* and *"I can submit a trade with these funds"* is one confirmed `DEPOSIT` transfer. ## Deposits Create a `DEPOSIT` transfer when the participant allocates wallet funds to trading: * **Event-driven, typically 1:1.** One transfer per wallet allocation event is the intended pattern. If wallet deposits and withdrawals are the only ways a participant's allocation changes, mirroring them keeps on-platform buying power in lock-step with the wallet. * **Latency-sensitive.** A participant is typically waiting to trade behind a deposit. Give deposits priority over batch work (fee collections, scheduled withdrawals) in your transfer queue — see [rate limits and smoothing](/partners/funding/transfers#rate-limits-and-smoothing). * **Funded from the pool.** A deposit is rejected if the partner funding account can't cover it — size and top up the pool for peak allocation demand, per [sizing the funding account](/partners/funding/overview#sizing-the-funding-account). * **Consider batching micro-events.** If your product produces many small allocations per participant (e.g. round-ups), aggregate them into fewer, larger deposits rather than one transfer each — transfers scale with wallet events, and the [budget](/partners/funding/overview#the-transfer-budget) is shared across your integration. ## Withdrawals Create a `WITHDRAWAL` transfer when the participant de-allocates funds from trading. The amount must be covered by the participant's **free cash**: ``` free cash = account cash − collateral locked by open orders and positions ``` * **Check before you initiate.** Confirm free cash via your ledger projection (authoritative read: `PositionAPI/GetAccountBalance` — see [Reconciliation](/partners/reconciliation#cash-balances-polymarket-v1-positionapi)). A withdrawal exceeding free cash is [rejected](/partners/funding/transfers#insufficient-funds). * **Leave the fee earmark behind.** Accrued, uncollected vendor fees are cash in the same account. Cap withdrawable amount at `free cash − accrued uncollected vendor fees`, or your fee collection becomes uncollectable — see [Vendor Fees](/partners/funding/vendor-fees#accrued-fees-are-credit-exposure). * **Open orders lock collateral.** If the participant wants to withdraw more than their free cash, they (or you, on their behalf) must first cancel open orders or close positions to release collateral. * **Trading proceeds are withdrawable.** Settlement credits, realized profit, and released collateral accumulate in the account as free cash — a participant "cashing out winnings" is just a withdrawal like any other. ## Keeping the mirror honest Both legs of every confirmed transfer post to the [balance ledger](/streaming-endpoints/balance-ledger-stream) as typed entries (`DEPOSIT` / `WITHDRAWAL`), alongside trading events. Reconcile per account: ``` account cash = Σ deposits − Σ withdrawals − Σ vendor fee collections − collateral locked ± realized P&L + settlement credits − exchange fees ``` Your funding entity's wallet ledger and the platform's account ledger should agree on the allocation at all times; the [Reconciliation](/partners/reconciliation) page covers the stream-first pattern for maintaining that projection across your participant base. ## Related pages The API — request shape, idempotency, rate limits. The model and the transfer budget. The fee earmark that withdrawals must respect. Ledger projection and authoritative balance reads. # Partner Funding Source: https://docs.polymarket.us/partners/funding/overview How participant trading accounts are funded — instant deposits from your funding entity's pre-positioned pool, order-time vendor fee declaration, and periodic fee collection. **BETA — SUBJECT TO CHANGE.** Partner funding is in beta, enabled per partner. Email **[institutional@polymarket.us](mailto:institutional@polymarket.us)** to discuss access for your integration. Partner funding is the funding model for IB/ISV partners: your funding entity **pre-positions pooled funds** in a **partner funding account** at Polymarket US — the banking happens ahead of time. When a participant allocates funds to trade, your funding entity moves cash from that pool into their trading account with an instant **deposit transfer**; when they withdraw, cash moves back with a **withdrawal transfer**. Orders then trade against the cash in the participant's account like any other order — no money moves at order time. Your **vendor fee** is *declared* on each order placement and recorded by Polymarket US, but it is **not** moved per order. Fees accrue as a receivable and are collected periodically — at most once per day — with a single **vendor fee transfer** per participant account, reconciled against a daily [Vendor Fees report](/partners/funding/vendor-fees). Partner funding is available to **IB and ISV partners with a configured funding relationship** only. The service authenticates your partner firm and looks up your funding configuration; calls from firms without a funding relationship are rejected. ## Why this model exists **The problem is bank speed.** If every Retail Participant had to move cash from their bank into the clearinghouse before trading, fiat rails would set the pace of your product: a newly onboarded user couldn't place their first trade until their deposit cleared, and an existing user topping up would wait just as long between requesting a deposit and having funds available to trade. Bank transfers take hours to days; a trading opportunity doesn't. **The solution is to keep the cash close.** Participant cash is custodied by your *funding entity* (typically the wallet provider affiliated with your firm), which maintains one pooled **partner funding account** at Polymarket US, pre-positioned ahead of trading. When a participant decides to trade, funding their account is an **instant ledger transfer** — pool → participant account, seconds not days. The time between *"I want to trade with these funds"* and *"I can submit a trade with these funds"* collapses to the time of one platform transfer. **The structure follows the rules on who may hold customer funds.** Regulation constrains which intermediaries are permitted to hold customer funds — and an IB or ISV is not one of them. The model is designed around that constraint: * The pooled funds are held by the **funding entity** — a separate legal entity in your corporate structure — not by your firm. * Your firm is a pure **message facilitator**: you authenticate as the Firm, submit orders and transfer instructions on behalf of Retail Participants and your funding entity. Your firm's own identity never holds a balance. * Money moves between exactly two places — the partner funding account and a participant account under your Firm — for exactly three reasons: **deposit**, **withdrawal**, and **vendor fee collection**. Any other movement is rejected by the platform. Every dollar has a typed, directional audit trail by design. ## The three parties | Party | Role | Holds funds at Polymarket US? | | ----------------------- | ------------------------------------------------------------------------------------------------------------------ | ------------------------------------- | | **Retail Participant** | The trader. Owns a trading account that holds their cash, order collateral, and positions. | Yes — their allocated trading balance | | **Your firm (IB/ISV)** | Message facilitator. Submits orders and transfer instructions on behalf of the participant and the funding entity. | **No — never** | | **Your funding entity** | Separate legal entity that custodies participant cash off-platform and holds the **partner funding account**. | Yes — the unallocated pool | The participant, your firm, and your funding entity coordinate through your product's UX and the legal agreements between the three parties. From the participant's point of view they allocate funds in their wallet and trade — your backend turns that into a deposit transfer and order placements. ## Getting set up The structure is established once, coordinated with your Polymarket US integration lead during [partner onboarding](/partners/get-connected/onboarding): 1. **Create your funding entity** — a separate legal entity that will custody participant cash and hold the pooled funding account. Your firm cannot hold participant funds itself. 2. **Two onboardings, coordinated as one workflow** — your firm onboards as an access shell (holds no funds, no positions); your funding entity onboards as its own entity with an active, funded account. **Your funding entity must exist and be active before your firm's API credentials can be enabled.** 3. **Credentials linked to the funding account** — your firm's API credentials are provisioned against your funding entity's account. Every transfer you submit has the partner funding account as one side, enforced by the platform. 4. **Each participant gets a \$0 account** — provisioned automatically on KYC approval (see [Onboard Participants](/partners/onboarding/onboard-participants)). Accounts hold cash from their first deposit transfer onward. 5. **Three-party agreement in place** — participant, your firm, and your funding entity must have an agreement authorizing the deposits, withdrawals, and vendor fee collections. The funding relationship is fixed per firm: a firm's participants are either all funded through the partner funding account or not — there is no per-participant mixing. ## How money moves ```mermaid theme={null} sequenceDiagram participant RP as Retail Participant participant App as Your App participant FE as Your Funding Entity participant PM as Polymarket US RP->>FE: Allocate funds to trade (wallet, off-platform) FE->>PM: Deposit transfer — funding account → participant account Note over PM: Buying power available in seconds RP->>App: Place order App->>PM: Order placement (+ declared vendor fee) PM-->>App: Accepted — collateral & exchange fee checked against account cash Note over PM: Fills, fees, settlement credits, and P&L
post to the participant account PM-->>FE: Vendor Fees report (daily) FE->>PM: Vendor fee transfer — participant account → funding account (≤ 1/day) RP->>FE: Withdraw (wallet) FE->>PM: Withdrawal transfer — participant account → funding account ``` | Flow | Direction | When | Moved by | | ------------------------- | ------------------------------------- | --------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | | **Deposit** | Funding account → participant account | Participant allocates funds to trade | Your funding entity, via [transfer](/partners/funding/transfers) | | **Trading** | Within the participant account | Continuously | Polymarket US — collateral locks, exchange fees, fills, settlement credits, realized P\&L all post to the account | | **Vendor fee accrual** | *No movement* | Each order placement | Recorded only — Polymarket US stores your declared fee against the order | | **Vendor fee collection** | Participant account → funding account | Periodic, at most once per day | Your funding entity, via [transfer](/partners/funding/transfers), reconciled against the [Vendor Fees report](/partners/funding/vendor-fees) | | **Withdrawal** | Participant account → funding account | Participant withdraws from their wallet | Your funding entity, via [transfer](/partners/funding/transfers) | **Trading proceeds stay put.** Settlement credits, realized profit, and released collateral remain in the participant's account — they *are* the participant's buying power for the next trade. Nothing needs to be swept after fills or settlements; cash only leaves an account for a withdrawal or a vendor fee collection. ## Buying power Two numbers matter for every order, and they are owned by different systems: | Check | Owner | Formula | | ------------------ | ------------------------- | -------------------------------------------------------------------------------------------------------------------- | | **Platform check** | Polymarket US | An order is rejected unless the participant account's available cash covers **worst-case collateral + exchange fee** | | **Your gate** | You & your funding entity | Spendable balance = account cash − **accrued, uncollected vendor fees** | Polymarket US knows nothing about your fee basis and does not reserve for vendor fees — it only records the amounts you declare. Between collections, accrued fees are cash sitting in the participant's account that the participant could otherwise trade with. **Your platform must gate order submission off-platform** so a participant cannot spend the cash that is earmarked for your accrued fees. See [Vendor Fees](/partners/funding/vendor-fees) for the accrual model and the credit-risk consequences of not gating. If deposits and withdrawals are the only balance events in your wallet product, mirroring them 1:1 with deposit and withdrawal transfers keeps on-platform buying power exactly in step with the participant's wallet allocation — see [Deposits & Withdrawals](/partners/funding/deposits-withdrawals). ## The transfer budget Platform transfers are a **limited, shared resource — budget for 5 transfers per second** across your integration. The model is designed to fit comfortably inside that: * **Deposits and withdrawals are event-driven** — typically one transfer per wallet deposit/withdrawal event per participant. No per-order movement. * **Vendor fees are batched** — one transfer per participant account per collection period, **at most once per day** (weekly or monthly are fine too). * **Trading itself uses zero transfers** — collateral, exchange fees, fills, and settlement all post inside the participant account. Do not design flows that scale transfers with order volume, and smooth out bursts — for example, spread an end-of-day vendor fee collection run across minutes rather than firing one call per account simultaneously. See [Transfers](/partners/funding/transfers#rate-limits-and-smoothing) for concrete rate-limit handling patterns. ## Sizing the funding account The partner funding account is the **unallocated pool** — participant trading balances live in their own accounts. Size the pool to cover expected **deposit demand** between top-ups from your funding entity's treasury: peak concurrent "allocate funds" flow, not open interest. A deposit transfer fails if the pool can't cover it. Use `GetFundingAccountBalance` to read the authoritative, current pool balance on demand. The service resolves the partner funding account from your authenticated firm identity, so you do not supply an account ID. Poll at modest rates to monitor available capacity, and read the balance before or after treasury top-ups and large transfer batches when you need a current value. A real-time mirror remains useful for alerting and reconciliation. Seed and periodically verify it with `GetFundingAccountBalance`, and investigate any drift from this identity: ``` pool balance = treasury top-ups (external) − Σ deposit transfers (out to participant accounts) + Σ withdrawal transfers (back in) + Σ vendor fee collections (back in) ``` The RPC is the source of truth when the mirror and the returned balance differ. See [GetFundingAccountBalance](/partners/funding/transfers#getfundingaccountbalance) for the request, response, and retry behavior. ### Deposits and withdrawals at the wallet A participant's fiat deposits and withdrawals are movements between the participant and your funding entity — part of their wallet relationship, settled through your payment provider, entirely outside Polymarket US. What reaches Polymarket US is the allocation: when a participant designates wallet funds for trading, your funding entity mirrors that allocation onto the platform with a deposit transfer, and mirrors de-allocations back with withdrawal transfers. ## Directional guarantees The platform enforces the money-flow rules at the API level. Your partner funding account is **always one side** of every transfer, and a participant account under your Firm is always the other: | Money movement | Reason | Allowed? | | ------------------------------------------------------------------------------ | ------------- | ---------- | | Funding account → participant account | `DEPOSIT` | ✅ | | Participant account → funding account | `WITHDRAWAL` | ✅ | | Participant account → funding account | `VENDOR_FEES` | ✅ | | Participant account → your firm, another participant, or any other destination | — | ❌ Rejected | This is what lets your firm operate without ever holding participant funds: you can only route money between the participant and the funding entity that custodies their wallet. ## API surface The partner funding APIs are **gRPC-only**. Connection and authentication follow the standard conventions — TLS and a Bearer access token in the `authorization` metadata header; see the [gRPC API Overview](/grpc-api/overview) and [Authentication](/streaming-endpoints/authentication). | Concern | Service | Pages | | -------------------------------------------- | --------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- | | Order placement with vendor fee declaration | `polymarket.us.orderfunding.v1.OrderFundingService/CreateVendorOrder` | [Create Order](/partners/orders/create-order) · [Data Model](/partners/orders/data-model) | | Deposits, withdrawals, vendor fee collection | `polymarket.us.cashmovement.v1.CashMovementService` | [Transfers](/partners/funding/transfers) | | Fee accrual reporting | Daily Vendor Fees report | [Vendor Fees](/partners/funding/vendor-fees) | **Orders that carry a vendor fee must go through `OrderFundingService`.** It records your declared fee against the order and passes the order through to the exchange. Orders placed via generic order entry or FIX carry no vendor fee and accrue nothing. ## Reconciliation Every cash event — deposits, withdrawals, vendor fee collections, fills, exchange fees, settlement credits — is visible on the account's cash ledger. Use the standard surfaces to keep your books and your funding entity's records in sync: Real-time balance impact of every cash event, with replay. Funding transaction state changes. Firm-wide execution reports for placed orders. See [Reconciliation](/partners/reconciliation) for the stream-first operating pattern — including how to consume ledger data across your whole participant base. ## Next steps Mirror wallet allocations into on-platform buying power. Declare fees per order, track accrual, collect daily. The transfer API — reasons, direction locks, rate limits. Place an order with a declared vendor fee. # Transfers Source: https://docs.polymarket.us/partners/funding/transfers The cash-movement API for deposits, withdrawals, and vendor fee collection between the partner funding account and participant accounts. **BETA — SUBJECT TO CHANGE.** This API is in beta and may change without notice. A **transfer** moves cash between your **partner funding account** and a **participant account** under your Firm. It is the only way money enters or leaves a participant account outside of trading itself, and it exists for exactly three reasons: | Reason | Direction | Business event | | ------------- | ------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | | `DEPOSIT` | Funding account → participant account | The participant allocated wallet funds to trade — see [Deposits & Withdrawals](/partners/funding/deposits-withdrawals) | | `WITHDRAWAL` | Participant account → funding account | The participant withdrew funds from trading — see [Deposits & Withdrawals](/partners/funding/deposits-withdrawals) | | `VENDOR_FEES` | Participant account → funding account | Periodic collection of accrued vendor fees — see [Vendor Fees](/partners/funding/vendor-fees) | **Direction is fixed by the reason.** You name only the participant account; the platform resolves your funding account from your firm's configured funding relationship and derives the source and destination from the reason. There is no way to express any other movement — transfers to your firm, between participants, or to an external destination are structurally impossible. ## Service **Service:** `polymarket.us.cashmovement.v1.CashMovementService` | RPC | Purpose | | -------------------------- | ----------------------------------------------------------------------- | | `CreateCashMovement` | Create an idempotent deposit, withdrawal, or vendor fee transfer. | | `GetCashMovement` | Read a transfer workflow by `workflow_id`. | | `GetFundingAccountBalance` | Read the authoritative current balance of your partner funding account. | All methods require the `write:cash-movements` scope. Cash-movement calls are **firm-scoped** — do not send `x-participant-id`. Your firm submits transfers and reads the funding account on behalf of your funding entity under the three-party agreement; the platform verifies your firm is authorized for the funding account on every call. ### CreateCashMovement ```protobuf theme={null} message CreateCashMovementRequest { string idempotency_key; oneof intent { Transfer transfer; } } message Transfer { TransferReason reason; // DEPOSIT | WITHDRAWAL | VENDOR_FEES string participant_account_id; // the participant account — the other side is always your funding account string amount; // exact decimal string currency; // ISO 4217, must be USD string external_reference; // your reference (required) string memo; // free text (optional) } enum TransferReason { TRANSFER_REASON_UNSPECIFIED = 0; TRANSFER_REASON_DEPOSIT = 1; TRANSFER_REASON_WITHDRAWAL = 2; TRANSFER_REASON_VENDOR_FEES = 3; } message CreateCashMovementResponse { CashMovement cash_movement; } ``` | Field | Required? | Notes | | ------------------------ | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `reason` | **Required** | Determines the direction — see the table above. | | `participant_account_id` | **Required** | The participant trading account, in the same format returned by the account onboarding/list APIs. Must belong to your Firm. | | `amount` | **Required** | The exact decimal amount to move. | | `currency` | **Required** | ISO 4217 code matching the account. `USD`. | | `external_reference` | **Required** | Your journal/ledger entry ID, or the instruction reference from your funding entity — it joins Polymarket US records to your books and to the three-party instruction chain. Quote it alongside `workflow_id` in support requests. | | `memo` | Optional | Human-readable note for support/audit — not parsed. | ### GetCashMovement ```protobuf theme={null} message GetCashMovementRequest { string workflow_id; } message GetCashMovementResponse { CashMovement cash_movement; } message CashMovement { string workflow_id; string idempotency_key; string intent_type; // "transfer" CashMovementStatus status; // PENDING, CONFIRMED, REJECTED, AMBIGUOUS string amount; string currency; string source_account_id; // derived from the reason string destination_account_id; // derived from the reason string dco_transfer_id; string rejection_reason; google.protobuf.Timestamp created_at; google.protobuf.Timestamp updated_at; TransferReason reason; } ``` The read model returns the typed `reason` and derived source and destination accounts, but it does not echo `participant_account_id`, `external_reference`, or `memo`. Persist those request fields in your own ledger alongside `workflow_id`. There are no order, execution, or market correlation fields on a transfer. Both sides of a confirmed transfer appear on the [balance ledger](/streaming-endpoints/balance-ledger-stream) of the affected accounts, so your projection and your funding entity's records see the same event. ### GetFundingAccountBalance `GetFundingAccountBalance` returns the authoritative balance of your partner funding account. The service resolves the account from your authenticated firm identity; there is no account parameter, and a firm can read only its own configured funding account. This is a pure read with no side effects and no idempotency key. It is suitable for polling at modest rates and for seeding or verifying a real-time balance mirror. ```protobuf theme={null} message GetFundingAccountBalanceRequest { string currency; } message GetFundingAccountBalanceResponse { string funding_account_id; string balance; string currency; google.protobuf.Timestamp as_of; } ``` #### Request fields | Field | Type | Required | Description | | ---------- | -------- | -------- | ------------------------------------------------------------------------------------------- | | `currency` | `string` | Yes | Currency configured for your funding relationship. Use `USD`; matching is case-insensitive. | #### Response fields | Field | Type | Description | | -------------------- | --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | | `funding_account_id` | `string` | Your partner funding account. This is the same identifier returned as `source_account_id` or `destination_account_id` on transfer reads. | | `balance` | `string` | Authoritative account balance as an exact decimal string. | | `currency` | `string` | Currency of the returned balance. | | `as_of` | `google.protobuf.Timestamp` | The exchange ledger's balance update time. | #### Example Request: ```json theme={null} { "currency": "USD" } ``` Response (Connect JSON representation): ```json theme={null} { "fundingAccountId": "firms/example-funding-entity/accounts/funding", "balance": "125000.50", "currency": "USD", "asOf": "2026-08-10T14:30:00Z" } ``` #### Errors | gRPC status | Meaning | Retry guidance | | ------------------- | -------------------------------------------------------------------------------- | -------------------------------------------------------------------------- | | `INVALID_ARGUMENT` | `currency` does not match the currency configured for your funding relationship. | Send the configured currency (`USD`). Do not retry the unchanged request. | | `UNAUTHENTICATED` | Missing or invalid access token. | Refresh the token and retry. | | `PERMISSION_DENIED` | Your firm is not configured for transfers. | Contact [institutional@polymarket.us](mailto:institutional@polymarket.us). | | `UNAVAILABLE` | The upstream balance read failed. | Retry with backoff. | ## Insufficient funds A transfer is rejected if the **source account** cannot cover the amount: | Reason | Source | Insufficient-funds case | | ------------- | ------------------- | ---------------------------------------------------------------------------------------------------------- | | `DEPOSIT` | Funding account | The pool is short — top it up from your funding entity's treasury and retry. | | `WITHDRAWAL` | Participant account | The participant's *free* cash (net of collateral locked by open orders and positions) is below the amount. | | `VENDOR_FEES` | Participant account | The participant's free cash is below the accrued amount — e.g. trading losses since accrual. | This is enforced across the system regardless of reason — no transfer can drive an account negative. **Managing the risk is on you and your funding entity**: gate spendable balance so accrued fees stay covered ([Vendor Fees](/partners/funding/vendor-fees#accrued-fees-are-credit-exposure)), and check free cash before initiating withdrawals ([Deposits & Withdrawals](/partners/funding/deposits-withdrawals#withdrawals)). ## Frequency rules Transfers are a limited, shared resource — see [the transfer budget](/partners/funding/overview#the-transfer-budget): * **`DEPOSIT` / `WITHDRAWAL`** — event-driven: **typically one transfer per wallet deposit/withdrawal event** per participant. Mirroring wallet events 1:1 is the intended pattern; batch micro-events into fewer, larger transfers where your product allows. * **`VENDOR_FEES`** — **at most once per day per participant account.** Collecting weekly or monthly is fine; collecting more often than daily is not permitted. ## Rate limits and smoothing Budget for a maximum of **5 transfer calls per second** across your whole integration. Requests over the limit are rejected with gRPC `RESOURCE_EXHAUSTED` (HTTP 429 on any REST mapping) — the transfer was **not** created, and it is always safe to retry with the same `idempotency_key`. Design for the budget rather than reacting to rejections: * **Single dispatcher, client-side queue.** Route every transfer through one queue per environment drained at a fixed rate below the cap (e.g. 4/s, leaving headroom for retries). Never fan transfers out from concurrent workers straight to the API. * **Spread scheduled runs.** An end-of-day vendor fee collection across 2,000 participant accounts at 4/s takes \~8–9 minutes — schedule the run as a paced drain, not 2,000 simultaneous calls at the stroke of EOD. * **Retry with backoff and the same key.** On `RESOURCE_EXHAUSTED`, re-enqueue with jittered exponential backoff (e.g. 1s → 2s → 4s, ±20% jitter) and the **same** `idempotency_key`. The rejection happened before creation, so the retry is a fresh, safe attempt. * **Deposits preempt fee collections.** If a participant is waiting to trade, their deposit is latency-sensitive; a fee collection is not. Give `DEPOSIT` transfers priority in your queue and let batch runs yield. ```python theme={null} # Paced drain: one dispatcher, 4 transfers/second, deposits first import time, queue deposit_q, batch_q = queue.Queue(), queue.Queue() def dispatch_loop(stub, metadata): while True: item = None try: item = deposit_q.get_nowait() # deposits preempt except queue.Empty: try: item = batch_q.get(timeout=1.0) # then batch work (fees, withdrawals) except queue.Empty: continue try: stub.CreateCashMovement(item.request, metadata=metadata) except grpc.RpcError as e: if e.code() == grpc.StatusCode.RESOURCE_EXHAUSTED: item.backoff = min(item.backoff * 2, 60) schedule_retry(item, delay=item.backoff * jitter()) # same idempotency_key else: raise time.sleep(0.25) # 4/s, headroom under the 5/s cap ``` ## Idempotency and recovery * Use a **distinct, durable idempotency key per logical transfer**, persisted before the call. * A transport timeout or an `AMBIGUOUS` status is **not** permission to create a second workflow — replay the same create request with the same key, or read the original workflow by `workflow_id`. * A replay must be **identical**: the request is hashed under the key, so retrying with a changed amount, account, reason, or reference is rejected as key reuse with a different request. * Use bounded recovery for `PENDING` workflows rather than a permanent polling scheduler. * Persist `workflow_id`, the reason, and `external_reference` through a terminal result — they are your join keys across your books, the [balance ledger](/streaming-endpoints/balance-ledger-stream), and support requests. ## Transfer workflow errors The following statuses apply to `CreateCashMovement` and `GetCashMovement`. | gRPC status | Meaning | Retry guidance | | --------------------- | ---------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | | `INVALID_ARGUMENT` | Malformed amount/currency, unknown reason, or missing required field. | Fix the request. Do not retry as-is. | | `UNAUTHENTICATED` | Missing or invalid access token. | Refresh the token and retry. | | `PERMISSION_DENIED` | Your firm is not configured for transfers, or the requested transfer reason is disabled. | Contact [institutional@polymarket.us](mailto:institutional@polymarket.us). | | `NOT_FOUND` | No customer relationship exists for your firm and `participant_account_id`. | Fix the request. | | `FAILED_PRECONDITION` | The customer relationship or resolved transfer-account configuration is unavailable. | Correct the relationship or account state before retrying. | | `ALREADY_EXISTS` | The `idempotency_key` was already used with a different request. | Replay the original request, or use a fresh key only for a genuinely new transfer. | | `RESOURCE_EXHAUSTED` | Transfer rate limit exceeded; the transfer was not created. | Re-enqueue with backoff and the **same** `idempotency_key` — see [Rate limits and smoothing](#rate-limits-and-smoothing). | | `UNAVAILABLE` | Transient service unavailability. | Retry with the **same** `idempotency_key`. | An insufficient-funds outcome is not a gRPC error at create time: the workflow resolves to `status = REJECTED` with a `rejection_reason` — see [Insufficient funds](#insufficient-funds). ## Related pages The model — parties, money flows, and directional guarantees. Mirroring wallet allocations into buying power. Accrual, the daily report, and collection. Keeping your books in sync with the ledger. # Vendor Fees Source: https://docs.polymarket.us/partners/funding/vendor-fees How vendor fees are declared per order, accrue as a receivable, appear on the daily Vendor Fees report, and are collected with one transfer per participant account. **BETA — SUBJECT TO CHANGE.** This capability is in beta and may change without notice. Your vendor fee is what you charge a Retail Participant for an order, per your agreement with them. On Polymarket US it follows a **declare → accrue → report → collect** lifecycle: the fee is *declared* when the order is placed, *recorded* against the order, *reported* to you daily, and *collected* periodically with a single [transfer](/partners/funding/transfers) per participant account. **No money moves for vendor fees at order time.** ```mermaid theme={null} graph LR A["Declare
fee on each order placement"] --> B["Accrue
recorded as fee ↔ order ID"] B --> C["Report
daily Vendor Fees report"] C --> D["Collect
one VENDOR_FEES transfer
per account, ≤ 1/day"] ``` ## Declaring the fee Every order you place through [`CreateVendorOrder`](/partners/orders/create-order) carries a `vendor_fee` — a **fixed USD amount you compute** per your agreement with the participant. Polymarket US validates that it is well-formed, non-negative, and allowed for your firm, then records the mapping *"this firm declared vendor fee \$X for order Y"*. **The platform never knows your fee basis.** If your agreement is 2% of principal and the order principal is \$100, you send `"2.00"` — Polymarket US does not compute fees from a percentage or schedule, does not know whether your basis is per-order, per-fill, or flat, and does not adjust the recorded amount if the order is partially filled or cancelled. What is recorded is exactly what you declared, keyed by order ID. Applying your own fee policy (for example, waiving fees on unfilled orders) happens in **your** books and in the amount you choose to collect. ## Accrual and your buying-power gate Between declaration and collection, an accrued fee is a **receivable**: the cash that will pay it sits in the participant's account, indistinguishable from their tradable balance. Polymarket US does not reserve for it — its order check covers collateral and exchange fees only. Your platform (with your funding entity) must therefore maintain a **shadow balance** per participant: ``` spendable balance = account cash − accrued, uncollected vendor fees ``` and gate order submission on the *spendable* balance, so a participant can never place an order that spends the cash earmarked for your fees. **Accrued-fee tracking is not exposed on the API.** Declared fees are recorded in the DCO reporting system only — there is no endpoint or stream that returns a participant's accrued vendor fee balance. Track accruals in your own systems (your funding entity declared every fee, so it knows the accrual in real time) and reconcile against the daily [Vendor Fees report](#the-vendor-fees-report). ### Accrued fees are credit exposure Until collected, accrued fees are an unsecured receivable of your funding entity against the participant's account balance. If the participant's cash drops below the accrued amount — trading losses are the obvious path — the eventual `VENDOR_FEES` transfer will be [rejected for insufficient funds](/partners/funding/transfers#insufficient-funds). The platform does not underwrite this: **managing the exposure is your and your funding entity's responsibility.** The levers are the gate above (which prevents *spending* the earmarked cash but not *losing* it), collection frequency (daily collection minimizes the window), and your own fee policy for loss scenarios. ## The Vendor Fees report Polymarket US produces a **daily Vendor Fees report** covering the fees declared by your firm, so your funding entity can reconcile its own accrual and drive collection without tracking every order itself. **Delivery method TBD.** The report's delivery mechanism is being finalized and will be confirmed with your integration lead during onboarding. The structure below is the planned content and may change during beta. One row per declared fee, plus a per-account net summary: | Column | Description | | --------------- | -------------------------------------------------------------------------------------------------------------------- | | `business_date` | Trade date the declaration belongs to (`YYYY-MM-DD`). | | `account` | The participant trading account the order belongs to. | | `order_id` | Exchange order identifier the fee was declared against. | | `clord_id` | The client order ID you assigned at placement (`order.clord_id`) — joins the row directly to your own order records. | | `order_status` | The order's state as of report time — e.g. `FILLED`, `PARTIALLY_FILLED`, `CANCELLED`. | | `trade_ids` | Trade identifiers generated by the order — one order can produce multiple trades through partial fills. | | `vendor_fee` | The declared amount (decimal). | | `currency` | ISO 4217 — `USD`. | | Summary column | Description | | ----------------- | ------------------------------------------------------------ | | `account` | The participant trading account. | | `net_vendor_fees` | Sum of declared fees for the account over the report period. | | `currency` | `USD`. | Because rows carry your `clord_id`, the order's state, and its trades, your funding entity can apply its own policy before collecting — for example, waiving fees on cancelled or unfilled orders, or prorating by filled quantity — by joining against its own order records. The report is the **platform's record of what you declared**; the amount you collect is yours to determine, up to what the participant's cash can cover. ## Collecting the fees Collection is a standard [transfer](/partners/funding/transfers) with reason `VENDOR_FEES` — participant account → partner funding account: 1. **Ingest the report** (or close your own books — you declared every fee, so your accrual should match). 2. **Reconcile** report totals against your funding entity's accrual; investigate any mismatch before collecting. 3. **Create one `VENDOR_FEES` transfer per participant account** for the net amount due, with your journal reference in `external_reference`. Rules and mechanics: * **At most once per day per participant account.** Weekly or monthly collection is fine — pick the cadence that suits your funding entity, but never collect more often than daily. * **One transfer per account per period** — never one per order or per fee. * **Pace scheduled runs** inside the transfer budget — an EOD collection across your whole participant base should drain through a rate-limited queue, not fire simultaneously. See [rate limits and smoothing](/partners/funding/transfers#rate-limits-and-smoothing). * **Handle rejection**: an insufficient-funds rejection means the participant's free cash no longer covers the accrual — resolve per your participant agreement, then re-collect what is collectable. ## Related pages Where the fee is declared. The API that executes collection. The full money-flow model. Keeping your books in sync with the ledger. # Authentication Source: https://docs.polymarket.us/partners/get-connected/authentication High-level overview of Private Key JWT authentication for partners, with the partner-specific details and a pointer to the full implementation guide. The Polymarket US API uses **Private Key JWT** authentication with RSA keys — the **same mechanism used across the platform**, including by institutional traders. You sign a JWT with your RSA private key and exchange it for a short-lived access token. Key generation, JWT claims, code samples (Python, Go, curl), key rotation, troubleshooting, and the complete scope reference live in the **Institutional API → Getting Started** guide. This page covers what's specific to partners and links you there for the implementation details. Your RSA key pairs are generated during [Partner Onboarding](/partners/get-connected/onboarding) — you share only the **public** keys with Polymarket US and keep the private keys secure. ## How it works ```mermaid theme={null} sequenceDiagram participant Client as Your Application participant Auth as Polymarket US Auth participant API as Polymarket US API Client->>Client: Sign JWT with Private Key Client->>Auth: Token Request + Signed JWT Auth->>Auth: Verify with your Public Key Auth-->>Client: API Access Token Client->>API: API Request + Access Token API->>API: Validate Token API-->>Client: API Response ``` 1. **Create a signed JWT assertion** — sign a JWT with your private key. 2. **Exchange it for an access token** — send the assertion to the token endpoint. 3. **Call the API with the access token** — include it as a `Bearer` token on each request. The [full guide](/trader-guide/authentication) has the exact JWT claims, token request, and ready-to-use Python and Go clients. ## Environments | Environment | Auth Domain | API Domain | | -------------- | -------------------------- | ------------------------------------ | | Pre-production | `pmx-preprod.us.auth0.com` | `api.preprod.polymarketexchange.com` | | Production | `pmx-prod.us.auth0.com` | `api.prod.polymarketexchange.com` | Use `https://[API Domain]` for both the JWT audience claim and the API base URL. Each environment requires separate onboarding — pre-production credentials do not work in production. ## Prerequisites After completing [Partner Onboarding](/partners/get-connected/onboarding), you will have everything you need to authenticate: | You have | From onboarding | | -------------------------- | ------------------------------------------------------------ | | Private key file | Generated by you (keep secure) | | Client ID | Provided by Polymarket US in your shared Google Drive folder | | Auth Domain & API Audience | See [Environments](#environments) above | ## Acting on behalf of a participant This is the main partner-specific difference. You authenticate **once as your Firm**, then act on behalf of the Retail Participants you onboard by including the **`x-participant-id`** header on account-scoped requests (trading, positions, reports): ```bash theme={null} curl -X GET "https://api.preprod.polymarketexchange.com/v1/whoami" \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H "x-participant-id: firms/ISV-Participant-YourISV/users/participant-123" ``` Use [`GET /v1/users`](/institutional/accounts/overview#endpoints) to discover the participant IDs your Firm may act on behalf of. See [Participants](/partners/onboarding/users) for how to resolve identities, and [Accounts & Identity](/trader-guide/accounts-identity) for the full hierarchy. ## Scopes Your application is granted **scopes** that control which endpoints you can call. The scopes most relevant to partners include: | Scope | Grants | | -------------------------------- | -------------------------------------------------------- | | `read:kyc` / `write:kyc` | View KYC status; start verification and manage webhooks | | `read:accounts` | View identities (`/v1/whoami`, `/v1/users`) and accounts | | `read:orders` / `write:orders` | View and place/cancel/modify orders | | `read:positions` | Positions, balances, and balance/position ledgers | | `read:funding` / `write:funding` | View funding; create transfers | The **authoritative scope list and the full scope-by-endpoint mapping** are maintained in the [full authentication guide](/trader-guide/authentication#api-scopes). Missing a required scope returns `403 Forbidden` (REST) / `PERMISSION_DENIED` (gRPC). ## Next steps JWT claims, code samples, key rotation, and troubleshooting. Get your keys registered and receive your Client ID. Authenticate and place your first order end to end. Resolve common authentication errors. # Partner Onboarding Source: https://docs.polymarket.us/partners/get-connected/onboarding What partner onboarding involves: the inputs we need from you and the credentials and access you receive. Onboarding is how your firm goes from "interested partner" to "able to call the API." It establishes your legal agreements and provisions your API credentials in each environment. **Audience: business and technical leads.** Onboarding spans legal agreements and API credential setup, so expect both commercial and developer steps. Email **[institutional@polymarket.us](mailto:institutional@polymarket.us)** to kick off the process and get your onboarding folder set up. ## At a glance ```mermaid theme={null} graph TD A["Sign agreements"] --> B["Generate API keys per environment"] B --> D["Submit onboarding request"] D --> E["Receive credentials and access"] E --> F["Authenticate and verify"] ``` ## What you provide vs. what you receive | You provide | You receive | | ------------------------------------------------------------------------------ | ---------------------------------------------------- | | Signed partner agreement (ISV or IB) and participant agreements for your users | A **Client ID** for each environment | | RSA **public** keys (one per environment) | Access to **pre-production** and **production** | | AWS Account ID (only if using FIX connectivity) | FIX connection details (if requested) | | Primary technical and business contacts | A shared Google Drive folder for credential delivery | ## Step 1 — Sign the agreements The agreements depend on your partner type: The ISV Connectivity Agreement and the participant clickthrough agreements you present to your users. The IB Participant Agreement, plus the CFTC/NFA regulatory requirements specific to Introducing Brokers. Your Retail Participants must accept the appropriate participant agreement before they begin KYC. See [ISVs](/partners/partner-types/isvs) for the individual and entity participant agreement links. ## Step 2 — Generate your API keys Authentication uses Private Key JWT with RSA keys. Generate a key pair **per environment** and share only the **public** keys with Polymarket US — never your private keys. Step-by-step key generation and the full authentication flow. Keep your private keys secure and never share them. You submit only the public keys during onboarding. ## Step 3 — Submit your onboarding request 1. Create a Google Drive folder containing your **public key file(s)** and your completed agreement(s). 2. Grant access to the folder as directed by your Polymarket US contact. 3. Email [institutional@polymarket.us](mailto:institutional@polymarket.us) with your firm name and a link to the folder. **Using FIX connectivity?** Also include your **AWS Account ID** so a private connection can be established, and note FIX in your request. You still generate and submit RSA key pairs even if FIX is your primary connectivity method. See the [FIX API](/institutional/fix-api/fix-overview) for protocol details. ## Step 4 — Receive your credentials Polymarket US reviews your submission and provisions your access: * A **Client ID** for each environment, delivered to your shared Google Drive folder. * Access to **pre-production** (for integration and testing) and **production**. Your **pre-production** environment is provisioned with test funds so you can exercise the full flow — authentication, KYC, and trading — before going live. ## Next steps Exchange your signed JWT for an access token and call the API. Place your first order end to end. # Quickstart Source: https://docs.polymarket.us/partners/get-connected/quickstart Onboard a participant, fund their trading account, and place their first order with a declared vendor fee. **Audience: developers.** This is a hands-on, copy-paste guide. For eligibility, agreements, and the business setup, see [ISVs](/partners/partner-types/isvs) / [IBs](/partners/partner-types/ibs) and [Partner Onboarding](/partners/get-connected/onboarding). This guide walks the core partner loop end to end — the same moves your production integration will repeat for every participant: ```mermaid theme={null} flowchart LR A["1 · KYC"] --> B["2 · Deposit"] --> C["3 · Order"] --> D["4 · Monitor"] ``` By the end you'll have: 1. Authenticated with the API as your Firm — your only credential; every action after this is **on behalf of a participant** 2. Onboarded a Retail Participant via KYC and captured their trading account 3. Funded their account with a **deposit transfer** from your partner funding account 4. Placed a FOK limit order with a **declared vendor fee** on their behalf 5. Seen where the money moves — and how fees are collected later **Prerequisites:** Complete [Partner Onboarding](/partners/get-connected/onboarding) to receive your Client ID and register your public key, and have your **funding relationship** configured ([Partner Funding](/partners/funding/overview) is Beta, enabled per partner). This guide uses **pre-production** (`api.preprod.polymarketexchange.com`). ## Step 1: Authenticate Authentication uses **Private Key JWT** — you sign a JWT with your private key and exchange it for a short-lived access token. The full mechanism, claims, and key rotation are covered in the [Authentication](/partners/get-connected/authentication) guide; the complete, runnable token code is in the [full script](#complete-example) at the bottom of this page. Once you have a token, set your auth header and confirm your identity with [`GET /v1/whoami`](/institutional/accounts/overview#endpoints): ```python theme={null} import requests BASE_URL = "https://api.preprod.polymarketexchange.com" headers = { "Authorization": f"Bearer {access_token}", # from the auth guide "Content-Type": "application/json", } resp = requests.get(f"{BASE_URL}/v1/whoami", headers=headers) resp.raise_for_status() print(f"Authenticated as: {resp.json()}") ``` Expected response: ```json theme={null} { "user": "firms/ISV-Participant-Acme/users/admin", "userDisplayName": "Your Company", "firm": "ISV-Participant-Acme", "firmDisplayName": "Acme Trading", "firmType": "FIRM_TYPE_PARTICIPANT" } ``` **Your Firm authenticates; it never trades or holds funds.** The Firm identity above is a permissions container — every trading action in this guide is performed **on behalf of a Retail Participant**. Participant-scoped REST calls (positions, reports) identify them with the `x-participant-id` header — see [Authentication → Acting on behalf of a participant](/partners/get-connected/authentication#acting-on-behalf-of-a-participant). Partner order entry and transfers carry the participant's provisioned DCM **trading account** in the request body. You'll capture both identifiers in Step 2. ## Step 2: Onboard a participant (KYC) Before you can fund an account or place an order for a Retail Participant, they must pass KYC. Submit their identity data with [`POST /v1/kyc/start`](/partners/onboarding/kyc/verification-flow): ```python theme={null} kyc = { "external_id": "user-123", # your internal ID for this participant "ssn": "123456789", "first_name": "Jane", "last_name": "Smith", "date_of_birth": "1990-06-15", "email": "jane.smith@example.com", "phone_number": "+12125551234", "address": { "address_line_1": "123 Main St", "city": "New York", "state": "NY", "postal_code": "10001", "country": "US", }, "agreement": {"version": "PMX.ISV.v1.0", "signed_at": "2026-04-24T14:30:00Z"}, "ip_address": "203.0.113.42", # the participant's IP } resp = requests.post(f"{BASE_URL}/v1/kyc/start", headers=headers, json=kyc) resp.raise_for_status() print(resp.json()["status"]) # decision path — see the decision matrix ``` The outcome may be instant approval, document verification, or manual review — handle each per the [decision matrix](/partners/onboarding/kyc/verification-flow#decision-matrix). **Approval is asynchronous**: the terminal outcome arrives on your registered [webhook](/partners/onboarding/kyc/webhooks) as a `kyc.approved` event, which carries the two identifiers you need: * **`provisioned_participant`** (e.g. `firms/ISV-Participant-Acme/users/user-123`) — the `x-participant-id` value for participant-scoped REST calls. * The participant's provisioned **DCM trading account** — use it as `order.account` for `CreateVendorOrder`, `participant_account_id` for transfers, and the account identity on Drop Copy and reconciliation records. You can also list accounts under your Firm at any time: ```python theme={null} resp = requests.get(f"{BASE_URL}/v1/accounts", headers=headers) resp.raise_for_status() print(resp.json()["accounts"]) # ["firms/ISV-Participant-Acme/accounts/user-123-trading", ...] participant_account = resp.json()["accounts"][0] ``` The account is provisioned with a **\$0 balance**. The participant can trade as soon as you fund it — the next step. ## Step 3: Fund the account (deposit transfer) When the participant allocates wallet funds to trading, mirror the allocation onto the platform with a [`DEPOSIT` transfer](/partners/funding/deposits-withdrawals) — partner funding account → participant account. The transfer API is **gRPC-only** ([`CashMovementService`](/partners/funding/transfers)): ```python theme={null} import uuid import grpc from polymarket.us.cashmovement.v1 import cash_movement_pb2, cash_movement_pb2_grpc channel = grpc.secure_channel(":443", grpc.ssl_channel_credentials()) cash_stub = cash_movement_pb2_grpc.CashMovementServiceStub(channel) metadata = [("authorization", f"Bearer {access_token}")] idempotency_key = str(uuid.uuid4()) # persist BEFORE sending, so you can retry safely request = cash_movement_pb2.CreateCashMovementRequest( idempotency_key=idempotency_key, transfer=cash_movement_pb2.Transfer( reason=cash_movement_pb2.TRANSFER_REASON_DEPOSIT, participant_account_id=participant_account, amount="500.00", currency="USD", external_reference="wallet-alloc-8842", # your journal reference ), ) deposit = cash_stub.CreateCashMovement(request, metadata=metadata) print(deposit.cash_movement.workflow_id, deposit.cash_movement.status) ``` Persist the request fields alongside `workflow_id`; the transfer read model does not echo `participant_account_id`, `external_reference`, or `memo`. Wait until the workflow is `CONFIRMED` before placing the order. If it remains `PENDING`, recover the same workflow rather than creating another; if it is `AMBIGUOUS`, replay the identical request with the same idempotency key. Once confirmed, the participant has \$500 of buying power — seconds after they allocated it in their wallet. See [Transfers](/partners/funding/transfers) for recovery, terminal statuses, and rate-limit handling. ## Step 4: Pick a market and instrument Discover a market with the [Market API](/api-reference/market/overview), then confirm the one you want: ```python theme={null} market_slug = "example-market-slug" resp = requests.get(f"{BASE_URL}/v1/market/slug/{market_slug}", headers=headers) resp.raise_for_status() market = resp.json() print(market["slug"], market["orderPriceMinTickSize"], market["minimumTradeQty"]) ``` Partner order entry uses the exchange `symbol`, not the market slug. Use the [Reference Data API](/institutional/refdata/overview) to obtain the selected instrument's symbol, `priceScale`, `fractionalQtyScale`, tick size, and minimum quantity. Validate the participant's price and quantity against that metadata before submitting. The embedded public order uses fixed-point integers. The `priceScale` and `fractionalQtyScale` values published by Reference Data are the multipliers: ```text theme={null} wire price = decimal price × priceScale wire share quantity = decimal shares × fractionalQtyScale wire cash quantity = decimal USD × priceScale ``` The examples below assume both scales are `100`, making \$0.45 equal to `45`, 100 shares equal to `10000`, and “spend \$20” equal to `2000`. ## Step 5: Estimate cost and gate buying power Placement returns no economics. The exchange is authoritative and checks that account cash covers worst-case collateral plus the applicable exchange fee. Your system must additionally subtract accrued, uncollected vendor fees: ```text theme={null} spendable balance = account cash − accrued uncollected vendor fees ``` For a buy share limit order, estimate collateral as quantity × limit price; for a cash order, `cash_order_qty` is the requested maximum spend. Add the maximum exchange fee from the [fee schedule](/fees). If you need an informational server-side preview, call the optional public `polymarket.v1.OrderEntryAPI/PreviewOrder` with the same public order shape. It is not a locked quote; the exchange validates again at placement. ## Step 6: Place the order `CreateVendorOrder` is the static-model order RPC partners should use. It embeds the public `polymarket.v1.InsertOrderRequest`, adds your declared vendor fee and idempotency key, and returns a durable order outcome. There is no separate customer-account field: the customer is identified solely by `order.account`. ```python theme={null} from polymarket.v1 import trading_pb2 from polymarket.us.orderfunding.v1 import order_funding_pb2, order_funding_pb2_grpc order_stub = order_funding_pb2_grpc.OrderFundingServiceStub(channel) # Persist both identifiers before the first call. clord_id = f"order-{uuid.uuid4()}" idempotency_key = str(uuid.uuid4()) request = order_funding_pb2.CreateVendorOrderRequest( order=trading_pb2.InsertOrderRequest( type=trading_pb2.ORDER_TYPE_LIMIT, side=trading_pb2.SIDE_BUY, order_qty=10_000, # 100.00 shares at fractionalQtyScale=100 symbol="", price=45, # $0.45 at priceScale=100 time_in_force=trading_pb2.TIME_IN_FORCE_FILL_OR_KILL, clord_id=clord_id, account=participant_account, manual_order_indicator=trading_pb2.MANUAL_ORDER_INDICATOR_MANUAL, ), vendor_fee=order_funding_pb2.MoneyAmount(value="0.10", currency="USD"), idempotency_key=idempotency_key, ) result = order_stub.CreateVendorOrder(request, metadata=metadata) print(result.status, result.id, result.funding_request_id) ``` The partner launch allowlist is intentionally narrow. Set only the fields shown for this use case; `order.user`, `order.session_id`, non-FOK time-in-force values, non-default `good_till_time`, and every unsupported feature are rejected with `INVALID_ARGUMENT` naming the offending field. See the [supported-fields table](/partners/orders/data-model) and the [cash-order example](/partners/orders/create-order#consumer-cash-order-spend-20). Handle all three durable outcomes: | Status | Meaning | What you do | | ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- | | `VENDOR_ORDER_STATUS_ACCEPTED` | The exchange durably accepted the order — including an order that matched immediately or was cancelled under fill-or-kill. Acceptance does not mean the order is resting or filled. | Learn execution outcomes from Drop Copy and add the declared fee to your accrual. | | `VENDOR_ORDER_STATUS_REJECTED` | The order was rejected. Nothing was recorded and no fee accrues. | Surface the rejection; nothing to clean up. | | `VENDOR_ORDER_STATUS_PENDING` | The durable exchange outcome was not confirmed within the deadline. | Retry the identical request with the **same** `idempotency_key` and `clord_id` until terminal. | The service guarantees **at most one order per idempotency key — retrying with the same key can never place a duplicate order or double-charge the declared vendor fee.** On transport errors (`UNAVAILABLE`, timeouts), also retry the identical request with the same key and `clord_id`. Match the participant's Drop Copy activity by `order.account` and `clord_id`. See [idempotency and retries](/partners/orders/create-order#idempotency-and-retries) and the [funding request lifecycle](/partners/orders/create-order#funding-request-lifecycle). ## Step 7: Watch the money Every cash event is visible in real time — this is how your backend keeps its books and its buying-power gate current: * The **deposit transfer** appears on the [Balance Ledger Stream](/streaming-endpoints/balance-ledger-stream) for the participant account and your funding account. * **Fills** arrive on the [Drop Copy Stream](/streaming-endpoints/dropcopy-stream); per-execution exchange fees and settlement credits land on the ledger. * **Trading proceeds stay in the account** — settlement credits and realized profit simply increase the participant's buying power. Nothing needs to move after fills or settlements. * Persist the response's `correlation` identifiers with `order.account`, `clord_id`, and the exchange order ID. They tie ledger entries and vendor-fee reporting back to the placement workflow. **Scaling note — per-account balance streams are provided now for accelerated development.** The balance ledger subscription is **per-account**, and concurrent ledger streams per firm are capped — opening one stream per participant does not scale to thousands of accounts. A **firm-level balance stream** delivering ledger entries for every account under your Firm on a single subscription is coming soon. Build against the per-account stream today, but plan for the firm-level stream in production — see [firm-wide ledger consumption](/partners/reconciliation#firm-wide-ledger-consumption) for the interim pattern. ## Step 8: Fees and withdrawals — later, not per order Two flows complete the lifecycle, and neither happens at order time: * **Vendor fees**: the fee you declared in Step 6 accrued as a receivable. Collect accrued fees periodically — **at most once per day per participant account** — with one [`VENDOR_FEES` transfer](/partners/funding/transfers) per participant account, reconciled against the daily [Vendor Fees report](/partners/funding/vendor-fees). * **Withdrawals**: when the participant de-allocates funds in their wallet, mirror it with a [`WITHDRAWAL` transfer](/partners/funding/deposits-withdrawals#withdrawals) — checking their free cash (net of accrued fees) first. ## Complete example A single runnable script covering the deposit-and-order path — including the full token exchange (see the [Authentication](/partners/get-connected/authentication) guide for an explanation of each claim). KYC is omitted because its terminal outcome arrives on your webhook; run Step 2 once and plug in the resulting account and the instrument metadata from Step 4: ```python theme={null} #!/usr/bin/env python3 """Polymarket US partner quickstart — deposit and place a FOK order.""" import jwt import uuid import time import grpc import requests from cryptography.hazmat.primitives import serialization from polymarket.v1 import trading_pb2 from polymarket.us.cashmovement.v1 import cash_movement_pb2, cash_movement_pb2_grpc from polymarket.us.orderfunding.v1 import order_funding_pb2, order_funding_pb2_grpc # Configuration (from Partner Onboarding) AUTH_DOMAIN = "pmx-preprod.us.auth0.com" CLIENT_ID = "your_client_id" AUDIENCE = "https://api.preprod.polymarketexchange.com" PRIVATE_KEY_PATH = "private_key.pem" BASE_URL = "https://api.preprod.polymarketexchange.com" GRPC_ENDPOINT = ":443" # From Step 2 (kyc.approved webhook / GET /v1/accounts) and Step 4 PARTICIPANT_ACCOUNT = "firms/ISV-Participant-Acme/accounts/user-123-trading" MARKET_SLUG = "example-market-slug" MARKET_SYMBOL = "" def get_access_token(): with open(PRIVATE_KEY_PATH, "rb") as f: private_key = serialization.load_pem_private_key(f.read(), password=None) now = int(time.time()) claims = { "iss": CLIENT_ID, "sub": CLIENT_ID, "aud": f"https://{AUTH_DOMAIN}/oauth/token", "iat": now, "exp": now + 300, "jti": str(uuid.uuid4()), } assertion = jwt.encode(claims, private_key, algorithm="RS256") response = requests.post( f"https://{AUTH_DOMAIN}/oauth/token", json={ "client_id": CLIENT_ID, "client_assertion_type": "urn:ietf:params:oauth:client-assertion-type:jwt-bearer", "client_assertion": assertion, "audience": AUDIENCE, "grant_type": "client_credentials", }, ) response.raise_for_status() return response.json()["access_token"] def main(): token = get_access_token() headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} # Verify the Firm identity. resp = requests.get(f"{BASE_URL}/v1/whoami", headers=headers) resp.raise_for_status() print(f"Logged in as: {resp.json()}") # Confirm the market; resolve symbol and scales through Reference Data in production. resp = requests.get(f"{BASE_URL}/v1/market/slug/{MARKET_SLUG}", headers=headers) resp.raise_for_status() print(f"Trading market: {resp.json()['slug']}") # gRPC channel shared by the transfer and order APIs. channel = grpc.secure_channel(GRPC_ENDPOINT, grpc.ssl_channel_credentials()) metadata = [("authorization", f"Bearer {token}")] # Fund the account — persist this logical transfer and key before sending. cash_stub = cash_movement_pb2_grpc.CashMovementServiceStub(channel) deposit_key = str(uuid.uuid4()) deposit_request = cash_movement_pb2.CreateCashMovementRequest( idempotency_key=deposit_key, transfer=cash_movement_pb2.Transfer( reason=cash_movement_pb2.TRANSFER_REASON_DEPOSIT, participant_account_id=PARTICIPANT_ACCOUNT, amount="500.00", currency="USD", external_reference="wallet-alloc-8842", ), ) deposit = cash_stub.CreateCashMovement(deposit_request, metadata=metadata) print(f"Deposit: {deposit.cash_movement.workflow_id} " f"{cash_movement_pb2.CashMovementStatus.Name(deposit.cash_movement.status)}") if deposit.cash_movement.status != cash_movement_pb2.CASH_MOVEMENT_STATUS_CONFIRMED: raise RuntimeError("Wait for the deposit workflow to reach CONFIRMED before ordering") # Place a 100-share, $0.45 FOK limit order. Values assume both scales are 100. order_stub = order_funding_pb2_grpc.OrderFundingServiceStub(channel) clord_id = f"order-{uuid.uuid4()}" order_key = str(uuid.uuid4()) order_request = order_funding_pb2.CreateVendorOrderRequest( order=trading_pb2.InsertOrderRequest( type=trading_pb2.ORDER_TYPE_LIMIT, side=trading_pb2.SIDE_BUY, order_qty=10_000, symbol=MARKET_SYMBOL, price=45, time_in_force=trading_pb2.TIME_IN_FORCE_FILL_OR_KILL, clord_id=clord_id, account=PARTICIPANT_ACCOUNT, manual_order_indicator=trading_pb2.MANUAL_ORDER_INDICATOR_MANUAL, ), vendor_fee=order_funding_pb2.MoneyAmount(value="0.10", currency="USD"), idempotency_key=order_key, ) result = order_stub.CreateVendorOrder(order_request, metadata=metadata) print(f"Status: {order_funding_pb2.VendorOrderStatus.Name(result.status)}") print(f"Order ID: {result.id} funding_request_id: {result.funding_request_id}") if __name__ == "__main__": main() ``` **Required packages:** ```bash theme={null} pip install PyJWT cryptography requests grpcio ``` The `polymarket.v1`, `polymarket.us.cashmovement.v1`, and `polymarket.us.orderfunding.v1` stubs are generated from API protos provided during beta enablement — contact [institutional@polymarket.us](mailto:institutional@polymarket.us) if you don't have them. ## Next steps Request and response contract, supported examples, and retry behavior. Fixed-point values and the fail-closed launch allowlist. Deposits, withdrawals, vendor fee collection, and paced queues. The full model — parties, money flows, and the transfer budget. All verification outcomes and webhook handling. Track every deposit, fill, and settlement credit in real time. # Partner Glossary Source: https://docs.polymarket.us/partners/glossary Terminology used throughout the Partner Integration docs, including the entities you act on and the people you onboard. This glossary defines the terms used across the Partner Integration docs. For market-structure and trading terms (instruments, order book, fills, settlement), see the [main Glossary](/getting-started/glossary). ## People and organizations | Term | Definition | | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Partner** | An Introducing Broker (IB) or Independent Software Vendor (ISV) integrating with Polymarket US to provide a trading experience to retail traders. | | **Retail Participant** | **The canonical term in these docs for an end user you onboard** — a retail trader who trades through your platform. You act on their behalf. | | **Customer** | Used in the **FCM context** (a Futures Commission Merchant's customer). It is acceptable to use "customer" informally for a Retail Participant, but because it has a specific meaning for FCMs, prefer **Retail Participant** in partner integrations to avoid ambiguity. | **"Participant" appears in two unrelated senses across Polymarket docs.** In these partner docs it always means a *trading identity* (a person you onboard — a Retail Participant). The [main glossary](/getting-started/glossary) separately uses "participant" in a market-structure sense (an outcome side of an instrument). The two meanings are unrelated; this section always means the trading identity. ## Platform and entities | Term | Definition | | ------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **DCM (Designated Contract Market)** | The regulated matching function: maintains the order book, matches orders, and publishes market data. | | **DCO (Derivatives Clearing Organization)** | The regulated clearing function: holds collateral, clears trades, and settles contracts. | | **Polymarket US** | The platform as a whole. Throughout these docs it is represented as a single actor, regardless of whether a call is served by the matching or clearing function. | | **Firm** | Your IB/ISV organization — the top-level permissions container you authenticate as. You act on behalf of the Retail Participants beneath your Firm. | | **Participant** | A Retail Participant's trading identity at the DCM, used to scope order entry and account queries. Referenced by an ID such as `firms/your-firm/users/their-id`. | | **Clearing Member** | A funds- and position-holding identity at the DCO. Each Retail Participant has a Clearing Member account. | | **Account** | The trading account holding a Retail Participant's balances and positions. Provisioned automatically alongside the Retail Participant on KYC approval. | ## Onboarding and identity | Term | Definition | | ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **KYC (Know Your Customer)** | Identity verification. You **collect** the required information from a Retail Participant and submit it; Polymarket US (the DCM) **performs the verification and makes the decision**. On approval, the Participant and Account are provisioned automatically. | | **Auto-provisioning** | The automatic creation of a Retail Participant's trading identity and Account when their KYC is approved — there is no separate "create user/account" call. | | **Access token** | The short-lived bearer token you obtain via [Private Key JWT](/partners/get-connected/authentication) and send on each API request. | | **`x-participant-id`** | The request header identifying which Retail Participant an account-scoped action is for. | ## Funding | Term | Definition | | --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Partner Funding** | The partner funding model: the funding entity pre-positions pooled funds at Polymarket US and funds participant trading accounts with instant deposit transfers; vendor fees are declared per order and collected periodically. See [Partner Funding](/partners/funding/overview). | | **Partner funding account** | The pooled account at Polymarket US, held by your funding entity — the unallocated pool that deposits draw from and that withdrawals and vendor fee collections return to. | | **Funding entity** | The separate legal entity in your corporate structure that custodies participant cash off-platform and holds the partner funding account. Your firm itself never holds funds. | | **Transfer** | A cash movement between the partner funding account and a participant account, for one of three reasons: `DEPOSIT`, `WITHDRAWAL`, or `VENDOR_FEES`. The funding account is always one side. See [Transfers](/partners/funding/transfers). | | **Deposit** | A transfer from the partner funding account into a participant's account, mirroring the participant's wallet allocation — this is what creates buying power. | | **Withdrawal** | A transfer from a participant's account back to the partner funding account, mirroring a wallet de-allocation. Limited to the participant's free cash. | | **Buying power** | The cash in a participant's trading account. Polymarket US rejects an order unless it covers the order's worst-case collateral plus exchange fee. | | **Collateral** | The funds required to back an order or position: `quantity × price` for buys, `quantity × (1 − price)` for sells. Locked within the participant's account while at risk. | | **Vendor fee** | A fixed USD amount you declare per order, per your agreement with the participant. Recorded against the order — never moved at order time — and collected periodically. See [Vendor Fees](/partners/funding/vendor-fees). | | **Vendor fee accrual** | The running total of declared, uncollected vendor fees per participant — a receivable your platform must track off-platform and subtract from spendable balance. | | **Vendor Fees report** | The daily report of fees your firm declared, per order and netted per account, used by your funding entity to reconcile and drive collection. | ## Related references Market structure, trading, and execution terms. How these entities relate in practice. # Integration Journey Source: https://docs.polymarket.us/partners/integration-journey The end-to-end path from partner onboarding to live trading — and the recommended order to build it. This page is your map. It lays out the partner integration end to end so you can see how the pieces fit before you start building, then points you to the right guide for each step. ## The journey at a glance ```mermaid theme={null} graph TD A["1 · Understand platform & your role"] --> C["2 · Get connected — onboarding + auth"] C --> D["3 · Onboard participants — KYC"] D --> F["4 · Trade — order entry"] F --> G["5 · Monitor & reconcile — streams + webhooks"] ``` **Funding is in beta.** Your funding entity **pre-positions pooled funds** at Polymarket US and funds each participant's trading account with instant deposit transfers that mirror their wallet allocation, via [Partner Funding](/partners/funding/overview), enabled per partner. There is no per-participant bank-deposit step in the journey. ## What you'll build A complete partner integration has a handful of components. You can build them incrementally in the order below. | Component | Purpose | Primary interface | | -------------------- | ----------------------------------------------------------------- | ----------------------------------------------------------- | | **Auth client** | Authenticate as your Firm and refresh access tokens | [Private Key JWT](/partners/get-connected/authentication) | | **KYC flow** | Onboard and verify Retail Participants | [KYC](/partners/onboarding/kyc/overview) | | **Order entry** | Submit orders with declared vendor fees on behalf of participants | [Submitting Orders](/partners/orders/create-order) *(Beta)* | | **Funding** | Fund participant trading accounts and collect vendor fees | [Partner Funding](/partners/funding/overview) *(Beta)* | | **Stream consumers** | Maintain a live mirror of orders, positions, and balances | [gRPC streaming](/streaming-endpoints/grpc-overview) | | **Webhook receiver** | Receive KYC and account lifecycle notifications | Account notifications *(coming soon)* | Polymarket US is a **streaming-first** platform. REST endpoints are intended for one-off queries and are rate-limited; continuous data should come from streams. Keep this in mind as you design every component above. ## Recommended reading order Start with [Your Role](/partners/your-role) and the [Platform Model](/partners/platform-model). Keep the [Glossary](/partners/glossary) open in a tab. Your funding entity **pre-positions pooled funds** and funds participant accounts with instant deposit transfers, with vendor fees declared per order and collected periodically — read the [Partner Funding](/partners/funding/overview) *(Beta)* model before designing your deposit, order, and fee flows. Contact your Polymarket US integration lead to enable funding for your integration. Complete partner [onboarding](/partners/get-connected/onboarding), then set up [authentication](/partners/get-connected/authentication). Confirm you can call `GET /v1/whoami` successfully. Implement the [KYC flow](/partners/onboarding/kyc/overview). You **collect** each participant's required information and submit it; Polymarket US **performs the verification and decision**. On approval, the Retail Participant and their [account](/partners/onboarding/accounts) are provisioned automatically. See [Onboard Participants](/partners/onboarding/users). Walk through the [Quickstart](/partners/get-connected/quickstart) to authenticate, list instruments, and place and cancel an order end to end. Subscribe to the [order, position, and balance streams](/streaming-endpoints/grpc-overview) and maintain a local mirror. Add a webhook receiver for KYC and account lifecycle events. ## Start here What you do, what we do, and what you never handle. DCM + DCO and the entities you act on. Authenticate as your Firm with Private Key JWT. Place your first order end to end. # Accounts Source: https://docs.polymarket.us/partners/onboarding/accounts Trading account management for partners The Accounts API provides trading account information for partners. Accounts are the containers for positions, balances, and order history. **Accounts are created by KYC, not by a separate call.** A trading account is provisioned automatically when a Retail Participant's KYC verification is approved — there is no manual account-creation endpoint. See [Onboard Participants](/partners/onboarding/onboard-participants) for how KYC creates the Participant and Account together. ## Endpoints | Method | Endpoint | Description | API reference | | ------ | -------------- | --------------------- | ------------------------------------------------------------- | | `GET` | `/v1/accounts` | List trading accounts | [List accounts ↗](/institutional/accounts/overview#endpoints) | For participant **identity**, see [Participants](/partners/onboarding/users). The underlying identity endpoints are documented in the API reference: [Get who am I ↗](/institutional/accounts/overview#endpoints) (`GET /v1/whoami`) and [List users ↗](/institutional/accounts/overview#endpoints) (`GET /v1/users`). ## Account Hierarchy ``` Partner (Firm) └── Retail Participants (created via KYC) └── Accounts (auto-provisioned) └── Positions & Orders ``` * **Firm**: Your partner organization * **Retail Participants**: Individual retail traders (created when KYC is approved) * **Accounts**: Trading accounts (automatically provisioned with the participant) ## List Accounts Returns the trading accounts available to the authenticated user. ### Request ```bash theme={null} GET /v1/accounts ``` Optional query parameter: * `user` - Filter by user ID ### Response ```json theme={null} { "accounts": [ "firms/ISV-Participant-Acme/accounts/user-123-trading", "firms/ISV-Participant-Acme/accounts/user-456-trading" ], "displayNames": [ "John's Trading Account", "Jane's Trading Account" ] } ``` ### Response Fields | Field | Type | Description | | -------------- | ----- | --------------------------------------------- | | `accounts` | array | Account identifiers | | `displayNames` | array | Human-readable account names (parallel array) | ## Participant vs Account | Entity | Description | | --------------- | ------------------------------------------------------------------------------------- | | **Participant** | A person with a verified identity (KYC). Created automatically on KYC approval. | | **Account** | A trading account with balances and positions. Auto-provisioned with the participant. | A Retail Participant can have multiple accounts for different purposes (e.g., separate trading strategies). ## Related guides * [Onboard Participants](/partners/onboarding/onboard-participants) — How KYC creates the Participant and Account * [Participants](/partners/onboarding/users) — Resolve who you can act for * [KYC Verification](/partners/onboarding/kyc/overview) — The participant onboarding process * [Positions API](/institutional/positions/overview) — Check account balances and positions # Digital Intelligence Source: https://docs.polymarket.us/partners/onboarding/kyc/digital-intelligence Capture the Socure Digital Intelligence session_token to keep your instant-approval rate high and your REVIEW rate low. **BETA — SUBJECT TO CHANGE.** This API is in beta and may change without notice. Socure **Digital Intelligence (DI)** is a device and behavioural risk-assessment script that runs in the participant's browser or app during your KYC form. It produces a short-lived **`session_token`** that you include in `POST /v1/kyc/start`. **Audience: developers (with a product-team section below).** The `session_token` is **optional but strongly recommended** — it is the single most effective lever for keeping your REVIEW rate low and your instant-approval rate high. ## Why it matters Socure's evaluation combines the identity data you submit (PII) with device and behavioural context. The DI signals are what tip borderline cases into a clear **ACCEPT**: * **More participants approved instantly.** Without DI signals, Socure has less confidence in legitimate participants, and more of them land in **REVIEW**. * **Lower REVIEW rate.** A REVIEW means onboarding isn't finished — the participant must either upload a photo ID ([DocV](/partners/onboarding/kyc/verification-flow#document-verification-docv) — adds friction and drop-off) or wait for a [manual review](/partners/onboarding/kyc/verification-flow#manual-compliance-review) (hours to days). Every reduction in REVIEW is a direct conversion win. * **Device-level fraud detection.** DI also flags velocity abuse, bot patterns, and fraud rings, improving the quality of your participant population. Omitting the `session_token` will **not** cause an evaluation to fail — but expect a higher false-REVIEW rate. ## What data DI collects DI collects **non-PII** device and session signals only. PII (name, SSN, address) is transmitted solely when you call `POST /v1/kyc/start`. | Signal type | Examples | | ------------------- | --------------------------------------------------------- | | Device fingerprint | Browser type/version, OS, screen resolution, fonts | | Network signals | IP address, ASN, proxy/VPN detection | | Behavioural signals | Typing cadence, form-interaction timing, pointer patterns | | Session metadata | Session duration, page-interaction sequence | ## Implementation The Socure `sdk_key` is provided by the Polymarket US onboarding team. A **single** SDK key initialises both Digital Intelligence and [DocV](/partners/onboarding/kyc/verification-flow#document-verification-docv). It is a **public** key and safe to include in client-side code. Initialise DI when the KYC form page loads, then call `getSessionToken()` immediately before submitting the form. ```javascript Web theme={null} // 1. On KYC form page load — initialise early import { Socure } from '@socure-inc/socure-sdk'; Socure.init(sdkKey); // 2. Just before form submission — get the token const sessionToken = await Socure.getSessionToken(); // 3. Include it in your KYC request await fetch('https://api.preprod.polymarketexchange.com/v1/kyc/start', { method: 'POST', headers: { Authorization: `Bearer ${accessToken}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ external_id: 'your-internal-user-id-123', // ...other fields... session_token: sessionToken, ip_address: userIpAddress, }), }); ``` ```swift iOS theme={null} import SocureSDK // On KYC view load SocureSDK.shared.initialize(publicKey: sdkKey) // Before submission SocureSDK.shared.getSessionToken { sessionToken, error in guard let token = sessionToken else { return } // submit KYC with session_token = token } ``` ```kotlin Android theme={null} import com.socure.android.sdk.SocureSDK // On KYC screen creation SocureSDK.init(context, sdkKey) // Before submission SocureSDK.getSessionToken { sessionToken -> // submit KYC with session_token = sessionToken } ``` Call `init()` when the form page loads so the signal collection is spread across the participant's time on the page. `getSessionToken()` typically completes in under 100ms. ## Addressing product-team concerns The DI script is hosted by Socure and loaded from `sdk.socure.com`. Load it **only** on your KYC form page(s) — not site-wide. It runs during the KYC flow and collects non-PII signals. No. The DI session token is cryptographically tied to Socure's own collection event; it cannot be replicated by forwarding equivalent data through another channel. No. The script loads asynchronously and `getSessionToken()` typically returns in under 100ms. Initialising on page load spreads the work across the participant's time on the page. ## Next steps Submit the participant with the `session_token` and handle each outcome. How the Socure-backed KYC process fits together. # Overview Source: https://docs.polymarket.us/partners/onboarding/kyc/overview How identity verification works for partners: the Socure-backed KYC process, its four outcomes, and the integration architecture. **BETA — SUBJECT TO CHANGE.** This API is in beta and may change without notice. Before any Retail Participant can trade, they must pass a **KYC (Know Your Customer)** identity check. Polymarket US uses [Socure](https://www.socure.com/) as its identity verification (IDV) provider. You collect the participant's information and submit it; Socure performs the identity evaluation; and on approval Polymarket US provisions the participant's trading account. You receive the result via a webhook you register in advance. **You don't talk to Socure for evaluation — Polymarket US does.** You call the KYC endpoints on your participant's behalf, and Polymarket US handles the Socure evaluation and account provisioning. The only time a participant's device touches Socure directly is the optional [Digital Intelligence](/partners/onboarding/kyc/digital-intelligence) script and, when required, the document-upload (DocV) UI. ## What a participant experiences 1. They complete an identity form on your platform (name, address, SSN, date of birth) and accept the participant agreement. Optionally, the [Prefill Flow](/partners/onboarding/kyc/prefill-flow) can auto-populate most of the form from their phone number and date of birth via an SMS one-time passcode. 2. In most cases, verification is **instant** — approved or rejected within seconds. 3. Sometimes Socure asks them to **upload a photo ID** (document verification, "DocV") via a web URL or a mobile SDK. *This happens only if you [opted the evaluation in](/partners/onboarding/kyc/verification-flow#document-verification-docv) with `docv_eligible: true`; otherwise these cases go to manual review.* 4. In a small number of cases, the application needs **manual review** by the Polymarket US compliance team — no action required from the participant. 5. Once approved, their trading account is ready and you receive a webhook. ## The four outcomes Every `POST /v1/kyc/start` resolves to one of four outcomes: | Outcome | What it means | Your next step | | -------------------------------- | -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Instant approval** | The identity was verified immediately | Await the [`kyc.approved` webhook](/partners/onboarding/kyc/webhooks) for the provisioned `participantId` (provisioning is async — see below) | | **Document verification (DocV)** | The provider needs a photo ID | Direct the participant to `docv.url` or the Socure SDK. Requires opting in with [`docv_eligible: true`](/partners/onboarding/kyc/verification-flow#document-verification-docv); otherwise these cases go to manual review | | **Manual compliance review** | Automated checks were inconclusive | Tell the participant to wait; await the webhook | | **Rejection** | Verification or risk assessment failed | Notify the participant — they cannot trade | See [Verification Flow](/partners/onboarding/kyc/verification-flow) for the request/response of each outcome and the decision matrix. **Approval is asynchronous even when instant.** A successful `POST /v1/kyc/start` may return a **non-terminal status with no `participantId` yet** while provisioning completes in the background. The final `participantId` arrives via the [webhook](/partners/onboarding/kyc/webhooks) and a later [`GET /v1/kyc/status`](/partners/onboarding/kyc/verification-flow#check-status). Don't assume it's on the initial response. ## Integration architecture Polymarket US sits between your platform and Socure. You never call Socure directly for evaluation; for document verification, the participant's browser or app connects to Socure's hosted UI. ```mermaid theme={null} sequenceDiagram participant U as Retail Participant participant ISV as Your Platform participant PM as Polymarket US participant S as Socure ISV->>PM: POST /v1/kyc/start PM->>S: Identity evaluation alt Instant decision (most participants) S-->>PM: ACCEPT or REJECT PM-->>ISV: Response with participantId (or rejection) else Document verification required (if docv_eligible: true) S-->>PM: REVIEW + DocV credentials PM-->>ISV: Response with docv.url / QR / transaction token ISV-->>U: Direct to DocV U->>S: Upload ID (Socure-hosted) S->>PM: Final decision PM->>ISV: Webhook: participant ready (or rejected) else Manual compliance review S-->>PM: REVIEW, no DocV data PM-->>ISV: Response: decision=REVIEW, no docv field Note over PM: Polymarket US compliance reviews PM->>ISV: Webhook: participant ready (or rejected) end ``` ## REST and gRPC Every KYC action is available over **both REST and gRPC**. They are the same underlying service — identical fields, semantics, outcomes, and scopes — so choose whichever transport fits your stack and mix freely: | Action | REST | gRPC (`polymarket.us.kyc.v1.KYCAPI`) | | ------------------------------------- | -------------------------- | ------------------------------------ | | Submit a participant for verification | `POST /v1/kyc/start` | `StartKYCVerification` | | Check current status | `GET /v1/kyc/status` | `GetKYCStatus` | | Register your webhook URL | `POST /v1/kyc/webhook` | `SetWebhookURL` | | Start identity prefill *(optional)* | `POST /v1/kyc/prefill` | `StartKYCPrefill` | | Submit prefill OTP *(optional)* | `POST /v1/kyc/prefill/otp` | `SubmitKYCPrefillOTP` | Both transports authenticate with the same firm [access token](/partners/get-connected/authentication) — REST in the `Authorization` header, gRPC as `authorization: Bearer ` metadata on each RPC. These pages use REST for the worked examples; the gRPC message shapes are in [Verification Flow → Using gRPC](/partners/onboarding/kyc/verification-flow#using-grpc). ## Field naming The REST API is a JSON mapping of the same protobuf-defined service, which is why casing differs by direction — map fields by meaning rather than assuming one style across the whole flow: * **Request bodies and query parameters** use `snake_case` (e.g. `external_id`, `date_of_birth`). * **REST JSON responses** use `camelCase` (e.g. `externalId`, `participantId`, `subStatus`) — they follow protobuf JSON naming. * **gRPC messages** use the proto field names (`snake_case`) natively. * **Webhook payloads** use `snake_case` (e.g. `external_id`, `provisioned_participant`). We may align casing across the API in a future version; any change will be announced in the [changelog](/changelog). ## Prerequisites Before going live, make sure you have: | Item | Provided by | | --------------------------------------------------------------------------- | --------------------------------------- | | API credentials (Client ID + private key) | Polymarket US onboarding team | | Socure **SDK key** — one key initialises both Digital Intelligence and DocV | Polymarket US onboarding team | | Participant agreement version string | Polymarket US onboarding team | | An HTTPS **webhook URL**, registered via `POST /v1/kyc/webhook` | You | | Socure mobile SDK *(only if you have a native app)* | [Socure](https://github.com/socure-inc) | ## Next steps Capture the `session_token` that keeps your approval rate high. Submit a participant and handle each of the four outcomes. Receive the async decision instead of polling. # Prefill Flow Source: https://docs.polymarket.us/partners/onboarding/kyc/prefill-flow Optionally pre-populate the KYC form from a phone number and date of birth to reduce data entry. **BETA — SUBJECT TO CHANGE.** This API is in beta and may change without notice. Prefill lets a participant auto-populate their KYC information from their phone number, reducing manual data entry and form abandonment. It is **optional** and sits entirely *in front of* the standard flow — you can skip it, collect all fields yourself, and go straight to [`POST /v1/kyc/start`](/partners/onboarding/kyc/verification-flow). Nothing about verification, outcomes, or webhooks changes. ## How it works 1. The participant provides their phone number and date of birth. 2. Polymarket US sends a one-time passcode (OTP) to the phone. 3. The participant enters the OTP to verify ownership. 4. You receive prefilled identity data (name, address, last 4 of SSN, etc.). 5. The participant reviews and corrects it, provides the full SSN, accepts the agreement, and you submit the standard [verification request](/partners/onboarding/kyc/verification-flow). ## Step 1: Start prefill ```bash theme={null} POST /v1/kyc/prefill ``` ```json theme={null} { "phone_number": "+15551234567", "date_of_birth": "1990-01-15", "user_id": "your-internal-user-id-123", "session_token": "{socure_di_session_token}", "ip_address": "203.0.113.42" } ``` | Field | Type | Required | Description | | --------------- | ------ | -------- | --------------------------------------------------------------------------------------------------------- | | `phone_number` | string | Yes | E.164 format (e.g. `+15551234567`) | | `date_of_birth` | string | Yes | `YYYY-MM-DD` | | `user_id` | string | Yes | Your stable internal identifier for this participant | | `session_token` | string | No | Socure [Digital Intelligence](/partners/onboarding/kyc/digital-intelligence) token — strongly recommended | | `ip_address` | string | No | Participant's IP address | Response: ```json theme={null} { "status": { "decision": "pending", "status": "otp_sent", "subStatus": "", "externalId": "your-internal-user-id-123" }, "externalId": "your-internal-user-id-123" } ``` Persist the returned `externalId` — it is the correlation identifier for the OTP step and for the subsequent verification request. ## Step 2: Submit OTP ```bash theme={null} POST /v1/kyc/prefill/otp ``` ```json theme={null} { "otp": "123456", "external_id": "your-internal-user-id-123" } ``` | Field | Type | Required | Description | | ------------- | ------ | -------- | ------------------------------------------ | | `otp` | string | Yes | The 6-digit code the participant received | | `external_id` | string | Yes | The `externalId` from the prefill response | On success, the response includes the prefilled identity data: ```json theme={null} { "status": { "decision": "pending", "status": "prefill_complete", "subStatus": "", "externalId": "your-internal-user-id-123" }, "firstName": "John", "middleName": "Michael", "lastName": "Doe", "dateOfBirth": "1990-01-15", "phoneNumber": "+15551234567", "email": "john.doe@example.com", "ssn": "1234", "address": { "addressLine1": "123 Main Street", "addressLine2": "Apt 4B", "city": "New York", "state": "NY", "postalCode": "10001", "country": "US" } } ``` | Field | Description | | --------------------------------------- | ---------------------- | | `firstName` / `middleName` / `lastName` | Legal name | | `dateOfBirth` | Date of birth | | `phoneNumber` | Verified phone number | | `email` | Email (if available) | | `ssn` | **Last 4 digits only** | | `address` | Residential address | All identity fields are optional and may be absent if the prefill provider has no data for this phone/DOB. Always let the participant review and edit prefilled values before submission. ## Step 3: Submit to verification Map the prefilled data into the standard [`POST /v1/kyc/start`](/partners/onboarding/kyc/verification-flow) request, collect the **full SSN** and agreement acceptance, and submit. Pass the prefill `externalId` as the request's `external_id` so the inquiry correlates end to end. Prefill returns only the **last 4 digits** of the SSN. Collect the full SSN from the participant before calling `/v1/kyc/start`. From here the flow is exactly the [standard verification flow](/partners/onboarding/kyc/verification-flow) — same outcomes, decision matrix, and webhooks. ## Complete flow ```mermaid theme={null} sequenceDiagram participant U as Retail Participant participant App as Your Platform participant PM as Polymarket US U->>App: Enter phone + DOB App->>PM: POST /v1/kyc/prefill PM-->>App: externalId + OTP sent U->>App: Enter OTP from SMS App->>PM: POST /v1/kyc/prefill/otp PM-->>App: Prefilled data (name, address, SSN last 4) App-->>U: Review prefilled data U->>App: Confirm + full SSN + accept agreement App->>PM: POST /v1/kyc/start (with prefill data) PM-->>App: Verification result ``` ## Using gRPC Both steps are also available on `polymarket.us.kyc.v1.KYCAPI` with identical fields and semantics — see the [transport mapping](/partners/onboarding/kyc/overview#rest-and-grpc): ```protobuf theme={null} // POST /v1/kyc/prefill ≡ KYCAPI/StartKYCPrefill message StartKYCPrefillRequest { string date_of_birth; // YYYY-MM-DD string phone_number; string session_token; string user_id; // your stable customer id string ip_address; } message StartKYCPrefillResponse { KYCStatus status; string external_id; } // POST /v1/kyc/prefill/otp ≡ KYCAPI/SubmitKYCPrefillOTP message SubmitKYCPrefillOTPRequest { string otp; string external_id; } message SubmitKYCPrefillOTPResponse { KYCStatus status; KYCAddress address; optional string ssn; // last 4 digits optional string date_of_birth; optional string phone_number; optional string email; optional string first_name; optional string middle_name; optional string last_name; } ``` ## Error handling | Error | Cause | Resolution | | --------------------- | -------------------------- | ----------------------------- | | Invalid phone number | Wrong format | Use E.164 (`+1XXXXXXXXXX`) | | OTP expired | Usually after 10 minutes | Restart the prefill flow | | OTP invalid | Wrong code | Re-enter or request a new OTP | | Prefill not available | No data for this phone/DOB | Proceed with manual entry | Allow up to 3 OTP attempts before requiring a new code, and rate-limit OTP requests to prevent abuse. Prefill failures should never block onboarding — fall back to the manual form. ## Sandbox testing | Scenario | `date_of_birth` | `phone_number` | | ---------------- | --------------- | -------------- | | Prefill match | `1985-03-30` | `14155551212` | | No prefill match | `1985-03-30` | `12067890036` | | OTP code | Result | | -------- | -------------------------------------- | | `123456` | Success — returns prefilled data | | `00000` | Reject — OTP verification fails | | `000000` | Pending — verification remains pending | ## Next steps Submit the (prefilled) data and handle each outcome. Capture the `session_token` to lower your REVIEW rate. # Verification Flow Source: https://docs.polymarket.us/partners/onboarding/kyc/verification-flow Submit a participant for KYC and handle each of the four outcomes: instant approval, document verification, manual review, and rejection. **BETA — SUBJECT TO CHANGE.** This API is in beta and may change without notice. `POST /v1/kyc/start` is the primary endpoint for onboarding. You submit the participant's identity data (optionally with a Socure [Digital Intelligence](/partners/onboarding/kyc/digital-intelligence) `session_token`), and the response tells you which of the [four outcomes](/partners/onboarding/kyc/overview#the-four-outcomes) applies. Every action on this page is also available over gRPC with identical fields and semantics — see [Using gRPC](#using-grpc) and the [transport mapping](/partners/onboarding/kyc/overview#rest-and-grpc). To reduce form friction, the optional [Prefill Flow](/partners/onboarding/kyc/prefill-flow) can pre-populate most of the identity fields before you submit. ## Start verification ```bash theme={null} POST /v1/kyc/start ``` ```json theme={null} { "external_id": "your-internal-user-id-123", "ssn": "123456789", "first_name": "Jane", "middle_name": "", "last_name": "Smith", "date_of_birth": "1990-06-15", "email": "jane.smith@example.com", "phone_number": "+12125551234", "address": { "address_line_1": "123 Main St", "address_line_2": "Apt 4B", "city": "New York", "state": "NY", "postal_code": "10001", "country": "US" }, "agreement": { "version": "PMX.ISV.v1.0", "signed_at": "2026-04-24T14:30:00Z" }, "session_token": "{socure_di_session_token}", "ip_address": "203.0.113.42", "docv_eligible": true } ``` ### Request fields | Field | Type | Required | Description | | --------------------- | ------- | -------- | ------------------------------------------------------------------------------------------------------------------------- | | `external_id` | string | Yes | Your internal identifier for this participant. Max 49 chars, unique per firm. Echoed back in all responses and webhooks. | | `ssn` | string | Yes | Social Security Number, digits only (no dashes) | | `first_name` | string | Yes | Legal first name (max 50) | | `middle_name` | string | No | Legal middle name | | `last_name` | string | Yes | Legal last name (max 50) | | `date_of_birth` | string | Yes | `YYYY-MM-DD` | | `email` | string | Yes | Email address | | `phone_number` | string | Yes | E.164 format (e.g. `+12125551234`) | | `address` | object | Yes | Residential address (see below) | | `agreement.version` | string | Yes | Version of the participant agreement accepted. Confirm the current value with the onboarding team — do not hardcode. | | `agreement.signed_at` | string | Yes | UTC ISO-8601 timestamp of acceptance | | `session_token` | string | No | Socure [Digital Intelligence](/partners/onboarding/kyc/digital-intelligence) token — **strongly recommended** | | `docv_eligible` | boolean | No | Whether this evaluation may use [document verification (DocV)](#document-verification-docv). Omitted = not DocV-eligible. | | `ip_address` | string | Yes | Participant's IP address | ### Address object | Field | Required | Description | | ---------------- | -------- | ----------------------------------------------------------------------------------------------------- | | `address_line_1` | Yes | Street address | | `address_line_2` | No | Apartment, suite, unit | | `city` | Yes | City | | `state` | Yes | Two-letter US state code | | `postal_code` | Yes | Five-digit US ZIP code (for example `10001`) | | `country` | No | Two-letter country code; use `US`. Not validated platform-side — passed to the verification provider. | ## Decision matrix The response's `status` object carries `decision`, `status`, `subStatus`, and `externalId`. Together with the presence of a `docv` object, the synchronous response indicates the path: | `decision` | `docv` present | Meaning | | --------------------- | -------------- | --------------------------------------------------------------------------- | | `ACCEPT` / `APPROVED` | No | **Approved** — provisioning runs (often asynchronously; see below) | | `REVIEW` | Yes | **Document verification** — direct the participant to `docv.url` or the SDK | | `REVIEW` | No | **Manual compliance review** — inform the participant to wait | | `RESUBMIT` | No | Participant must resubmit documents | | `REJECT` | No | **Rejection** — no account created | **Treat `decision` / `status` / `subStatus` as informational, not control flow.** These values are passed through from the verification provider and the provider may emit values beyond the set above. Key your logic off the **`docv` presence** on the start and [status](#resuming-an-interrupted-docv-session) responses, and off the **[webhook](/partners/onboarding/kyc/webhooks) `event_type` / `status`** (`kyc.approved` / `kyc.rejected`) for the terminal outcome. ## Approval The most common path. Note that **approval is asynchronous even when the decision is instant**: a successful `POST /v1/kyc/start` may return a **non-terminal status** while backend account provisioning completes in the background. `participantId` is **often empty on this response** — you learn the final values from the [`kyc.approved` webhook](/partners/onboarding/kyc/webhooks) and from a later [`GET /v1/kyc/status`](#check-status) read. You send `snake_case`, but REST responses come back in `camelCase` (protobuf JSON naming) — e.g. `externalId`, `participantId`. See [Field naming](/partners/onboarding/kyc/overview#field-naming). ```json theme={null} { "status": { "decision": "ACCEPT", "status": "ON_HOLD", "subStatus": "Accept", "externalId": "your-internal-user-id-123" }, "participantId": "" } ``` A `GET /v1/kyc/status` read before provisioning completes returns empty provisioning identifiers: ```json theme={null} { "status": { "decision": "ACCEPT", "status": "ON_HOLD", "subStatus": "Accept", "externalId": "your-internal-user-id-123" }, "participantId": "", "provisionedAccount": "" } ``` Once provisioning completes, `GET /v1/kyc/status` (and the webhook) return the engine-neutral identifiers: ```json theme={null} { "status": { "decision": "ACCEPT", "status": "CLOSED", "subStatus": "Accept", "externalId": "your-internal-user-id-123" }, "participantId": "firms/ISV-Participant-YourFirmID/users/your-internal-user-id-123", "provisionedAccount": "firms/ISV-YourFirmID/accounts/8f3a2c...e1" } ``` `provisionedAccount` is the fully qualified DCM account name. Its account identifier is opaque and cannot be derived from participant or user data. **Automatic provisioning.** On approval, Polymarket US automatically creates the participant's trading identity and account — there is no separate account-creation step (see [Onboard Participants](/partners/onboarding/onboard-participants)). Because provisioning is asynchronous, **wait for the webhook (or a populated `participantId` from status) before enabling trading** rather than assuming the start response carries it. **Persist the account mapping.** At approval time, store the `externalId` → `provisionedAccount` mapping. `provisionedAccount` exactly matches `BalanceLedgerEntry.account` on the [balance ledger stream](/streaming-endpoints/balance-ledger-stream); exact string matching is the only way to attribute those stream entries to a user. **Which field identifies the participant when you trade?** Use `participantId` (the webhook calls the same value `provisioned_participant`) as the [`x-participant-id`](/partners/get-connected/authentication#acting-on-behalf-of-a-participant) header — that is *who* the order is for. `provisionedAccount` identifies the participant's balance-ledger account. Your `externalId` is **your** reference only and is never sent to identify the participant. See [Using these identifiers to trade](/partners/onboarding/kyc/webhooks#using-these-identifiers-to-trade). ## Document verification (DocV) DocV is **opt-in per request** via the `docv_eligible` field: ```json theme={null} { "docv_eligible": true, ... } // this evaluation may use DocV { "docv_eligible": false, ... } // DocV disabled for this evaluation ``` When Socure can't verify from the submitted data alone and the evaluation is DocV-eligible, the response includes a `docv` object. **Detect it by the presence of a non-empty `docv` field** (with `decision: "REVIEW"`). ```json theme={null} { "status": { "decision": "REVIEW", "status": "ON_HOLD", "subStatus": "Document Request Initiated", "externalId": "your-internal-user-id-123" }, "docv": { "url": "https://verify.socure.com/doc/abc123", "qrCode": "data:image/png;base64,...", "docvTransactionToken": "dt_abc123...", "eventId": "evt_456", "sdkKey": "" } } ``` | Field | Use | | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `url` | Redirect or embed for browser-based document upload | | `qrCode` | Base64 PNG — display on desktop for mobile handoff | | `docvTransactionToken` | Launch token for the Socure mobile SDK | | `sdkKey` | **Always empty — ignore it.** Initialise the Socure SDK with the SDK key issued by the Polymarket US onboarding team; a [single key initialises both Digital Intelligence and DocV](/partners/onboarding/kyc/digital-intelligence#implementation) | | `eventId` | Socure event identifier | You can direct the participant three ways — a **web URL**, a **QR code** for desktop→mobile handoff, or the **native Socure SDK**. Document capture is fully handled by Socure's UI; you don't build capture logic yourself. The SDK `onSuccess` callback (or completing the web upload) only means the participant **submitted** documents — not that they were **approved**. After submission, await the [`kyc.approved` webhook](/partners/onboarding/kyc/webhooks) or poll `GET /v1/kyc/status`. After the participant completes DocV, Socure notifies Polymarket US, which provisions the account (on approval) and sends you the final-decision webhook. ## Manual compliance review If Socure can't make a determination and no DocV path is available, the response is `REVIEW`/`OPEN` **with no `docv` field**. The Polymarket US compliance team reviews the case. ```json theme={null} { "status": { "decision": "REVIEW", "status": "OPEN", "subStatus": "In Review", "externalId": "your-internal-user-id-123" } } ``` Tell the participant their application is under review (typically 1–2 business days) and await the webhook. Implement the [polling fallback](#error-handling--polling) for resilience. ## Rejection ```json theme={null} { "status": { "decision": "REJECT", "status": "CLOSED", "subStatus": "Reject", "externalId": "your-internal-user-id-123" } } ``` No account is created. Rejection can also occur **after** DocV or **after** a manual review, in which case you receive a [`kyc.rejected` webhook](/partners/onboarding/kyc/webhooks). ## Check status Poll the current status with the `external_id` you submitted. The response mirrors the `start` response and includes `participantId` and `provisionedAccount` once provisioned. ```bash theme={null} GET /v1/kyc/status?external_id=your-internal-user-id-123 ``` Partner-relevant `status.status` values: | `status.status` | Meaning | | ---------------------- | ----------------------------------------------------------------------------------------------------------------------- | | `OPEN` | In progress (for example, manual review) | | `REVIEW` / `IN REVIEW` | Under review | | `ON_HOLD` | Non-terminal provider status (for example, an instant approval while provisioning completes, or a document-upload step) | | `CLOSED` | Terminal — check `decision` | Values are passed through from the verification provider and may extend beyond this set. Use populated provisioning identifiers (and the [webhook](/partners/onboarding/kyc/webhooks)) as your signal that the participant is ready to trade — not a specific `status` string. ### Resuming an interrupted DocV session Participants may close the browser or app mid-upload. While the evaluation is **paused awaiting document capture**, `GET /v1/kyc/status` returns the same `docv` object as [`POST /v1/kyc/start`](#document-verification-docv), so you can re-present the URL, QR code, or SDK token and let them pick up where they left off. ```json theme={null} { "status": { "decision": "REVIEW", "status": "ON_HOLD", "subStatus": "Document Request Initiated", "externalId": "your-internal-user-id-123" }, "docv": { "url": "https://verify.socure.com/doc/abc123", "qrCode": "data:image/png;base64,...", "docvTransactionToken": "dt_abc123...", "eventId": "evt_456", "sdkKey": "" }, "participantId": "", "provisionedAccount": "" } ``` The fields are identical to the start response — including `sdkKey`, which is [always empty](#document-verification-docv). `docv` is returned **only while a capture step is genuinely outstanding**. It is absent once the participant finishes uploading, absent on terminal evaluations, and absent on every evaluation that never needed DocV — including the instant-approval `ON_HOLD` shown under [Approval](#approval), which is a *provisioning* hold rather than a document hold. So `docv` presence, not `status.status`, is what tells you a participant still owes documents. **The capture URL and transaction token are credentials.** Deliver them only to the participant who owns the evaluation; never log them, cache them in a shared store, or expose them on an endpoint another user can reach. ## Error handling & polling | HTTP | gRPC | Meaning | Action | | ---- | ------------------ | --------------------------------------------- | ---------------------------------------- | | 200 | OK | Success | Process the response | | 400 | INVALID\_ARGUMENT | Missing or invalid field | Fix the request | | 401 | UNAUTHENTICATED | Invalid/expired token | Refresh the token | | 403 | PERMISSION\_DENIED | Firm lacks KYC API access | Contact the onboarding team | | 409 | ALREADY\_EXISTS | Participant already has approved KYC | Fetch via `GET /v1/kyc/status` | | 429 | — | Throttled at the edge before reaching the API | Retry with backoff (honor `Retry-After`) | | 500 | INTERNAL | Gateway error | Retry with exponential backoff | **`external_id` is idempotent.** Re-submitting `POST /v1/kyc/start` for a participant who already passed KYC returns `409 ALREADY_EXISTS`. Handle it by fetching the existing status rather than treating it as an error. **Prefer [webhooks](/partners/onboarding/kyc/webhooks) over polling.** As a fallback, poll `GET /v1/kyc/status` every 5–10 seconds for up to 5 minutes after DocV submission. For manual-review cases, poll on a longer cadence (e.g. every 30 minutes for up to 2 business days) and notify the participant asynchronously. ## Using gRPC The same actions are exposed by `polymarket.us.kyc.v1.KYCAPI` over gRPC (TLS, port 443), authenticated with `authorization: Bearer ` metadata. Fields, validation, the decision matrix, and the four outcomes are identical to the REST flow — only the transport differs. Proto stubs are supplied during onboarding. ```protobuf theme={null} // POST /v1/kyc/start ≡ KYCAPI/StartKYCVerification message StartKYCVerificationRequest { string ssn; KYCAddress address; // address_line_1/2, city, state, postal_code, country string date_of_birth; // YYYY-MM-DD string phone_number; string email; string first_name; string middle_name; string last_name; string session_token; // Socure Digital Intelligence string external_id; // your stable inquiry correlation id KYCAgreement agreement; // version, signed_at optional string referral_code; string ip_address; optional bool docv_eligible; // opt this evaluation into DocV; unset = not eligible } message StartKYCVerificationResponse { KYCStatus status; // decision, status, sub_status, external_id KYCDocv docv; // DocV URL / QR / transaction token when required string participant_id; } // GET /v1/kyc/status ≡ KYCAPI/GetKYCStatus message GetKYCStatusRequest { string external_id; } message GetKYCStatusResponse { KYCStatus status; string participant_id; string provisioned_account; // field 4; field 3 is reserved KYCDocv docv; // field 5; live capture session while DocV is outstanding } ``` Handle outcomes exactly as described above: key off `docv` presence for the synchronous step and the [webhook](/partners/onboarding/kyc/webhooks) for the terminal outcome. gRPC status codes map to the REST errors in the [table above](#error-handling--polling). ## Outcome flowchart ```mermaid theme={null} flowchart TD A["POST /v1/kyc/start"] --> B{decision?} B -- "ACCEPT / APPROVED" --> P["Provisioning runs (async)"] B -- "REJECT" --> E["Notify participant: verification failed"] B -- "REVIEW + docv present" --> D["Direct to DocV (URL / QR / SDK)"] B -- "REVIEW + no docv" --> M["Inform participant: under review"] P --> G["Await kyc.approved webhook"] D --> G M --> G G -- "approved + provisioned_participant" --> C["Store participantId. Participant ready."] G -- "rejected" --> E G -- "no webhook" --> H["Poll GET /v1/kyc/status"] H --> C ``` ## Sandbox testing In sandbox, with **DocV disabled** (`docv_eligible` false or omitted), Socure decides the outcome from the **name and email** you submit: | Outcome | How to trigger | | --------- | ---------------------------------------------------------------------------------------------------------- | | Rejection | Set `email` to `reject@example.com` | | Review | Set `first_name` to `Paulina` and `last_name` to `Gizela` (with any email other than `reject@example.com`) | | Approval | Use any other name / email combination | With **DocV enabled** (`docv_eligible: true`), sandbox outcomes are driven by the **date of birth** instead: | Outcome | `date_of_birth` | | ---------------------------------------------------- | --------------- | | Immediate approval | `1985-10-02` | | Immediate rejection | `1985-09-04` | | DocV — approved after upload | `1985-09-02` | | DocV — rejected after upload | `1985-09-05` | | DocV — review after upload (pending manual decision) | `1985-09-25` | Use these placeholder agreement values in sandbox (the onboarding team provides production values): | Field | Sandbox value | | --------------------- | ---------------------- | | `agreement.version` | `PMX.ISV.SANDBOX.v1.0` | | `agreement.signed_at` | Current UTC timestamp | ## Next steps Receive the async approval/rejection notifications. Lower your REVIEW rate with the `session_token`. # Webhooks Source: https://docs.polymarket.us/partners/onboarding/kyc/webhooks Receive terminal KYC outcomes (approved / rejected) via signed webhooks. **BETA — SUBJECT TO CHANGE.** This API is in beta and may change without notice. KYC outcomes that resolve after the initial request — instant approvals (whose account provisioning completes asynchronously), DocV results, and manual-review decisions — are delivered to a **webhook URL you register**. Webhooks are how you learn a participant's final provisioned `provisioned_participant` and any rejection. **Only terminal outcomes are delivered.** Intermediate states (DocV in progress, manual review) produce **no** webhook — poll [`GET /v1/kyc/status`](/partners/onboarding/kyc/verification-flow#check-status) if you need to track them. ## Register your webhook `POST /v1/kyc/webhook` — requires the `write:kyc` scope. Register **once per firm** (not per participant). Registration runs a test POST to your URL before saving; if the test fails, nothing is saved and you may retry. Also available over gRPC as `polymarket.us.kyc.v1.KYCAPI/SetWebhookURL` with the same two fields and the same validation behavior — see the [transport mapping](/partners/onboarding/kyc/overview#rest-and-grpc). ```bash theme={null} POST /v1/kyc/webhook ``` ```json theme={null} { "webhook_url": "https://your-platform.com/kyc-notifications", "signing_secret": "whsec_" } ``` ### Request fields | Field | Type | Required | Notes | | ---------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `webhook_url` | string | Yes | HTTPS URL where notifications are delivered | | `signing_secret` | string | No | Your Standard Webhooks signing secret in `whsec_` format, where the decoded key is at least 24 bytes (e.g. `whsec_` followed by 32 random bytes in standard padded base64). Omit to receive **unsigned** webhooks. | **You bring your own signing secret.** Polymarket US never generates or returns a secret — you supply and keep it. Use the Standard Webhooks `whsec_` format (e.g. `whsec_` followed by the output of `openssl rand -base64 32`). Registration is **last-writer-wins**: re-registering without a `signing_secret` **clears** any previously stored one (switching you to unsigned). ### Response ```json theme={null} { "validated": true, "message": "Webhook URL registered and validated successfully" } ``` | Field | Type | Notes | | ----------- | ------ | --------------------------------------------- | | `validated` | bool | `true` if the test POST to your URL succeeded | | `message` | string | Status / failure detail | ### Test-POST contract Registration sends a `POST` to your URL with a normal notification envelope whose `event_type` is **`webhook.test`** and whose `data` is a fixed informational message. It is signed (same scheme as real deliveries) when you supplied a `signing_secret`. Your endpoint passes only by returning a **2xx**. Redirects are not followed, and URLs that resolve to private or internal IP ranges are refused. ## Receiving notifications Polymarket US sends an HTTP `POST` with a JSON body to your registered URL. ### Headers Signed deliveries follow the [Standard Webhooks](https://www.standardwebhooks.com/) convention. The three signing headers are present **only when a signing secret is configured**. | Header | Notes | | ------------------- | ------------------------------------------------------------------------------------------------- | | `webhook-id` | Stable, retry-invariant event ID — use it to deduplicate | | `webhook-timestamp` | Unix seconds at send time | | `webhook-signature` | Space-delimited list of versioned signatures; currently a single `v1,` entry | | `Content-Type` | `application/json` | ### Body Webhook `data` fields are `snake_case` (e.g. `external_id`, `provisioned_participant`), unlike the `camelCase` REST responses. See [Field naming](/partners/onboarding/kyc/overview#field-naming). ```json theme={null} { "event_type": "kyc.approved", "event_id": "01J0...", "event_time": "2026-04-24T15:32:00Z", "data": { "external_id": "your-internal-user-id-123", "user_id": "your-internal-user-id-123", "kyc_eval_id": "", "firm_id": "your-firm-id", "status": "KYC_STATUS_APPROVED", "provisioned_participant": "firms/ISV-Participant-YourFirmID/users/...", "provisioned_account": "firms/ISV-YourFirmID/accounts/8f3a2c...e1", "status_set_at": "2026-04-24T15:32:00.123456789Z" } } ``` ### Event types | `event_type` | Meaning | | -------------- | ------------------------------------------------------------------------ | | `kyc.approved` | Participant approved and backend entities provisioned | | `kyc.rejected` | Participant rejected | | `webhook.test` | Registration-time test event only (see above); never delivered afterward | ### `data` fields Empty fields are omitted from the JSON. | Field | Present on | Notes | | ------------------------- | -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `external_id` | both | The identifier you supplied at start. Your correlation key; matches `status.externalId` and the `GET /v1/kyc/status` lookup | | `user_id` | both | Same value as `external_id` (legacy field name) | | `kyc_eval_id` | both | Verification-provider evaluation id, carried under its own field. Optional — present only when the provider id is exposed. Never use it as your correlation key | | `firm_id` | both | Your firm ID | | `status` | both | `KYC_STATUS_APPROVED` or `KYC_STATUS_REJECTED` | | `status_set_at` | both | RFC 3339 timestamp of the decision | | `provisioned_participant` | `kyc.approved` | Engine-neutral participant identifier — opaque string. Use it to act on behalf of the participant | | `provisioned_account` | `kyc.approved` | Fully qualified DCM account name provisioned on approval. It exactly matches `BalanceLedgerEntry.account` on the [balance ledger stream](/streaming-endpoints/balance-ledger-stream); store it to route ledger entries to the user | | `date_of_birth` | `kyc.approved` | The participant's date of birth may be included. Treat as sensitive PII | | `referral_code_owned` | `kyc.approved` | Present when an owned referral code is assigned | | `rejection_reason` | `kyc.rejected` | Finer-grained rejection detail (provider sub-status) | ## Using these identifiers to trade When you place an order on behalf of a participant, **`provisioned_participant` is the "who"** — pass it as the [`x-participant-id`](/partners/get-connected/authentication#acting-on-behalf-of-a-participant) header on participant-scoped requests (trading, positions, reports). It is the only provisioning identifier you need. | KYC field (webhook) | Same value in [`GET /v1/kyc/status`](/partners/onboarding/kyc/verification-flow#approval) | Format | What to do with it | | ------------------------- | ----------------------------------------------------------------------------------------- | -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | | `provisioned_participant` | `participantId` | `firms/ISV-Participant-YourFirmID/users/...` | Send as the **`x-participant-id` header** — this is *who* the order is for | | `external_id` / `user_id` | `status.externalId` | your own string | **Your** correlation key only — store it to map back to your user. **Never** sent to Polymarket US to identify the participant on a trading call | **Use `provisioned_participant`, not `external_id` / `user_id`, to identify the participant on a trading call.** The `external_id` (and its legacy duplicate `user_id`) is the identifier *you* supplied — it is meaningful only inside your own systems. The exchange identifies the participant by the `provisioned_participant` value carried in the `x-participant-id` header. The webhook and `GET /v1/kyc/status` carry the **same value** under different field names — the webhook calls it `provisioned_participant`, while the status read calls it `participantId`. ```bash theme={null} # Placing an order for the participant from a kyc.approved webhook curl -X POST https://api.polymarket.us/v1/orders \ -H "Authorization: Bearer {access_token}" \ -H "Content-Type: application/json" \ -H "x-participant-id: firms/ISV-Participant-YourFirmID/users/your-internal-user-id-123" \ -d '{ "symbol": "tec-nfl-sbw-2026-02-08-kc", "side": "SIDE_BUY", "order_qty": 100, "price": 550, "type": "ORDER_TYPE_LIMIT", "time_in_force": "TIME_IN_FORCE_GOOD_TILL_CANCEL" }' ``` ## Delivery semantics * **At-least-once.** Deduplicate on `event_id` (the `webhook-id` header) — you may receive the same event more than once. * **Retries.** Only a `2xx` counts as delivered. Redirects are not followed (a `3xx` is a failed delivery); every non-2xx or transport error is retried with jittered exponential backoff up to a maximum attempt count. `Retry-After` is honored on `408` / `429`. A repeatedly-failing endpoint is **circuit-broken**: deliveries pause for a cooldown that grows with each consecutive re-open, then resume. * **Respond fast.** Return `2xx` promptly once you've durably accepted the event; do heavy processing asynchronously. ## Verifying the signature Because the secret is in the standard `whsec_`/base64 format, a [Standard Webhooks SDK](https://github.com/standard-webhooks/standard-webhooks/tree/main/libraries) verifies deliveries out of the box — initialise it with the **default** constructor (e.g. `NewWebhook` in Go), passing the secret exactly as you registered it. No raw-key escape hatch (`NewWebhookRaw`) is needed. If you verify inline instead, the signed content is `..` — the **raw request bytes**, before any JSON re-serialization. The HMAC key is the **decoded** Standard Webhooks key: strip the optional `whsec_` prefix from your `signing_secret` and base64-decode the remainder using standard padded base64. Compute the expected signature, then **constant-time compare** it against each space-delimited candidate in `webhook-signature`; accept if any matches. ```go theme={null} // Derive the key: drop the optional "whsec_" prefix, then base64-decode. key, err := base64.StdEncoding.DecodeString(strings.TrimPrefix(secret, "whsec_")) if err != nil { /* reject: malformed secret */ } // signed content: ".." + raw body mac := hmac.New(sha256.New, key) mac.Write([]byte(id + "." + timestamp + ".")) mac.Write(body) expected := "v1," + base64.StdEncoding.EncodeToString(mac.Sum(nil)) for _, candidate := range strings.Fields(signatureHeader) { if hmac.Equal([]byte(candidate), []byte(expected)) { return true // valid } } return false // reject ``` Reject deliveries whose `webhook-timestamp` is too old to limit replay exposure, and always verify against the **raw** body you received rather than a re-encoded copy. ## Next steps The synchronous outcomes that precede these webhooks. How the KYC process fits together. # End-User Legal Agreements Source: https://docs.polymarket.us/partners/onboarding/legal-agreements The four documents every end user accepts during onboarding, the exact acceptance language and flow, and how agreement versioning works. Every end user accepts the Polymarket US legal document set during onboarding, inside your app. This page covers the required documents, the acceptance language and flow, and how agreement versioning works. You can build against this now — the structure will not change. Identity verification (KYC), funding, and account provisioning are covered in the [Integration Journey](/partners/integration-journey) and the [KYC](/partners/onboarding/kyc/overview) documentation. **Regulatory context.** Polymarket US is a CFTC-regulated marketplace for event contracts. Two distinct regulated entities are party to the customer relationship: **QCX LLC** d/b/a Polymarket US, the Designated Contract Market (DCM), and **QC Clearing LLC** d/b/a Polymarket Clearing, the Derivatives Clearing Organization (DCO). ## Required documents Four documents make up the **complete** legal set. No additional terms and conditions apply beyond these four. | Document | Issuing entity | Location | | ----------------------------- | ----------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | | **Participant Agreement** | QCX LLC and QC Clearing LLC (jointly, "PMUS") | [polymarketexchange.com/files/legal/latest/participant-agreement](https://polymarketexchange.com/files/legal/latest/participant-agreement) | | **Exchange Rulebook** | QCX LLC d/b/a Polymarket US (DCM) | [polymarketexchange.com/regulatory.html](https://polymarketexchange.com/regulatory.html) | | **Clearinghouse Rulebook** | QC Clearing LLC d/b/a Polymarket Clearing (DCO) | [polymarketexchange.com/clearing/](https://polymarketexchange.com/clearing/) | | **Risk Disclosure Statement** | PMUS | [polymarketexchange.com/files/legal/latest/risk-disclosure-statement](https://polymarketexchange.com/files/legal/latest/risk-disclosure-statement) | The Participant Agreement is a **click-through agreement**: its first-page language makes clicking "I Accept" the legal equivalent of a manual signature. Customers accept the unmodified document through an affirmative in-app action; it is not filled out or signed by hand. ## Acceptance flow Display the following language, exactly as written, immediately above the acceptance button. The four document names must render as live hyperlinks to the documents in [Required documents](#required-documents): > "By clicking below, I hereby (i) acknowledge that I have read and understood, and consent to the terms of the [Participant Agreement](https://polymarketexchange.com/files/legal/latest/participant-agreement) and the Rulebooks ([Polymarket Clearing Rulebook](https://polymarketexchange.com/clearing/), [Polymarket US Rulebook](https://polymarketexchange.com/regulatory.html)) and (ii) certify that I will abide by the Rules stated therein, as may be amended from time to time, and any applicable laws or regulations affecting PMUS, my use of PMUS and the transactions executed and/or cleared through PMUS. I also hereby acknowledge that I have read and understood the [Risk Disclosure](https://polymarketexchange.com/files/legal/latest/risk-disclosure-statement)." ### Requirements | # | Requirement | What it means | | ------- | ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **R-1** | Verbatim text | Display the acceptance language exactly as written — no paraphrasing, abbreviation, translation, or splitting across multiple checkboxes. | | **R-2** | Single affirmative action | Capture acceptance through one explicit, user-initiated action. Pre-checked boxes, implied consent, or acceptance bundled into an unrelated action do not satisfy this requirement. | | **R-3** | Live document links | All four hyperlinks must resolve to the documents in [Required documents](#required-documents) and be accessible at the moment of acceptance. Do not host modified, excerpted, or re-rendered copies. | | **R-4** | Unmodified documents | The Participant Agreement is accepted as-is. There is no per-user fill-in, countersignature, or partner-specific rider. | | **R-5** | Timing | Acceptance is captured at time of application — before onboarding completes and before the customer's first trade. A customer who has not completed the acceptance action must not be provisioned for trading. | | **R-6** | Record of acceptance | Retain a record of each acceptance event: the customer identifier, timestamp, and the document set accepted. The accepted agreement version is also transmitted to Polymarket US on the KYC start request (see [Agreement versioning](#agreement-versioning)). | ## Agreement versioning The [KYC start request](/partners/onboarding/kyc/verification-flow) (`POST /v1/kyc/start`) carries an `agreement.version` field identifying the version of the Participant Agreement the customer accepted. **Version value.** The version is the **effective date** of the current Participant Agreement, formatted `MM.DD.YYYY`. The current value is `08.06.2026`. ```json theme={null} { "agreement": { "version": "08.06.2026", "signed_at": "2026-04-24T14:30:00Z" } } ``` How updates work: * Notice of agreement updates is posted on the Polymarket US website. * When the agreement is updated, the new effective date becomes the value to send in `agreement.version`. * The updated Participant Agreement publishes automatically to its location in [Required documents](#required-documents), so the document link in your acceptance flow always resolves to the current version. **Do not hard-code the version.** Treat the version value as configuration your system can rotate without a code change. ## Next steps Submit the accepted `agreement.version` on `POST /v1/kyc/start`. How acceptance fits into the wider participant onboarding flow. # Onboard Participants Source: https://docs.polymarket.us/partners/onboarding/onboard-participants How a retail trader becomes a tradable Participant: KYC is the onboarding process, and a Participant and Account are created automatically on approval. Onboarding a retail trader onto your platform **is the KYC process**. You collect the trader's required information and submit it; Polymarket US performs the identity verification and makes the decision. When KYC is approved, a **Participant** (their trading identity) and an **Account** (their balances and positions) are **created automatically** — there is no separate "create user" or "create account" step. This section has three parts that fit together: **The process.** Collect and submit identity information; Polymarket US verifies and decides. **The identity created.** The trading identity you act on behalf of after approval. **The account created.** The container for the participant's balances and positions. ## How they fit together KYC verification is **asynchronous**: you submit the participant's information, and Polymarket US notifies your platform of the decision via a **webhook**. On approval, the Participant and Account already exist and are ready for funding and trading. ```mermaid theme={null} sequenceDiagram participant RP as Retail Trader participant P as Partner participant PM as Polymarket US RP->>P: Provide identity details P->>PM: Submit KYC PM-->>P: Acknowledged — verification in progress Note over PM: Verifies identity asynchronously PM->>P: Webhook — KYC status change alt Approved Note over PM: Participant and Account
created automatically P->>RP: Ready to fund and trade else Rejected P->>RP: Onboarding declined end ``` 1. **You collect** each trader's required details and present the appropriate participant agreement. 2. **You submit** the information through the [KYC](/partners/onboarding/kyc/overview) endpoints. 3. **Polymarket US verifies** the identity asynchronously and makes the decision — you do not perform the verification yourself. 4. **You're notified by [webhook](/partners/onboarding/kyc/webhooks)** when the outcome is terminal. On approval, the `kyc.approved` event carries the participant's `provisioned_account` and `provisioned_participant` once the trading account is provisioned (provisioning is asynchronous, so this can arrive shortly after the initial response). 5. **On approval**, the trader's [Participant](/partners/onboarding/users) identity and [Account](/partners/onboarding/accounts) are already provisioned. Once their account is funded with a [deposit transfer](/partners/funding/deposits-withdrawals) *(Beta)*, they can trade. **Prefer the webhook over polling.** Register a [KYC webhook](/partners/onboarding/kyc/webhooks) so your platform is informed the moment a decision is made. An `ACCEPT` decision means the Participant and Account are provisioned for you with no separate setup call, but provisioning is asynchronous: their identifiers arrive on the [`kyc.approved`](/partners/onboarding/kyc/webhooks) event (which can follow shortly after the initial response), so don't assume they're on the first response. You can still fall back to `GET /v1/kyc/status` for one-off checks. **One process, two results.** Think of KYC as the onboarding action and the Participant and Account as its outputs. You never create them directly — a successful KYC produces both. ## Where to go next The verification workflow and document verification. Receive the async KYC decision instead of polling. Resolve who you can act for and use the `x-participant-id` header. How an approved participant's account is funded *(Beta)*. The four documents each trader accepts, the exact acceptance language, and agreement versioning. # Participants Source: https://docs.polymarket.us/partners/onboarding/users The lifecycle of a Retail Participant and how to see who your Firm can act on behalf of. A **Retail Participant** is a person you onboard who trades through your platform. You don't create participants directly — they are **provisioned automatically when KYC is approved** — so your work here is to verify identity and then read back who your Firm can act for. ## Participant lifecycle ```mermaid theme={null} graph LR A[Start KYC] --> B{KYC Approved?} B -->|Yes| C[Participant & Account
auto-provisioned] C --> D[Funded] D --> E[Trading] B -->|No| F[KYC Rejected] ``` When a Retail Participant completes KYC and is approved: 1. Polymarket US automatically provisions their **trading identity** (Participant). 2. A **trading account** is created for them. 3. They can then be funded and trade. There is no separate "create user" or "create account" call. See the [KYC Verification Flow](/partners/onboarding/kyc/verification-flow) for the steps, and [Partner Funding](/partners/funding/overview) *(Beta)* for how participant trading is funded. **Terminology.** These docs use **Retail Participant** for the end user. In API payloads the underlying field names use `user`/`users`; treat those as the Retail Participant's trading identity. See the [Glossary](/partners/glossary). ## Resolve who you can act for You discover the identities available to your Firm with three endpoints from the Accounts API. Each links to its full API reference below; the partner-specific usage is summarized here. | Endpoint | Use it to | API reference | | ------------------ | ----------------------------------------------------------- | ------------------------------------------------------------- | | `GET /v1/whoami` | Confirm your Firm identity and entitlements | [Get who am I ↗](/institutional/accounts/overview#endpoints) | | `GET /v1/users` | List the Retail Participants your Firm may act on behalf of | [List users ↗](/institutional/accounts/overview#endpoints) | | `GET /v1/accounts` | List the trading accounts you can access | [List accounts ↗](/institutional/accounts/overview#endpoints) | For the account and identity **hierarchy** and entitlements model, see [Accounts & Identity](/trader-guide/accounts-identity). In partner usage, IDs of the form `firms/your-firm/users/participant-123` are the values you place in the **`x-participant-id`** header to act on behalf of a specific Retail Participant. **`GET /v1/users` is a roster read, not your starting point.** It is account-scoped and requires an `x-participant-id` header itself, so it cannot be used to find your first participant ID. Each Retail Participant's ID is delivered to you when their KYC reaches `ACCEPT` — as `participantId` on the [`kyc.approved` webhook](/partners/onboarding/kyc/webhooks) or from [`GET /v1/kyc/status`](/partners/onboarding/kyc/verification-flow#check-status). That is the authoritative source; record it against your own user record as you onboard. Use `GET /v1/users` afterwards to reconcile or re-list the Retail Participants your Firm may act for. Call `GET /v1/whoami` and `GET /v1/users` after authenticating and before trading, so you use the correct participant and account IDs in subsequent requests. ## Participant vs Account | Entity | Description | | --------------- | ------------------------------------------------------------------------ | | **Participant** | A person with a verified identity (KYC). Has a trading identity ID. | | **Account** | A trading account with balances and positions. Belongs to a Participant. | A Participant can have multiple accounts for different purposes (e.g., separate trading strategies). ## Related guides * [Onboard Participants](/partners/onboarding/onboard-participants) — How KYC creates the Participant and Account * [Accounts & Identity](/trader-guide/accounts-identity) — The account and identity hierarchy * [KYC Verification](/partners/onboarding/kyc/overview) — Identity verification (provisions participants automatically) * [Accounts](/partners/onboarding/accounts) — List trading accounts * [Funding](/partners/funding/overview) — How participant trading is funded *(Beta)* # Create an Order Source: https://docs.polymarket.us/partners/orders/create-order Place an order with a declared vendor fee — recorded against the order and passed through to the exchange in one idempotent call. **BETA — SUBJECT TO CHANGE.** This API is in beta and may change without notice. `CreateVendorOrder` is the partner order-entry call: it validates the supported structure of an embedded public `polymarket.v1.InsertOrderRequest`, **records your declared vendor fee against the order**, passes the order through to the exchange, and waits for its durable outcome before returning. No money moves in this call — the order trades against cash already in the participant's account, and the vendor fee [accrues for later collection](/partners/funding/vendor-fees). The exchange is authoritative for order economics and buying-power validation; `CreateVendorOrder` does not calculate or return order economics. ## Service definition * **Service:** `polymarket.us.orderfunding.v1.OrderFundingService` * **RPC:** `CreateVendorOrder` * **Type:** Unary (request/response) ```protobuf theme={null} service OrderFundingService { rpc CreateVendorOrder(CreateVendorOrderRequest) returns (CreateVendorOrderResponse); } message CreateVendorOrderRequest { polymarket.v1.InsertOrderRequest order = 1; MoneyAmount vendor_fee = 2; string idempotency_key = 3; } ``` ## Request ### CreateVendorOrderRequest | Field | Type | Required | Description | | ----------------- | ---------------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `order` | `polymarket.v1.InsertOrderRequest` | Yes | The public order-entry shape. Set only the [supported fields](/partners/orders/data-model#supported-fields). | | `vendor_fee` | `MoneyAmount` | Yes | Your fixed USD vendor fee for this order. It is recorded against the exchange order ID and collected later with a [`VENDOR_FEES` transfer](/partners/funding/vendor-fees), never moved at order time. | | `idempotency_key` | `string` | Yes | Your unique key for this placement request. Persist it before calling the service. | The participant is identified **only by `order.account`**. Use the DCM trading account returned by the KYC approval webhook—the same account identifier used to match the participant's Drop Copy activity. There is no separate customer-account field in this request. Do not set `order.user` or `order.session_id`; both are rejected. The embedded public message is wider than the partner launch surface. The service uses a fail-closed allowlist: if you populate an unsupported field, the request is rejected with `INVALID_ARGUMENT` and an error that names the offending field. Do not copy a generic `InsertOrderRequest` wholesale; construct one of the supported shapes below. ## Response ### CreateVendorOrderResponse | Field | Type | Description | | -------------------- | -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `funding_request_id` | `string` | Service-assigned durable identifier for this placement workflow. Persist it even if `id` is empty, and include it in support requests. | | `id` | `string` | Exchange order identifier, when the exchange assigned one. The vendor fee for an accepted order is recorded under this ID, which is also `order_id` on the [Vendor Fees report](/partners/funding/vendor-fees#the-vendor-fees-report). | | `status` | `VendorOrderStatus` | Durable or current placement outcome. See below. | | `correlation` | `FundingCorrelation` | Partner and service identifiers for recovery, audit, and support. Log these. | ### FundingCorrelation | Field | Type | Description | | -------------------- | -------- | -------------------------------------------------------------------------------------------------------- | | `idempotency_key` | `string` | Your idempotency key from the request. | | `funding_request_id` | `string` | The same durable placement-workflow identifier as the top-level field. It remains stable across retries. | | `request_id` | `string` | Per-attempt service request identifier for support and audit. It changes on each retry. | | `clord_id` | `string` | Your `order.clord_id`, echoed for recovery and Drop Copy correlation. | ### VendorOrderStatus | Status | Meaning | | ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `VENDOR_ORDER_STATUS_ACCEPTED` | The exchange durably accepted the order — including an order that matched immediately or was cancelled under fill-or-kill. Acceptance does not mean the order is resting or filled; learn execution outcomes from Drop Copy. The vendor fee is recorded against the order. **Terminal.** | | `VENDOR_ORDER_STATUS_REJECTED` | The order was rejected. Nothing was recorded and no fee accrues. **Terminal.** | | `VENDOR_ORDER_STATUS_PENDING` | The service could not establish the durable exchange outcome before its deadline. No collectible vendor fee exists yet. **Durable, but not terminal.** | **`ACCEPTED` means durable acceptance, not an execution state.** The service does not report acceptance based on submission alone, but an accepted order may have rested, matched immediately, or been cancelled under FOK. Track fills and other execution outcomes on [Drop Copy](/streaming-endpoints/dropcopy-stream). An order the exchange rejects asynchronously after submission is returned as `REJECTED`, not as a phantom accepted order. ## Funding request lifecycle Every successful `CreateVendorOrder` call returns a durable `funding_request_id` and the workflow's current `status`. `PENDING` is a valid durable response, not a gRPC error: submission may have happened, but the service has not yet established the final exchange outcome. When a response is `PENDING`, re-check it by calling `CreateVendorOrder` again with the **identical request**, including the same `idempotency_key` and `order.clord_id`. The service resumes the existing workflow and returns the same `funding_request_id` with its current status. Continue bounded re-checks until the status is terminal: * `VENDOR_ORDER_STATUS_ACCEPTED` — terminal accepted outcome. * `VENDOR_ORDER_STATUS_REJECTED` — terminal rejected outcome. Do **not** rotate the idempotency key while a funding request is pending. A new key identifies a new placement workflow and can create a duplicate order. If you reuse the original key with any caller-controlled field changed, the service returns gRPC `ALREADY_EXISTS` instead of modifying the existing workflow. Persist the `funding_request_id`, `idempotency_key`, and `clord_id` together. The funding request ID identifies the workflow even before an exchange order ID exists and is the primary identifier to quote in support requests. ## Supported order examples These examples deliberately populate only the launch fields. They assume the instrument publishes `priceScale = 100` and `fractionalQtyScale = 100` in [Reference Data](/institutional/refdata/overview). Use each instrument's published scales when converting decimal prices and quantities to integers. Every order must set exactly one of `order_qty` or `cash_order_qty`. ### Consumer FOK share limit order This order buys 100 shares at a limit price of \$0.45 and must fill immediately in full or cancel. ```python theme={null} import uuid from polymarket.v1 import trading_pb2 from polymarket.us.orderfunding.v1 import order_funding_pb2 clord_id = f"order-{uuid.uuid4()}" idempotency_key = str(uuid.uuid4()) request = order_funding_pb2.CreateVendorOrderRequest( order=trading_pb2.InsertOrderRequest( type=trading_pb2.ORDER_TYPE_LIMIT, side=trading_pb2.SIDE_BUY, order_qty=10_000, # 100.00 shares at fractionalQtyScale=100 symbol="", price=45, # $0.45 at priceScale=100 time_in_force=trading_pb2.TIME_IN_FORCE_FILL_OR_KILL, clord_id=clord_id, account="", manual_order_indicator=trading_pb2.MANUAL_ORDER_INDICATOR_MANUAL, ), vendor_fee=order_funding_pb2.MoneyAmount(value="0.10", currency="USD"), idempotency_key=idempotency_key, ) response = stub.CreateVendorOrder(request, metadata=metadata) ``` ### Consumer share order: BUY 10 NO at \$0.20 There is one order book per market, in instrument (YES) terms, and `price` is always the YES price. Express BUY 10 NO at \$0.20 as SELL 10 YES at the complementary \$0.80 price. The contract quantity remains 10. ```python theme={null} request = order_funding_pb2.CreateVendorOrderRequest( order=trading_pb2.InsertOrderRequest( type=trading_pb2.ORDER_TYPE_LIMIT, side=trading_pb2.SIDE_SELL, order_qty=1_000, # 10.00 contracts at fractionalQtyScale=100 symbol="", price=80, # YES $0.80 at priceScale=100; equivalent to NO $0.20 time_in_force=trading_pb2.TIME_IN_FORCE_FILL_OR_KILL, clord_id=f"order-{uuid.uuid4()}", account="", manual_order_indicator=trading_pb2.MANUAL_ORDER_INDICATOR_MANUAL, ), vendor_fee=order_funding_pb2.MoneyAmount(value="0.10", currency="USD"), idempotency_key=str(uuid.uuid4()), ) response = stub.CreateVendorOrder(request, metadata=metadata) ``` A fill changes the participant's position by -10 in YES terms. Economically, buying 10 NO contracts at \$0.20 costs \$2.00, with \$0.08 commission in this example charged additionally. The cost is reflected in the account's buying power. Partners can query the authoritative cash `balance` and `buying_power` with `polymarket.v1.PositionAPI/GetAccountBalance`; see [Reconciliation](/partners/reconciliation). The same complementary convention applies in the other direction: SELL `q` NO at `p` is BUY `q` YES at `1 − p`. See [Outcomes and prices](/partners/orders/data-model#outcomes-and-prices). ### Consumer cash BUY: spend \$20 on YES Set `cash_order_qty` instead of `order_qty` when the participant specifies a dollar principal. `ORDER_TYPE_LIMIT` supports this shape. This order spends \$20.00 of principal at a YES limit price of \$0.80. ```python theme={null} request = order_funding_pb2.CreateVendorOrderRequest( order=trading_pb2.InsertOrderRequest( type=trading_pb2.ORDER_TYPE_LIMIT, side=trading_pb2.SIDE_BUY, cash_order_qty=2_000, # $20.00 principal at priceScale=100; omit order_qty symbol="", price=80, # YES $0.80 at priceScale=100 time_in_force=trading_pb2.TIME_IN_FORCE_FILL_OR_KILL, clord_id=f"order-{uuid.uuid4()}", account="", manual_order_indicator=trading_pb2.MANUAL_ORDER_INDICATOR_MANUAL, ), vendor_fee=order_funding_pb2.MoneyAmount(value="0.10", currency="USD"), idempotency_key=str(uuid.uuid4()), ) response = stub.CreateVendorOrder(request, metadata=metadata) ``` For a cash BUY, contract quantity is the principal divided by the actual execution price. The \$20.00 principal fills 25 contracts when execution occurs at the \$0.80 limit, or 62.5 contracts with price improvement to \$0.32; the limit bounds the worst-case price. In the fill-at-limit example, a \$0.20 commission makes the total cash decrease \$20.20. Commission is charged in addition to `cash_order_qty`. ### Consumer cash SELL: spend \$20 on NO On `SIDE_SELL`, `cash_order_qty` is complementary NO collateral, not target proceeds. “Spend \$20 on NO at up to \$0.20” is expressed as a SELL with a YES limit price of \$0.80: ```python theme={null} request = order_funding_pb2.CreateVendorOrderRequest( order=trading_pb2.InsertOrderRequest( type=trading_pb2.ORDER_TYPE_LIMIT, side=trading_pb2.SIDE_SELL, cash_order_qty=2_000, # $20.00 NO collateral at priceScale=100; omit order_qty symbol="", price=80, # YES $0.80 at priceScale=100; equivalent to NO $0.20 time_in_force=trading_pb2.TIME_IN_FORCE_FILL_OR_KILL, clord_id=f"order-{uuid.uuid4()}", account="", manual_order_indicator=trading_pb2.MANUAL_ORDER_INDICATOR_MANUAL, ), vendor_fee=order_funding_pb2.MoneyAmount(value="0.10", currency="USD"), idempotency_key=str(uuid.uuid4()), ) response = stub.CreateVendorOrder(request, metadata=metadata) ``` The exchange divides the \$20.00 cash quantity by the \$0.20 complement, so the \$20.00 buys 100 NO contracts at \$0.20. Commission is additional—\$0.80 in this example. On a SELL, `cash_order_qty = 2_000` does not mean “sell enough to receive \$20.00.” It means spend \$20.00 of complementary NO collateral. ## Buying power and previews The exchange validates buying power when the order is placed and rejects an order unless the participant account's available cash covers its worst-case collateral plus the applicable exchange fee. This authoritative placement check does not reserve accrued vendor fees. Before submission, you may provide client-side guidance by gating against: ``` spendable balance = account cash − accrued uncollected vendor fees ``` For a BUY share limit order, client-side worst-case collateral is the contract quantity multiplied by the YES limit price. For a SELL share limit order expressing a NO position, it is the contract quantity multiplied by the complementary NO price. For a cash order, `cash_order_qty` is the principal: YES principal on BUY or complementary NO collateral on SELL. Add the maximum exchange fee from the [fee schedule](/fees) when estimating required cash; fees are additional to `cash_order_qty`. `CreateVendorOrder` does not calculate these amounts. You may calculate them in your client or separately call the public `polymarket.v1.OrderEntryAPI/PreviewOrder` with the participant's order shape. The preview is optional and informational, not a locked quote; the exchange validates the order again at placement. ## Idempotency and retries The `idempotency_key` identifies one placement request for your authenticated firm, while `order.clord_id` is your standard FIX tag-11 client order ID and recovery key. Persist both **before** the first call. The service guarantees **at most one order per idempotency key — retrying with the same key can never place a duplicate order or double-charge the declared vendor fee.** * **Retry transport failures and timeouts** with the same complete request and the **same `idempotency_key` and `clord_id`**. If the original attempt completed, the service returns its recorded result; otherwise it resumes or resolves the workflow. * **Retry `PENDING`** the same way. The service re-checks the exchange outcome and returns the same funding request with its current status. * **Match Drop Copy by account and `clord_id`.** `order.account` is the DCM account on Drop Copy, and the exchange echoes `order.clord_id` as `clOrdID`. * **Never change the request under an existing key.** A different `clord_id`, account, order field, or vendor fee returns `ALREADY_EXISTS`. * **Never rotate the key for a pending request.** A new key creates a new workflow and can submit a duplicate order. * **Never reuse a `clord_id` across live orders for the same participant.** Use a fresh idempotency key and client order ID for each new order. ## Cancelling an order An order placed through this service is a standard exchange order. Cancel it through the standard [order entry cancel](/institutional/trading/overview). Cancellation does not alter the recorded vendor fee: the platform records exactly what you declared at placement. If your fee policy waives fees on cancelled or unfilled orders, apply that policy in your books and in the amount you [collect](/partners/funding/vendor-fees#collecting-the-fees). ## Errors | gRPC status | Meaning | Retry guidance | | --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- | | `INVALID_ARGUMENT` | A required field is missing; an order or vendor fee is malformed; or an unsupported embedded field is set. The error names an offending unsupported field. | Fix the request. Do not retry as-is. | | `UNAUTHENTICATED` | Missing or invalid access token. | Refresh the token and retry. | | `PERMISSION_DENIED` | Your firm is disabled or the vendor-fee policy denies the request. | Do not retry unchanged until authorization or policy is corrected. | | `NOT_FOUND` | No customer relationship exists for your firm and `order.account`. The same generic error is returned if the account belongs to another firm. | Correct `order.account`. | | `FAILED_PRECONDITION` | The customer relationship is inactive, ambiguous, stale, or draining. | Correct the relationship state, then retry according to whether the placement intent changed. | | `ALREADY_EXISTS` | The `idempotency_key` is already bound to a different caller-controlled request. | Replay the original request, or use a fresh key only for a genuinely new placement intent. | | `UNAVAILABLE` | Transient relationship, policy, persistence, token-minting, or exchange unavailability before a known submission outcome. | Retry the identical request with the **same** `idempotency_key`. | An order-level rejection from the exchange, such as **insufficient buying power** or a price outside market limits, is not a gRPC error. The call returns `OK` with `status = VENDOR_ORDER_STATUS_REJECTED`; `FAILED_PRECONDITION` is reserved for relationship-state problems. Quote the `correlation` identifiers when requesting the underlying rejection detail from support. # Order Data Model Source: https://docs.polymarket.us/partners/orders/data-model Supported fields for the public InsertOrderRequest embedded in partner order entry. **BETA — SUBJECT TO CHANGE.** This API is in beta and may change without notice. `CreateVendorOrder` embeds the public `polymarket.v1.InsertOrderRequest`. The supported order shape for partner entry is intentionally narrow: **limit orders** with **fill-or-kill (FOK)** time in force and either a share quantity or a cash quantity such as “spend \$20.” The service validates populated fields against a fail-closed allowlist. Any field marked **Rejected if set** below causes `INVALID_ARGUMENT`, with the offending field named in the error. This prevents newly added public-order features from becoming available to partner callers unintentionally. ## Supported fields | `InsertOrderRequest` field | Type | Partner support | Requirements | | ----------------------------------- | -------------------------------- | ------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `type` | `OrderType` | **Required** | Must be `ORDER_TYPE_LIMIT`. | | `side` | `Side` | **Required** | `SIDE_BUY` or `SIDE_SELL`, expressed in YES terms. Use the [complementary order](#outcomes-and-prices) for NO. | | `order_qty` | `int64` | **Optional; exactly one quantity required** | Positive contract quantity in the instrument's fixed-point quantity scale. Set exactly one of `order_qty` and `cash_order_qty`. | | `symbol` | `string` | **Required** | Exchange symbol for the market. | | `price` | `int64` | **Required** | Positive YES limit price in the instrument's fixed-point price scale, including for an order expressing a NO position. | | `time_in_force` | `TimeInForce` | **Required** | Must be `TIME_IN_FORCE_FILL_OR_KILL`. Every other time-in-force value is rejected. | | `clord_id` | `string` | **Required** | Your FIX tag-11 client order ID: 1–64 visible ASCII characters, unique across live orders for the participant. Echoed as `clOrdID` on Drop Copy and included in the Vendor Fees report. | | `account` | `string` | **Required** | The participant's DCM trading account from the KYC approval webhook. This is the sole customer identifier and the account used to match Drop Copy. | | `stop_price` | `int64` | **Rejected if set** | Stop and stop-limit orders are unsupported. | | `min_qty` | `int64` | **Rejected if set** | Minimum-quantity instructions are unsupported. | | `self_match_prevention_id` | `string` | **Rejected if set** | Custom self-match prevention identifiers are unsupported. | | `quote` | `string` | **Rejected if set** | Quote-linked orders are unsupported. | | `all_or_none` | `bool` | **Rejected if set** | Use FOK for the supported all-or-cancel execution behavior. | | `session_id` | `string` | **Rejected if set** | The service derives submission context. Do not copy a session from another API call. | | `user` | `string` | **Rejected if set** | The service derives the submitting participant from the authenticated firm's relationship to `account`. | | `client_account_id` | `string` | **Rejected if set** | Use `account` as the account identifier. | | `client_participant_id` | `string` | **Rejected if set** | The service derives the participant associated with `account`. | | `participate_dont_initiate` | `bool` | **Rejected if set** | Post-only behavior is unsupported. | | `cash_order_qty` | `int64` | **Optional; exactly one quantity required** | Positive USD principal in the instrument's fixed-point price scale. On BUY, this is YES principal; on SELL, it is complementary NO collateral—not target proceeds. Set exactly one of `cash_order_qty` and `order_qty`. | | `strict_limit` | `bool` | **Rejected if set** | Strict-limit behavior is unsupported. | | `good_till_time` | `Timestamp` | **Rejected if set** | Non-default `good_till_time` is unsupported and rejected. | | `best_limit` | `bool` | **Rejected if set** | Best-limit pricing is unsupported. | | `immediately_executable_limit` | `bool` | **Rejected if set** | Immediately-executable-limit behavior is unsupported. | | `self_match_prevention_instruction` | `SelfMatchPreventionInstruction` | **Rejected if set** | Custom self-match prevention instructions are unsupported. | | `order_capacity` | `OrderCapacity` | **Rejected if set** | Custom order-capacity values are unsupported. | | `ignore_price_validity_checks` | `bool` | **Rejected if set** | Price validity checks cannot be bypassed. | | `manual_order_indicator` | `ManualOrderIndicator` | **Required** | `MANUAL_ORDER_INDICATOR_MANUAL` for a human-entered order or `MANUAL_ORDER_INDICATOR_AUTOMATED` for a system-generated order. | In proto3, scalar fields at their default value are not populated on the wire. “Rejected if set” means do not send a non-default value for that field. Construct the supported request directly rather than reusing a broad public-order object. ## Outcomes and prices There is one order book per market, expressed in instrument (YES) terms. `price` is always the YES price; there is no separate NO book or NO price field. Express a NO position with the complementary YES order: ```text theme={null} BUY q NO @ p ≡ SELL q YES @ (1 − p) SELL q NO @ p ≡ BUY q YES @ (1 − p) ``` The quantity remains `q` contracts; do not convert it to a dollar amount. For example, buying 10 NO at \$0.20 requires `SIDE_SELL`, `order_qty = 1000`, and `price = 80` when both scales are `100`. A fill changes the YES-terms position by -10 contracts. Economically, buying the 10 NO contracts costs \$2.00, and the cost is reflected in the account's buying power. Commission is additional—\$0.08 in this example. ## Share and cash quantities Set exactly one of `order_qty` or `cash_order_qty`. The service rejects requests that set both or neither. * `order_qty` specifies a number of contracts and is unchanged when translating between YES and NO. * On `SIDE_BUY`, `cash_order_qty` is the principal to spend on YES, and fill quantity is principal divided by actual execution price. A \$20.00 principal fills 25 contracts at the \$0.80 limit or 62.5 contracts with price improvement to \$0.32. The limit bounds the worst-case price; commission is additional to the principal. * On `SIDE_SELL`, `cash_order_qty` is the complementary NO collateral to spend, not a target for sale proceeds. At a YES limit price of \$0.80, \$20.00 is divided by the \$0.20 complement and buys 100 NO contracts. "Spend \$20 on NO at up to \$0.20" therefore uses `SIDE_SELL`, `cash_order_qty = 2000`, and `price = 80` when `priceScale = 100`. On `SIDE_SELL`, `cash_order_qty = 2000` does not mean “sell enough to receive \$20.00.” It means commit \$20.00 of complementary NO collateral. Cash quantities are valid with the partner surface's required `ORDER_TYPE_LIMIT`. Exchange commission is charged in addition to the principal represented by `cash_order_qty`. ## Fixed-point values `price`, `order_qty`, and `cash_order_qty` are integers on the public exchange message. Convert user-facing decimals with the scales published for the instrument — the `priceScale` and `fractionalQtyScale` fields returned by the [Reference Data API](/institutional/refdata/overview): ```text theme={null} wire price = decimal YES price × priceScale wire contract quantity = decimal contracts × fractionalQtyScale wire cash quantity = decimal USD principal × priceScale ``` For example, when both scales are `100`, a \$0.45 YES limit price is `price = 45`, 100 contracts is `order_qty = 10000`, and \$20.00 of principal is `cash_order_qty = 2000`. Divide by the same scales to convert wire integers back to decimals. Always use each instrument's published values; do not hard-code them. ## Fees on execution reports Execution reports — on the [Drop Copy stream](/streaming-endpoints/dropcopy-stream) and from `SearchExecutions` / `SearchOrders` — carry the exchange fee in the same fixed-point convention as every other wire value. The commission fields are **notional units**, scaled by *both* the price scale and the quantity scale: ```text theme={null} contracts = order_qty / fractional_quantity_scale price ($) = price / price_scale commission ($) = commission_notional_collected / (price_scale × fractional_quantity_scale) ``` One dollar is `price_scale × fractional_quantity_scale` notional units. When both scales are `100`, `commission_notional_collected = 100` means **\$0.01** — not \$1.00. The relevant fields: | Field | Where | Meaning | | ------------------------------------------ | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `commission_notional_collected` | execution | Fee collected for this fill, in notional units. Negative values are rebates credited to the account. | | `commission_notional_total_collected` | embedded order | Cumulative fee across all fills of the order, in notional units. | | `fractional_quantity_scale`, `price_scale` | embedded order | The scales to decode with. Also published per instrument as `fractionalQtyScale` / `priceScale` by the [Reference Data API](/institutional/refdata/overview); treat an instrument that does not publish `fractionalQtyScale` as scale `1`. | The dollar amount always reconciles with the [fee schedule](/fees) formula `Fee = Θ × C × p × (1 − p)` — where `C` is **contracts** (not raw `order_qty` units) and `p` is the **decimal** price — rounded to \$0.01. The most common reconciliation mistake is using raw `order_qty` units as `C`. On an instrument with `fractional_quantity_scale = 100`, that overstates the fee 100×. Instruments with scale `1` make the naive math accidentally correct, so the error often surfaces only on the first fill in a scale-`100` market. ### Worked example — quantity scale 100 A taker buys `order_qty = 312` at `price = 97` on an instrument with `priceScale = 100` and `fractionalQtyScale = 100`. The fill execution report (captured from Drop Copy; identifiers anonymized): ```json theme={null} { "id": "BW9QY8RWN52Z", "order": { "id": "BT3DNMHB94XJ", "type": "ORDER_TYPE_LIMIT", "side": "SIDE_BUY", "orderQty": "312", "symbol": "aachc-cfb-undefeated-2026-11-28-boise", "clordId": "example-taker-buy-01", "timeInForce": "TIME_IN_FORCE_FILL_OR_KILL", "account": "firms/ISV-Example-ClearingMember/accounts/example-trading-account", "cumQty": "312", "avgPx": "97", "state": "ORDER_STATE_FILLED", "priceToQuantityFilled": { "97": "312" }, "commissionNotionalTotalCollected": "100", "priceScale": "100", "fractionalQuantityScale": "100" }, "lastShares": "312", "lastPx": "97", "type": "EXECUTION_TYPE_FILL", "aggressor": true, "commissionNotionalCollected": "100" } ``` Decode: ```text theme={null} contracts = 312 / 100 = 3.12 price = 97 / 100 = $0.97 fee formula = 0.06 × 3.12 × 0.97 × 0.03 = $0.005448 → rounds to $0.01 wire fee = 100 / (100 × 100) = $0.01 ✓ ``` The account's balance-ledger entry for this fill confirms the same number: a debit of `3.0364` = `3.12 × $0.97` principal (`$3.0264`) plus the `$0.01` commission. ### Worked example — quantity scale 1 A taker buys `order_qty = 100` at `price = 97` on an instrument with `priceScale = 100` and `fractionalQtyScale = 1`, so `order_qty` **is** the contract count. The fill carries `commissionNotionalCollected = "17"` with `"fractionalQuantityScale": "1"`: ```text theme={null} contracts = 100 / 1 = 100 price = 97 / 100 = $0.97 fee formula = 0.06 × 100 × 0.97 × 0.03 = $0.1746 → rounds to $0.17 wire fee = 17 / (100 × 1) = $0.17 ✓ ``` This matches the 100-lot examples in the [fee schedule](/fees) — those tables assume scale-`1` instruments. The ledger debit is `97.17` = `$97.00` principal + `$0.17` commission. ### Maker rebates and zero-fee fills The same decoding applies to the maker side, where the fee coefficient is negative. The resting sell in the scale-`1` example above received `commissionNotionalCollected = "-4"` → `−$0.04`, matching `−0.0125 × 100 × 0.97 × 0.03 = −$0.036` rounded to the cent. Because fees round to \$0.01, small fills legitimately produce **zero** fee: the maker rebate in the scale-`100` example rounds `−$0.0011` to `$0.00`, and the field is simply absent from the JSON (proto3 omits default values). Treat a missing commission field as `0`, not as missing data. ## Customer identity `order.account` is the only customer identifier accepted by `CreateVendorOrder`. Persist the DCM account delivered when KYC is approved and use it consistently for: * `order.account` on partner order placement * `participant_account_id` on [transfers](/partners/funding/transfers) * the account field on Drop Copy and reconciliation records Do not set `order.user` or `order.session_id`. The service authenticates your firm, verifies that `order.account` belongs to an enabled participant relationship, and derives the submitting participant. ## MoneyAmount | Field | Type | Description | | ---------- | -------- | ----------------------------------------- | | `value` | `string` | Base-10 decimal string, such as `"0.10"`. | | `currency` | `string` | ISO 4217 code. Must be `USD`. | `MoneyAmount` is used for the top-level `vendor_fee`; it is not used for the fixed-point fields in the embedded public order. ## Next step See complete YES, NO, cash-BUY, and cash-SELL order examples. # Partner Integration Source: https://docs.polymarket.us/partners/overview Build a trading experience on Polymarket US as an Introducing Broker or Independent Software Vendor. This section is for **Introducing Brokers (IBs)** and **Independent Software Vendors (ISVs)** building a trading experience on Polymarket US. It teaches the **workflow** — how to onboard yourself, onboard and verify your users, fund their trading, place orders, and monitor activity — and points you to the exact APIs and streams for each step. **New here?** Read [Your Role](/partners/your-role) and the [Platform Model](/partners/platform-model) first, then follow the [Integration Journey](/partners/integration-journey). ## The integration journey ```mermaid theme={null} graph TD A["1 · Understand platform & your role"] --> C["2 · Get connected — onboarding + auth"] C --> D["3 · Onboard participants — KYC"] D --> F["4 · Trade — order entry"] F --> G["5 · Monitor & reconcile — streams + webhooks"] ``` ## Which partner are you? An Independent Software Vendor provides the software experience for retail traders. Not a broker. An Introducing Broker integrates the same way, plus carries CFTC/NFA regulatory obligations. A Futures Commission Merchant connects and clears differently. See the FCM guide. IBs and ISVs **integrate identically** — same APIs and same workflow. The difference is legal classification. FCMs are handled separately. ## If you're asking… | Question | Go to | | ------------------------------------- | -------------------------------------------------------- | | "What does a partner actually do?" | [Your Role](/partners/your-role) | | "How is Polymarket US structured?" | [Platform Model](/partners/platform-model) | | "What's the end-to-end build order?" | [Integration Journey](/partners/integration-journey) | | "How is funding handled?" | [Partner Funding](/partners/funding/overview) *(Beta)* | | "How do I authenticate?" | [Authentication](/partners/get-connected/authentication) | | "How do I onboard and verify a user?" | [KYC Verification](/partners/onboarding/kyc/overview) | | "How do I place my first order?" | [Quickstart](/partners/get-connected/quickstart) | | "What does a term mean?" | [Partner Glossary](/partners/glossary) | ## Get started The recommended path from onboarding to live trading. **Ready to onboard?** Contact [institutional@polymarket.us](mailto:institutional@polymarket.us) to begin. See [Partner Onboarding](/partners/get-connected/onboarding) for what we need from you and what you'll receive. # FCMs Source: https://docs.polymarket.us/partners/partner-types/fcms Guide for Futures Commission Merchant integrations ## Overview Download the FCM Participant and Clearing Member Agreement for Polymarket Exchange and Polymarket Clearing Polymarket US partners with licensed Futures Commission Merchants (FCMs) to provide customers access to prediction markets on our platform; it maintains comprehensive compliance and operational standards for them to that end. All FCM records must be maintained for the periods specified by CFTC regulations and be readily accessible for examination. To learn more about becoming an FCM with Polymarket US, please contact **[institutional@polymarket.us](mailto:institutional@polymarket.us)** ## Requirements To become an FCM with Polymarket US, you must: * Be a U.S.-based entity in good standing * Maintain all required regulatory registrations and licenses * Comply with CFTC regulations and Polymarket US Rules * Ensure adequate capital and financial resources * Implement and maintain comprehensive supervisory procedures * Designate qualified supervisory personnel * Establish written supervisory procedures and conduct regular reviews of trading activity * Train staff on compliance requirements FCMs must provide their customers with: * Account opening services including identity verification and eligibility assessment * Collection of required documentation and disclosures * Assessment of appropriateness of prediction market trading for each customer * Accurate account records and ongoing oversight * Monitoring of customer trading activity to identify and investigate unusual patterns * Adequate systems for account surveillance * Prompt responses to customer inquiries and concerns FCMs are also required to: * Implement credit and position limits for each customer account * Validate orders against account limits before submission and reject orders that exceed established thresholds * Maintain audit trails of all order decisions * Route orders promptly and efficiently, providing best execution reasonably available * Monitor order flow for conflicts of interest and document order handling procedures * Assign unique credentials to authorized personnel only and limit API access to approved individuals * Monitor and log all system access, immediately revoking credentials upon termination or role change * Conduct periodic access reviews * Clearly identify and segregate FCM principal trading accounts (proprietary) from customer accounts * Subject proprietary accounts to enhanced monitoring * Establish policies governing employee personal trading with pre-clearance requirements * Restrict trading ahead of customer orders and require disclosure of personal positions that may create conflicts * Accept responsibility for all activity under their credentials * Promptly report any unauthorized access or suspicious activity * Maintain adequate insurance and financial resources * Accept liability for errors or misconduct by associated persons * Provide timely reports to Polymarket US including large trader position reports, suspicious activity reports, material changes to registration or financial condition, and system outages or operational disruptions * Maintain comprehensive records of all customer orders and executions, account documentation and correspondence, supervisory reviews and exception reports, system access logs, and risk limit changes and overrides Approved FCMs receive: * Access to Polymarket US platform for managing customer and proprietary accounts * Support for individual retail accounts, institutional accounts, and managed accounts (with proper authorization) * Ability to maintain separate customer accounts and proprietary accounts * API access for trading and account management * Rights to intermediate customer transactions FCM customers must: * Adhere to both FCM procedures and Polymarket US rules * Provide all required documentation during account opening * Maintain accurate account information * Follow all position limits across all accounts * Use properly segregated customer accounts (separate from FCM proprietary trading) ## Connectivity FCMs connect to Polymarket US over a secure, private connection using VPC-peering. Once connectivity is established, you'll receive API endpoints for order entry and market data. We support multiple protocols: * **FIX API** - Industry-standard for order entry and execution * **gRPC** - High-performance streaming for real-time data * **REST API** - HTTP-based interface for trading and account management ## Funding * **Deposits**: Funds wired to the custodian are available for trading the next business day * **Withdrawals**: Withdrawal requests can be automatically approved or require administrator approval depending on commercial terms ## Account Structure FCMs are set up as Clearing Members and can manage multiple participant firms and trading accounts: * **Clearing account** - Acts as the collateral account for trading * **Customer accounts** - Segregated customer funds * **Non-customer accounts** - Proprietary trading Additional accounts can be created as needed via API or administrator interface. ## Order Entry **FIX API** * Use `NewOrderSingle` to submit orders * Use `OrderCancelReplace` to modify orders * View our [FIX API documentation](/institutional/fix-api/fix-overview) **REST & gRPC APIs** * Download [proto files](/streaming-endpoints/proto-reference) and generate bindings * Subscribe to order updates before sending orders * View our [REST API documentation](/api-reference/introduction) ## Risk Management FCMs manage daily trading limits and position risk for their customers. The clearing account provides collateral for trading operations. ## Reporting FCMs are responsible for Part 17 reporting for their customers in accordance with CFTC requirements. # IBs Source: https://docs.polymarket.us/partners/partner-types/ibs Introducing Brokers building a trading experience on Polymarket US. Introducing Brokers (IBs) integrate with Polymarket US using the **same workflow and APIs as ISVs** — the difference is regulatory, not technical. Start with [Your Role](/partners/your-role) and the [Integration Journey](/partners/integration-journey) for the build path; this page covers the IB-specific agreement and regulatory requirements. **Audience: business, legal, and compliance teams.** This page is about IB eligibility and regulatory obligations. Developers can go straight to the [Integration Journey](/partners/integration-journey). ## Overview Download the IB Participant Agreement for Polymarket Exchange Polymarket US partners with licensed Introducing Brokers (IBs) to provide customers access to prediction markets on our platform. Polymarket US reviews all IB applications and may approve, deny, or condition applications at its discretion in an impartial and transparent manner. Decisions are communicated in writing with specified rationale. IBs may request reconsideration within 28 business days. To learn more about becoming an Introducing Broker with Polymarket US, please contact **[institutional@polymarket.us](mailto:institutional@polymarket.us)** ## How IBs integrate The technical integration is identical to an ISV's — same APIs, same workflow. Follow the standard path: What you do, what Polymarket US does, and what you never handle. The recommended order to build your integration. **Funding.** Participant trading accounts are funded by your funding entity via [Partner Funding](/partners/funding/overview) *(Beta)*. **The requirements below are regulatory obligations** specific to Introducing Brokers — they apply *in addition to* the technical integration above. ## Requirements To become an IB with Polymarket US, you must: * Be a U.S.-based entity in good standing * Hold current CFTC registration as an Introducing Broker * Be a member of the National Futures Association (NFA) * Maintain adequate financial resources per CFTC Rule 1.17 * Execute an IB Agreement with Polymarket US * Designate a supervisor responsible for all employee trading activities * Maintain written compliance and supervisory policies per NFA Compliance Rule 2-9 * Submit annual Business Continuity and Disaster Recovery plans that coordinate with Polymarket US systems IBs must provide their customers with: * Current and complete copy of Polymarket US Rulebook * Terms and conditions for all listed contracts * Rules and mechanisms for executing transactions * Updates on new products, rule changes, and platform modifications * All information relevant to platform operations IBs are also required to: * Ensure customers clear through an FCM that is a clearing member, or are Self-Clearing Members * Maintain and enforce NFA-compliant compliance policies * Provide compliance documentation to Polymarket US, CFTC, or NFA upon request Approved IBs receive: * Access to intermediate customer transactions on Polymarket US * Rights to distribute market data to customers under data distribution agreements * API access for trading and account management IB customers must: * Adhere to both IB procedures and Polymarket US rules * Acknowledge the Rulebook and Source Agency Prohibition before placing orders * Use clearing services through an FCM or be a Self-Clearing Member * Disclose all accounts if trading through multiple IBs * Follow all position limits across all accounts # ISVs Source: https://docs.polymarket.us/partners/partner-types/isvs Independent Software Vendors building a trading experience on Polymarket US. Independent Software Vendors (ISVs) provide the software experience for retail traders on Polymarket US. ISVs are **not** brokers and carry no broker registration. You integrate using the **same workflow** as Introducing Brokers — see [Your Role](/partners/your-role) for what that involves and the [Integration Journey](/partners/integration-journey) for the end-to-end build path. **Audience: business and onboarding leads.** This page covers ISV eligibility and agreements. Developers can go straight to the [Integration Journey](/partners/integration-journey). ## Overview Download the ISV Connectivity Agreement for Polymarket Exchange ## Requirements ISVs must provide their users with the appropriate clickthrough agreements during onboarding: **For Individuals:** [Individual Participant Agreement](https://drive.google.com/uc?export=download\&id=1VpM7iqGY9sX5shA9fSd7gvZObsw3nSzt) **For Corporate Entities:** [Entity Participant Agreement](https://drive.google.com/uc?export=download\&id=1KTJaIlu_qONjnSlTwsdXnaQ3f7Vjl9xh) Users must review and accept the terms before beginning KYC verification. ## How ISVs integrate ISVs use the same end-to-end workflow as all partners. Rather than repeat it here, follow the [Integration Journey](/partners/integration-journey) — it covers onboarding, authentication, KYC, funding, trading, and monitoring in order. What you do, what Polymarket US does, and what you never handle. The recommended order to build your integration. **Participants are provisioned automatically.** When a Retail Participant's KYC is approved, their trading identity and account are created automatically — there is no separate account-creation step. See [Onboard Participants](/partners/onboarding/users). **Funding.** Participant trading accounts are funded by your funding entity via [Partner Funding](/partners/funding/overview) *(Beta)*. # Platform Model Source: https://docs.polymarket.us/partners/platform-model How Polymarket US is structured as a DCM and DCO, and the entity model you integrate against. Polymarket US operates as a CFTC-regulated **Designated Contract Market (DCM)** and **Derivatives Clearing Organization (DCO)**. As a partner you integrate against a **single platform** — the internal split between matching and clearing is ours to manage, not something your integration needs to model. **Audience: developers and solution architects.** A conceptual model of the platform and the entities your integration acts on. ## One platform, two functions | Function | What it does | What you interact with | | ------------------ | --------------------------------------------------------------- | -------------------------------------- | | **DCM** (matching) | Maintains the order book, matches orders, publishes market data | Order entry, market data | | **DCO** (clearing) | Holds collateral, clears trades, settles contracts | Balances, positions, funding transfers | In every diagram and API in these docs, the platform is represented as a single actor: **Polymarket US**. You authenticate once and use one set of credentials regardless of whether a given call is served by the matching or clearing function. ## The entity model Your integration acts on a small, consistent set of entities: ```mermaid theme={null} graph TD F["Firm — your IB/ISV organization"] --> P1["Retail Participant"] F --> P2["Retail Participant"] P1 --> A1["Account (balances & positions)"] P2 --> A2["Account (balances & positions)"] ``` | Entity | What it is | When it's created | | ---------------------- | ------------------------------------------------------------------------- | ----------------------------------------------- | | **Firm** | Your IB/ISV organization — the permissions container you authenticate as | At partner onboarding | | **Retail Participant** | A person you onboard who trades through your platform | Automatically, when their KYC is approved | | **Account** | The trading account holding a Retail Participant's balances and positions | Automatically, alongside the Retail Participant | You authenticate as the **Firm** and act **on behalf of** the Retail Participants beneath it. You **collect** each participant's KYC information and submit it; Polymarket US **performs the verification and decision**. On approval, identity and accounts are **provisioned automatically** — there is no separate "create user" or "create account" call. In these partner docs, **Participant** means a *trading identity* — a person you onboard. Be aware the [main glossary](/getting-started/glossary) also uses the word "participant" in an unrelated market-structure sense; the [Partner Glossary](/partners/glossary) explains the distinction. ## How messages flow A typical action — placing an order on behalf of a Retail Participant — looks like this from the outside: ```mermaid theme={null} sequenceDiagram participant RP as Retail Participant participant App as Your App participant PM as Polymarket US RP->>App: Place order App->>PM: Submit order (as Firm, on behalf of Participant) PM-->>App: Acknowledgement PM-->>App: Order & position updates (stream) App-->>RP: Confirmation & live updates ``` Your application is the bridge: you translate participant actions into authenticated requests, and translate Polymarket US streams back into your UI. ## Identifying who you act for API calls that are scoped to a participant carry an `x-participant-id` header identifying which Retail Participant the action is for. You can discover the identities available to your Firm at any time: * [`GET /v1/whoami`](/institutional/accounts/overview#endpoints) — your Firm identity * [`GET /v1/users`](/institutional/accounts/overview#endpoints) — the Retail Participants your Firm may act on behalf of See [Accounts & Identity](/trader-guide/accounts-identity) for the identity hierarchy, or [Onboard Participants](/partners/onboarding/onboard-participants) for how identities are created and listed. ## Next steps The recommended order to build your integration. Authenticate as your Firm with Private Key JWT. # Reconciliation Source: https://docs.polymarket.us/partners/reconciliation How to keep a correct local mirror of orders, positions, and cash — and the read APIs that anchor it. Your integration maintains a local projection of each participant's orders, positions, and cash. This page describes the operating pattern that keeps that projection correct, and the read APIs that anchor it to authoritative state. ## The operating pattern **Streams are the system of record; reads are anchors.** The firm-scoped [Drop Copy execution and position streams](/streaming-endpoints/dropcopy-stream) drive your projection in real time. Unary reads exist for discovery, user-driven views, and reconciliation — they are not polling feeds. * **Replay before new work.** Persist a stream response's `resume_token` only after every record in it is durably applied. Reconnect with the last committed token; deduplicate replayed events by stable exchange IDs. * **Single active subscriber.** Run one leader-elected consumer per firm, environment, and stream. Followers take over from the durable cursor — never one subscription per pod, and never one stream per customer. * **Reconcile after gaps.** After an outage: resume and replay the streams first, then compare your projections against the reads below, and surface freshness caveats rather than presenting incomplete state as final. * **No cron pollers.** Reads are triggered by onboarding, user views, bounded workflow recovery, reconnect reconciliation, or an incident — not recurring schedules. **A process restart is not a cold start.** Resume from your persisted checkpoint against your existing local database — don't re-run the unary reads below. "Cold start" means true first contact for a firm/account/environment your integration has never seen before — no persisted `resume_token` or `resume_time`, and no local projection to reconcile against. If you already persist your checkpoint, a restart just reconnects with it and keeps applying deltas to the state you already have, deduplicating anything replayed. Only a genuine no-checkpoint start (or a checkpoint too old to resume) falls back to the reads below. ## Reconciliation reads The anchors are the standard gRPC read APIs — the same `polymarket.v1` services documented under [Institutional API](/grpc-api/overview), called with your partner credentials over TLS with a Bearer token. Full message definitions are in the [proto download](/grpc-api/overview); the tables below map each reconciliation concern to its RPC. **These are low-volume anchors, not a data-access layer.** Every RPC below is rate-limited per firm and must not be polled. The correct architecture is **stream-first**: build your local database from [Drop Copy](/streaming-endpoints/dropcopy-stream) and [Order Stream](/streaming-endpoints/order-stream), serve reads from that database, and call these RPCs only for cold-start hydration, point-in-time queries, and gap recovery — never as a substitute for a subscription. See [Rate Limits](/trader-guide/rate-limits) for the full list; `SearchOrders`, `SearchExecutions`, and `SearchTrades`, for example, are capped at 12 requests/minute, and sustained call volume against any of these endpoints will get throttled. ### Cash balances — `polymarket.v1.PositionAPI` An account balance is the authoritative cash figure for one account and currency, plus the risk context used to size orders — see [Balance Data](/institutional/positions/overview#balance-data) for the full field reference. | RPC | Use | | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `GetAccountBalance` | Authoritative cash balance for one account and currency. Returns `balance` plus risk context: `buying_power`, `capital_requirement`, `excess_capital`, `unsettled_funds`, `margin_requirement`, `open_orders`, and an `update_time`. | | `ListAccountBalances` | All currencies for one account in one call. | This balance read is the authoritative confirmation to take before initiating a [`WITHDRAWAL` transfer](/partners/funding/deposits-withdrawals#withdrawals) — confirm the participant's free cash covers the amount (net of accrued vendor fees) before creating the transfer. #### Stream it instead **Streaming equivalent:** no dedicated balance-push stream exists — derive live balances from [Balance Ledger](#cash-ledger-polymarket-v1-fundingapi) deltas instead of polling this RPC. * Apply `CreateBalanceLedgerSubscription` entries (`before_balance` / `after_balance`) to your projection as they arrive. * Reserve `GetAccountBalance` / `ListAccountBalances` for a true first-time cold start and the pre-withdrawal confirmation above. * On an ordinary restart, resume from the last `update_time` you persisted and keep applying deltas to the balance you already have — don't re-fetch it. ### Positions — `polymarket.v1.PositionAPI` A position tracks net quantity, cost basis, and realized P\&L for one account and symbol — see [Position Data](/institutional/positions/overview#position-data) for the full field reference. | RPC | Use | | ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `ListAccountPositions` | Positions for one account, optionally filtered by `symbol`. Supports point-in-time queries via `as_of_time` (timestamp) or `as_of_date` (end of trade date) for after-the-fact reconciliation. Each `Position` carries net position, bought/sold quantities, cost, realized value, and `update_time`; the response carries `available_position` per row. | #### Stream it instead **Streaming equivalent:** `CreatePositionChangeSubscription` on the [Drop Copy stream](/streaming-endpoints/dropcopy-stream) keeps positions current in real time. * Call `ListAccountPositions` to hydrate a brand-new projection (true cold start) or to answer point-in-time (`as_of_time` / `as_of_date`) queries a stream can't answer — not on every process restart. * A restarting service persists its position projection alongside the stream's `resume_token` and reconnects with that token; a routine restart is never a reason to re-fetch positions. ### Orders — `polymarket.v1.ReportAPI` An order carries its full lifecycle state — quantity, price, fills, and status — see [Order Message Structure](/streaming-endpoints/order-stream#order-message-structure) for the full field reference (the same fields these reads return). | RPC | Use | | ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `SearchOrders` | Paginated order search with filters for `order_id`, `clord_id`, `accounts`, `symbol`, time range, trade-date range, and order state (`order_state_filter`; use it to list open orders after an outage). Set `with_last_execution` to get each order's latest execution inline. | | `GetOrder` | Single order by ID. | | `SearchExecutions` / `SearchTrades` | Execution- and trade-level detail when reconciling fills rather than order state. | #### Stream it instead **Streaming equivalent:** `CreateOrderSubscription` on the [Order Stream](/streaming-endpoints/order-stream) is snapshot **and** stream on one connection — no unary call is needed for cold start. * Open with the default `snapshot_only: false`; the first message carries a `snapshot` of every currently-open order, then `update` messages push executions continuously. * This request carries no `resume_token`, so a restart simply reconnects and gets a fresh snapshot for free — there's no checkpoint to persist for this one. * Reserve `SearchOrders` / `GetOrder` for one-off lookups the live snapshot doesn't cover (e.g. a specific historical/closed order by ID). * Drop Copy's execution and Trade Capture Report subscriptions, by contrast, carry a `resume_token` and no snapshot — hydrate firm-wide historical fills once via `SearchExecutions` / `SearchTrades` on true cold start, then resume the stream with the persisted token on every subsequent restart instead of re-querying. ### Cash ledger — `polymarket.v1.FundingAPI` A ledger entry records a single cash-balance change with its before/after amounts — see [BalanceLedgerEntry Fields](/institutional/funding/overview#balanceledgerentry-fields) for the full field reference. | RPC | Use | | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `GetAccountBalanceLedger` | Paginated, typed ledger of every cash balance change (deposits, withdrawals, fills, fees, adjustments) with before/after balances — see the [Funding reference](/institutional/funding/overview) for entry types and query semantics. | | `CreateBalanceLedgerSubscription` | The streaming counterpart with replay — see [Balance Ledger Stream](/streaming-endpoints/balance-ledger-stream). | The cash ledger is where every [transfer](/partners/funding/transfers) (deposits, withdrawals, vendor fee collections), fill, exchange fee, and settlement credit lands with before/after balances — it is the platform-side record your funding entity's books reconcile against, per account. #### Stream it instead **Streaming equivalent:** `CreateBalanceLedgerSubscription` *is* the streaming equivalent here — it replays from `resume_time` and then pushes new entries live. * Use `GetAccountBalanceLedger` only for paginated historical lookups and CSV exports, not as a live feed. * Persist the `update_time` of the last applied entry and pass it back as `resume_time` on reconnect — a restart resumes the stream from that point, it does not re-run history from the beginning. #### Firm-wide ledger consumption The ledger subscription is **per-account** — one fully-qualified account per stream — and concurrent ledger streams per firm are tightly capped. Opening one stream per customer does not scale and is not the intended pattern. **COMING SOON — firm-wide balance ledger subscription.** A firm-scoped subscription that delivers ledger entries for **all accounts under your Firm** on a single stream — with the same `resume_time` replay semantics and `entry.account` on every row for routing — is planned. The final consumption pattern will be documented when it ships; contact [institutional@polymarket.us](mailto:institutional@polymarket.us) if your rollout depends on it. Until then, cover the firm-wide need with the surfaces that are already firm-scoped: * **Executions and positions:** the [Drop Copy streams](/streaming-endpoints/dropcopy-stream) are firm-wide — one execution stream and one position stream cover every account's trading activity. * **Cash events:** drive per-account cash projections from your own transfer records (you initiate every deposit, withdrawal, and fee collection) plus firm-wide execution data, and reconcile against `GetAccountBalanceLedger` / CSV export per account on a batch cadence — not with per-account live streams. **Decoding fees on fills.** Commission fields on execution reports are fixed-point notional units, scaled by both `price_scale` and `fractional_quantity_scale` — see [Fees on execution reports](/partners/orders/data-model#fees-on-execution-reports) before reconciling fill-level fees against the [fee schedule](/fees). **Joining orders to fees and funding.** For orders placed through the partner order API, the `clord_id` on execution streams is the **partner-assigned** `order.clord_id` from [`CreateVendorOrder`](/partners/orders/create-order), and the execution account is the same DCM identifier supplied as `order.account`. `SearchOrders` filters by `clord_id` directly. Keep the exchange `order_id` as the canonical venue identifier: it and your `clord_id` are the join keys on the [Vendor Fees report](/partners/funding/vendor-fees#the-vendor-fees-report). Persist the create response's correlation fields so ledger rows, stream events, fee records, and transfers all join cleanly. ## Related pages The cash-movement API these reads gate. The firm-scoped execution stream that drives your projection. Real-time order and execution updates for your accounts. Per-endpoint limits for the reads on this page. # Your Role as a Partner Source: https://docs.polymarket.us/partners/your-role How IBs and ISVs integrate with Polymarket US as message facilitators — what you do, what we do, and what you never have to handle. As an Introducing Broker (IB) or Independent Software Vendor (ISV), you integrate with Polymarket US as a **message facilitator**: you authenticate as a **Firm**, act on behalf of the **Retail Participants** you onboard, and route their activity to and from the exchange. You build the experience; Polymarket US runs the regulated market and clearing. **You never hold participant funds.** Participant accounts are funded by your **funding entity** through [Partner Funding](/partners/funding/overview) *(Beta)* — deposits and withdrawals that mirror each participant's wallet allocation. Your role is to onboard and verify participants, route their orders, and relay transfer instructions — never to custody funds. ## What you do vs. what Polymarket US does | Responsibility | You (Partner) | Polymarket US | | ----------------------------------------------------------------- | ------------------------------------------------------- | ---------------------------------- | | Onboard Retail Participants (collect details, present agreements) | ✅ | — | | Collect KYC information and submit it | ✅ | — | | Verify identity and decide (KYC) | — | ✅ Performs verification & decision | | Provision trading identities and accounts | — | ✅ Automatic on KYC approval | | Present markets, prices, and a trading UI | ✅ | — | | Match orders and maintain the order book | — | ✅ (DCM) | | Hold collateral, clear, and settle | — | ✅ (DCO) | | Monitor orders, positions, balances | ✅ Consume [streams](/streaming-endpoints/grpc-overview) | ✅ Emit streams | ## You are a facilitator, not a counterparty Every message you send is **on behalf of a Retail Participant** you have onboarded. You are a permissions and routing layer: ```mermaid theme={null} graph LR RP["Retail Participant"] -->|uses your app| P["Partner (IB / ISV)"] P -->|acts on their behalf| PM["Polymarket US"] PM -->|streams & responses| P P -->|surfaced in your app| RP ``` * You authenticate **once** as your Firm and act for any Retail Participant under it. * Polymarket US matches, clears, and settles — you never take the other side of a trade. * Funding of participant trading accounts flows between your funding entity and each participant's account — see [Partner Funding](/partners/funding/overview) *(Beta)*. ## ISV or IB? The two partner types **integrate identically** — the same APIs and the same workflow. The difference is legal classification, not mechanics. An Independent Software Vendor provides the software experience. ISVs are **not** brokers and carry no broker registration. An Introducing Broker integrates the same way **plus** carries CFTC registration and NFA membership obligations. **IB-specific obligations.** If you are an Introducing Broker, you must maintain CFTC registration, NFA membership, and the supervisory/compliance requirements described on the [IBs](/partners/partner-types/ibs) page **in addition to** completing this integration. These are regulatory obligations, not technical ones. ## Next steps How the DCM and DCO fit together, and the entities you act on. The end-to-end path from onboarding to live trading. # Authentication Source: https://docs.polymarket.us/streaming-endpoints/authentication How to authenticate with the Polymarket Exchange API using Private Key JWT The Polymarket Exchange API uses **Private Key JWT** authentication. You sign a JWT with your private key, exchange it for an access token, then include that token in every API request. **CRITICAL: Access tokens must be refreshed every 3 minutes.** Access tokens have a short expiration. Your application MUST implement automatic token refresh before expiration to maintain uninterrupted API and streaming connections. ## Authentication Flow ```mermaid theme={null} sequenceDiagram participant Client participant Auth as Polymarket US Auth participant API as Polymarket US API Client->>Client: Sign JWT with private key Client->>Auth: Token request + signed JWT assertion Auth->>Auth: Verify signature with your public key Auth->>Client: Access Token Client->>API: API request with access token API->>Client: Response Note over Client,API: Refresh token before expiration (every 3 min) ``` ## Authentication Configuration ### Auth Domains | Environment | Auth Domain | | ------------------ | -------------------------- | | **Pre-production** | `pmx-preprod.us.auth0.com` | | **Production** | `pmx-prod.us.auth0.com` | During onboarding, you'll provide your **public key** and receive your `client_id` and `audience` values. ## Step 1: Create Client Assertion JWT Create a JWT with these claims, signed with your private key using RS256: ```json theme={null} { "iss": "YOUR_CLIENT_ID", "sub": "YOUR_CLIENT_ID", "aud": "https://pmx-preprod.us.auth0.com/oauth/token", "iat": 1703270400, "exp": 1703270700, "jti": "unique-uuid-per-request" } ``` ## Step 2: Request Access Token Exchange your signed JWT for an access token: ```bash theme={null} curl --request POST \ --url "https://pmx-preprod.us.auth0.com/oauth/token" \ --header "content-type: application/json" \ --data '{ "client_id": "YOUR_CLIENT_ID", "client_assertion_type": "urn:ietf:params:oauth:client-assertion-type:jwt-bearer", "client_assertion": "YOUR_SIGNED_JWT_ASSERTION", "audience": "YOUR_API_AUDIENCE", "grant_type": "client_credentials" }' ``` ### Response ```json theme={null} { "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIs...", "token_type": "Bearer", "expires_in": 180 } ``` The `expires_in` value is in seconds. With a 3-minute (180 second) expiration, you must refresh tokens frequently. ## Step 3: Use Token in API Requests Include the access token in the `Authorization` header for every request. ### gRPC Streaming Include the token in gRPC metadata: ```python theme={null} import grpc from polymarket.v1 import marketdatasubscription_pb2_grpc # Create secure channel credentials = grpc.ssl_channel_credentials() channel = grpc.secure_channel('grpc-api.preprod.polymarketexchange.com:443', credentials) # Create stub stub = marketdatasubscription_pb2_grpc.MarketDataSubscriptionAPIStub(channel) # Include access token in metadata metadata = [ ('authorization', f'Bearer {access_token}') ] # Make streaming call with metadata response_stream = stub.CreateMarketDataSubscription(request, metadata=metadata) ``` The metadata key **must** be `authorization` (lowercase). Include `Bearer ` prefix before the token. ## Token Refresh Strategy Since tokens expire every 3 minutes, implement automatic refresh: ```python theme={null} import jwt import uuid import time import requests from cryptography.hazmat.primitives import serialization class TokenManager: def __init__(self, auth0_domain, client_id, audience, private_key_path): self.auth0_domain = auth0_domain self.client_id = client_id self.audience = audience self.private_key_path = private_key_path self.token = None self.expires_at = None def _load_private_key(self): with open(self.private_key_path, 'rb') as f: return serialization.load_pem_private_key(f.read(), password=None) def _create_client_assertion(self): private_key = self._load_private_key() now = int(time.time()) claims = { "iss": self.client_id, "sub": self.client_id, "aud": f"https://{self.auth0_domain}/oauth/token", "iat": now, "exp": now + 300, "jti": str(uuid.uuid4()), } return jwt.encode(claims, private_key, algorithm="RS256") def get_token(self): # Refresh if token is missing or expires within 30 seconds if not self.token or time.time() >= self.expires_at - 30: self._refresh_token() return self.token def _refresh_token(self): assertion = self._create_client_assertion() response = requests.post( f"https://{self.auth0_domain}/oauth/token", json={ "client_id": self.client_id, "client_assertion_type": "urn:ietf:params:oauth:client-assertion-type:jwt-bearer", "client_assertion": assertion, "audience": self.audience, "grant_type": "client_credentials" } ) response.raise_for_status() data = response.json() self.token = data["access_token"] self.expires_at = time.time() + data["expires_in"] # Usage token_manager = TokenManager( auth0_domain="pmx-preprod.us.auth0.com", client_id="YOUR_CLIENT_ID", audience="YOUR_API_AUDIENCE", private_key_path="/path/to/private_key.pem" ) # Always use get_token() - it handles refresh automatically token = token_manager.get_token() ``` **Required packages:** ```bash theme={null} pip install PyJWT cryptography requests ``` ## Handling Authentication Errors ### Common Authentication Errors | Error Code | Description | Solution | | -------------------------- | ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | | `invalid_client` | JWT signature verification failed | Verify private key matches registered public key | | `invalid_client_assertion` | Malformed JWT or wrong claims | Check JWT claims (iss, sub, aud, exp, jti) | | `UNAUTHENTICATED` | Access token invalid or expired | Refresh token and retry | | `PERMISSION_DENIED` | Token valid but lacks required scopes | Add the missing scope to your Auth0 application and request a fresh token. The error body is `permission denied: missing required scope `. | | `UNAVAILABLE` | Cannot reach authentication service | Check network connectivity, retry with backoff | ### Streaming Scopes Scopes are enforced on streaming RPCs the same way they are on REST endpoints. The most common gRPC streams and their required scopes: | RPC | Required Scope | | --------------------------------- | ----------------- | | `CreateMarketDataSubscription` | `read:marketdata` | | `BiDirectionalStreamMarketData` | `read:marketdata` | | `CreateOrderSubscription` | `read:orders` | | `StreamRFQEvents` | `read:orders` | | `CreatePositionSubscription` | `read:positions` | | `CreateBalanceLedgerSubscription` | `read:positions` | | `CreateDropCopySubscription` | `read:dropcopy` | | `CreateFundingSubscription` | `read:funding` | See the full scope reference in the [trader guide authentication page](/trader-guide/authentication#api-scopes). ## Key Rotation You can rotate your keys without downtime: 1. Generate a new key pair 2. Submit the new public key to us 3. We add the new key (both old and new work during transition) 4. Update your systems to use the new private key 5. Notify us to remove the old public key ## Next Steps Complete onboarding guide with key generation Learn how to stream market data Subscribe to order updates Handle errors and implement reconnection # Balance Ledger Streaming Source: https://docs.polymarket.us/streaming-endpoints/balance-ledger-stream Real-time balance ledger entries via gRPC, with replay-from-resume_time Subscribe to real-time balance ledger entries (deposits, withdrawals, fills, fees, adjustments) using gRPC streaming. The stream first replays any entries since `resume_time`, then pushes new entries as they happen. For paginated historical queries and CSV exports of the same data, see the [Balance Ledger REST API](/institutional/funding/overview). This stream is distinct from [Funding Transaction Streaming](/streaming-endpoints/funding-stream): that stream tracks **transaction state changes** (PENDING → COMPLETED, etc.) for deposits/withdrawals; this stream tracks the **balance impact** of every cash event in the ledger. ## Service Definition **Service:** `polymarket.v1.FundingAPI` **RPC:** `CreateBalanceLedgerSubscription` **Type:** Server-side streaming **Required Scope:** `read:positions` ```protobuf theme={null} service FundingAPI { rpc CreateBalanceLedgerSubscription(CreateBalanceLedgerSubscriptionRequest) returns (stream CreateBalanceLedgerSubscriptionResponse); } ``` ## Request Parameters ### CreateBalanceLedgerSubscriptionRequest | Field | Type | Required | Description | | ------------- | ----------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------- | | `account` | `str` | Yes | Fully qualified account name. Example: `firms/ISV-Alice/accounts/alice-trading`. | | `currency` | `str` | No | ISO currency code (e.g., `USD`). Empty = all currencies for the account. | | `entry_types` | `list[LedgerEntryType]` | No | Filter by one or more allowlisted entry types. Empty = all allowlisted types. Suppressed types are filtered server-side regardless. | | `resume_time` | `Timestamp` | No | Replay entries with `update_time >= resume_time` before switching to live push. Clamped upstream to `2026-05-01T00:00:00Z`. | **Cross-firm access is rejected.** The `account` must belong to the firm in the JWT `firm_id` claim. Cross-firm subscriptions return `PERMISSION_DENIED` immediately. ## Response Messages The stream returns `CreateBalanceLedgerSubscriptionResponse` messages. ### CreateBalanceLedgerSubscriptionResponse | Field | Type | Description | | --------- | -------------------------- | --------------------------------------------------------------------------------- | | `entries` | `list[BalanceLedgerEntry]` | Zero or more ledger entries in this batch. Empty messages function as heartbeats. | ### BalanceLedgerEntry | Field | Type | Description | | ---------------------- | ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `id` | `str` | Unique entry identifier. | | `account` | `str` | Account this entry belongs to. For IB/ISV-managed end users, this exactly matches `provisioned_account` from the [`kyc.approved` webhook](/partners/onboarding/kyc/webhooks#data-fields) and `provisionedAccount` from [KYC status](/partners/onboarding/kyc/verification-flow#check-status). Route entries to users by exact string match on `entry.account`. | | `currency` | `str` | ISO currency code. | | `before_balance` | `str` | Balance immediately before this change (decimal). | | `after_balance` | `str` | Balance immediately after this change (decimal). | | `description` | `str` | Human-readable reason for the change. | | `update_time` | `Timestamp` | Timestamp of the balance change. Persist for use as the next `resume_time`. | | `modified_security_id` | `str` | Security ID associated with the change, if any. | | `entry_type` | `LedgerEntryType` | One of the allowlisted entry types (see below). | | `symbol` | `str` | Instrument symbol associated with the change, if any. | | `update_business_date` | `str` | Business date in `YYYY-MM-DD`. | Firm-level omnibus and reserve account identifiers are static configuration provided during onboarding; they are not discovered through the KYC API. ## Stream Behavior 1. **Replay phase.** On connect, the server first delivers entries with `update_time >= resume_time` (clamped to the `2026-05-01` floor). If `resume_time` is omitted, only live entries are delivered. 2. **Live phase.** After the replay drains, the server pushes new entries as they are committed. 3. **Suppressed entry types** are filtered server-side and never reach clients. 4. **Empty `entries` messages** are heartbeats and should be passed through (do not treat as termination). ## LedgerEntryType Allowlist | Wire Value | Name | | ---------- | ----------------------------- | | `1` | `DEPOSIT` | | `2` | `WITHDRAWAL` | | `3` | `ORDER_EXECUTION` | | `4` | `CORRECTION` | | `6` | `RESOLUTION` | | `7` | `MANUAL_ADJUSTMENT` | | `10` | `ACCOUNT_PROPERTY_ADJUSTMENT` | | `11` | `COMMISSION` | | `16` | `WITHDRAWAL_REJECTION` | | `17` | `MANUAL_TRANSFER` | | `22` | `PENDING_WITHDRAWAL_CREATION` | The full allowlist plus suppressed (internal) types are documented on the [Balance Ledger REST overview](/institutional/funding/overview#ledgerentrytype). ## Stream Limits | Limit | Value | | ----------------------------------------------------------- | ----------------------------------------- | | Concurrent streams per firm (across all gRPC subscriptions) | **20** | | Historical floor on `resume_time` | `2026-05-01T00:00:00Z` (clamped upstream) | Exceeding the per-firm concurrent stream cap returns `ResourceExhausted`. ## Metrics The gateway exposes Prometheus metrics for balance ledger subscriptions: | Metric | Type | Labels | Description | | ------------------------------------ | ------- | ----------------------- | ----------------------------------------------- | | `balance_ledger_stream_active` | gauge | `firm_id` | Number of active subscriptions per firm | | `balance_ledger_stream_events_total` | counter | `firm_id`, `entry_type` | Total entries delivered per firm and entry type | ## Complete Python Example ```python theme={null} import grpc from datetime import datetime from google.protobuf import timestamp_pb2 from polymarket.v1 import funding_pb2 from polymarket.v1 import funding_pb2_grpc class BalanceLedgerStreamer: def __init__(self, grpc_server: str = "grpc-api.preprod.polymarketexchange.com:443"): self.grpc_server = grpc_server self.access_token = None self.last_update_time: timestamp_pb2.Timestamp | None = None # for resume_time def stream_balance_ledger( self, account: str, currency: str = "", entry_types: list | None = None, resume_time: timestamp_pb2.Timestamp | None = None, ): """Stream balance ledger entries for one account.""" if not self.access_token: raise ValueError("Not authenticated. Please login first.") credentials = grpc.ssl_channel_credentials() channel = grpc.secure_channel(self.grpc_server, credentials) stub = funding_pb2_grpc.FundingAPIStub(channel) request = funding_pb2.CreateBalanceLedgerSubscriptionRequest( account=account, currency=currency, entry_types=entry_types or [], ) if resume_time is not None: request.resume_time.CopyFrom(resume_time) metadata = [("authorization", f"Bearer {self.access_token}")] try: print(f"Subscribing to balance ledger for {account} (currency={currency or 'ALL'})") response_stream = stub.CreateBalanceLedgerSubscription( request, metadata=metadata ) for response in response_stream: if not response.entries: continue for entry in response.entries: self._process_entry(entry) self.last_update_time = entry.update_time except grpc.RpcError as e: print(f"gRPC error: {e.code()} - {e.details()}") raise except KeyboardInterrupt: print("\nStream interrupted by user") finally: channel.close() def _process_entry(self, entry): ts = entry.update_time.ToDatetime().isoformat() entry_type_name = funding_pb2.LedgerEntryType.Name(entry.entry_type) delta = float(entry.after_balance) - float(entry.before_balance) print( f"[{ts}] {entry_type_name:30s} " f"{entry.before_balance} -> {entry.after_balance} " f"(delta={delta:+.2f} {entry.currency}) " f"{entry.description}" ) if __name__ == "__main__": streamer = BalanceLedgerStreamer() # streamer.access_token = "your_access_token" # see Authentication docs streamer.stream_balance_ledger( account="firms/ISV-Alice/accounts/alice-trading", currency="USD", ) ``` ### Sample Output ``` Subscribing to balance ledger for firms/ISV-Alice/accounts/alice-trading (currency=USD) [2026-05-02T14:30:15.123000] ORDER_EXECUTION 10000.00 -> 9474.03 (delta=-525.97 USD) Buy 100 @ 525 + commission [2026-05-02T14:30:15.123000] COMMISSION 9474.03 -> 9472.53 (delta=-1.50 USD) Maker fee [2026-05-02T14:32:01.001000] DEPOSIT 9472.53 -> 14472.53 (delta=+5000.00 USD) ACH deposit ``` ## Reconnection Handling Persist the most recent `update_time` so reconnections resume without gaps: ```python theme={null} if streamer.last_update_time is not None: streamer.stream_balance_ledger( account="firms/ISV-Alice/accounts/alice-trading", currency="USD", resume_time=streamer.last_update_time, ) ``` `resume_time` is clamped upstream to `2026-05-01T00:00:00Z`; passing an earlier value is allowed but only entries from the floor forward are replayed. ## REST vs Streaming | Aspect | REST `/v1/funding/balance-ledger` | gRPC `CreateBalanceLedgerSubscription` | | -------------- | --------------------------------------------- | ---------------------------------------------- | | Delivery model | Polled, paginated query | Push-based stream | | Replay | Via `start_time` / `end_time` + paging | Via `resume_time` (clamped to floor) | | CSV export | Yes (`/download` endpoint) | No | | Best for | Reconciliation, audit reports, ad-hoc queries | Real-time balance dashboards, automated alerts | ## Error Codes | gRPC Code | Cause | | --------------------- | ------------------------------------------------------------------------------------ | | `PERMISSION_DENIED` | Account belongs to a different firm, or token is missing the `read:positions` scope. | | `UNAUTHENTICATED` | Missing or invalid JWT. | | `FAILED_PRECONDITION` | ISV credentials not configured. | | `UNAVAILABLE` | Upstream exchange service not connected. | | `RESOURCE_EXHAUSTED` | Per-firm 20 concurrent stream cap exceeded. | ## Next Steps Paginated query and CSV download Position changes (quantity, cost, realized P\&L) Deposit / withdrawal state changes gRPC authentication setup # DropCopy & Trade Capture Streaming Source: https://docs.polymarket.us/streaming-endpoints/dropcopy-stream Real-time execution reports and trade capture via gRPC Subscribe to real-time execution reports, trade captures, instrument state changes, and position updates for your firm using gRPC streaming. **gRPC Only** - DropCopy endpoints are gRPC streaming only. There are no REST equivalents. ## Service Definition **Service:** `polymarket.v1.DropCopyAPI` **Type:** Server-side streaming (all endpoints) ```protobuf theme={null} service DropCopyAPI { rpc CreateDropCopySubscription(CreateDropCopySubscriptionRequest) returns (stream CreateDropCopySubscriptionResponse); rpc CreateTradeCaptureReportSubscription(CreateTradeCaptureReportSubscriptionRequest) returns (stream CreateTradeCaptureReportSubscriptionResponse); rpc CreateInstrumentStateChangeSubscription(CreateInstrumentStateChangeSubscriptionRequest) returns (stream CreateInstrumentStateChangeSubscriptionResponse); rpc CreatePositionChangeSubscription(CreatePositionChangeSubscriptionRequest) returns (stream CreatePositionChangeSubscriptionResponse); } ``` ## Available Streams | Stream | Description | Use Case | | --------------------------- | ---------------------------------- | ------------------------------------ | | **DropCopy** | Execution reports (fills, cancels) | Real-time order execution monitoring | | **Trade Capture Report** | Completed trades | Trade reconciliation, compliance | | **Instrument State Change** | Market state updates | Trading halts, market open/close | | **Position Change** | Position updates | Real-time P\&L, risk monitoring | *** ## 1. DropCopy Subscription Stream execution reports as they occur for your firm. ### Request Parameters | Field | Type | Required | Description | | -------------- | ----------- | -------- | ------------------------------------------- | | `resume_token` | `bytes` | No | Resume from previous position | | `resume_time` | `Timestamp` | No | Resume from specific time | | `symbols` | `list[str]` | No | Filter by symbols. Empty = all symbols | | `firms` | `list[str]` | No | Filter by firms. Empty = authenticated firm | ### Response Fields | Field | Type | Description | | -------------- | ----------------- | ------------------------------- | | `resume_token` | `bytes` | Store for reconnection | | `executions` | `list[Execution]` | Execution reports in this batch | Commission fields on executions (`commission_notional_collected`, `commission_notional_total_collected`) are fixed-point notional units scaled by `price_scale` × `fractional_quantity_scale` — see [Fees on execution reports](/partners/orders/data-model#fees-on-execution-reports) for decoding rules and worked examples. ### Example ```python theme={null} import grpc from datetime import datetime from polymarket.v1 import dropcopy_pb2 from polymarket.v1 import dropcopy_pb2_grpc from polymarket.v1 import enums_pb2 class DropCopyStreamer: def __init__(self, grpc_server: str = "grpc-api.preprod.polymarketexchange.com:443"): self.grpc_server = grpc_server self.access_token = None self.last_resume_token = None def stream_executions(self, symbols: list = None, resume_token: bytes = None): """Stream execution reports via DropCopy.""" credentials = grpc.ssl_channel_credentials() channel = grpc.secure_channel(self.grpc_server, credentials) stub = dropcopy_pb2_grpc.DropCopyAPIStub(channel) request = dropcopy_pb2.CreateDropCopySubscriptionRequest( symbols=symbols or [] ) if resume_token: request.resume_token = resume_token metadata = [('authorization', f'Bearer {self.access_token}')] try: print("Starting DropCopy stream...") print(f"Symbols: {symbols or 'ALL'}") print("-" * 60) for response in stub.CreateDropCopySubscription(request, metadata=metadata): self.last_resume_token = response.resume_token for execution in response.executions: self._display_execution(execution) except grpc.RpcError as e: print(f"gRPC error: {e.code()} - {e.details()}") finally: channel.close() def _display_execution(self, execution): """Display execution details.""" print(f"\n[{datetime.now().strftime('%H:%M:%S')}] Execution") print(f" ID: {execution.id}") print(f" Type: {enums_pb2.ExecutionType.Name(execution.type)}") if execution.HasField('order'): order = execution.order print(f" Order ID: {order.id}") print(f" Symbol: {order.symbol}") print(f" Side: {enums_pb2.Side.Name(order.side)}") print(f" State: {enums_pb2.OrderState.Name(order.state)}") if execution.last_shares > 0: print(f" Fill Qty: {execution.last_shares}") if execution.last_px > 0: print(f" Fill Price: {execution.last_px}") if execution.trade_id: print(f" Trade ID: {execution.trade_id}") print("-" * 60) # Usage if __name__ == "__main__": streamer = DropCopyStreamer() # streamer.access_token = "your_token" streamer.stream_executions() ``` ### Sample Output ``` Starting DropCopy stream... Symbols: ALL ------------------------------------------------------------ [14:30:15] Execution ID: exec_abc123 Type: EXECUTION_TYPE_FILL Order ID: order_xyz789 Symbol: tec-nfl-sbw-2026-02-08-kc Side: SIDE_BUY State: ORDER_STATE_FILLED Fill Qty: 100 Fill Price: 525 Trade ID: trade_def456 ------------------------------------------------------------ [14:30:16] Execution ID: exec_ghi789 Type: EXECUTION_TYPE_CANCELED Order ID: order_abc123 Symbol: tec-nfl-sbw-2026-02-08-kc Side: SIDE_SELL State: ORDER_STATE_CANCELED ------------------------------------------------------------ ``` *** ## 2. Trade Capture Report Subscription Stream completed trades for reconciliation and compliance. ### Request Parameters | Field | Type | Required | Description | | -------------- | ----------- | -------- | ----------------------------- | | `resume_token` | `bytes` | No | Resume from previous position | | `resume_time` | `Timestamp` | No | Resume from specific time | | `symbols` | `list[str]` | No | Filter by symbols | | `firms` | `list[str]` | No | Filter by firms | ### Response Fields | Field | Type | Description | | ----------------------- | ------------- | ---------------------- | | `resume_token` | `bytes` | Store for reconnection | | `trade_capture_reports` | `list[Trade]` | Trade records | ### Example ```python theme={null} def stream_trade_captures(self, symbols: list = None): """Stream trade capture reports.""" credentials = grpc.ssl_channel_credentials() channel = grpc.secure_channel(self.grpc_server, credentials) stub = dropcopy_pb2_grpc.DropCopyAPIStub(channel) request = dropcopy_pb2.CreateTradeCaptureReportSubscriptionRequest( symbols=symbols or [] ) metadata = [('authorization', f'Bearer {self.access_token}')] for response in stub.CreateTradeCaptureReportSubscription(request, metadata=metadata): for trade in response.trade_capture_reports: print(f"Trade ID: {trade.id}") print(f" Aggressor: {trade.aggressor.order.id if trade.aggressor else 'N/A'}") print(f" Passive: {trade.passive.order.id if trade.passive else 'N/A'}") print(f" State: {trade.state}") ``` ### Trade Structure Each trade contains two executions: | Field | Description | | ------------ | ---------------------------------------------- | | `id` | Unique trade ID | | `aggressor` | Execution for the incoming (taker) order | | `passive` | Execution for the resting (maker) order | | `trade_type` | Type of trade (REGULAR, CROSS, etc.) | | `state` | Trade state; see [Trade States](#trade-states) | ### Trade States Each `Trade` carries a `state` field with one of the following values: | State | Value | Description | | ----------------------------------- | ----- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `TRADE_STATE_NEW` | 1 | Trade created. | | `TRADE_STATE_CLEARED` | 2 | Trade successfully cleared by the clearing house. | | `TRADE_STATE_BUSTED` | 3 | The trade was voided post-execution by the exchange (an error trade) and the resulting position was rolled back. This is a terminal reversal, **not** a pending state. | | `TRADE_STATE_INFLIGHT` | 4 | Trade information sent to the clearinghouse. | | `TRADE_STATE_PENDING_RISK` | 5 | Clearinghouse is pending at least one DCM claim for the trade. | | `TRADE_STATE_PENDING_CLEARED` | 6 | Clearinghouse is pending the counterparty DCM claim. | | `TRADE_STATE_REJECTED` | 7 | Clearinghouse rejected the trade. | | `TRADE_STATE_CLEARING_ACKNOWLEDGED` | 8 | Clearing request acknowledged by the clearing house. | | `TRADE_STATE_RETRY_REQUEST` | 9 | Retry requested; pending resubmission to the clearing house. | **Handling `TRADE_STATE_BUSTED`** A busted trade was executed and then voided by the exchange (via an error-trade bust), and its position impact was reversed. Busted trades remain visible in trade history — treat them as reversed for position, P\&L, and reconciliation purposes. Do not silently drop them. `TRADE_STATE_UNDEFINED` (value `0`) is the unset/unknown sentinel. It is omitted from API responses and does not appear in the published enum lists. Treat any unrecognized state value defensively rather than assuming the values above are exhaustive. **A bust reverses a prior trade record** A trade can be re-sent on this stream with the same `id` but an updated `state`. When a trade is re-sent with `TRADE_STATE_BUSTED`, that frame **supersedes and reverses** the earlier record carrying the same `id`. Deduplicate on `id` and always apply the latest `state`: a subsequent `TRADE_STATE_BUSTED` unwinds the position and P\&L impact of a trade you previously recorded as `TRADE_STATE_CLEARED`. *** ## 3. Instrument State Change Subscription Stream market state changes (halts, opens, closes). **No Participant ID Required** This endpoint only requires Auth0 JWT authentication with `read:instruments` scope. You do not need to provide the `x-participant-id` header or complete KYC onboarding to access instrument state changes. ### Request Parameters | Field | Type | Required | Description | | -------------- | ----------- | -------- | ----------------------------- | | `resume_token` | `bytes` | No | Resume from previous position | | `resume_time` | `Timestamp` | No | Resume from specific time | | `symbols` | `list[str]` | No | Filter by symbols | ### Response Fields | Field | Type | Description | | -------------- | ------------------ | ------------------------- | | `resume_token` | `bytes` | Store for reconnection | | `instruments` | `list[Instrument]` | Updated instrument states | ### Instrument States #### Primary State Flow | State                                               | Description | | --------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `INSTRUMENT_STATE_PENDING` | Initial state for a newly created instrument which has not yet begun trading. | | `INSTRUMENT_STATE_OPEN` | In this state, the instrument is open for continuous order entry and matching. | | `INSTRUMENT_STATE_CLOSED` | In this state, orders can not be entered, modified, or canceled, and no matching occurs. Any existing Day orders will be expired. | | `INSTRUMENT_STATE_EXPIRED` | An instrument moves to this state when its Expiration Date/Time is reached. In this state, any resting orders are expired and no new orders can be entered. | | `INSTRUMENT_STATE_TERMINATED` | When an instrument's Termination Date is reached, the order book is removed from the matching engine, orders are canceled, and positions are closed. Historical data will still remain in Polymarket US ledgers. | #### Exception States | State                                               | Description | | --------------------------------------------------- | --------------------------------------------------------------------------------------------- | | `INSTRUMENT_STATE_SUSPENDED` | Orders can be canceled but no matching occurs, and no order entry or modification is allowed. | | `INSTRUMENT_STATE_HALTED` | This state is similar to SUSPENDED, with the exception that orders cannot be canceled. | #### Other Possible States | State                                               | Description | | --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `INSTRUMENT_STATE_PREOPEN` | Orders can be entered and modified, but no matching occurs. When the instrument transitions to an OPEN state, the orders entered during PREOPEN will match at a single opening price that is automatically determined by an algorithm that is designed to maximize the volume traded at the open. | | `INSTRUMENT_STATE_MATCH_AND_CLOSE_AUCTION` | This state is similar to PREOPEN, with the exception that matching will occur upon the transition of this state to any other state. This state is useful if you want matching to occur at the end of the state, but you don't want the instrument to be open after. | ### Example ```python theme={null} def stream_instrument_states(self, symbols: list = None): """Stream instrument state changes.""" credentials = grpc.ssl_channel_credentials() channel = grpc.secure_channel(self.grpc_server, credentials) stub = dropcopy_pb2_grpc.DropCopyAPIStub(channel) request = dropcopy_pb2.CreateInstrumentStateChangeSubscriptionRequest( symbols=symbols or [] ) metadata = [('authorization', f'Bearer {self.access_token}')] for response in stub.CreateInstrumentStateChangeSubscription(request, metadata=metadata): for instrument in response.instruments: print(f"Symbol: {instrument.symbol}") print(f" State: {instrument.state}") print(f" Description: {instrument.description}") ``` *** ## 4. Position Change Subscription Stream real-time position updates. ### Request Parameters | Field | Type | Required | Description | | -------------- | ----------- | -------- | ----------------------------- | | `resume_token` | `bytes` | No | Resume from previous position | | `resume_time` | `Timestamp` | No | Resume from specific time | | `symbols` | `list[str]` | No | Filter by symbols | | `firms` | `list[str]` | No | Filter by firms | ### Response Fields | Field | Type | Description | | ------------------ | ---------------------- | ---------------------- | | `resume_token` | `bytes` | Store for reconnection | | `position_changes` | `list[PositionChange]` | Position updates | ### PositionChange Structure | Field | Type | Description | | ------------- | ----------- | ---------------------- | | `position` | `Position` | Current position state | | `change_time` | `Timestamp` | When change occurred | ### Example ```python theme={null} def stream_position_changes(self, symbols: list = None): """Stream position changes.""" credentials = grpc.ssl_channel_credentials() channel = grpc.secure_channel(self.grpc_server, credentials) stub = dropcopy_pb2_grpc.DropCopyAPIStub(channel) request = dropcopy_pb2.CreatePositionChangeSubscriptionRequest( symbols=symbols or [] ) metadata = [('authorization', f'Bearer {self.access_token}')] for response in stub.CreatePositionChangeSubscription(request, metadata=metadata): for change in response.position_changes: pos = change.position print(f"Position Update: {pos.symbol}") print(f" Account: {pos.account}") print(f" Net Qty: {pos.net_position}") print(f" Avg Price: {pos.average_price}") print(f" Change Time: {change.change_time}") ``` *** ## Reconnection Handling All DropCopy streams support resume tokens for seamless reconnection: ```python theme={null} # Store resume_token from each response last_token = response.resume_token # On reconnect, pass the token request = dropcopy_pb2.CreateDropCopySubscriptionRequest( resume_token=last_token ) ``` **Resume Token Expiry** Resume tokens may expire after extended disconnection periods. If resumption fails, start a fresh subscription and reconcile with the Report API for any missed data. ## Comparing DropCopy vs Order Stream | Feature | DropCopy | Order Stream | | ------------------ | ----------------------- | ---------------------------- | | **Scope** | Firm-wide executions | User's orders only | | **Use Case** | Back-office, compliance | Trading UI, order management | | **Data** | Executions, trades | Orders, executions | | **Authentication** | Firm-level token | User token | **When to Use DropCopy** Use DropCopy when you need: * Firm-wide visibility across all users * Trade capture reports for reconciliation * Instrument state change notifications * Real-time position monitoring across accounts ## Next Steps Stream user-level order updates Stream real-time market data Stream funding transaction updates Handle errors and reconnections # Error Handling & Best Practices Source: https://docs.polymarket.us/streaming-endpoints/error-handling Handle errors, implement reconnection strategies, and follow best practices with Python Learn how to handle errors gracefully, implement robust reconnection strategies, and follow production-ready best practices for gRPC streaming in Python. ## gRPC Status Codes gRPC uses standard status codes to indicate errors. Understanding these codes is essential for proper error handling in Python. ### Common Status Codes | Code | Name | Description | Action | | ---- | ------------------- | -------------------------- | ----------------------------------------------------------------------------- | | `0` | `OK` | Success | Continue processing | | `1` | `CANCELLED` | Operation canceled | Clean up resources | | `3` | `INVALID_ARGUMENT` | Invalid request parameters | Fix request and retry. See [Request Parameters](proto-reference) for details. | | `4` | `DEADLINE_EXCEEDED` | Operation timeout | Retry with backoff | | `7` | `PERMISSION_DENIED` | Insufficient permissions | Check account permissions | | `14` | `UNAVAILABLE` | Service unavailable | Reconnect with backoff | | `16` | `UNAUTHENTICATED` | Authentication failed | Refresh token and retry | ### Checking Error Codes in Python ```python theme={null} import grpc try: for response in response_stream: # Process response pass except grpc.RpcError as e: status_code = e.code() if status_code == grpc.StatusCode.UNAUTHENTICATED: print("Authentication failed") elif status_code == grpc.StatusCode.UNAVAILABLE: print("Service unavailable") elif status_code == grpc.StatusCode.INVALID_ARGUMENT: print("Invalid request") else: print(f"Error: {status_code} - {e.details()}") ``` *** ## Common Errors and Solutions ### 1. UNAUTHENTICATED - Authentication Failed **Causes:** * Invalid access token * Expired access token * Missing authorization metadata ### 2. UNAVAILABLE - Service Unavailable **Causes:** * Network connectivity issues * Server temporarily unavailable * Firewall blocking connection ### 3. INVALID\_ARGUMENT - Bad Request **Causes:** * Invalid symbol * Invalid depth value * Malformed request *** ## Next Steps Review authentication best practices Learn about market data streaming Learn about order streaming # Funding Transaction Streaming Source: https://docs.polymarket.us/streaming-endpoints/funding-stream Real-time funding transaction state changes via gRPC Subscribe to real-time funding transaction state changes (deposits, withdrawals) using gRPC streaming. This is the **recommended approach** for monitoring funding status. **Rate Limiting Notice** The REST endpoint `GET /v1/funding/transactions` is rate limited. For real-time funding status updates, **prefer the gRPC streaming connection** to avoid rate limit issues and reduce latency. ## Service Definition **Service:** `polymarket.v1.FundingAPI` **RPC:** `CreateFundingTransactionSubscription` **Type:** Server-side streaming ```protobuf theme={null} service FundingAPI { rpc CreateFundingTransactionSubscription(CreateFundingTransactionSubscriptionRequest) returns (stream CreateFundingTransactionSubscriptionResponse); } ``` ## Request Parameters ### CreateFundingTransactionSubscriptionRequest | Field | Type | Required | Description | | ------------------- | ------------------------------ | -------- | --------------------------------------------------------------- | | `account_ids` | `list[str]` | No | Filter by funding account IDs. Empty = all authorized accounts. | | `transaction_types` | `list[FundingTransactionType]` | No | Filter by transaction type. Empty = all types. | | `resume_time` | `Timestamp` | No | Resume from a previous position for reconnection. | ### Example Request ```python theme={null} from polymarket.v1 import funding_pb2 # Subscribe to all funding transactions for your accounts request = funding_pb2.CreateFundingTransactionSubscriptionRequest( account_ids=[], transaction_types=[] ) # Subscribe to deposits only request = funding_pb2.CreateFundingTransactionSubscriptionRequest( account_ids=[], transaction_types=[funding_pb2.TRANSACTION_TYPE_DEPOSIT] ) # Subscribe to specific account request = funding_pb2.CreateFundingTransactionSubscriptionRequest( account_ids=["your-account-id"], transaction_types=[] ) ``` ## Response Messages The stream returns `CreateFundingTransactionSubscriptionResponse` messages when transaction states change. ### Response Fields | Field | Type | Description | | ------------- | -------------------------------- | ---------------------------------------------------------- | | `changes` | `list[FundingTransactionChange]` | Transaction state changes in this batch | | `server_time` | `Timestamp` | Server timestamp (store for `resume_time` on reconnection) | ### FundingTransactionChange | Field | Type | Description | | ---------------- | ------------------------- | ------------------------------------------ | | `transaction` | `FundingTransaction` | The updated transaction with current state | | `previous_state` | `FundingTransactionState` | The state before this change | | `change_time` | `Timestamp` | When this change was detected | ## Transaction States | State | Value | Description | | -------------------------------------- | ----- | -------------------------------------------- | | `TRANSACTION_STATE_PENDING` | 0 | Transaction initiated, awaiting processing | | `TRANSACTION_STATE_PROCESSING` | 9 | Transaction being processed | | `TRANSACTION_STATE_ACKNOWLEDGED` | 1 | Transaction acknowledged by payment provider | | `TRANSACTION_STATE_COMPLETED` | 2 | **Transaction successfully completed** | | `TRANSACTION_STATE_CANCELLED` | 3 | Transaction cancelled | | `TRANSACTION_STATE_ALLOCATED` | 4 | Funds allocated | | `TRANSACTION_STATE_REFUNDED` | 6 | Transaction fully refunded | | `TRANSACTION_STATE_PARTIALLY_REFUNDED` | 5 | Transaction partially refunded | | `TRANSACTION_STATE_RELEASED` | 8 | Funds released | | `TRANSACTION_STATE_PARTIALLY_RELEASED` | 7 | Funds partially released | ### Common State Transitions ``` Deposit Flow: PENDING → PROCESSING → ACKNOWLEDGED → COMPLETED Deposit Failure: PENDING → PROCESSING → CANCELLED Withdrawal Flow: PENDING → PROCESSING → COMPLETED Refund Flow: COMPLETED → PARTIALLY_REFUNDED → REFUNDED ``` ## Transaction Types | Type | Value | Description | | ------------------------------------ | ----- | ----------------------- | | `TRANSACTION_TYPE_DEPOSIT` | 1 | Deposit into account | | `TRANSACTION_TYPE_WITHDRAWAL` | 2 | Withdrawal from account | | `TRANSACTION_TYPE_TRANSFER` | 3 | Internal transfer | | `TRANSACTION_TYPE_MANUAL_ADJUSTMENT` | 4 | Manual adjustment | | `TRANSACTION_TYPE_SETTLEMENT_FEE` | 5 | Settlement fee | | `TRANSACTION_TYPE_EXECUTION_FEE` | 7 | Trading execution fee | ## Complete Example ```python theme={null} import grpc from datetime import datetime from polymarket.v1 import funding_pb2 from polymarket.v1 import funding_pb2_grpc class FundingTransactionStreamer: def __init__(self, grpc_server: str = "grpc-api.preprod.polymarketexchange.com:443"): self.grpc_server = grpc_server self.access_token = None self.last_server_time = None # For resume capability def stream_funding_transactions(self, account_ids: list = None, transaction_types: list = None, resume_time=None): """Stream funding transaction state changes.""" if not self.access_token: raise ValueError("Not authenticated. Please login first.") # Create credentials and channel credentials = grpc.ssl_channel_credentials() channel = grpc.secure_channel(self.grpc_server, credentials) stub = funding_pb2_grpc.FundingAPIStub(channel) # Create request request = funding_pb2.CreateFundingTransactionSubscriptionRequest( account_ids=account_ids or [], transaction_types=transaction_types or [] ) # Add resume_time if reconnecting if resume_time: request.resume_time.CopyFrom(resume_time) # Set up metadata with authorization metadata = [('authorization', f'Bearer {self.access_token}')] try: print("Starting funding transaction stream...") print(f"Account filters: {account_ids or 'ALL'}") print(f"Type filters: {transaction_types or 'ALL'}") print("-" * 60) response_stream = stub.CreateFundingTransactionSubscription( request, metadata=metadata ) for response in response_stream: self._process_response(response) except grpc.RpcError as e: print(f"gRPC error: {e.code()} - {e.details()}") raise except KeyboardInterrupt: print("\nStream interrupted by user") finally: channel.close() def _process_response(self, response): """Process funding transaction change response.""" # Store server_time for resume capability if response.HasField('server_time'): self.last_server_time = response.server_time for change in response.changes: tx = change.transaction print(f"\n[{datetime.now().strftime('%H:%M:%S')}] Transaction State Change") print(f" Transaction ID: {tx.transaction_id}") print(f" Type: {funding_pb2.FundingTransactionType.Name(tx.transaction_type)}") print(f" Amount: {tx.amount} {tx.currency}") print(f" Previous State: {funding_pb2.FundingTransactionState.Name(change.previous_state)}") print(f" Current State: {funding_pb2.FundingTransactionState.Name(tx.transaction_state)}") print(f" Account ID: {tx.account_id}") if tx.after_balance: print(f" New Balance: {tx.after_balance}") print("-" * 60) # Usage if __name__ == "__main__": streamer = FundingTransactionStreamer() # Authenticate first (see Authentication docs) # streamer.access_token = "your_access_token" # Stream all funding transactions streamer.stream_funding_transactions() # Or filter by type streamer.stream_funding_transactions( transaction_types=[funding_pb2.TRANSACTION_TYPE_DEPOSIT] ) ``` ### Sample Output ``` Starting funding transaction stream... Account filters: ALL Type filters: ALL ------------------------------------------------------------ [14:30:15] Transaction State Change Transaction ID: tx_abc123 Type: TRANSACTION_TYPE_DEPOSIT Amount: 100.00 USD Previous State: TRANSACTION_STATE_PENDING Current State: TRANSACTION_STATE_PROCESSING Account ID: acct_xyz789 ------------------------------------------------------------ [14:30:45] Transaction State Change Transaction ID: tx_abc123 Type: TRANSACTION_TYPE_DEPOSIT Amount: 100.00 USD Previous State: TRANSACTION_STATE_PROCESSING Current State: TRANSACTION_STATE_COMPLETED Account ID: acct_xyz789 New Balance: 250.00 ------------------------------------------------------------ ``` ## Reconnection Handling Store the `server_time` from each response to enable seamless reconnection: ```python theme={null} # On disconnect, reconnect with resume_time if streamer.last_server_time: streamer.stream_funding_transactions( resume_time=streamer.last_server_time ) ``` The server polls for transaction changes every 15 seconds. State changes are broadcast to subscribers as they are detected. ## Comparing REST vs Streaming | Aspect | REST (`/v1/funding/transactions`) | gRPC Streaming | | ----------------- | --------------------------------- | -------------------------------- | | **Rate Limiting** | Yes - subject to rate limits | No - single connection | | **Latency** | Poll-based, higher latency | Real-time push (\~15s detection) | | **Efficiency** | Multiple requests needed | Single persistent connection | | **Use Case** | One-time queries, historical data | Real-time monitoring | **Recommendation:** Use the gRPC streaming endpoint for monitoring deposit/withdrawal status. Reserve the REST endpoint for one-time queries or fetching historical transaction data. ## Next Steps Stream every cash balance change (fills, fees, deposits, ...) Stream real-time order updates Stream real-time market data Handle errors and reconnections gRPC authentication setup # Getting Started with gRPC Streaming Source: https://docs.polymarket.us/streaming-endpoints/getting-started Quick start guide for setting up gRPC streaming with Python This guide will help you set up gRPC streaming with Python and connect to your first data stream in minutes. ## Prerequisites Before you begin, ensure you have: * **API Credentials**: Client ID from [authentication setup](/trader-guide/authentication) * **Python 3.7+**: Python development environment * **Network Access**: Ability to connect to `grpc-api.preprod.polymarketexchange.com:443` ## Step 1: Install Python gRPC Libraries ```bash theme={null} pip install grpcio grpcio-tools protobuf requests ``` **Required packages:** * `grpcio`: gRPC runtime library * `grpcio-tools`: Tools for generating Python code from proto files * `protobuf`: Protocol buffer runtime * `requests`/`httpx`: For REST API authentication ## Step 2: Obtain Protocol Buffer Definitions Proto files define the gRPC service interfaces and message structures. The downloadable bundle is the canonical client contract: Download the proto files directly: [Polymarket - Proto Files.zip](https://drive.google.com/uc?export=download\&id=1oT9gaeBEn0vukHD9GOoj_YvzPnR3otng) Do not make client startup depend on gRPC reflection. Reflection availability can differ by environment. ```bash theme={null} unzip polymarket-protos.zip -d protos ``` ## Step 3: Generate Python Client Code Once you have the proto files, generate Python client code: ```bash theme={null} mkdir -p gen && python -m grpc_tools.protoc --python_out=gen --grpc_python_out=gen --proto_path=protos/api protos/api/polymarket/v1/*.proto protos/api/google/api/*.proto protos/api/protoc-gen-openapiv2/options/*.proto ``` This generates: * `*_pb2.py` files: Message definitions * `*_pb2_grpc.py` files: Service stubs ## Step 4: Authenticate **CRITICAL: Tokens must be refreshed every 3 minutes.** Access tokens have a short expiration. Your application MUST implement automatic token refresh before expiration to maintain uninterrupted streaming connections. ### Auth Domains | Environment | Auth Domain | | -------------- | -------------------------- | | **Production** | `pmx-prod.us.auth0.com` | | **Preprod** | `pmx-preprod.us.auth0.com` | Obtain a JWT token using Private Key JWT authentication: ```python theme={null} import jwt import uuid import time import requests from cryptography.hazmat.primitives import serialization # Load your private key with open("private_key.pem", "rb") as f: private_key = serialization.load_pem_private_key(f.read(), password=None) # Create signed JWT assertion now = int(time.time()) claims = { "iss": "YOUR_CLIENT_ID", "sub": "YOUR_CLIENT_ID", "aud": "https://pmx-preprod.us.auth0.com/oauth/token", "iat": now, "exp": now + 300, "jti": str(uuid.uuid4()), } assertion = jwt.encode(claims, private_key, algorithm="RS256") # Exchange for access token response = requests.post( "https://pmx-preprod.us.auth0.com/oauth/token", json={ "client_id": "YOUR_CLIENT_ID", "client_assertion_type": "urn:ietf:params:oauth:client-assertion-type:jwt-bearer", "client_assertion": assertion, "audience": "YOUR_API_AUDIENCE", "grant_type": "client_credentials" } ) access_token = response.json()["access_token"] ``` The response contains your `access_token`: ```json theme={null} { "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIs...", "token_type": "Bearer", "expires_in": 180 } ``` The `expires_in` is 180 seconds (3 minutes). Implement automatic token refresh before expiration. See the [Authentication Setup Guide](/trader-guide/authentication) for complete authentication details. ## Step 5: Connect to Market Data Stream Create your first streaming connection: ```python theme={null} import grpc from datetime import datetime from polymarket.v1 import marketdatasubscription_pb2 from polymarket.v1 import marketdatasubscription_pb2_grpc # Create secure channel credentials = grpc.ssl_channel_credentials() channel = grpc.secure_channel('grpc-api.preprod.polymarketexchange.com:443', credentials) # Create stub stub = marketdatasubscription_pb2_grpc.MarketDataSubscriptionAPIStub(channel) # Create request request = marketdatasubscription_pb2.CreateMarketDataSubscriptionRequest( symbols=["tec-nfl-sbw-2026-02-08-kc"], unaggregated=False, depth=10, snapshot_only=False ) # Set up metadata with authorization metadata = [ ('authorization', f'Bearer {access_token}') ] # Start streaming print("Starting market data stream...") response_stream = stub.CreateMarketDataSubscription(request, metadata=metadata) for response in response_stream: if response.HasField('heartbeat'): timestamp = datetime.now().strftime('%H:%M:%S') print(f"[{timestamp}] Heartbeat received") elif response.HasField('update'): update = response.update timestamp = datetime.now().strftime('%H:%M:%S') print(f"\n[{timestamp}] Market Update for {update.symbol}") print(f" Bids: {len(update.bids)}") print(f" Offers: {len(update.offers)}") # Get price_scale from instrument metadata (via list_instruments API) # You must implement this function to fetch from your instrument cache price_scale = get_instrument_price_scale(update.symbol) # implement this if update.bids: top_bid_px = update.bids[0].px / price_scale print(f" Top Bid: ${top_bid_px:.4f} x {update.bids[0].qty}") if update.offers: top_offer_px = update.offers[0].px / price_scale print(f" Top Offer: ${top_offer_px:.4f} x {update.offers[0].qty}") ``` **Price Representation**: All prices are `int64` values. Divide by the instrument's `price_scale` to get the decimal price. `price_scale` varies by instrument. Get it from: * Instrument metadata via `list_instruments` or `get_instrument_metadata` * The `price_scale` field in order responses ```python theme={null} price_scale = get_instrument_price_scale(symbol) decimal_price = raw_price / price_scale ``` ## Common Setup Issues ### Connection Refused * **Cause**: Firewall blocking outbound gRPC connections * **Solution**: Ensure port 443 is open for outbound connections ### Authentication Failed * **Cause**: Invalid or expired access token * **Solution**: Verify JWT, refresh token if expired (tokens expire every 3 minutes) ### Import Errors * **Cause**: Generated proto files not in Python path * **Solution**: Ensure proto files are generated in the correct directory, or add to `PYTHONPATH`: ```bash theme={null} export PYTHONPATH="${PYTHONPATH}:." ``` ### Module Not Found: polymarket * **Cause**: Proto files not generated or in wrong location * **Solution**: Re-run the `protoc` command from step 3, ensure you're in the correct directory ## Next Steps Deep dive into authentication and token management Learn about all market data streaming features Subscribe to order execution updates ## Need Help? If you encounter issues during setup: 1. Check the [Error Handling Guide](/streaming-endpoints/error-handling) 2. Review the [Market Data Stream](/streaming-endpoints/market-data-stream) or [Order Stream](/streaming-endpoints/order-stream) pages for complete implementations 3. Contact [onboarding@polymarket.us](mailto:onboarding@polymarket.us) for assistance # gRPC Streaming Overview Source: https://docs.polymarket.us/streaming-endpoints/grpc-overview Introduction to real-time data streaming on Polymarket Exchange The Polymarket Exchange provides **gRPC streaming services** for real-time market data and order execution updates. This enables low-latency, efficient data delivery for applications that need continuous updates. ## Why Use gRPC Streaming? gRPC streaming offers several advantages: * **Bidirectional Communication**: Server can push updates without client polling * **Type Safety**: Strongly-typed messages defined in Protocol Buffers * **Real-time Updates**: Receive market data and order updates as they happen ## REST + gRPC Hybrid Approach Most participants use **REST for requests** and **gRPC for streaming**. This hybrid approach combines the simplicity of REST with the efficiency of gRPC streaming. ### Typical Integration Pattern 1. **REST API** - Used for: * Placing orders (`/v1/trading/orders`) * Canceling orders (`/v1/trading/orders/cancel`) * Querying account information * One-time data requests 2. **gRPC Streaming** - Used for: * Real-time market data updates * Live order execution reports * Continuous position monitoring * Order book changes ## Available Streaming Services ### Market Data Streaming Subscribe to real-time market data updates including: * Order book (bids and offers) * Instrument state changes * Trade statistics (last price, OHLC, volume) * Open interest **Service:** `MarketDataSubscriptionAPI.CreateMarketDataSubscription` [Learn more about Market Data Streaming →](/streaming-endpoints/market-data-stream) ### Order Execution Streaming Subscribe to real-time order and execution updates: * New order confirmations * Partial and complete fills * Order cancellations and rejections * Execution reports with trade details **Service:** `OrderEntryAPI.CreateOrderSubscription` [Learn more about Order Streaming →](/streaming-endpoints/order-stream) ### RFQ Events Streaming Subscribe to live combo RFQ and quote lifecycle events: * RFQ creation and closure * Quote creation, deletion, acceptance, confirmation, and execution **Service:** `RFQAPI.StreamRFQEvents` [Learn more about RFQ Events Streaming →](/streaming-endpoints/rfq-events-stream) ## Server Endpoints ### Pre-Production Environment ``` grpc-api.preprod.polymarketexchange.com:443 ``` ### Production Environment ``` grpc-api.prod.polymarketexchange.com:443 ``` Both endpoints use **TLS/SSL** for secure communication. All connections must be encrypted. ## Protocol Buffer Definitions The exchange uses **Protocol Buffers (proto3)** to define message structures. Download the canonical [Polymarket - Proto Files.zip](https://drive.google.com/uc?export=download\&id=1oT9gaeBEn0vukHD9GOoj_YvzPnR3otng) bundle. Do not make client startup depend on gRPC reflection. Reflection availability can differ by environment. ### Package Structure ``` polymarket.v1 # Core services ├── MarketDataSubscriptionAPI # Market data streaming ├── OrderEntryAPI # Order streaming and entry ├── ComboAPI # Combo instrument creation and reads ├── RFQAPI # Combo RFQs, quotes, and RFQ event streaming ├── RefDataAPI # Reference data (instruments, symbols) ├── AccountsAPI # Account information ├── PositionAPI # Positions and balances ├── ReportAPI # Order and trade reports ├── DropCopyAPI # Trade execution feed ``` ## Quick Start Ready to get started? Follow our [Getting Started Guide](/streaming-endpoints/getting-started) to: 1. Install Python gRPC libraries 2. Obtain and compile proto files 3. Authenticate and connect 4. Subscribe to your first data stream ## Architecture Overview ```mermaid theme={null} graph LR A[Python Client] -->|REST| B[REST API] A -->|gRPC Stream| C[gRPC Server] A -->|M2M Auth| D[Polymarket US Auth] D -->|JWT Token| A A -->|Bearer Token| B A -->|Bearer Token| C C -->|Market Data| A C -->|Order Updates| A ``` ### Authentication Flow 1. **Obtain JWT Token**: Request M2M token using client credentials 2. **Attach Token**: Include token in gRPC metadata as `authorization` header 3. **Stream Data**: Receive continuous updates over persistent connection [Learn more about Authentication →](/streaming-endpoints/authentication) ## Rate Limits | Setting | Value | | --------------------------------------------------- | ----------- | | Max concurrent streams per firm | 20 | | Ingress message rate (per firm, across all streams) | 100 msg/sec | | Egress (server to client) | Unlimited | **Ingress Rate Limit** Client-to-server messages are limited to **100 messages per second** across all streams per firm, averaged over a 1-minute window. Short bursts above this rate are allowed. This applies to requests you send, not to server-pushed updates like market data. Exceeding the average limit will result in throttled or rejected messages. ## Key Concepts ### Heartbeats Periodic keep-alive messages ensure connection health. If heartbeats stop, the connection may be stale. ### Snapshots Initial state of data (e.g., all open orders) sent when subscription starts. ### Updates Incremental changes streamed continuously after the snapshot. ### Session IDs Unique identifiers for each streaming session, useful for logging and debugging. ## Next Steps Set up your first gRPC stream Learn how to authenticate gRPC connections Stream real-time market data Subscribe to order execution updates Subscribe to combo RFQ and quote events # Market Data Streaming Source: https://docs.polymarket.us/streaming-endpoints/market-data-stream Real-time market data streaming via gRPC with Python Subscribe to real-time market data updates including order book depth, instrument states, and trade statistics using Python and gRPC. **No Participant ID Required** This streaming endpoint only requires Auth0 JWT authentication with `read:marketdata` scope. You do not need to provide the `x-participant-id` header or complete KYC onboarding to access market data streams. ## Service Definition **Service:** `polymarket.v1.MarketDataSubscriptionAPI` The service provides two streaming methods: 1. **`CreateMarketDataSubscription`** - Server-side streaming with fixed symbols at subscription time 2. **`BiDirectionalStreamMarketData`** - Bidirectional streaming with dynamic symbol management ```protobuf theme={null} service MarketDataSubscriptionAPI { // Server-side streaming - symbols fixed at subscription time rpc CreateMarketDataSubscription(CreateMarketDataSubscriptionRequest) returns (stream CreateMarketDataSubscriptionResponse); // Bidirectional streaming - dynamically add/remove symbols rpc BiDirectionalStreamMarketData(stream BiDirectionalStreamMarketDataRequest) returns (stream BiDirectionalStreamMarketDataResponse); } ``` **When to use which method:** * Use `CreateMarketDataSubscription` for simple subscriptions where symbols are known upfront * Use `BiDirectionalStreamMarketData` when you need to dynamically add/remove symbols without reconnecting ## Request Parameters ### CreateMarketDataSubscriptionRequest | Field | Type | Required | Description | | --------------- | ----------- | -------- | ------------------------------------------------------------------------------------------------------------- | | `symbols` | `list[str]` | No | List of symbols to subscribe to. Empty list subscribes to all instruments. | | `unaggregated` | `bool` | No | If `True`, receive raw order book. If `False` (default), receive aggregated book by price level. | | `depth` | `int` | No | Number of price levels to include in order book. Default: `10` | | `snapshot_only` | `bool` | No | If `True`, receive only initial snapshot then close stream. If `False` (default), receive continuous updates. | **Symbol Limit**: Each gRPC stream is limited to **1000 symbols maximum**. If you need to subscribe to more than 1000 symbols, create multiple streams. ### Example Request ```python theme={null} from polymarket.v1 import marketdatasubscription_pb2 # Subscribe to specific symbols request = marketdatasubscription_pb2.CreateMarketDataSubscriptionRequest( symbols=["tec-nfl-sbw-2026-02-08-kc", "tec-nfl-sbw-2026-02-08-phi"], unaggregated=False, depth=10, snapshot_only=False ) # Subscribe to all symbols request = marketdatasubscription_pb2.CreateMarketDataSubscriptionRequest( symbols=[], # Empty = all symbols depth=20 ) ``` ## Response Messages The stream returns `CreateMarketDataSubscriptionResponse` messages with two possible event types: ### 1. Heartbeat Messages Keep-alive messages to confirm connection is active. ```python theme={null} if response.HasField('heartbeat'): print(f"[{datetime.now().strftime('%H:%M:%S')}] Heartbeat received") ``` If you stop receiving heartbeats, the connection may be stale. Consider reconnecting. ### 2. Market Data Updates Real-time market data changes. ```python theme={null} if response.HasField('update'): update = response.update print(f"Symbol: {update.symbol}") print(f"State: {update.state}") print(f"Bids: {len(update.bids)}") print(f"Offers: {len(update.offers)}") ``` ## Update Message Structure ### Fields | Field | Type | Description | | --------------- | ----------------- | ----------------------------------------------------- | | `symbol` | `str` | Instrument symbol (e.g., "tec-nfl-sbw-2026-02-08-kc") | | `bids` | `list[BookEntry]` | Bid side of order book (buy orders) | | `offers` | `list[BookEntry]` | Offer/ask side of order book (sell orders) | | `state` | `InstrumentState` | Current trading state of instrument (optional) | | `stats` | `InstrumentStats` | Market statistics (optional) | | `transact_time` | `Timestamp` | Server timestamp of update | | `book_hidden` | `bool` | If `True`, order book is hidden | **Instrument State Tracking:** The `state` field in `MarketDataUpdate` is optional and should not be relied upon for tracking instrument state changes. The preferred approach is to use `ListInstruments` to get and cache the initial state for each instrument, then subscribe to the **instrument state change subscription** for real-time state updates. ### BookEntry Structure Each price level in the order book contains: | Field | Type | Description | | ----- | ------- | --------------------------------------------- | | `px` | `int64` | **Price as integer** (divide by price\_scale) | | `qty` | `int64` | Aggregate quantity at this price level | **Price Representation:** All prices are `int64` values. Divide by the instrument's `price_scale` to get the decimal value. `price_scale` varies by instrument. Query instrument metadata to get the correct value. ```python theme={null} px = bid.px / price_scale # Convert from price representation print(f"${px:.4f}") ``` ### InstrumentStats Structure Market statistics include: | Field | Type | Description | | ------------------------------------- | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | | `last_trade_px` | `int64` | Last trade price (÷ price\_scale) | | `last_trade_qty` | `int64` | Quantity of the most recent trade. Populated after any trade executes on the instrument. | | `open_px` | `int64` | Opening price (÷ price\_scale) | | `high_px` | `int64` | High price of session (÷ price\_scale) | | `low_px` | `int64` | Low price of session (÷ price\_scale) | | `close_px` | `int64` | Closing price (÷ price\_scale) | | `shares_traded` | `int64` | Total volume traded | | `open_interest` | `int64` | Current open interest | | `notional_traded` | `int64` | Total notional value traded | | `settlement_px` | `int64` | Settlement/resolution price (÷ price\_scale). Only populated when instrument state is `CLOSED`, `TERMINATED`, or `EXPIRED`. | | `settlement_set_time` | `Timestamp` | Timestamp when the settlement price was set. Only populated when instrument is in a settled state (`CLOSED`, `TERMINATED`, or `EXPIRED`). | | `settlement_preliminary` | `bool` | If `true`, settlement price may still change (awaiting final approval). If `false`, settlement is final and positions will be resolved at this price. | | `settlement_price_calculation_method` | `string` | Method used to calculate the settlement price. See below for values. | | `settlement_price_calculation_text` | `string` | Free-form text describing the outcome that determined the settlement (e.g., "Buffalo Bills win", "Kansas City Chiefs win"). | Stats fields use protobuf `oneof`, so they may not always be present. Always check with `HasField()` before accessing. ```python theme={null} if update.HasField('stats') and update.stats.HasField('last_trade_px'): last_px = update.stats.last_trade_px / price_scale ``` #### Settlement Fields **Settlement Price Calculation Methods:** | Value | Description | | -------------------------------------------------- | --------------------------------------------------------------------------------------------------------- | | `SETTLEMENT_PRICE_CALCULATION_METHOD_EVENT_TIER_1` | Settlement via event resolution. This is the primary method for Polymarket event markets. | | Other methods | Used for daily settlement of futures-style products (VWAP-based). These can be ignored for event markets. | For Polymarket event markets, `settlement_price_calculation_method` will be `SETTLEMENT_PRICE_CALCULATION_METHOD_EVENT_TIER_1`. If you see a different method, you can treat it as a daily mark rather than final resolution. **Example: Settled Market (YES wins)** ```json theme={null} { "symbol": "aec-nfl-buf-kc-2026-01-26", "state": "INSTRUMENT_STATE_EXPIRED", "stats": { "settlement_px": 1000, "settlement_set_time": "2026-01-27T03:42:11Z", "settlement_preliminary": false, "settlement_price_calculation_method": "SETTLEMENT_PRICE_CALCULATION_METHOD_EVENT_TIER_1", "settlement_price_calculation_text": "Buffalo Bills win" } } ``` In this example, `settlement_px = 1000` with a `price_scale` of 1000 equals \$1.00 (YES won, Buffalo won the game). **Example: Settled Market (NO wins)** ```json theme={null} { "symbol": "aec-nfl-buf-kc-2026-01-26", "state": "INSTRUMENT_STATE_EXPIRED", "stats": { "settlement_px": 0, "settlement_preliminary": false, "settlement_price_calculation_method": "SETTLEMENT_PRICE_CALCULATION_METHOD_EVENT_TIER_1", "settlement_price_calculation_text": "Kansas City Chiefs win" } } ``` In this example, `settlement_px = 0` equals \$0.00 (NO won, Kansas City won so Buffalo did not win). ## Instrument States **Preferred approach:** Use `ListInstruments` to get and cache the initial state for each instrument, then subscribe to the instrument state change stream for ongoing state updates. The `state` field on `MarketDataUpdate` is now optional. ### Primary State Flow | State                                               | Value | Description | | --------------------------------------------------- | ----- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `INSTRUMENT_STATE_PENDING` | 8 | Initial state for a newly created instrument which has not yet begun trading. | | `INSTRUMENT_STATE_OPEN` | 1 | In this state, the instrument is open for continuous order entry and matching. | | `INSTRUMENT_STATE_CLOSED` | 0 | In this state, orders can not be entered, modified, or canceled, and no matching occurs. Any existing Day orders will be expired. | | `INSTRUMENT_STATE_EXPIRED` | 4 | An instrument moves to this state when its Expiration Date/Time is reached. In this state, any resting orders are expired and no new orders can be entered. | | `INSTRUMENT_STATE_TERMINATED` | 5 | When an instrument's Termination Date is reached, the order book is removed from the matching engine, orders are canceled, and positions are closed. Historical data will still remain in Polymarket US ledgers. | ### Exception States | State                                               | Value | Description | | --------------------------------------------------- | ----- | --------------------------------------------------------------------------------------------- | | `INSTRUMENT_STATE_SUSPENDED` | 3 | Orders can be canceled but no matching occurs, and no order entry or modification is allowed. | | `INSTRUMENT_STATE_HALTED` | 6 | This state is similar to SUSPENDED, with the exception that orders cannot be canceled. | ### Other Possible States | State                                               | Value | Description | | --------------------------------------------------- | ----- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `INSTRUMENT_STATE_PREOPEN` | 2 | Orders can be entered and modified, but no matching occurs. When the instrument transitions to an OPEN state, the orders entered during PREOPEN will match at a single opening price that is automatically determined by an algorithm that is designed to maximize the volume traded at the open. | | `INSTRUMENT_STATE_MATCH_AND_CLOSE_AUCTION` | 7 | This state is similar to PREOPEN, with the exception that matching will occur upon the transition of this state to any other state. This state is useful if you want matching to occur at the end of the state, but you don't want the instrument to be open after. | ```python theme={null} from polymarket.v1 import refdata_pb2 # Get state name state_name = refdata_pb2.InstrumentState.Name(update.state) print(f"State: {state_name}") ``` ## Complete Example (from stream.py) This example matches the implementation from the Python examples repository: ```python theme={null} import grpc import requests from datetime import datetime, timedelta from typing import Optional from polymarket.v1 import marketdatasubscription_pb2 from polymarket.v1 import marketdatasubscription_pb2_grpc from polymarket.v1 import refdata_pb2 class PolymarketStreamer: def __init__(self, base_url: str = "https://rest.preprod.polymarketexchange.com", grpc_server: str = "grpc-api.preprod.polymarketexchange.com:443"): self.base_url = base_url self.grpc_server = grpc_server self.access_token: Optional[str] = None self.refresh_token: Optional[str] = None self.access_expiration: Optional[datetime] = None self.price_scales: dict = {} # symbol -> price_scale cache def get_price_scale(self, symbol: str) -> int: """Get price_scale for symbol from cache. Populate via list_instruments API.""" # WARNING: Replace this with actual API lookup. Do not rely on default. return self.price_scales.get(symbol, 1000) def login(self, auth0_domain: str, client_id: str, private_key_path: str, audience: str) -> dict: """Authenticate using Private Key JWT and store the access token.""" import jwt import uuid from cryptography.hazmat.primitives import serialization # Load private key with open(private_key_path, 'rb') as f: private_key = serialization.load_pem_private_key(f.read(), password=None) # Create JWT assertion now = int(datetime.now().timestamp()) claims = { "iss": client_id, "sub": client_id, "aud": f"https://{auth0_domain}/oauth/token", "iat": now, "exp": now + 300, "jti": str(uuid.uuid4()), } assertion = jwt.encode(claims, private_key, algorithm="RS256") # Exchange for access token response = requests.post( f"https://{auth0_domain}/oauth/token", json={ "client_id": client_id, "client_assertion_type": "urn:ietf:params:oauth:client-assertion-type:jwt-bearer", "client_assertion": assertion, "audience": audience, "grant_type": "client_credentials" } ) response.raise_for_status() token_data = response.json() self.access_token = token_data["access_token"] # Set expiration with 30-second buffer (tokens expire in 180 seconds) expires_in = token_data.get("expires_in", 180) self.access_expiration = datetime.now() + timedelta(seconds=expires_in - 30) return token_data def stream_market_data(self, symbols: list, unaggregated: bool = False, depth: int = 10, snapshot_only: bool = False): """Stream market data for the given symbols using gRPC.""" if not self.access_token: raise ValueError("Not authenticated. Please login first.") # Create credentials credentials = grpc.ssl_channel_credentials() # Create channel channel = grpc.secure_channel(self.grpc_server, credentials) # Create stub stub = marketdatasubscription_pb2_grpc.MarketDataSubscriptionAPIStub(channel) # Create request request = marketdatasubscription_pb2.CreateMarketDataSubscriptionRequest( symbols=symbols, unaggregated=unaggregated, depth=depth, snapshot_only=snapshot_only ) # Set up metadata with authorization metadata = [ ('authorization', f'Bearer {self.access_token}') ] try: print(f"Starting market data stream for symbols: {symbols}") print(f"Parameters: unaggregated={unaggregated}, depth={depth}, snapshot_only={snapshot_only}") print("-" * 60) # Start streaming response_stream = stub.CreateMarketDataSubscription(request, metadata=metadata) for response in response_stream: self._process_market_data_response(response) except grpc.RpcError as e: print(f"gRPC error: {e.code()} - {e.details()}") raise except KeyboardInterrupt: print("\nStream interrupted by user") finally: channel.close() def _process_market_data_response(self, response): """Process and display market data response.""" if response.HasField('heartbeat'): print(f"[{datetime.now().strftime('%H:%M:%S')}] Heartbeat received") elif response.HasField('update'): update = response.update print(f"\n[{datetime.now().strftime('%H:%M:%S')}] Market Update for {update.symbol}") # Display instrument state state_name = refdata_pb2.InstrumentState.Name(update.state) print(f" State: {state_name}") # Get price_scale from instrument metadata (via list_instruments API) price_scale = self.get_price_scale(update.symbol) # Display order book if update.bids: print(" Bids:") for i, bid in enumerate(update.bids[:5]): # Show top 5 bids px = bid.px / price_scale # Convert from price representation qty = bid.qty print(f" [{i+1}] ${px:.4f} x {qty}") if update.offers: print(" Offers:") for i, offer in enumerate(update.offers[:5]): # Show top 5 offers px = offer.px / price_scale # Convert from price representation qty = offer.qty print(f" [{i+1}] ${px:.4f} x {qty}") # Display stats if available if update.HasField('stats'): stats = update.stats print(" Stats:") if stats.HasField('last_trade_px'): last_px = stats.last_trade_px / price_scale print(f" Last Trade: ${last_px:.4f}") if stats.HasField('open_px'): open_px = stats.open_px / price_scale print(f" Open: ${open_px:.4f}") if stats.HasField('high_px'): high_px = stats.high_px / price_scale print(f" High: ${high_px:.4f}") if stats.HasField('low_px'): low_px = stats.low_px / price_scale print(f" Low: ${low_px:.4f}") if stats.HasField('shares_traded'): print(f" Shares Traded: {stats.shares_traded}") if stats.HasField('open_interest'): print(f" Open Interest: {stats.open_interest}") print("-" * 60) # Usage if __name__ == "__main__": streamer = PolymarketStreamer() # Login using Private Key JWT streamer.login( auth0_domain="pmx-preprod.us.auth0.com", client_id="your_client_id", private_key_path="private_key.pem", audience="https://api.preprod.polymarketexchange.com" ) # Stream market data streamer.stream_market_data( symbols=["tec-nfl-sbw-2026-02-08-kc"], depth=10 ) ``` *** ## Bidirectional Streaming The `BiDirectionalStreamMarketData` RPC allows you to dynamically add and remove symbols during the subscription lifetime without reconnecting. ### Request Messages Send `BiDirectionalStreamMarketDataRequest` messages to manage your subscription: ```protobuf theme={null} message BiDirectionalStreamMarketDataRequest { oneof command { SubscribeCommand subscribe = 1; // Add symbols UnsubscribeCommand unsubscribe = 2; // Remove symbols KeepAliveCommand keepalive = 7; // Application-level keepalive (no-op) } bool unaggregated = 3; // Options (read from first request only) int32 depth = 4; bool snapshot_only = 5; bool slow_consumer_skip_to_head = 6; } message SubscribeCommand { repeated string symbols = 1; } message UnsubscribeCommand { repeated string symbols = 1; } // Application-level keepalive for BiDirectionalStreamMarketData. Sending one // puts a client-to-server frame on the wire without modifying subscription // state; the server returns no response. message KeepAliveCommand {} ``` ### Commands | Command | Field | Description | | ------------- | ----- | --------------------------------------------------------------------------------------------------------------------------------------------------- | | `subscribe` | 1 | Add the listed symbols to the active subscription. | | `unsubscribe` | 2 | Remove the listed symbols from the active subscription. | | `keepalive` | 7 | Application-level keepalive. Server handles it as a no-op (no response, no state change). Safe to send any time after the first `SubscribeCommand`. | **ALB Idle Timeout & Keepalives** Long-lived bidirectional streams that send no client-to-server traffic for \~1 hour will be terminated with `RST_STREAM` by the AWS Application Load Balancer (idle timeout is **3600 seconds** by default). To keep an otherwise-quiet stream alive, send a `KeepAliveCommand` every **30–60 minutes**. The server treats it as a no-op: no response, no change to subscription state or symbol list. This only applies to `BiDirectionalStreamMarketData`. Server-streaming RPCs like `CreateMarketDataSubscription` are not affected. ### Response Messages The stream returns `BiDirectionalStreamMarketDataResponse` messages with four possible event types: | Event Type | Description | | -------------------- | --------------------------------------------- | | `heartbeat` | Keep-alive message | | `update` | Market data update (same as server-streaming) | | `subscription_ack` | Acknowledgment of subscribe/unsubscribe | | `subscription_error` | Error for subscription operations | ```protobuf theme={null} message BiDirectionalStreamMarketDataResponse { oneof event { Heartbeat heartbeat = 1; MarketDataUpdate update = 2; SubscriptionAck subscription_ack = 3; SubscriptionError subscription_error = 4; } } message SubscriptionAck { repeated string symbols_added = 1; // Symbols added in this operation repeated string symbols_removed = 2; // Symbols removed in this operation repeated string active_symbols = 3; // All currently active symbols } message SubscriptionError { string error_code = 1; // e.g., "INVALID_SYMBOL", "ALREADY_SUBSCRIBED" string message = 2; // Human-readable error message repeated string symbols = 3; // Symbols that caused the error } ``` ### Error Codes | Code | Description | | -------------------- | ----------------------------------------------------------- | | `INVALID_SYMBOL` | Symbol does not exist or is not valid | | `ALREADY_SUBSCRIBED` | Already subscribed to the symbol | | `NOT_SUBSCRIBED` | Trying to unsubscribe from a symbol not in the subscription | ### Python Example ```python theme={null} import grpc import threading import queue from polymarket.v1 import marketdatasubscription_pb2 from polymarket.v1 import marketdatasubscription_pb2_grpc def request_generator(request_queue): """Generator that yields requests from a queue.""" while True: try: request = request_queue.get(timeout=0.1) if request is None: break yield request except queue.Empty: continue def stream_bidi_market_data(stub, access_token): """Demonstrate bidirectional market data streaming.""" request_queue = queue.Queue() # Set up metadata with authorization metadata = [('authorization', f'Bearer {access_token}')] # Start bidirectional stream response_stream = stub.BiDirectionalStreamMarketData( request_generator(request_queue), metadata=metadata ) # Subscribe to initial symbol request_queue.put(marketdatasubscription_pb2.BiDirectionalStreamMarketDataRequest( subscribe=marketdatasubscription_pb2.SubscribeCommand( symbols=["tec-nfl-sbw-2026-02-08-kc"] ), depth=10 )) # Process responses for response in response_stream: if response.HasField('subscription_ack'): ack = response.subscription_ack print(f"Subscription ACK - Active: {list(ack.active_symbols)}") elif response.HasField('subscription_error'): err = response.subscription_error print(f"Error: {err.error_code} - {err.message}") elif response.HasField('update'): update = response.update print(f"Update: {update.symbol} - {len(update.bids)} bids") elif response.HasField('heartbeat'): print("Heartbeat") # Dynamically add another symbol after receiving first update # request_queue.put(marketdatasubscription_pb2.BiDirectionalStreamMarketDataRequest( # subscribe=marketdatasubscription_pb2.SubscribeCommand( # symbols=["tec-nfl-sbw-2026-02-08-phi"] # ) # )) def keepalive_worker(request_queue, interval_seconds: int = 1800): """Periodically push a KeepAliveCommand onto the request queue (every 30 minutes by default) so the connection stays inside the AWS ALB idle window.""" import time while True: time.sleep(interval_seconds) request_queue.put(marketdatasubscription_pb2.BiDirectionalStreamMarketDataRequest( keepalive=marketdatasubscription_pb2.KeepAliveCommand() )) ``` ### Go Example ```go theme={null} import ( "context" polymarketv1 "github.com/polymarket/client-sample-code/go/gen/polymarket/v1" ) func streamBidiMarketData(client polymarketv1.MarketDataSubscriptionAPIClient) error { ctx := context.Background() stream, err := client.BiDirectionalStreamMarketData(ctx) if err != nil { return err } // Subscribe to initial symbol err = stream.Send(&polymarketv1.BiDirectionalStreamMarketDataRequest{ Command: &polymarketv1.BiDirectionalStreamMarketDataRequest_Subscribe{ Subscribe: &polymarketv1.SubscribeCommand{ Symbols: []string{"tec-nfl-sbw-2026-02-08-kc"}, }, }, Depth: 10, }) if err != nil { return err } // Process responses for { resp, err := stream.Recv() if err != nil { return err } switch { case resp.GetSubscriptionAck() != nil: ack := resp.GetSubscriptionAck() fmt.Printf("ACK - Active: %v\n", ack.ActiveSymbols) case resp.GetSubscriptionError() != nil: subErr := resp.GetSubscriptionError() fmt.Printf("Error: %s - %s\n", subErr.ErrorCode, subErr.Message) case resp.GetUpdate() != nil: update := resp.GetUpdate() fmt.Printf("Update: %s - %d bids\n", update.Symbol, len(update.Bids)) // Dynamically add another symbol // stream.Send(&polymarketv1.BiDirectionalStreamMarketDataRequest{ // Command: &polymarketv1.BiDirectionalStreamMarketDataRequest_Subscribe{ // Subscribe: &polymarketv1.SubscribeCommand{ // Symbols: []string{"tec-nfl-sbw-2026-02-08-phi"}, // }, // }, // }) case resp.GetHeartbeat() != nil: fmt.Println("Heartbeat") } } } // Run alongside the receive loop above to keep the stream alive when the // client is not actively (un)subscribing. Send every 30 minutes (1800s) so // the connection stays inside the AWS ALB 3600s idle window. func sendKeepalives(stream polymarketv1.MarketDataSubscriptionAPI_BiDirectionalStreamMarketDataClient) { ticker := time.NewTicker(30 * time.Minute) defer ticker.Stop() for range ticker.C { _ = stream.Send(&polymarketv1.BiDirectionalStreamMarketDataRequest{ Command: &polymarketv1.BiDirectionalStreamMarketDataRequest_Keepalive{ Keepalive: &polymarketv1.KeepAliveCommand{}, }, }) } } ``` *** ## Next Steps Subscribe to order execution updates Detailed protocol buffer reference Handle errors and reconnections # Order Execution Streaming Source: https://docs.polymarket.us/streaming-endpoints/order-stream Real-time order and execution updates via gRPC with Python Subscribe to real-time order updates, execution reports, fills, and cancellations for your trading activity using Python and gRPC. ## Service Definition **Service:** `polymarket.v1.OrderEntryAPI` **RPC:** `CreateOrderSubscription` **Type:** Server-side streaming ```protobuf theme={null} service OrderEntryAPI { rpc CreateOrderSubscription(CreateOrderSubscriptionRequest) returns (stream CreateOrderSubscriptionResponse); } ``` ## Request Parameters ### CreateOrderSubscriptionRequest | Field | Type | Required | Description | | --------------- | ----------- | -------- | ------------------------------------------------------------------------------------------------------------------ | | `symbols` | `list[str]` | No | Filter by symbols. Empty list = all symbols. | | `accounts` | `list[str]` | No | Filter by trading accounts. Empty list = all accounts for authenticated user. | | `snapshot_only` | `bool` | No | If `True`, receive snapshot of current orders then close stream. If `False` (default), receive continuous updates. | ### Example Request ```python theme={null} from polymarket.v1 import trading_pb2 # Subscribe to all orders for your accounts request = trading_pb2.CreateOrderSubscriptionRequest( symbols=[], accounts=[], snapshot_only=False ) # Subscribe to specific symbol request = trading_pb2.CreateOrderSubscriptionRequest( symbols=["tec-nfl-sbw-2026-02-08-kc"], accounts=[], snapshot_only=False ) # Get snapshot only (current state) request = trading_pb2.CreateOrderSubscriptionRequest( symbols=[], accounts=[], snapshot_only=True ) ``` ## Response Messages The stream returns `CreateOrderSubscriptionResponse` messages with multiple event types: ### Response Fields | Field | Type | Description | | --------------------- | ----------- | -------------------------------------------- | | `event` | `oneof` | One of: `heartbeat`, `snapshot`, or `update` | | `session_id` | `str` | Unique session identifier for this stream | | `processed_sent_time` | `Timestamp` | Server timestamp when response was sent | ### 1. Heartbeat Messages Keep-alive messages to confirm connection health. ```python theme={null} if response.HasField('heartbeat'): print(f"[{datetime.now().strftime('%H:%M:%S')}] Heartbeat received") ``` ### 2. Snapshot Messages Initial state of all matching orders when subscription starts. ```python theme={null} from polymarket.v1 import enums_pb2 if response.HasField('snapshot'): snapshot = response.snapshot print(f"Snapshot received: {len(snapshot.orders)} orders") for order in snapshot.orders: print(f" Order ID: {order.id}") print(f" Symbol: {order.symbol}") print(f" Side: {enums_pb2.Side.Name(order.side)}") print(f" State: {enums_pb2.OrderState.Name(order.state)}") ``` ### 3. Update Messages Real-time order updates and executions. ```python theme={null} if response.HasField('update'): update = response.update # Process executions for execution in update.executions: print(f"Execution: {enums_pb2.ExecutionType.Name(execution.type)}") # Process cancel rejects if update.HasField('cancel_reject'): print(f"Cancel rejected: {enums_pb2.CxlRejReason.Name(update.cancel_reject.reject_reason)}") ``` ## Order Message Structure ### Core Order Fields | Field | Type | Description | | --------------------------- | --------------- | --------------------------------------------------------------------------------------- | | `id` | `str` | Exchange-assigned order ID | | `clord_id` | `str` | Client-assigned order ID (from your order request) | | `symbol` | `str` | Trading symbol | | `side` | `Side` | `SIDE_BUY` or `SIDE_SELL` | | `type` | `OrderType` | Order type (LIMIT, MARKET\_TO\_LIMIT, etc.) | | `state` | `OrderState` | Current order state | | `account` | `str` | Trading account | | `order_qty` | `int` | Original order quantity | | `price` | `int` | **Order price** (÷ price\_scale for decimal) | | `cum_qty` | `int` | Cumulative filled quantity | | `leaves_qty` | `int` | Remaining unfilled quantity | | `avg_px` | `int` | **Average fill price** (÷ price\_scale) | | `fractional_quantity_scale` | `int` | Quantity scale copied from the instrument at order creation (÷ to get decimal quantity) | | `price_to_quantity_filled` | `map` | Quantity filled at each price point; key = price, value = filled qty | | `insert_time` | `Timestamp` | When order was accepted | | `create_time` | `Timestamp` | When order was created | **Price Fields:** * `price`, `avg_px`, `stop_price` are `int64` values * Divide by the instrument's `price_scale` to get decimal prices * `price_scale` is available from instrument metadata via the RefDataAPI ```python theme={null} price = order.price / price_scale print(f"Price: ${price:.4f}") ``` ### Order States | State | Value | Description | | ------------------------------ | ----- | --------------------------------------------- | | `ORDER_STATE_NEW` | 0 | Order accepted, resting in book | | `ORDER_STATE_PARTIALLY_FILLED` | 1 | Order partially executed | | `ORDER_STATE_FILLED` | 2 | **Order completely filled** | | `ORDER_STATE_CANCELED` | 3 | Order canceled (leaves\_qty = 0) | | `ORDER_STATE_REPLACED` | 4 | Order modified/replaced | | `ORDER_STATE_REJECTED` | 5 | Order rejected by exchange | | `ORDER_STATE_EXPIRED` | 6 | Order expired (e.g., Day order at end of day) | | `ORDER_STATE_PENDING_NEW` | 7 | Order pending acceptance | | `ORDER_STATE_PENDING_REPLACE` | 8 | Replace request pending | | `ORDER_STATE_PENDING_CANCEL` | 9 | Cancel request pending | ```python theme={null} from polymarket.v1 import enums_pb2 # Get state name state_name = enums_pb2.OrderState.Name(order.state) print(f"State: {state_name}") ``` ### Order Sides | Side | Value | | ----------- | ----- | | `SIDE_BUY` | 1 | | `SIDE_SELL` | 2 | ```python theme={null} side_name = enums_pb2.Side.Name(order.side) print(f"Side: {side_name}") ``` ### Order Types | Type | Value | Description | | ---------------------------- | ----- | ----------------------------------- | | `ORDER_TYPE_LIMIT` | 2 | Limit order with specified price | | `ORDER_TYPE_MARKET_TO_LIMIT` | 1 | Market order that converts to limit | | `ORDER_TYPE_STOP` | 3 | Stop order | | `ORDER_TYPE_STOP_LIMIT` | 4 | Stop-limit order | ## Execution Message Structure Executions represent order lifecycle events (new, fill, cancel, reject). ### Execution Fields | Field | Type | Description | | --------------------- | ----------------- | --------------------------------------------- | | `id` | `str` | Unique execution ID | | `type` | `ExecutionType` | Type of execution event | | `order` | `Order` | Current order state after this execution | | `last_shares` | `int` | Quantity filled in this execution (for fills) | | `last_px` | `int` | **Price of this fill** (÷ price\_scale) | | `trade_id` | `str` | Trade ID (for fills) | | `aggressor` | `bool` | True if you were the aggressor in the trade | | `transact_time` | `Timestamp` | When execution occurred | | `text` | `str` | Free-form text (e.g., reject reason) | | `order_reject_reason` | `OrdRejectReason` | Rejection reason (if rejected) | ### Execution Types | Type | Value | Description | | ----------------------------- | ----- | ---------------------------------------- | | `EXECUTION_TYPE_NEW` | 0 | Order accepted (confirmed) | | `EXECUTION_TYPE_PARTIAL_FILL` | 1 | Partial fill occurred | | `EXECUTION_TYPE_FILL` | 2 | **Complete fill** (order fully executed) | | `EXECUTION_TYPE_CANCELED` | 3 | Order canceled | | `EXECUTION_TYPE_REPLACE` | 4 | Order modified | | `EXECUTION_TYPE_REJECTED` | 5 | Order rejected | | `EXECUTION_TYPE_EXPIRED` | 6 | Order expired | | `EXECUTION_TYPE_DONE_FOR_DAY` | 7 | Order done for day | ### Order Reject Reasons | Reason | Value | Description | | ------------------------------------------- | ----- | ----------------------- | | `ORD_REJECT_REASON_EXCHANGE_OPTION` | 0 | Exchange option | | `ORD_REJECT_REASON_UNKNOWN_SYMBOL` | 1 | Symbol not found | | `ORD_REJECT_REASON_EXCHANGE_CLOSED` | 2 | Market is closed | | `ORD_REJECT_REASON_INCORRECT_QUANTITY` | 3 | Invalid quantity | | `ORD_REJECT_REASON_INVALID_PRICE_INCREMENT` | 4 | Invalid price increment | | `ORD_REJECT_REASON_INCORRECT_ORDER_TYPE` | 5 | Incorrect order type | | `ORD_REJECT_REASON_PRICE_OUT_OF_BOUNDS` | 6 | Price out of bounds | | `ORD_REJECT_REASON_NO_LIQUIDITY` | 7 | No liquidity available | ## Complete Example (from order\_stream.py) This example matches the implementation from the Python examples repository: ```python theme={null} import grpc import requests from datetime import datetime, timedelta from typing import Optional from polymarket.v1 import trading_pb2 from polymarket.v1 import trading_pb2_grpc from polymarket.v1 import enums_pb2 class PolymarketOrderStreamer: def __init__(self, base_url: str = "https://rest.preprod.polymarketexchange.com", grpc_server: str = "grpc-api.preprod.polymarketexchange.com:443"): self.base_url = base_url self.grpc_server = grpc_server self.access_token: Optional[str] = None self.refresh_token: Optional[str] = None self.access_expiration: Optional[datetime] = None self.session_id: Optional[str] = None self.price_scale: int = 1000 # Get from instrument metadata def login(self, auth0_domain: str, client_id: str, private_key_path: str, audience: str) -> dict: """Authenticate using Private Key JWT and store the access token.""" import jwt import uuid from cryptography.hazmat.primitives import serialization # Load private key with open(private_key_path, 'rb') as f: private_key = serialization.load_pem_private_key(f.read(), password=None) # Create JWT assertion now = int(datetime.now().timestamp()) claims = { "iss": client_id, "sub": client_id, "aud": f"https://{auth0_domain}/oauth/token", "iat": now, "exp": now + 300, "jti": str(uuid.uuid4()), } assertion = jwt.encode(claims, private_key, algorithm="RS256") # Exchange for access token response = requests.post( f"https://{auth0_domain}/oauth/token", json={ "client_id": client_id, "client_assertion_type": "urn:ietf:params:oauth:client-assertion-type:jwt-bearer", "client_assertion": assertion, "audience": audience, "grant_type": "client_credentials" } ) response.raise_for_status() token_data = response.json() self.access_token = token_data["access_token"] # Set expiration with 30-second buffer (tokens expire in 180 seconds) expires_in = token_data.get("expires_in", 180) self.access_expiration = datetime.now() + timedelta(seconds=expires_in - 30) return token_data def stream_orders(self, symbols: list = None, accounts: list = None, snapshot_only: bool = False): """Stream order updates using gRPC.""" if not self.access_token: raise ValueError("Not authenticated. Please login first.") # Create credentials credentials = grpc.ssl_channel_credentials() # Create channel channel = grpc.secure_channel(self.grpc_server, credentials) # Create stub stub = trading_pb2_grpc.OrderEntryAPIStub(channel) # Create request request = trading_pb2.CreateOrderSubscriptionRequest( symbols=symbols or [], accounts=accounts or [], snapshot_only=snapshot_only ) # Set up metadata with authorization metadata = [ ('authorization', f'Bearer {self.access_token}') ] try: print(f"Starting order stream") print(f"Symbols: {symbols or 'ALL'}") print(f"Accounts: {accounts or 'ALL'}") print(f"Snapshot only: {snapshot_only}") print("-" * 60) # Start streaming response_stream = stub.CreateOrderSubscription(request, metadata=metadata) for response in response_stream: self._process_order_response(response) except grpc.RpcError as e: print(f"gRPC error: {e.code()} - {e.details()}") raise except KeyboardInterrupt: print("\nStream interrupted by user") finally: channel.close() def _process_order_response(self, response): """Process and display order response.""" # Capture session ID on first message if response.session_id and not self.session_id: self.session_id = response.session_id print(f"\n[{datetime.now().strftime('%H:%M:%S')}] Session established") print(f" Session ID: {self.session_id}") print("-" * 60) if response.HasField('heartbeat'): print(f"[{datetime.now().strftime('%H:%M:%S')}] Heartbeat received") elif response.HasField('snapshot'): snapshot = response.snapshot print(f"\n[{datetime.now().strftime('%H:%M:%S')}] Order Snapshot") print(f" Total orders: {len(snapshot.orders)}") for order in snapshot.orders: self._display_order(order) print("-" * 60) elif response.HasField('update'): update = response.update print(f"\n[{datetime.now().strftime('%H:%M:%S')}] Order Update") # Display executions if update.executions: print(f" Executions: {len(update.executions)}") for execution in update.executions: self._display_execution(execution) # Display cancel rejects if update.HasField('cancel_reject'): self._display_cancel_reject(update.cancel_reject) print("-" * 60) def _display_order(self, order): """Display order details.""" print(f" Order ID: {order.id}") print(f" Client Order ID: {order.clord_id}") print(f" Symbol: {order.symbol}") print(f" Side: {enums_pb2.Side.Name(order.side)}") print(f" Type: {enums_pb2.OrderType.Name(order.type)}") print(f" State: {enums_pb2.OrderState.Name(order.state)}") if order.price > 0: price = order.price / self.price_scale print(f" Price: ${price:.4f}") print(f" Order Qty: {order.order_qty}") print(f" Filled Qty: {order.cum_qty}") print(f" Remaining Qty: {order.leaves_qty}") if order.avg_px > 0: avg_px = order.avg_px / self.price_scale print(f" Avg Price: ${avg_px:.4f}") if order.account: print(f" Account: {order.account}") print() def _display_execution(self, execution): """Display execution details.""" print(f" Execution ID: {execution.id}") print(f" Type: {enums_pb2.ExecutionType.Name(execution.type)}") if execution.HasField('order'): order = execution.order print(f" Order ID: {order.id}") print(f" Symbol: {order.symbol}") print(f" Side: {enums_pb2.Side.Name(order.side)}") print(f" State: {enums_pb2.OrderState.Name(order.state)}") if execution.last_shares > 0: print(f" Last Shares: {execution.last_shares}") if execution.last_px > 0: last_px = execution.last_px / self.price_scale print(f" Last Price: ${last_px:.4f}") if execution.trade_id: print(f" Trade ID: {execution.trade_id}") if execution.text: print(f" Text: {execution.text}") if execution.order_reject_reason != enums_pb2.ORD_REJECT_REASON_EXCHANGE_OPTION: print(f" Reject Reason: {enums_pb2.OrdRejectReason.Name(execution.order_reject_reason)}") print() def _display_cancel_reject(self, cancel_reject): """Display cancel reject details.""" print(f" Cancel Reject:") print(f" Order ID: {cancel_reject.order_id}") print(f" Client Order ID: {cancel_reject.clord_id}") print(f" Reject Reason: {enums_pb2.CxlRejReason.Name(cancel_reject.reject_reason)}") if cancel_reject.text: print(f" Text: {cancel_reject.text}") print() # Usage if __name__ == "__main__": streamer = PolymarketOrderStreamer() # Login using Private Key JWT streamer.login( auth0_domain="pmx-preprod.us.auth0.com", client_id="your_client_id", private_key_path="private_key.pem", audience="https://api.preprod.polymarketexchange.com" ) # Stream orders streamer.stream_orders( symbols=["tec-nfl-sbw-2026-02-08-kc"], accounts=[] ) ``` ### Sample Output ``` Starting order stream Symbols: ['tec-nfl-sbw-2026-02-08-kc'] Accounts: ALL Snapshot only: False ------------------------------------------------------------ [14:30:15] Session established Session ID: session_abc123 ------------------------------------------------------------ [14:30:15] Order Snapshot Total orders: 3 Order ID: order_12345 Client Order ID: clord_abc Symbol: tec-nfl-sbw-2026-02-08-kc Side: SIDE_BUY Type: ORDER_TYPE_LIMIT State: ORDER_STATE_NEW Price: $0.525 Order Qty: 1000 Filled Qty: 0 Remaining Qty: 1000 ------------------------------------------------------------ [14:30:45] Heartbeat received [14:31:02] Order Update Executions: 1 Execution ID: exec_67890 Type: EXECUTION_TYPE_PARTIAL_FILL Order ID: order_12345 Symbol: tec-nfl-sbw-2026-02-08-kc Side: SIDE_BUY State: ORDER_STATE_PARTIALLY_FILLED Last Shares: 300 Last Price: $0.525 Trade ID: trade_xyz ------------------------------------------------------------ ``` ## Order Lifecycle ### Typical Order Flow ``` 1. NEW -> Order accepted, resting in book 2. PARTIAL_FILL -> First partial fill 3. PARTIAL_FILL -> Additional partial fills (if any) 4. FILL -> Final fill, order complete ``` ### Cancel Flow ``` 1. NEW -> Order resting 2. PENDING_CANCEL -> Cancel request received 3. CANCELED -> Cancel confirmed ``` ### Reject Flow ``` 1. REJECTED -> Order rejected immediately ``` ## Next Steps Detailed message and field reference Handle errors and reconnections Stream real-time market data # Protocol Buffer Reference Source: https://docs.polymarket.us/streaming-endpoints/proto-reference Complete reference for gRPC message definitions and Python code generation Complete reference documentation for all Protocol Buffer messages, fields, and enumerations used in the gRPC streaming API. ## Available Services The Polymarket Exchange API exposes the following gRPC services: | Service | Description | | ----------------------------------------- | ------------------------------------------------ | | `polymarket.v1.MarketDataSubscriptionAPI` | Real-time market data streaming | | `polymarket.v1.OrderEntryAPI` | Order submission and streaming | | `polymarket.v1.ComboAPI` | Combo instrument creation and exact-symbol reads | | `polymarket.v1.RFQAPI` | Combo RFQs, quotes, and RFQ event streaming | | `polymarket.v1.OrderAPI` | Order search and history | | `polymarket.v1.PositionAPI` | Position and balance queries | | `polymarket.v1.AccountsAPI` | Account information | | `polymarket.v1.MarketDataAPI` | Instrument and symbol data | | `polymarket.v1.DropCopyAPI` | Execution feed | | `polymarket.v1.KYCAPI` | KYC verification | | `polymarket.v1.AeropayAPI` | ACH payments | | `polymarket.v1.CheckoutAPI` | Card payments | | `polymarket.v1.FundingAPI` | Funding management | | `polymarket.v1.HealthAPI` | Health check | *** ## Obtaining Proto Files Get the complete Protocol Buffer definitions to generate client libraries in any language Use the downloaded definitions as the client contract. Do not make client startup depend on gRPC reflection, whose availability can differ by environment. *** ## Generating Python Client Code After downloading the proto files, generate Python code: ```bash theme={null} unzip polymarket-protos.zip -d protos python -m grpc_tools.protoc --python_out=. --grpc_python_out=. --proto_path=protos/api protos/api/polymarket/v1/*.proto protos/api/google/api/*.proto protos/api/protoc-gen-openapiv2/options/*.proto ``` This generates: * `*_pb2.py` - Message and enum definitions * `*_pb2_grpc.py` - Service stubs *** ## Market Data Streaming ### MarketDataSubscriptionAPI Service ```protobuf theme={null} service MarketDataSubscriptionAPI { rpc CreateMarketDataSubscription(CreateMarketDataSubscriptionRequest) returns (stream CreateMarketDataSubscriptionResponse); } ``` ### Python Usage ```python theme={null} from polymarket.v1 import marketdatasubscription_pb2 from polymarket.v1 import marketdatasubscription_pb2_grpc # Create request request = marketdatasubscription_pb2.CreateMarketDataSubscriptionRequest( symbols=["SYMBOL-123"], depth=10 ) # Use stub stub = marketdatasubscription_pb2_grpc.MarketDataSubscriptionAPIStub(channel) response_stream = stub.CreateMarketDataSubscription(request, metadata=metadata) ``` ### Request Fields | Field | Type | Description | | --------------- | ----------- | ------------------------------------------------------- | | `symbols` | `list[str]` | Symbols to subscribe to. Empty = all symbols. | | `unaggregated` | `bool` | If true, receive raw orders. If false, aggregated book. | | `depth` | `int` | Number of price levels. Default: 10 | | `snapshot_only` | `bool` | If true, receive snapshot then close. | ### Response Fields ```python theme={null} if response.HasField('heartbeat'): # Keep-alive message pass elif response.HasField('update'): update = response.update print(f"Symbol: {update.symbol}") print(f"Bids: {len(update.bids)}") print(f"Offers: {len(update.offers)}") ``` | Field | Type | Description | | -------- | ----------------- | ----------------------------------- | | `symbol` | `str` | Instrument symbol | | `bids` | `list[BookEntry]` | Bid side of order book | | `offers` | `list[BookEntry]` | Offer/ask side of order book | | `state` | `InstrumentState` | Current instrument state (optional) | | `stats` | `InstrumentStats` | Market statistics | **Instrument State Tracking:** The `state` field is optional. Use `ListInstruments` to get and cache the initial state, then subscribe to the instrument state change subscription for real-time state updates. *** ## Order Entry Streaming ### OrderEntryAPI Service ```protobuf theme={null} service OrderEntryAPI { rpc CreateOrderSubscription(CreateOrderSubscriptionRequest) returns (stream CreateOrderSubscriptionResponse); rpc InsertOrder(InsertOrderRequest) returns (InsertOrderResponse); rpc CancelOrder(CancelOrderRequest) returns (CancelOrderResponse); } ``` ### Python Usage ```python theme={null} from polymarket.v1 import trading_pb2 from polymarket.v1 import trading_pb2_grpc # Create order subscription request request = trading_pb2.CreateOrderSubscriptionRequest( symbols=["SYMBOL-123"], accounts=[], snapshot_only=False ) # Use stub stub = trading_pb2_grpc.OrderEntryAPIStub(channel) response_stream = stub.CreateOrderSubscription(request, metadata=metadata) ``` ### Subscription Request Fields | Field | Type | Description | | --------------- | ----------- | ------------------------------------------------ | | `symbols` | `list[str]` | Filter by symbols. Empty = all. | | `accounts` | `list[str]` | Filter by accounts. Empty = all user's accounts. | | `snapshot_only` | `bool` | If true, snapshot only. | ### Response Processing ```python theme={null} if response.HasField('heartbeat'): pass elif response.HasField('snapshot'): for order in response.snapshot.orders: print(f"Order: {order.id} - {order.symbol}") elif response.HasField('update'): for execution in response.update.executions: print(f"Execution: {execution.id}") ``` ## RFQ Events Streaming ### ComboAPI and RFQAPI Services ```protobuf theme={null} service ComboAPI { rpc CreateCombo(CreateComboRequest) returns (CreateComboResponse); rpc GetCombos(GetCombosRequest) returns (GetCombosResponse); } service RFQAPI { rpc GetRFQUserID(GetRFQUserIDRequest) returns (GetRFQUserIDResponse); rpc GetRFQs(GetRFQsRequest) returns (GetRFQsResponse); rpc CreateRFQ(CreateRFQRequest) returns (CreateRFQResponse); rpc DeleteRFQ(DeleteRFQRequest) returns (DeleteRFQResponse); rpc GetQuotes(GetQuotesRequest) returns (GetQuotesResponse); rpc CreateQuote(CreateQuoteRequest) returns (CreateQuoteResponse); rpc DeleteQuote(DeleteQuoteRequest) returns (DeleteQuoteResponse); rpc AcceptQuote(AcceptQuoteRequest) returns (AcceptQuoteResponse); rpc ConfirmQuote(ConfirmQuoteRequest) returns (ConfirmQuoteResponse); rpc StreamRFQEvents(StreamRFQEventsRequest) returns (stream StreamRFQEventsResponse); } ``` ### Request Fields `StreamRFQEventsRequest` is currently empty. ### RFQ Combo Legs `RFQ.combo_legs` preserves the component order and sides captured at RFQ creation: ```protobuf theme={null} message RFQComboLeg { string symbol = 1; Side side = 2; optional string settlement_price = 3; } ``` `settlement_price` is the raw YES/LONG settlement normalized to `[0,1]`. It is not inverted for SELL legs. Optional presence distinguishes an unavailable settlement from a present zero. ### Response Events Each `StreamRFQEventsResponse` has one `event` payload: | Event | Description | | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | | `rfq_created` | A new combo RFQ was created. | | `rfq_closed` | An RFQ closed because it was deleted or a quote was accepted. On acceptance, this public event may arrive before private `quote_accepted`. | | `quote_created` | A quote was created. | | `quote_deleted` | A quote was deleted. | | `quote_accepted` | One quote side was accepted and last look started. | | `quote_confirmed` | The maker confirmed and paired order submission was scheduled. | | `quote_executed` | Both exchange orders were accepted for submission. | For request/response field detail and a Python example, see [RFQ Events Streaming](/streaming-endpoints/rfq-events-stream). *** ## Funding Streaming ### FundingAPI Service ```protobuf theme={null} service FundingAPI { rpc CreateFundingTransactionSubscription(CreateFundingTransactionSubscriptionRequest) returns (stream CreateFundingTransactionSubscriptionResponse); rpc CreateBalanceLedgerSubscription(CreateBalanceLedgerSubscriptionRequest) returns (stream CreateBalanceLedgerSubscriptionResponse); } ``` | RPC | Description | Required Scope | | -------------------------------------- | ---------------------------------------------------------- | ---------------- | | `CreateFundingTransactionSubscription` | Real-time deposit / withdrawal state changes | `read:funding` | | `CreateBalanceLedgerSubscription` | Real-time balance ledger entries with `resume_time` replay | `read:positions` | ### CreateBalanceLedgerSubscriptionRequest | Field | Type | Description | | ------------- | ----------------------- | ---------------------------------------------------------------------------------------------- | | `account` | `str` | Required. Fully qualified account name. | | `currency` | `str` | Optional. ISO currency code (e.g., `USD`). | | `entry_types` | `list[LedgerEntryType]` | Optional. Filter by allowlisted entry types. | | `resume_time` | `Timestamp` | Optional. Replay entries with `update_time >= resume_time`. Clamped to `2026-05-01T00:00:00Z`. | For full request/response field detail and a Python example, see [Balance Ledger Streaming](/streaming-endpoints/balance-ledger-stream). *** ## Enumerations ### Side | Name | Value | | ----------- | ----- | | `SIDE_BUY` | 1 | | `SIDE_SELL` | 2 | ### OrderType | Name | Value | | ---------------------------- | ----- | | `ORDER_TYPE_MARKET_TO_LIMIT` | 1 | | `ORDER_TYPE_LIMIT` | 2 | | `ORDER_TYPE_STOP` | 3 | | `ORDER_TYPE_STOP_LIMIT` | 4 | ### TimeInForce | Name                                                 | Value | Description | | ---------------------------------------------------- | ----- | ------------------- | | `TIME_IN_FORCE_DAY` | 1 | Expires end of day | | `TIME_IN_FORCE_GTC` | 2 | Good-till-canceled | | `TIME_IN_FORCE_IOC` | 3 | Immediate-or-cancel | | `TIME_IN_FORCE_FOK` | 4 | Fill-or-kill | | `TIME_IN_FORCE_GTT` | 5 | Good-till-time | ### OrderState | Name                                                 | Value | Description | | ---------------------------------------------------- | ----- | -------------------- | | `ORDER_STATE_NEW` | 1 | Accepted and resting | | `ORDER_STATE_PARTIALLY_FILLED` | 2 | Partially executed | | `ORDER_STATE_FILLED` | 3 | Completely filled | | `ORDER_STATE_CANCELED` | 4 | Canceled | | `ORDER_STATE_REJECTED` | 7 | Rejected | | `ORDER_STATE_EXPIRED` | 9 | Expired | ### ExecutionType | Name                                                 | Value | Description | | ---------------------------------------------------- | ----- | ------------------ | | `EXECUTION_TYPE_NEW` | 1 | Order confirmation | | `EXECUTION_TYPE_PARTIAL_FILL` | 2 | Partial fill | | `EXECUTION_TYPE_FILL` | 3 | Complete fill | | `EXECUTION_TYPE_CANCELED` | 4 | Cancellation | | `EXECUTION_TYPE_REJECTED` | 7 | Rejection | | `EXECUTION_TYPE_TRADE` | 9 | Trade execution | | `EXECUTION_TYPE_EXPIRED` | 10 | Expiration | ### InstrumentState #### Primary State Flow | Name                                                 | Value | Description | | ---------------------------------------------------- | ----- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `INSTRUMENT_STATE_PENDING` | 8 | Initial state for a newly created instrument which has not yet begun trading. | | `INSTRUMENT_STATE_OPEN` | 1 | In this state, the instrument is open for continuous order entry and matching. | | `INSTRUMENT_STATE_CLOSED` | 0 | In this state, orders can not be entered, modified, or canceled, and no matching occurs. Any existing Day orders will be expired. | | `INSTRUMENT_STATE_EXPIRED` | 4 | An instrument moves to this state when its Expiration Date/Time is reached. In this state, any resting orders are expired and no new orders can be entered. | | `INSTRUMENT_STATE_TERMINATED` | 5 | When an instrument's Termination Date is reached, the order book is removed from the matching engine, orders are canceled, and positions are closed. Historical data will still remain in Polymarket US ledgers. | #### Exception States | Name                                                 | Value | Description | | ---------------------------------------------------- | ----- | --------------------------------------------------------------------------------------------- | | `INSTRUMENT_STATE_SUSPENDED` | 3 | Orders can be canceled but no matching occurs, and no order entry or modification is allowed. | | `INSTRUMENT_STATE_HALTED` | 6 | This state is similar to SUSPENDED, with the exception that orders cannot be canceled. | #### Other Possible States | Name                                                 | Value | Description | | ---------------------------------------------------- | ----- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `INSTRUMENT_STATE_PREOPEN` | 2 | Orders can be entered and modified, but no matching occurs. When the instrument transitions to an OPEN state, the orders entered during PREOPEN will match at a single opening price that is automatically determined by an algorithm that is designed to maximize the volume traded at the open. | | `INSTRUMENT_STATE_MATCH_AND_CLOSE_AUCTION` | 7 | This state is similar to PREOPEN, with the exception that matching will occur upon the transition of this state to any other state. This state is useful if you want matching to occur at the end of the state, but you don't want the instrument to be open after. | ### LedgerEntryType Used by `CreateBalanceLedgerSubscription` and the [Balance Ledger REST endpoints](/institutional/funding/overview). Only the **allowed** values are returned to clients; suppressed values are filtered server-side. #### Allowed | Name | Value | Description | | ----------------------------- | ----- | ------------------------------------- | | `DEPOSIT` | 1 | Funds deposited | | `WITHDRAWAL` | 2 | Funds withdrawn | | `ORDER_EXECUTION` | 3 | Cash impact of a trade execution | | `CORRECTION` | 4 | Manual correction | | `RESOLUTION` | 6 | Market resolution / settlement payout | | `MANUAL_ADJUSTMENT` | 7 | Admin adjustment | | `ACCOUNT_PROPERTY_ADJUSTMENT` | 10 | Account property change | | `COMMISSION` | 11 | Trading fee | | `WITHDRAWAL_REJECTION` | 16 | Failed withdrawal returned to balance | | `MANUAL_TRANSFER` | 17 | Internal transfer | | `PENDING_WITHDRAWAL_CREATION` | 22 | Withdrawal initiated (funds reserved) | #### Suppressed (internal — never returned to clients) | Name | Value | | ----------------------------- | ----- | | `NETTING` | 5 | | `SECURITY_BALANCE_ADJUSTMENT` | 8 | | `SECURITY_MARK_TO_MARKET` | 9 | | `CONTRACT_EXPIRATION` | 12 | | `PENDING_CREDIT_ADJUSTMENT` | 13 | | `BEGINNING_OF_DAY` | 14 | | `SECURITY_WITHDRAWAL` | 15 | | `AVERAGE_PRICE_TRANSFER` | 18 | | `GIVE_UP` | 19 | | `SYNCHRONIZATION` | 20 | | `INTEREST` | 21 | | `SETTLEMENT_FEE` | 23 | Requesting a suppressed value in `entry_types` returns `Aborted` (HTTP `409`). *** ## Price Representation **All prices are `int64` values.** Divide by the instrument's `price_scale` to get the decimal value. ```python theme={null} decimal_price = order.price / order.price_scale print(f"Price: ${decimal_price:.4f}") ``` *** ## Next Steps Learn about market data streaming Learn about order streaming Handle errors and reconnections # RFQ Events Streaming Source: https://docs.polymarket.us/streaming-endpoints/rfq-events-stream Live combo RFQ and quote events through RFQAPI Read the [Combos guide](/trader-guide/combos) before integrating. It explains quote construction, visibility, last look, and recovery. `StreamRFQEvents` is a gRPC server-side stream for live combo RFQ and quote changes. ## Service Definition * **Service:** `polymarket.v1.RFQAPI` * **RPC:** `StreamRFQEvents` * **Type:** Server-side streaming * **Required scope:** `read:orders` ```protobuf theme={null} service RFQAPI { rpc StreamRFQEvents(StreamRFQEventsRequest) returns (stream StreamRFQEventsResponse); } ``` The request is empty and exposes no filters: ```python theme={null} from polymarket.v1 import rfq_pb2 request = rfq_pb2.StreamRFQEventsRequest() ``` `StreamRFQEvents` is gRPC-only. REST and unary gRPC operations are documented in the [RFQ API Overview](/institutional/rfqs/overview). ## Rate Limit Each firm can open one new `StreamRFQEvents` connection per second, with one open attempt of burst capacity. The limit is checked only when the stream opens; it does not throttle server-pushed events on an established stream. Reconnect with backoff after a disconnect. ## Events Each response contains exactly one event payload. | Event | Payload | Visibility | Description | | ----------------- | --------------------- | ------------------------------------ | ------------------------------------------------------------- | | `rfq_created` | `RFQCreatedEvent` | Public | A new RFQ is open. | | `rfq_closed` | `RFQClosedEvent` | Public | An RFQ closed because it was deleted or a quote was accepted. | | `quote_created` | `QuoteCreatedEvent` | Requester and quote creator | A quote was created or replaced. | | `quote_deleted` | `QuoteDeletedEvent` | Requester and quote creator | A quote was deleted or declined. | | `quote_accepted` | `QuoteAcceptedEvent` | Requester and selected quote creator | The requester accepted one side and last look started. | | `quote_confirmed` | `QuoteConfirmedEvent` | Requester and selected quote creator | The maker confirmed and paired submission was scheduled. | | `quote_executed` | `QuoteExecutedEvent` | Requester and selected quote creator | Both exchange orders were accepted for submission. | Successful quote acceptance produces both events: public `rfq_closed` and participant-private `quote_accepted`. A client may receive `rfq_closed` first. Treat it as "stop quoting this RFQ," not "no quote was accepted." Keep existing quote state until the private quote event arrives, or reconcile it with `GetQuotes`. The current public stream does not emit expiration, done-away, pending-risk, pending-end-trade, action-rejected, or status-rejected events. ## Core Payloads ### RFQ | Field | Type | Description | | ------------------ | ---------------------- | ---------------------------------------------------------------- | | `id` | string | RFQ ID. | | `qtyDecimal` | optional string | Fixed contract quantity. Mutually exclusive with `cashOrderQty`. | | `cashOrderQty` | optional string | Cash notional. Mutually exclusive with `qtyDecimal`. | | `symbol` | string | Combo symbol. | | `rfqCreatorUserId` | string | Pseudonymous requester identity. | | `createdTime` | `Timestamp` | Creation time. | | `restRemainder` | bool | Whether an unfilled requester remainder may rest. | | `status` | `RFQStatus` | `RFQ_STATUS_OPEN` or `RFQ_STATUS_CLOSED`. | | `updatedTime` | `Timestamp` | Last durable state change. | | `comboLegs` | repeated `RFQComboLeg` | Ordered combo legs captured when the RFQ was created. | Each `RFQComboLeg` contains `symbol`, `side`, and an optional `settlementPrice`. The settlement is the raw YES/LONG price normalized to `[0,1]`; it is never inverted for a `SIDE_SELL` leg. Presence matters: `"0"` is a valid settled price, while an absent field means no valid settlement was available when the event was published. `rfq_created` includes settlement prices available at creation. The stream does not emit a separate event when a leg later settles; use `GetRFQs` during reconciliation to obtain the latest available settlement projection. Historical RFQs can have no `comboLegs`. ### Quote | Field | Type | Description | | ---------------------- | --------------- | ------------------------------------------------------- | | `id` | string | Quote ID. | | `rfqId` | string | Parent RFQ ID. | | `creatorRfqUserId` | string | Pseudonymous quote creator identity. | | `symbol` | string | Combo symbol. | | `status` | `QuoteStatus` | Current quote state. | | `createdTime` | `Timestamp` | Original quote creation time. | | `buyPrice` | string | Requester-buy price; maker sells. | | `sellPrice` | string | Requester-sell price; maker buys. | | `restRemainder` | bool | Whether the maker order may rest. | | `postOnly` | bool | Whether the maker order is participate-don't-initiate. | | `rfqCreatorUserId` | string | Pseudonymous requester identity. | | `rfqCashOrderQty` | optional string | Parent RFQ cash notional, when cash-sized. | | `buyQtyDecimal` | string | Server-derived requester-buy quantity. | | `sellQtyDecimal` | string | Server-derived requester-sell quantity. | | `updatedTime` | `Timestamp` | Last durable state change. | | `acceptedSide` | `Side` | Requester's accepted side, when selected. | | `acceptedTime` | `Timestamp` | Acceptance time, when selected. | | `confirmedTime` | `Timestamp` | Confirmation time, when confirmed. | | `confirmationDeadline` | `Timestamp` | Maker's last-look deadline, when accepted. | | `executionDeadline` | `Timestamp` | Scheduled paired-order submission time, when confirmed. | | `executedTime` | `Timestamp` | Durable execution-state timestamp, when executed. | | `rfqCreatorOrderId` | optional string | Requester's exchange order ID, when available. | | `creatorOrderId` | optional string | Quoter's exchange order ID, when available. | The requester and quoter can both see both exchange order IDs on the embedded `Quote`. Client order IDs are not part of durable `Quote` state. ## Lifecycle-Specific Fields | Event | Additional fields | | ----------------- | ---------------------------------------------------------------------------------------------------- | | `quote_accepted` | `confirmationDeadline`: authoritative deadline for `ConfirmQuote` or `DeleteQuote`. | | `quote_confirmed` | `executionDeadline`: scheduled paired-order submission time. | | `quote_executed` | `orderId`, `clientOrderId`, and `executedTime`. Order IDs are specific to the receiving participant. | These existing recipient-specific wrapper fields remain for compatibility. The embedded `Quote` contains the durable execution timestamps and both participants' exchange order IDs. `quote_executed` reports successful paired order submission, not a fill. Reconcile the subsequent exchange order and fill lifecycle through Drop Copy. ## Python Example ```python theme={null} import grpc from polymarket.v1 import rfq_pb2, rfq_pb2_grpc def stream_rfq_events(access_token: str, participant_id: str) -> None: credentials = grpc.ssl_channel_credentials() metadata = ( ("authorization", f"Bearer {access_token}"), ("x-participant-id", participant_id), ) with grpc.secure_channel( "grpc-api.prod.polymarketexchange.com:443", credentials, ) as channel: stub = rfq_pb2_grpc.RFQAPIStub(channel) request = rfq_pb2.StreamRFQEventsRequest() for response in stub.StreamRFQEvents(request, metadata=metadata): event_type = response.WhichOneof("event") if event_type == "rfq_created": rfq = response.rfq_created.rfq print(f"RFQ opened: {rfq.id} {rfq.symbol}") for leg in rfq.combo_legs: settlement = ( leg.settlement_price if leg.HasField("settlement_price") else "unavailable" ) print(f" {leg.side} {leg.symbol}: {settlement}") elif event_type == "rfq_closed": rfq = response.rfq_closed.rfq print(f"RFQ closed: {rfq.id}") elif event_type == "quote_accepted": event = response.quote_accepted print( f"Quote accepted: {event.quote.id}; " f"confirm by {event.confirmation_deadline}" ) elif event_type == "quote_confirmed": event = response.quote_confirmed print( f"Quote confirmed: {event.quote.id}; " f"execution scheduled for {event.execution_deadline}" ) elif event_type == "quote_executed": event = response.quote_executed print( f"Quote submitted: {event.quote.id}; " f"order={event.order_id}" ) ``` ## Delivery and Recovery | Behavior | Contract | | ---------- | ---------------------------------------------------------------------------------------------------- | | Delivery | Live, best-effort push after committed state changes. | | Filtering | No request filters. Public RFQ events and participant-visible private quote events share the stream. | | Replay | None. A new stream starts with new events only. | | Handoff | No gap-free handoff between a read and stream subscription. | | Ordering | No ordering guarantee across publishers or reconnects. | | Duplicates | Clients must tolerate duplicates. | On startup: 1. Open the stream. 2. Read current RFQs with `GetRFQs`. 3. Read the participant's current quotes with `GetQuotes`. 4. Apply subsequent events idempotently by RFQ or quote ID and `updatedTime`. After a disconnect, reopen the stream and repeat both durable reads. If any stream event may have been missed, `GetQuotes` is the durable recovery path for the current Quote execution state. An empty read collection is a valid snapshot. ## Errors | gRPC code | Typical cause | | --------------------- | -------------------------------------------------------------------------------- | | `INVALID_ARGUMENT` | Invalid request shape. | | `UNAUTHENTICATED` | Missing, expired, or invalid bearer token or participant authorization metadata. | | `PERMISSION_DENIED` | Token lacks `read:orders` or participant access. | | `FAILED_PRECONDITION` | RFQs are blocked for the participant or participant token setup is not ready. | | `RESOURCE_EXHAUSTED` | The firm exceeded the one-new-stream-per-second limit. | | `UNAVAILABLE` | Gateway or upstream RFQ service is unavailable. | | `DEADLINE_EXCEEDED` | Client or upstream deadline elapsed. | ## See Also Maker workflow and quote rules RFQ and quote REST and unary gRPC operations Metadata, tokens, and scopes Exchange order and fill reconciliation # Accounts & Identity Source: https://docs.polymarket.us/trader-guide/accounts-identity Understanding account hierarchy and identity resolution ## Account Hierarchy The API uses a four-level hierarchy: ``` Clearing Member └── Participant Firm (Legal Entity) └── User (with specific roles and scopes) └── Trading Account (Balances, Positions, Orders) ``` **Clearing Member**: The top-level clearing entity (e.g., `api-acme-clearingmember`) **Participant Firm**: The onboarded legal entity - your organization (e.g., `api-acme-participantfirm`) **User**: A user with specific roles and permissions, such as: * **Trading User** - Can place, modify, and cancel orders (scopes: `read:orders`, `write:orders`, `read:positions`) * **Drop Copy User** - Read-only access to execution reports (scope: `read:dropcopy`) * **Other roles** as defined during onboarding **Trading Account**: A specific account that holds balances, positions, and orders All trading and risk are scoped to the trading account level. User permissions (scopes) determine what operations each user can perform. ### Example Structure ``` api-acme-clearingmember (Clearing Member) │ └── api-acme-participantfirm (Participant Firm) │ ├── api-acme-trading (Trading User) │ ├── read:orders │ ├── write:orders │ └── read:positions │ └── api-acme-dropcopy (Drop Copy User) └── read:dropcopy ``` In this example: * The clearing member is `api-acme-clearingmember` * The participant firm is `api-acme-participantfirm` * Two users exist with different permissions: * `api-acme-trading` can read/write orders and read positions * `api-acme-dropcopy` can only read drop copy reports *** ## Finding Your Participant ID Your participant ID is the value you send in the `x-participant-id` header on every account-scoped request. It has the form `firms//users/`. **You are always given this value — you never discover or construct it.** Where it comes from depends on who the participant is: | Participant | Where the ID comes from | | ----------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Your own trading and drop copy users (institutional/DMA, market makers) | Provided during onboarding, when the users are provisioned for your firm | | An end user you onboard through KYC (brokers/partners) | Returned as `participantId` once the user's KYC reaches `ACCEPT` — on the [`kyc.approved` webhook](/partners/onboarding/kyc/webhooks) or from [`GET /v1/kyc/status`](/partners/onboarding/kyc/verification-flow#check-status) | **Do not assemble a participant ID by hand.** In particular, do not build one from the firm reported by `GET /v1/whoami`: the clearing member and the participant firm are different levels of the hierarchy above, so the firm shown there is not necessarily the firm segment of your participant ID. A participant ID whose firm segment does not match your participant firm is rejected as a cross-firm request. Always send the value you were given, unmodified. If you have lost the value, ask your Polymarket contact rather than reconstructing it. Once you hold one valid participant ID, `GET /v1/users` will return the rest of your firm's users. *** ## Identity Resolution Before trading, funding, or reporting, resolve your entitlements using the Accounts API. ### Get Your Firm Identity ```bash theme={null} GET /v1/whoami ``` Returns information about the authenticated firm, including firm ID, firm name, entitlements and permissions, and associated legal entities. ### List Users ```bash theme={null} GET /v1/users ``` Returns all users associated with your firm: user IDs, user details, KYC status, and associated trading accounts. This is a roster read for a caller that is already acting as a participant — it is account-scoped and **requires the `x-participant-id` header**, so it cannot be used to discover your first participant ID. See [Finding Your Participant ID](#finding-your-participant-id) above. ### List Trading Accounts ```bash theme={null} GET /v1/accounts ``` Returns all trading accounts you have access to: account IDs, account names, account status, balance information, and risk limits. ## Required Identity Resolution You must call these APIs before trading, funding, or reporting to ensure you: * Know which trading accounts you can access * Understand your entitlements * Use the correct account IDs in subsequent requests ## Multiple Trading Accounts **Can a firm control multiple trading accounts?** Yes. Each trading account has independent balances, independent positions, independent risk limits, and separate order flow. Use the appropriate account ID in your API requests to specify which account to trade on. ## Authentication vs Authorization **Authentication** identifies the firm using cryptographic signatures (JWT with private key). **Authorization** determines which users and trading accounts the firm may act on behalf of. Just because you're authenticated doesn't mean you can access all accounts - authorization is checked on each request. ## Typical Workflow 1. **Authenticate** - Sign a JWT with your private key to get an access token 2. **Have your participant ID ready** - From onboarding, or from KYC approval for an end user. See [Finding Your Participant ID](#finding-your-participant-id). Send it as `x-participant-id` on every account-scoped request from here on 3. **Call whoami** - Confirm your firm identity and entitlements 4. **List users** - See which users you can manage 5. **List accounts** - See which trading accounts you can access 6. **Trade/Fund/Report** - Use the appropriate account ID in your requests ## Example: Checking Account Access ```python theme={null} # 1. Get your firm identity whoami_response = api.get('/v1/whoami') firm_id = whoami_response['firmId'] # 2. List trading accounts accounts_response = api.get('/v1/accounts') trading_accounts = accounts_response['accounts'] # 3. Select account for trading account_id = trading_accounts[0]['accountId'] # 4. Place order using that account order_request = { 'accountId': account_id, 'instrument': 'tec-nfl-sbw-2026-02-08-kc', 'side': 'buy', 'quantity': 100 } api.post('/v1/trading/orders', order_request) ``` ## Account Status Trading accounts can have different statuses: **Active** (normal trading allowed), **Suspended** (temporarily restricted), **Closed** (no longer active). Always check account status before attempting to trade. # Registration Source: https://docs.polymarket.us/trader-guide/authentication Setting up Private Key JWT authentication to access the API The Polymarket Exchange API uses **Private Key JWT** authentication with RSA keys. You sign a JWT with your RSA private key and exchange it for an access token. Complete [Onboarding](/trader-guide/onboarding) first to generate your keys and receive your Client ID. ## Environments | Environment | Auth Domain | API Domain | | -------------- | -------------------------- | ------------------------------------ | | Pre-production | `pmx-preprod.us.auth0.com` | `api.preprod.polymarketexchange.com` | | Production | `pmx-prod.us.auth0.com` | `api.prod.polymarketexchange.com` | Use `https://[API Domain]` for both the JWT audience claim and API base URL. Each environment requires separate onboarding. Your pre-production credentials will not work in production. ## How It Works ```mermaid theme={null} sequenceDiagram participant Client as Your Application participant Auth as Polymarket US Auth participant API as Polymarket US API Client->>Client: Sign JWT with Private Key Client->>Auth: Token Request + Signed JWT Auth->>Auth: Verify with your Public Key Auth-->>Client: API Access Token Client->>API: API Request + Access Token API->>API: Validate Token API-->>Client: API Response ``` Authentication follows these steps: 1. **Create a signed JWT assertion** - Sign a JWT with your private key 2. **Exchange for API access token** - Send the assertion to the token endpoint 3. **Call API with access token** - Include the token in your API requests ## Prerequisites After completing [Onboarding](/trader-guide/onboarding), you will have: | You Have | From Onboarding | | ---------------- | ---------------------------------------------------------------------------- | | Private key file | Generated by you (keep secure!) | | Client ID | Provided by Polymarket via `clientid.txt` in your shared Google Drive folder | | Auth Domain | See [Environments](/trader-guide/environments) | | API Audience | See [Environments](/trader-guide/environments) | ## Create Client Assertion JWT Create a JWT with these claims, signed with your private key using RS256: ```json theme={null} { "iss": "YOUR_CLIENT_ID", "sub": "YOUR_CLIENT_ID", "aud": "https://pmx-preprod.us.auth0.com/oauth/token", "iat": 1703270400, "exp": 1703270700, "jti": "unique-random-uuid" } ``` | Claim | Description | | ----- | ----------------------------------------- | | `iss` | Your client ID (issuer) | | `sub` | Your client ID (subject) | | `aud` | Token endpoint URL | | `iat` | Issued at time (Unix timestamp) | | `exp` | Expiration time (max 5 minutes from iat) | | `jti` | Unique token ID (prevents replay attacks) | ## Request Access Token ```bash theme={null} curl --request POST \ --url "https://pmx-preprod.us.auth0.com/oauth/token" \ --header "content-type: application/json" \ --data '{ "client_id": "YOUR_CLIENT_ID", "client_assertion_type": "urn:ietf:params:oauth:client-assertion-type:jwt-bearer", "client_assertion": "YOUR_SIGNED_JWT_ASSERTION", "audience": "https://api.preprod.polymarketexchange.com", "grant_type": "client_credentials" }' ``` ### Token Response ```json theme={null} { "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIs...", "token_type": "Bearer", "expires_in": 180 } ``` ## Complete Python Example ```python theme={null} import jwt import uuid import time import requests from cryptography.hazmat.primitives import serialization class AuthClient: def __init__(self, domain: str, client_id: str, audience: str, private_key_path: str): self.domain = domain self.client_id = client_id self.audience = audience self.private_key_path = private_key_path self.token = None self.token_expiry = None def _load_private_key(self): """Load the RSA private key from file.""" with open(self.private_key_path, 'rb') as f: return serialization.load_pem_private_key(f.read(), password=None) def _create_client_assertion(self) -> str: """Create a signed JWT for client authentication.""" private_key = self._load_private_key() now = int(time.time()) claims = { "iss": self.client_id, "sub": self.client_id, "aud": f"https://{self.domain}/oauth/token", "iat": now, "exp": now + 300, # 5 minutes "jti": str(uuid.uuid4()), } return jwt.encode(claims, private_key, algorithm="RS256") def get_token(self) -> str: """Get a valid access token, refreshing if necessary.""" if self._is_token_valid(): return self.token # Create client assertion assertion = self._create_client_assertion() # Request access token response = requests.post( f"https://{self.domain}/oauth/token", json={ "client_id": self.client_id, "client_assertion_type": "urn:ietf:params:oauth:client-assertion-type:jwt-bearer", "client_assertion": assertion, "audience": self.audience, "grant_type": "client_credentials" }, headers={"content-type": "application/json"} ) response.raise_for_status() data = response.json() self.token = data["access_token"] # Set expiry with 30-second buffer self.token_expiry = time.time() + data["expires_in"] - 30 return self.token def _is_token_valid(self) -> bool: """Check if current token is still valid.""" if not self.token or not self.token_expiry: return False return time.time() < self.token_expiry # Usage auth_client = AuthClient( domain="pmx-preprod.us.auth0.com", client_id="YOUR_CLIENT_ID", audience="https://api.preprod.polymarketexchange.com", private_key_path="/path/to/my_private_key.pem" ) token = auth_client.get_token() ``` **Required packages:** ```bash theme={null} pip install PyJWT cryptography requests ``` ## Complete Go Example ```go theme={null} package main import ( "crypto/rsa" "crypto/x509" "encoding/json" "encoding/pem" "fmt" "net/http" "os" "time" "github.com/golang-jwt/jwt/v5" "github.com/google/uuid" ) func getAccessToken(domain, clientID, audience, privateKeyPath string) (string, error) { // Load private key keyData, err := os.ReadFile(privateKeyPath) if err != nil { return "", fmt.Errorf("read key file: %w", err) } block, _ := pem.Decode(keyData) privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes) if err != nil { return "", fmt.Errorf("parse private key: %w", err) } // Create client assertion JWT now := time.Now() claims := jwt.MapClaims{ "iss": clientID, "sub": clientID, "aud": fmt.Sprintf("https://%s/oauth/token", domain), "iat": now.Unix(), "exp": now.Add(5 * time.Minute).Unix(), "jti": uuid.New().String(), } token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims) assertion, err := token.SignedString(privateKey) if err != nil { return "", fmt.Errorf("sign assertion: %w", err) } // Request access token // (implement HTTP POST to token endpoint) // ... return accessToken, nil } ``` ## Using the Access Token Include the access token in the `Authorization` header for all API requests. For account-scoped endpoints (trading, positions, reports), you must also include the `x-participant-id` header. ### REST API ```bash theme={null} curl -X GET "https://api.preprod.polymarketexchange.com/v1/whoami" \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H "x-participant-id: firms/YourFirm/users/your-user" ``` ### gRPC ```python theme={null} import grpc # Create metadata with token metadata = [ ('authorization', f'Bearer {access_token}'), ('x-participant-id', 'firms/YourFirm/users/your-user') ] # Make gRPC call with metadata response = stub.SomeMethod(request, metadata=metadata) ``` Verify your token scopes and ensure `x-participant-id` is included for account-scoped endpoints. Send the participant ID exactly as it was given to you — from onboarding for your own users, or as `participantId` on KYC approval for an end user. Don't assemble it by hand from another response. You will have one participant firm but can have multiple users; see [Finding Your Participant ID](/trader-guide/accounts-identity#finding-your-participant-id). ## Key Rotation You can rotate your keys at any time: 1. Generate a new key pair 2. Complete a new [Onboarding](/trader-guide/onboarding) submission with the new public key 3. We add the new key to your application 4. Update your systems to use the new private key 5. Notify us to remove the old public key ## Troubleshooting ### Common Errors | Error | Cause | Solution | | -------------------------- | --------------------------------- | ------------------------------------------------ | | `invalid_client` | JWT signature verification failed | Verify private key matches registered public key | | `invalid_client_assertion` | Malformed JWT or wrong claims | Check JWT claims (iss, sub, aud, exp) | | `401 Unauthorized` | Invalid or expired access token | Request a new access token | ### Debugging JWT Claims If authentication fails, verify your client assertion JWT contains correct claims: ```json theme={null} { "iss": "YOUR_CLIENT_ID", "sub": "YOUR_CLIENT_ID", "aud": "https://pmx-preprod.us.auth0.com/oauth/token", "iat": 1703270400, "exp": 1703270700, "jti": "550e8400-e29b-41d4-a716-446655440000" } ``` Common mistakes: * Wrong `aud` (must be the token endpoint, not the API) * Expired JWT (exp in the past) * Reused `jti` (must be unique per request) *** ## API Scopes Your application is granted specific **scopes** that control which API endpoints you can access. Scopes are included in your access token and validated by the API. ### Available Scopes | Scope | Description | | ------------------- | ------------------------------------------------------------------------------------------------------------- | | `read:marketdata` | BBO (best bid/offer) and market data subscriptions (including `BiDirectionalStreamMarketData`) | | `read:l2marketdata` | L2 orderbook depth (premium) | | `read:instruments` | RefData, instrument listings and metadata | | `read:orders` | View open orders, preview orders, order subscriptions, combo instruments, RFQs, quotes, and RFQ event streams | | `write:orders` | Insert / cancel / replace / modify orders; create combo instruments and manage RFQs and quotes | | `read:reports` | Search/download orders, trades, executions, and incentives earnings | | `read:positions` | Position queries, balance queries, position ledger, and balance ledger | | `read:dropcopy` | Drop copy subscriptions | | `read:accounts` | View users (`/v1/whoami`, `/v1/users`) and account info | | `read:funding` | View funding sources and transactions | | `write:funding` | Update funding, create deposits and withdrawals | **Strict scope enforcement.** Calls that are missing a required scope fail with `403 Forbidden` (REST) / `PERMISSION_DENIED` (gRPC) and the message `permission denied: missing required scope `. Balance ledger endpoints use `read:positions` (not `read:funding`) to stay consistent with the existing balance-query endpoints (`GetAccountBalance`, `ListAccountBalances`). ### Scope Requirements by Endpoint | Endpoint | Method | Required Scope | | ------------------------------------------- | ------ | -------------------------------------------------- | | `/v1/trading/orders` | POST | `write:orders` | | `/v1/trading/orders/cancel` | POST | `write:orders` | | `/v1/trading/orders/open` | GET | `read:orders` | | `/v1/combos` | GET | `read:orders` | | `/v1/combos` | POST | `write:orders` | | `/v1/rfqs/user-id` | GET | `read:orders` | | `/v1/rfqs` | GET | `read:orders` | | `/v1/rfqs` | POST | `write:orders` | | `/v1/rfqs/{rfqId}` | DELETE | `write:orders` | | `/v1/rfqs/quotes` | GET | `read:orders` | | `/v1/rfqs/quotes` | POST | `write:orders` | | `/v1/rfqs/{rfqId}/quotes/{quoteId}` | DELETE | `write:orders` | | `/v1/rfqs/{rfqId}/quotes/{quoteId}/accept` | PUT | `write:orders` | | `/v1/rfqs/{rfqId}/quotes/{quoteId}/confirm` | PUT | `write:orders` | | `StreamRFQEvents` (gRPC) | — | `read:orders` | | `/v1/report/orders/search` | POST | `read:reports` | | `/v1/report/trades/search` | POST | `read:reports` | | `/v1/incentives/earnings` | GET | `read:reports`  *(disabled in preprod)* | | `/v1/positions` | GET | `read:positions` | | `/v1/positions/balance` | POST | `read:positions` | | `/v1/positions/balances` | POST | `read:positions` | | `/v1/positions/ledger` | GET | `read:positions` | | `/v1/positions/ledger/download` | GET | `read:positions` | | `/v1/funding/balance-ledger` | GET | `read:positions` | | `/v1/funding/balance-ledger/download` | GET | `read:positions` | | `CreateBalanceLedgerSubscription` (gRPC) | — | `read:positions` | | `/v1/orderbook/{symbol}` | GET | `read:l2marketdata` | | `/v1/orderbook/{symbol}/bbo` | GET | `read:marketdata` | | `BiDirectionalStreamMarketData` (gRPC) | — | `read:marketdata` | | `CreateMarketDataSubscription` (gRPC) | — | `read:marketdata` | | `/v1/refdata/symbols` | POST | `read:instruments` | | `/v1/refdata/instruments` | POST | `read:instruments` | | `/v1/refdata/metadata` | POST | `read:instruments` | | `/v1/whoami` | GET | `read:accounts` | | `/v1/users` | GET | `read:accounts` | | `/v1/funding/accounts` | GET | `read:funding` | | `/v1/aeropay/deposits` | POST | `write:funding` | | `/v1/checkout/deposits` | POST | `write:funding` | | `/v1/health` | GET | *(no auth required)* | **gRPC streams (`BiDirectionalStreamMarketData`, `CreateMarketDataSubscription`, `CreateBalanceLedgerSubscription`, `StreamRFQEvents`) run on a separate ALB** (`grpc-api.{env}.polymarketexchange.com:443`) that bypasses the API Gateway and its 30-second idle timeout. Scope validation for these streams happens at the application layer rather than at the load balancer, but the resulting `PERMISSION_DENIED` behavior is identical to REST endpoints. **`/v1/incentives/earnings` is currently disabled in `preprod`** and returns a route-not-found error there. The endpoint is live in `prod`. Earnings flow shape can still be validated against the [OpenAPI schema](/institutional/oapi-schemas/incentives-schema.json) and the [incentives overview](/institutional/incentives/overview). ### Permission Denied Response When a request's token is missing the required scope: ```json theme={null} { "code": 7, "message": "permission denied: missing required scope read:positions" } ``` | Surface | Status / Code | | ------- | ------------------------------ | | REST | `403 Forbidden` | | gRPC | `PERMISSION_DENIED` (code `7`) | If you receive this error, update your Auth0 application to include the missing scope and request a fresh access token. ### Checking Your Scopes Your granted scopes are included in your access token. You can decode the token to see them: ```python theme={null} import base64 import json # Decode the payload (middle part of JWT) payload = access_token.split('.')[1] payload += '=' * (4 - len(payload) % 4) # Add padding claims = json.loads(base64.urlsafe_b64decode(payload)) print("Granted scopes:", claims.get("scope", "")) ``` If you receive a `403 Forbidden` error, check that your application has been granted the required scope for that endpoint. Contact support to request additional scopes. *** ## Additional Resources For more details on Private Key JWT authentication: * [Private Key JWT Client Authentication](https://auth0.com/docs/get-started/authentication-and-authorization-flow/authenticate-with-private-key-jwt) * [Machine-to-Machine Applications](https://auth0.com/docs/get-started/applications/application-types#machine-to-machine-applications) * [RFC 7523 - JWT Profile for Client Authentication](https://datatracker.ietf.org/doc/html/rfc7523) # Authentication Source: https://docs.polymarket.us/trader-guide/authentication-troubleshooting Resolving authentication and authorization issues For authentication setup and code examples, see the main [Authentication](/trader-guide/authentication) guide. ## Common Authentication Errors ### "401 Unauthorized" Your JWT token is missing, invalid, or expired. **Check:** 1. Is the token included in the `Authorization: Bearer ` header? 2. Has the token expired? Tokens are typically valid for 180 seconds (3 minutes) 3. Is the token format correct? Should be `Bearer eyJ...` 4. Was the token issued by the correct Auth0 domain for your environment? **Solution**: Request a new access token from Auth0 and retry your request. **Decode your token** at jwt.io to verify claims and expiration. ### "403 Forbidden" Your token is valid but doesn't have the required scope for the endpoint. **Check:** 1. Decode your token at jwt.io to see which scopes you have 2. Verify the endpoint requires a scope you have (see [Authentication scopes](/trader-guide/authentication#api-scopes)) 3. Scopes must be space-separated in the `scope` claim (e.g., `"read:orders write:orders"`) **Solution**: Contact support to request additional scopes for your Client ID. ### "invalid\_client" JWT signature verification failed when requesting an access token from Auth0. **Causes:** * Private key doesn't match the public key registered with Polymarket * Wrong private key file being used * Private key file corrupted or invalid format **Solution**: Verify your private key matches the public key you submitted during onboarding. If keys are mismatched, you'll need to re-onboard with the correct public key. ### "invalid\_client\_assertion" The client assertion JWT is malformed or has incorrect claims. **Common causes:** * Wrong `aud` claim (must be `https://pmx-{env}.us.auth0.com/oauth/token`, NOT the API URL) * Expired `exp` claim (expiration in the past) * Missing required claims (`iss`, `sub`, `aud`, `iat`, `exp`, `jti`) * Reused `jti` (must be unique for each request) * Wrong signing algorithm (must be RS256) **Debug by decoding your client assertion JWT:** ```python theme={null} import jwt # Decode without verification to inspect claims decoded = jwt.decode(your_assertion, options={"verify_signature": False}) print(decoded) ``` **Required claims:** ```json theme={null} { "iss": "YOUR_CLIENT_ID", "sub": "YOUR_CLIENT_ID", "aud": "https://pmx-preprod.us.auth0.com/oauth/token", "iat": 1703270400, "exp": 1703270700, "jti": "unique-uuid-per-request" } ``` ## Environment-Specific Issues ### Using Wrong Environment Credentials **Problem**: Using development credentials in production (or vice versa). **Symptoms:** * `invalid_client` errors * `401 Unauthorized` on API calls * Token works in one environment but not another **Solution**: Each environment requires separate credentials: * Separate key pairs (different public/private keys) * Separate Client IDs * Separate Auth0 domains * Separate API audiences | Environment | Auth Domain | API Audience | | ------------------ | -------------------------- | -------------------------------------------- | | **Pre-production** | `pmx-preprod.us.auth0.com` | `https://api.preprod.polymarketexchange.com` | | **Production** | `pmx-prod.us.auth0.com` | `https://api.prod.polymarketexchange.com` | ### Cannot Reuse Keys Across Environments **Environments are completely isolated.** You cannot: * Use the same private key in multiple environments * Use preprod credentials in production * Transfer Client IDs between environments Each environment requires a complete separate onboarding. ## Token Expiration Issues ### Token Works Sometimes But Not Others **Cause**: Token is expiring mid-session. **Solution**: Implement proactive token refresh: ```python theme={null} class TokenManager: def __init__(self, auth_client): self.auth_client = auth_client self.token = None self.token_expiry = 0 def get_valid_token(self): # Refresh if expired or expiring soon (30 second buffer) if time.time() >= self.token_expiry - 30: self.token = self.auth_client.get_token() self.token_expiry = time.time() + 180 # 3 minutes return self.token ``` ### Token Expired Immediately After Creation **Cause**: System clock is incorrect. **Check:** Is your system time synchronized? ```bash theme={null} date # Compare with actual time ``` **Solution**: Synchronize your system clock using NTP. ## JWT Claim Issues ### Wrong Audience Claim **Common mistake**: Using API URL as the `aud` claim in the **client assertion**. **Incorrect:** ```json theme={null} { "aud": "https://api.preprod.polymarketexchange.com" // Wrong! } ``` **Correct:** ```json theme={null} { "aud": "https://pmx-preprod.us.auth0.com/oauth/token" // Correct } ``` The audience for the **client assertion** JWT must be the Auth0 token endpoint, not the API. The **access token** you receive will have the API URL as its audience. ### Reused JTI **Problem**: Using the same `jti` (JWT ID) for multiple requests. **Cause**: `jti` is meant to prevent replay attacks and must be unique per request. **Solution**: Generate a new UUID for each token request: ```python theme={null} import uuid claims["jti"] = str(uuid.uuid4()) ``` ## Verifying Token Claims Decode your access token to check what scopes and claims you have: ```python theme={null} import base64 import json def decode_token(access_token): # Split token and get payload payload = access_token.split('.')[1] # Add padding if needed payload += '=' * (4 - len(payload) % 4) # Decode claims = json.loads(base64.urlsafe_b64decode(payload)) print("Token expires:", claims.get("exp")) print("Granted scopes:", claims.get("scope", "")) print("Audience:", claims.get("aud")) print("Issuer:", claims.get("iss")) return claims claims = decode_token(your_access_token) ``` ## Authorization vs Authentication **Authentication** proves who you are (which firm). **Authorization** determines what you can do (which accounts, which scopes). **You may be authenticated but not authorized** to: * Access specific trading accounts * Call certain endpoints (missing scopes) * Perform certain actions (insufficient permissions) If you get 403 errors despite being authenticated, it's an authorization issue, not authentication. ## Missing or Incorrect x-participant-id ### Errors on Trading, Positions, or Report Endpoints **Problem**: Requests to account-scoped endpoints fail even though your access token is valid. **Cause**: The `x-participant-id` header is missing or contains an incorrect value. This header is required for all account-scoped endpoints (trading, positions, reports) but is **not** required for market data, order book, or reference data endpoints. **Solution**: Include the `x-participant-id` header in your requests: ```bash theme={null} curl -X POST "https://api.preprod.polymarketexchange.com/v1/trading/orders" \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -H "x-participant-id: firms/YourFirm/users/your-user" \ -d '{ ... }' ``` ### Finding Your Participant ID You are given your participant ID — there is no endpoint that discovers your first one, and you should not construct it yourself. **Institutional traders (DMA) and market makers:** Your participant ID is provided during onboarding, when your trading and drop copy users are provisioned. If you no longer have it, ask your Polymarket contact. **Brokers/Partners:** Participant IDs for your end users are returned once the user's KYC reaches `ACCEPT` — as `participantId` on the [`kyc.approved` webhook](/partners/onboarding/kyc/webhooks), or from [`GET /v1/kyc/status`](/partners/onboarding/kyc/verification-flow#check-status). Provisioning is asynchronous, so `participantId` may be absent on the initial `POST /v1/kyc/start` response even for an instant approval; prefer the webhook and poll the status endpoint as a fallback. Once you hold one valid participant ID, `GET /v1/users` lists the rest of your firm's users. It is account-scoped and requires `x-participant-id` itself, so it can't be your starting point. Do not build a participant ID from the firm reported by `GET /v1/whoami`. The clearing member and the participant firm are different levels of the account hierarchy, so the firm shown there is not necessarily the firm segment of your participant ID — and an ID whose firm segment doesn't match your participant firm is rejected as a cross-firm request. See [Accounts & Identity](/trader-guide/accounts-identity#finding-your-participant-id) for more details. ### Endpoints That Do NOT Require x-participant-id These endpoints only require a valid access token with the appropriate scope: * Market data: `/v1/orderbook/*`, market data streaming * Reference data: `/v1/refdata/*` * Trade statistics: `/v1/report/trades/stats` * Health check: `/v1/health` ## Cryptographic Issues ### Wrong Key Format **Private keys must be in PEM format:** ``` -----BEGIN RSA PRIVATE KEY----- MIIEpAIBAAKCAQEA... ... -----END RSA PRIVATE KEY----- ``` If your key is in a different format (DER, JWK, etc.), convert it to PEM: ```bash theme={null} # Convert DER to PEM openssl rsa -inform DER -in key.der -out key.pem ``` ### Wrong Algorithm **You must use RS256** (RSA with SHA-256) to sign your JWT. **Don't use:** * HS256 (HMAC - symmetric key) * Other RSA variants (RS384, RS512, PS256, etc.) ## Testing Authentication Test your authentication setup: **1. Verify you can create a client assertion:** ```bash theme={null} # Decode and inspect your assertion echo "YOUR_ASSERTION_JWT" | cut -d. -f2 | base64 -d | jq ``` **2. Test token request:** ```bash theme={null} curl -X POST https://pmx-preprod.us.auth0.com/oauth/token \ -H "Content-Type: application/json" \ -d '{ "client_id": "YOUR_CLIENT_ID", "client_assertion_type": "urn:ietf:params:oauth:client-assertion-type:jwt-bearer", "client_assertion": "YOUR_SIGNED_JWT", "audience": "https://api.preprod.polymarketexchange.com", "grant_type": "client_credentials" }' ``` **3. Test API call with access token:** ```bash theme={null} curl -X GET https://api.preprod.polymarketexchange.com/v1/whoami \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" ``` ## Getting Help If authentication issues persist: 1. Verify your credentials match your environment 2. Decode and inspect your JWTs (client assertion and access token) 3. Check system clock is synchronized 4. Test with curl commands to isolate client code issues 5. Contact support with: * Environment (dev, preprod, prod) * Client ID * Error messages * Decoded JWT claims (never share your private key!) # Combos Source: https://docs.polymarket.us/trader-guide/combos Quote combo requests distributed through the RFQ API > REST and gRPC unary calls are supported. RFQ events are gRPC-only. FIX support is coming later. A combo is an instrument with 2–10 component legs. Each leg contains an existing market symbol and a buy or sell side. Combo instruments trade through normal order entry, but most combo price discovery starts with a request for quote (RFQ). This guide covers the market-maker workflow. The public contract is split between: * `polymarket.v1.ComboAPI`: `CreateCombo` and `GetCombos`. * `polymarket.v1.RFQAPI`: RFQ and quote reads/writes plus `StreamRFQEvents`. Download the current [proto bundle](https://drive.google.com/uc?export=download\&id=1oT9gaeBEn0vukHD9GOoj_YvzPnR3otng). Use `combo.proto` and `rfq.proto`; the retired `combos.proto` interface is no longer part of the contract. ## Maker Startup 1. Call `GetRFQUserID` and retain the pseudonymous ID returned for your participant. 2. Open one `StreamRFQEvents` stream with `read:orders`. 3. Load open RFQs with `GetRFQs { status: RFQ_STATUS_OPEN }`. 4. Load your quotes with `GetQuotes { user_filter: USER_FILTER_SELF }`. 5. Read ordered legs from `rfq.combo_legs`. Call `GetCombos` when you need current combo state or tick size, or when a historical RFQ has no leg snapshot. The stream is live and best-effort. It does not replay missed events or guarantee gap-free handoff, ordering, or deduplication. Repeat the durable reads after every reconnect. ## Maker Flow ```mermaid theme={null} sequenceDiagram autonumber participant R as Requester participant API as RFQAPI participant M as Maker participant DC as Drop Copy M->>API: StreamRFQEvents({}) M->>API: GetRFQs(OPEN) + GetQuotes(SELF) R->>API: CreateRFQ API-->>M: rfq_created opt Current combo metadata needed M->>API: GetCombos(symbol) end M->>API: CreateQuote(buyPrice, sellPrice) API-->>M: CreateQuoteResponse(quoteId) API-->>M: quote_created R->>API: AcceptQuote(acceptedSide) API-->>M: rfq_closed API-->>M: quote_accepted + confirmationDeadline alt Maker confirms before deadline M->>API: ConfirmQuote API-->>M: quote_confirmed + executionDeadline API-->>M: quote_executed + durable Quote state DC-->>M: Exchange order and fill lifecycle else Maker declines M->>API: DeleteQuote API-->>M: quote_deleted end ``` Successful acceptance produces both `rfq_closed` and `quote_accepted`. A maker may receive public `rfq_closed` first. Stop creating or replacing quotes for that RFQ, but do not discard existing quote state. The selected maker then receives private `quote_accepted`, which starts last look. Confirm or delete the selected quote before its `confirmationDeadline`. `quote_confirmed` means paired order submission is scheduled; `quote_executed` means both exchange orders were accepted for submission. Use Drop Copy as the source of truth for fills. ## Read an RFQ An RFQ supplies either a contract quantity or a cash notional: ```json theme={null} { "id": "rfq_...", "cashOrderQty": "10.0000", "symbol": "caoc-...", "rfqCreatorUserId": "rfquser_...", "createdTime": "2026-07-29T14:00:00Z", "restRemainder": false, "status": "RFQ_STATUS_OPEN", "updatedTime": "2026-07-29T14:00:00Z", "comboLegs": [ { "symbol": "market-a", "side": "SIDE_BUY", "settlementPrice": "0.4" }, { "symbol": "market-b", "side": "SIDE_SELL", "settlementPrice": "0" } ] } ``` | Field | Meaning | | ------------------ | ---------------------------------------------------------- | | `qtyDecimal` | Exact contract quantity. Present only for a quantity RFQ. | | `cashOrderQty` | Cash notional. Present only for a cash RFQ. | | `symbol` | Combo symbol to quote and trade. | | `rfqCreatorUserId` | Pseudonymous requester identity. | | `restRemainder` | Whether the requester's unfilled order remainder may rest. | | `status` | `RFQ_STATUS_OPEN` or `RFQ_STATUS_CLOSED`. | | `comboLegs` | Ordered component legs captured when the RFQ was created. | Each combo leg contains its `symbol`, combo `side`, and optional `settlementPrice`. Settlement is the raw YES/LONG result normalized to `[0,1]`; do not invert it for `SIDE_SELL`. A present `"0"` is a valid settlement and differs from an absent field. Exact and list reads hydrate the latest available settlements, while `rfq_created` contains those available when the event was published. Historical RFQs can have no inline legs. Use `GetCombos { symbol: rfq.symbol }` as the fallback and whenever you need current combo metadata. An RFQ still has no requested side, expiration time, or client request ID. ## Construct a Quote `CreateQuote` is dual-sided: ```json theme={null} { "rfqId": "rfq_...", "buyPrice": "0.615", "sellPrice": "0.585", "restRemainder": false, "postOnly": true, "account": "firm/account" } ``` | Field | Maker behavior | | --------------- | ------------------------------------------------------------------------------------------------------ | | `buyPrice` | Price offered for a requester `SIDE_BUY`; the maker sells. | | `sellPrice` | Price offered for a requester `SIDE_SELL`; the maker buys. | | `"0"` price | Marks that side unavailable. At least one side must be positive. | | `restRemainder` | Required. If true, the maker order can remain GTC; otherwise it expires after the paired-order window. | | `postOnly` | If true, the maker order is participate-don't-initiate. | | `account` | Required fully qualified maker account. | Do not send a side, symbol, quantity, expiration, or client request ID. The API obtains the symbol and sizing from the RFQ. ### Price and Quantity Rules * Each positive price must be within the instrument's price limits and land on its `tickSize`. Current combo instruments use a `0.001` tick. * A quantity RFQ uses its `qtyDecimal` for each offered side. * A cash RFQ derives each side independently as `floor(cashOrderQty / sidePrice)` at the instrument's fractional quantity scale. * A derived quantity must meet the instrument minimum. A positive price can therefore be invalid even when the other side is valid. * The persisted quote reports the derived `buyQtyDecimal` and `sellQtyDecimal`; use those values rather than recomputing them. ### Replace a Quote Each maker has one deterministic quote ID for an RFQ. Calling `CreateQuote` again replaces the existing quote's economics, resets its status to `QUOTE_STATUS_ACTIVE`, preserves its `quoteId`, and emits another `quote_created` event. Treat replacement as a state update, not a second live quote. ### Quote Selection The RFQ Engine considers positive prices from `QUOTE_STATUS_ACTIVE` quotes independently for requester buy and sell: 1. For requester `SIDE_BUY`, `buyPrice` is the maker's ask; lower price wins. 2. For requester `SIDE_SELL`, `sellPrice` is the maker's bid; higher price wins. 3. Equal prices use the earlier `createdTime`. 4. An exact tie uses the lexicographically smaller `quoteId`. A two-sided quote may win both sides; different quotes may win each side. ## Last Look and Execution When a requester accepts one side: 1. The RFQ becomes `RFQ_STATUS_CLOSED`, and public `rfq_closed` is emitted. 2. The selected quote becomes `QUOTE_STATUS_ACCEPTED`. 3. The selected maker receives private `quote_accepted` with the authoritative `confirmationDeadline`. 4. The maker calls `ConfirmQuote` to trade or `DeleteQuote` to decline before the deadline. 5. Confirmation changes the quote to `QUOTE_STATUS_CONFIRMED` and emits `quote_confirmed` with `executionDeadline`. 6. Paired orders are submitted maker first, then requester. 7. Successful paired submission changes the quote to `QUOTE_STATUS_EXECUTED` and emits participant-private `quote_executed` events with the durable Quote state embedded. The requester's canonical side determines the selected economics: | `acceptedSide` | Selected price and quantity | Maker order | | -------------- | ----------------------------- | ----------- | | `SIDE_BUY` | `buyPrice`, `buyQtyDecimal` | Sell | | `SIDE_SELL` | `sellPrice`, `sellQtyDecimal` | Buy | The durable `Quote` returned by `GetQuotes` and embedded in stream events records: | REST JSON field | Meaning | | ------------------- | --------------------------------------- | | `executionDeadline` | Scheduled paired-order submission time. | | `executedTime` | Durable execution-state timestamp. | | `rfqCreatorOrderId` | Optional requester exchange order ID. | | `creatorOrderId` | Optional quoter exchange order ID. | Both the requester and quoter can see both exchange order IDs. Client order IDs are not stored on the public `Quote`. Existing recipient-specific stream wrapper fields remain available for compatibility. Current timing is: | Interval | Duration | Source of truth | | ----------------------------------------------- | --------- | -------------------------------------- | | Quote submission window for RFQ Engine requests | 200 ms | Quote immediately after `rfq_created`. | | Maker last look | 3 seconds | `quote_accepted.confirmationDeadline` | | Delay before paired order submission | 1 second | `quote_confirmed.executionDeadline` | These durations are configuration, not client-side timers. Use the deadlines on durable Quote state; existing event wrapper deadlines remain available for compatibility. ## Stream Visibility | Event | Visibility | Maker action | | ----------------- | ------------------------------------ | ----------------------------------------------------------------------------------------------------- | | `rfq_created` | Public | Inspect the combo and quote or ignore. | | `rfq_closed` | Public | Stop creating or replacing quotes, but retain quote state; acceptance also closes the RFQ. | | `quote_created` | Requester and quote creator | Store the returned quote state and ID. | | `quote_deleted` | Requester and quote creator | Stop treating the quote as live. | | `quote_accepted` | Requester and selected quote creator | Confirm or delete before `confirmationDeadline`. | | `quote_confirmed` | Requester and selected quote creator | Expect paired submission at `executionDeadline`. | | `quote_executed` | Requester and selected quote creator | Read both durable exchange order IDs from the embedded Quote and correlate your order with Drop Copy. | There are no expiration, done-away, pending-risk, pending-end-trade, action-rejected, or status-rejected events in the current public stream. ## Recovery Reads Use `GetQuotes` according to the visibility you need: | Request | Result | | --------------------------------------- | -------------------------------------------- | | `{ user_filter: USER_FILTER_SELF }` | Quotes created by your participant. | | `{ rfq_user_filter: USER_FILTER_SELF }` | Quotes on RFQs created by your participant. | | `{ rfq_id: "..." }` as requester | All visible quotes for that RFQ. | | `{ rfq_id: "..." }` as maker | Your deterministic quote for that RFQ. | | `{ rfq_id: "...", quote_id: "..." }` | Exact visible quote, or an empty collection. | Use opaque cursors only with the same participant, query path, and filters. If a write returns an unknown result because the connection fails, read the exact RFQ or quote before deciding whether to act again. `GetQuotes` is the durable recovery path when a stream event is missed. It returns the current execution timestamps and, once available, both the requester's `rfqCreatorOrderId` and the quoter's `creatorOrderId` to either participant. ## Rate Limits Use the stream for live state. Reserve `GetRFQs` and `GetQuotes` for startup, recovery, and targeted reconciliation. See [Rate Limits](/trader-guide/rate-limits) for current per-firm limits. ## Related Documentation Complete RFQ and quote REST and unary gRPC contract Combo instrument REST and unary gRPC contract Event payloads and Python example Exchange order and fill reconciliation Position and balance monitoring # Connection Issues Source: https://docs.polymarket.us/trader-guide/connection-issues Troubleshooting network and connectivity problems **Connection drops are normal for HTTP/REST APIs.** Various infrastructure layers (load balancers, proxies, firewalls) have timeout limits. All API clients must implement automatic reconnection logic with exponential backoff - this is not optional. ## Required Client Implementation All API clients must implement: 1. **Automatic reconnection** for both REST and gRPC when connections drop 2. **Retry logic** with exponential backoff for transient connection errors 3. **Keepalive configuration** for gRPC streams (60s interval recommended) 4. **Timeout detection** (15s for REST, 180s for stream silence) 5. **Connection pooling** with stale connection detection for REST 6. **Traffic activity** - don't leave connections idle for 10+ minutes *** ## ALB 10-Minute Timeout ### SocketError: "other side closed" every 10 minutes **Cause**: Application Load Balancer (ALB) timeout, set to 10 minutes by design. If your connection has no activity for 10 minutes, the ALB will close it. **Solution**: Implement automatic reconnection logic in your client. If you're not sending traffic for 10 minutes, your code should handle reconnect gracefully. **Note**: The 10-minute timeout is intentional and will not be increased. Proper client implementation (connection pooling, keepalives, reconnection) is the correct solution. *** ## gRPC Connection Issues ### 14 UNAVAILABLE Errors gRPC streams can drop with "14 UNAVAILABLE" errors for two reasons: **(a) 10-minute ALB timeout**: If you create a gRPC channel but don't open a stream immediately, or if there's no actual traffic on the stream for 10 minutes, the ALB will timeout the connection. **(b) Keepalives alone may not prevent timeout**: The ALB needs to see actual traffic, not just HTTP/2 PING frames. **Solutions**: * Implement automatic reconnection when streams drop (error code 14 or EOF) * Open streams promptly after creating channels * If you have long idle periods, consider periodic lightweight API calls to keep the connection active ### Recommended gRPC Keepalive Settings ```python theme={null} # Python example import grpc channel = grpc.secure_channel( 'grpc-preprod.polymarketexchange.com:443', grpc.ssl_channel_credentials(), options=[ ('grpc.keepalive_time_ms', 60000), # Send keepalive every 60s ('grpc.keepalive_timeout_ms', 20000), # Wait 20s for keepalive response ('grpc.keepalive_permit_without_calls', 1), # Send keepalives when idle ] ) ``` ```javascript theme={null} // Node.js example const grpc = require('@grpc/grpc-js'); const channel = new grpc.ChannelCredentials.createSsl(); const client = new YourServiceClient( 'grpc-preprod.polymarketexchange.com:443', channel, { 'grpc.keepalive_time_ms': 60000, 'grpc.keepalive_timeout_ms': 20000, 'grpc.keepalive_permit_without_calls': 1 } ); ``` **Note**: Keepalives help detect broken connections, but they may not prevent ALB timeouts if there's no actual API traffic for 10+ minutes. ### Stream Goes Dark (No Heartbeat for >180 Seconds) **Cause**: Connections being severed on the hosts. Recent infrastructure fixes should have resolved this issue. **Solution**: Implement 180-second timeout detection on your end. Trigger automatic reconnection when no data received for 180+ seconds. This error should be rare after recent stability improvements. *** ## REST Connection Errors ### ECONNRESET, EPIPE, "other side closed" **Cause**: Transient network issues, stale connections, or load balancer timeouts. **Solution**: Implement exponential backoff with 2-3 retries for connection errors: ```python theme={null} import time import requests from requests.exceptions import ConnectionError def make_request_with_retry(url, headers, data, max_retries=3): for attempt in range(max_retries): try: return requests.post(url, headers=headers, json=data, timeout=15) except ConnectionError as e: if attempt == max_retries - 1: raise wait_time = 2 ** attempt # 1s, 2s, 4s time.sleep(wait_time) ``` ### Stale Connection Handling Configure your HTTP client's connection pool to detect and remove closed connections before reuse: ```python theme={null} # Python with requests import requests from requests.adapters import HTTPAdapter from urllib3.util.retry import Retry session = requests.Session() retry = Retry( total=3, backoff_factor=1, status_forcelist=[502, 503, 504], allowed_methods=["GET", "POST"] ) adapter = HTTPAdapter(max_retries=retry, pool_connections=10, pool_maxsize=20) session.mount('https://', adapter) ``` ```javascript theme={null} // Node.js with Undici const { Pool } = require('undici'); const pool = new Pool('https://api.preprod.polymarketexchange.com', { connections: 10, pipelining: 1, keepAliveTimeout: 60000, keepAliveMaxTimeout: 600000 }); ``` ### Request Timeouts **Recommendation**: Add 15-second timeouts on client side to prevent hanging requests. A small percentage of calls may never resolve - timeout protection is essential. ```python theme={null} # Python response = requests.post(url, json=data, timeout=15) ``` ```javascript theme={null} // Node.js with fetch const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 15000); const response = await fetch(url, { signal: controller.signal, headers: headers, body: JSON.stringify(data) }); clearTimeout(timeoutId); ``` *** ## Connection Best Practices ### Use Batch Endpoints Batch endpoints reduce overall connection load significantly: * `/v1/trading/orders/list` - Retrieve multiple orders at once * `/v1/trading/orders/cancel/list` - Cancel multiple orders in one call ### Implement Connection Pooling * Reuse connections across requests * Configure appropriate pool sizes for your traffic patterns * Ensure stale connections are detected and recycled ### Avoid Creating Idle Connections * Don't create gRPC channels or HTTP connections unless you're actively using them * If you need to maintain a connection, ensure regular traffic (not just keepalives) *** ## Timeout / No Response If requests hang without returning a response: ### Step 1: Verify Your Token Ensure your access token is valid and not expired: ```python theme={null} import base64 import json # Decode the payload (middle part of JWT) payload = access_token.split('.')[1] payload += '=' * (4 - len(payload) % 4) # Add padding claims = json.loads(base64.urlsafe_b64decode(payload)) print("Token expires:", claims.get("exp")) print("Granted scopes:", claims.get("scope", "")) ``` ### Step 2: Check Endpoint URL Ensure you're using the correct API base URL for your environment: | Environment | API Base URL | | -------------- | -------------------------------------------- | | Pre-production | `https://api.preprod.polymarketexchange.com` | | Production | `https://api.prod.polymarketexchange.com` | ### Step 3: Verify Network Connectivity Test basic connectivity to the API: ```bash theme={null} curl -I https://api.preprod.polymarketexchange.com/v1/health ``` Expected response: `{"status":"ok"}` with HTTP 200. ## Error 464 When receiving a 464 error in prod or pre-prod environments, verify that the `Host` header is set correctly: ```bash theme={null} Host: api.preprod.polymarketexchange.com ``` **OR** ```bash theme={null} Host: api.prod.polymarketexchange.com ``` Without the correct host header, requests may be routed incorrectly. ## Connection Reset / Connection Refused This indicates network-level issues before reaching the API: **Possible causes:** 1. **DNS resolution failure** - Check that `api.{env}.polymarketexchange.com` resolves correctly 2. **TLS handshake failure** - Your client may not support required TLS version (TLS 1.2+) 3. **Firewall blocking** - Your network may block outbound HTTPS 4. **IP address blocked** - Your IP may be blocked for suspicious activity **Test connectivity:** ```bash theme={null} curl -v https://api.{env}.polymarketexchange.com/v1/health ``` The `-v` flag shows connection details to help identify where the failure occurs. ## Curl Command Issues ### Drops to Blinking Cursor If your curl command drops to a new line with a blinking cursor, this indicates a formatting issue with the command itself, not a connection problem. **Common causes:** * Missing quotation marks * Missing escape characters * Malformed command syntax **Example of correct curl formatting:** ```bash theme={null} curl -X POST https://api.preprod.polymarketexchange.com/v1/trading/orders \ -H "Authorization: Bearer YOUR_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "instrument": "tec-nfl-sbw-2026-02-08-kc", "side": "buy", "quantity": 100 }' ``` ## Platform-Specific Issues ### Windows and Python HTTP/2 Windows instances and Python applications may encounter HTTP/2 compatibility issues: **Windows**: Ensure HTTP/2 is enabled in your system settings. **Python**: Use the `httpx` library instead of `requests`: ```python theme={null} import httpx # Use httpx for HTTP/2 support with httpx.Client(http2=True) as client: response = client.get( "https://api.preprod.polymarketexchange.com/v1/health", headers={"Authorization": f"Bearer {token}"} ) ``` ### HTTP/2 vs HTTP/1.1 Both protocols are supported: * **HTTP/2**: Better performance for multiple concurrent requests * **HTTP/1.1**: Works fine for most use cases If you encounter issues with HTTP/2, you can force HTTP/1.1 in most HTTP clients. ## SSL/TLS Issues ### Certificate Verification Failures If you receive SSL certificate verification errors: **In production code**: Never disable certificate verification. This creates security vulnerabilities. **For debugging only**: You can temporarily disable verification to test connectivity: ```python theme={null} # DEBUGGING ONLY - DO NOT USE IN PRODUCTION import requests response = requests.get(url, verify=False) ``` **Proper solution**: Ensure your system has up-to-date CA certificates installed. ## Proxy and Firewall ### Behind a Corporate Proxy If you're behind a corporate proxy: ```bash theme={null} # Set proxy environment variables export HTTP_PROXY=http://proxy.example.com:8080 export HTTPS_PROXY=http://proxy.example.com:8080 # Or in Python proxies = { 'http': 'http://proxy.example.com:8080', 'https': 'http://proxy.example.com:8080', } response = requests.get(url, proxies=proxies) ``` ### Firewall Restrictions Ensure your firewall allows outbound HTTPS connections to: * `*.polymarketexchange.com` on port 443 * `*.auth0.com` on port 443 (for authentication) ## DNS Issues ### Cannot Resolve Hostname Test DNS resolution: ```bash theme={null} nslookup api.preprod.polymarketexchange.com # or dig api.preprod.polymarketexchange.com ``` If DNS fails, check: 1. Your DNS server configuration 2. Whether you can resolve other domains 3. Whether there's a local hosts file override ## Health Check Endpoint Use the health check endpoint to verify API availability: ```bash theme={null} curl https://api.{env}.polymarketexchange.com/v1/health ``` **Expected response**: `{"status":"ok"}` with HTTP 200 **No authentication required** - this endpoint is publicly accessible. If the health check fails, the API may be experiencing issues. Check the [status page](https://status.polymarketexchange.com) or contact support. # Connection Options Source: https://docs.polymarket.us/trader-guide/connection-options ## Connectivity Options The exchange provides two supported integration protocols for client applications: 1. **FIX Protocol** * **Type:** Financial Information eXchange (FIX) * **Usage:** Standardized message format for order entry, trade execution, and market data. * **Benefits:** Widely adopted in financial markets; high reliability for order flow and execution reporting. 2. **REST API with gRPC Streaming** * **Type:** HTTP-based REST interface combined with gRPC for real-time streaming. * **Usage:** REST for request/response operations (e.g., placing orders, querying market state) and gRPC for low-latency, bidirectional data streams. * **Benefits:** Simple integration for HTTP-based clients; gRPC enables efficient real-time data delivery. Both interfaces provide access to trading functionality and market data, allowing clients to select the protocol that best fits their integration requirements. # Environments Source: https://docs.polymarket.us/trader-guide/environments API endpoints for preprod and production environments The Polymarket Exchange API is available in two environments. Use the appropriate endpoints based on your integration stage. ## Endpoints | Environment | Purpose | REST API | gRPC | Auth Domain | | ----------- | ------------------------- | -------------------------------------------- | ----------------------------------------- | -------------------------- | | **Preprod** | Pre-production validation | `https://api.preprod.polymarketexchange.com` | `grpc-preprod.polymarketexchange.com:443` | `pmx-preprod.us.auth0.com` | | **Prod** | Production trading | `https://api.prod.polymarketexchange.com` | `grpc-prod.polymarketexchange.com:443` | `pmx-prod.us.auth0.com` | The Audience value for each environment matches its REST API base URL. ### API Path Patterns | API Type | Path Pattern | Example | | -------------- | --------------------------- | --------------------------------------------------- | | REST endpoints | `/v1/{service}/{operation}` | `/v1/trading/orders` | | Health check | `/v1/health` | `GET /v1/health` | | Auth token | `/oauth/token` | `POST https://pmx-preprod.us.auth0.com/oauth/token` | ### Authentication | Environment | Token URL | | ----------- | ---------------------------------------------- | | Preprod | `https://pmx-preprod.us.auth0.com/oauth/token` | | Prod | `https://pmx-prod.us.auth0.com/oauth/token` | **Tokens must be refreshed every 3 minutes** across all environments. ## Health Check All environments expose the same health check endpoint: ```bash theme={null} curl https://api.preprod.polymarketexchange.com/v1/health ``` ```json theme={null} { "status": "ok" } ``` ## AWS PrivateLink Connection **VPC connections are required only for FIX API access.** REST and gRPC APIs use the public endpoints listed above and do not require VPC setup. For secure, private connectivity from your AWS VPC for the FIX API, use AWS PrivateLink. This routes traffic over AWS's private network instead of the public internet. VPC Service Names and PrivateLink endpoints are provisioned per-firm during FIX onboarding. Contact [onboarding@polymarket.us](mailto:onboarding@polymarket.us) with your AWS Account ID to get started. ## Environment Progression We recommend the following integration progression: 1. **Preprod** - Integration testing and pre-launch validation 2. **Prod** - Live trading Contact [onboarding@polymarket.us](mailto:onboarding@polymarket.us) to request access credentials for each environment. # Error Handling Source: https://docs.polymarket.us/trader-guide/error-handling Understanding and handling API errors ## Connection-Level Errors Before receiving HTTP status codes, you may encounter connection-level errors: **Common connection errors:** * `ECONNRESET` - Connection reset by peer * `EPIPE` - Broken pipe * `ECONNREFUSED` - Connection refused * `ETIMEDOUT` - Connection timeout * `SocketError: other side closed` - ALB timeout (10 minutes) * `14 UNAVAILABLE` (gRPC) - Stream dropped See the full Connection Issues guide for detailed troubleshooting of network errors, ALB timeouts, gRPC streams, keepalive settings, and reconnection strategies *** ## HTTP Status Codes **4xx errors are client-side errors.** These indicate problems with your request that you need to fix (invalid data, missing authentication, wrong permissions, etc.). Debug these by examining your request. **5xx errors are server-side errors.** These indicate problems on the API server. Retry these with exponential backoff. ### 400 Bad Request Your request format is invalid. **Common causes:** * Invalid JSON format * Missing required fields * Invalid field values (wrong type, out of range, etc.) * Invalid parameter combinations **Solution**: Check the error message in the response for specific details about what's wrong. ### 401 Unauthorized Your JWT token is missing, invalid, or expired. **Solution**: Request a new token from Auth0 and retry your request. See [Authentication](/trader-guide/authentication-troubleshooting) for detailed troubleshooting. ### 403 Forbidden Your token is valid but doesn't have the required scope for the endpoint, or the `x-participant-id` header is missing or incorrect. **Common causes:** * Missing required scope for the endpoint * Missing `x-participant-id` header on an account-scoped endpoint (trading, positions, reports) * Incorrect `x-participant-id` value **Solution**: Verify your token scopes and ensure `x-participant-id` is included for account-scoped endpoints, exactly as it was given to you. Don't rebuild the value by hand from another response — a participant ID whose firm segment doesn't match your participant firm is rejected as a cross-firm request. See [Finding Your Participant ID](/trader-guide/accounts-identity#finding-your-participant-id) for where the value comes from. See [Authentication](/trader-guide/authentication-troubleshooting) for detailed troubleshooting. ### 404 Not Found The endpoint or resource doesn't exist. **Common causes:** * Incorrect API path * Resource ID doesn't exist * Typo in the URL **Solution**: Verify the endpoint path and resource ID are correct. ### 405 Method Not Allowed The HTTP method (GET, POST, etc.) is not supported for this endpoint. **Common causes:** * Using GET on a POST-only endpoint * Using POST on a GET-only endpoint **Solution**: Check the API documentation for the correct HTTP method. ### 409 Conflict The request conflicts with the current state of the resource. **Common causes:** * Duplicate order with same ClOrdID * Attempting to cancel an already-filled order * Resource already exists **Solution**: Check the current state of the resource and adjust your request. ### 413 Payload Too Large Your request body exceeds the maximum allowed size. **Common causes:** * Sending too many items in a bulk request * Large text fields or descriptions * Batch operations with too many records **Solution**: Reduce the request size or split into multiple smaller requests. ### 422 Unprocessable Entity Your request is well-formed but contains semantic errors. **Common causes:** * Invalid business logic (e.g., order quantity exceeds position limits) * Instrument not tradable in current state * Violates trading rules or risk limits **Solution**: Check the error message for specific validation failures. ### 429 Too Many Requests You've exceeded the rate limit. **Solution**: Check the `Retry-After` header in the response and wait that long before retrying. See [Rate Limits](/trader-guide/rate-limits) for details and troubleshooting. ### 500 Internal Server Error An unexpected error occurred on the server. **Solution**: These are usually temporary. Retry your request after a short delay (1-2 seconds). If errors persist for more than a few minutes, contact support. ### 502 Bad Gateway The API gateway cannot reach the backend service. **Solution**: This usually resolves within 60 seconds. Retry your request with exponential backoff. If issues persist, contact support. ### 503 Service Unavailable The API is temporarily unavailable. **Solution**: This usually resolves within 60 seconds. Retry your request with exponential backoff. If issues persist, check the [status page](https://status.polymarketexchange.com) or contact support. ### 504 Gateway Timeout Your request took too long to process (over 30 seconds). **Common causes:** * Large data queries without pagination * Complex report generation * Slow network connections **Solutions:** * Use pagination for large result sets (limit/offset parameters) * Break large operations into smaller requests * Download reports as files rather than querying all data * Consider polling for status updates instead of waiting synchronously ## Retry Strategy **Retry these errors** with exponential backoff: * **429** (rate limit) - Always retry with backoff * **500, 502, 503, 504** (server errors) - Retry up to 3-5 times **Don't retry these errors:** * **400** (bad request) - Fix your request instead * **401** (unauthorized) - Get a new token first * **403** (forbidden) - Request won't succeed without additional scopes * **404** (not found) - Resource doesn't exist * **405** (method not allowed) - Use the correct HTTP method * **409** (conflict) - Resolve the conflict first * **413** (payload too large) - Reduce request size * **422** (unprocessable entity) - Fix validation errors ## Exponential Backoff Implement this retry pattern: ``` Attempt 1: Immediate Attempt 2: Wait 1 second Attempt 3: Wait 2 seconds Attempt 4: Wait 4 seconds Attempt 5: Wait 8 seconds Max: 5 attempts, ~15 seconds total ``` For 429 errors, use the `Retry-After` header value instead of the exponential backoff schedule. ## Intermittent Failures **My request works sometimes but fails other times** This could indicate: * **Intermittent rate limiting** - You're close to the rate limit threshold * **Token expiration** - Your token expires mid-session * **Network issues** - Temporary connectivity problems **Solution**: Implement proper error handling with retries for transient errors (429, 502, 503, 504). ## Debugging Failed Requests **How to debug:** 1. **Check response body**: Error messages include details about what went wrong 2. **Log request/response**: Keep records of your API calls for debugging 3. **Test with curl**: Isolate whether the issue is with your code or the API 4. **Verify token**: Decode your JWT at jwt.io to check claims and expiration 5. **Check environment**: Ensure you're using the correct API endpoint for your environment ## Logging Best Practices **Which identifiers should be logged?** At minimum, log these identifiers for troubleshooting: * `accountId` - Trading account * `orderId` - Order identifier * `execId` - Execution identifier * `tradeId` - Trade identifier * `traceId` - Request trace ID (when present in responses) **Example log entry:** ``` 2024-01-15 10:30:45 | ORDER_SUBMITTED | accountId=acc_123 | orderId=ord_456 | instrument=tec-nfl-sbw-2026-02-08-kc | side=buy | qty=100 ``` **Should error text be used for logic?** No. Never parse error message text in your code. **Use instead:** * Structured error codes * HTTP status codes * Specific error fields in response Error message text may change. Error codes are stable. ## Reporting Issues Contact support with: * Environment (dev, preprod, prod) * Timestamp of the issue * Request details (endpoint, method, sanitized request body) * Response (status code, error message) * Your Client ID (never share your Client Secret) * Relevant identifiers (accountId, orderId, tradeId, traceId) # Health Check Source: https://docs.polymarket.us/trader-guide/health-check Monitor API service status The Health Check endpoint allows you to verify the API service status before making trading requests. ## Endpoint | Method | Endpoint | Description | | ------ | ------------ | --------------------------- | | `GET` | `/v1/health` | Check service health status | ## Request ```bash theme={null} curl -X GET "https://api.prod.polymarketexchange.com/v1/health" ``` No authentication is required for the health check endpoint. ## Response ```json theme={null} { "status": "ok" } ``` ### Response Fields | Field | Type | Description | | -------- | ------ | ---------------------------------- | | `status` | string | Service status (`ok` when healthy) | ## Use Cases ### Pre-flight Check Before initiating trading operations, verify the API is healthy: ```python theme={null} import requests def check_health(): response = requests.get("https://api.prod.polymarketexchange.com/v1/health") if response.status_code == 200 and response.json().get("status") == "ok": return True return False if check_health(): # Proceed with trading operations pass else: # Handle service unavailability pass ``` ### Monitoring Use the health endpoint for: * **Load balancer health checks** - Configure your load balancer to poll this endpoint * **Alerting systems** - Set up alerts when the endpoint fails * **Dashboard status** - Display real-time service status in your application ## Best Practices 1. **Don't over-poll** - Check health at reasonable intervals (e.g., every 30 seconds) 2. **Implement retry logic** - Transient failures can occur; retry before alerting 3. **Cache results** - Don't check health before every API call 4. **Handle gracefully** - If health check fails, queue operations and retry later # Market Data Source: https://docs.polymarket.us/trader-guide/market-data Accessing market data and order books ## Available Market Data **Instrument Reference Data**: Symbols and metadata, trading state and limits, contract specifications, expiration dates **Order Book Data**: Aggregated order book depth, best bid and offer (BBO), multiple price levels **Trading State**: Market status (open, closed, halted), trading hours, circuit breakers ## Live Market Data Delivery Market data is delivered via the **Market Data Subscription API** over long-lived HTTP connections. **Key characteristics**: Server pushes updates over persistent connection; snapshot-style updates (each message is complete); treat each message as a full update unless documentation specifies delta semantics; no WebSockets (uses HTTP streaming). ## Order Books **Are order books aggregated?** Yes. REST order book endpoints return aggregated depth only. Multiple orders at the same price level are combined; individual order IDs are not visible in market data; only aggregate quantity is shown per price level. **Price levels available**: Multiple levels of depth (configurable), best bid/offer always included, deeper book available with appropriate scopes. ## Price and Quantity Encoding **Are prices and quantities floating point?** No. All prices and quantities are **integer-encoded strings**. **Example:** ```json theme={null} { "price": "550", // Divide by price_scale (e.g., 550 / 1000 = 0.55) "quantity": "1000" // Integer quantity } ``` Always use string parsing for prices and quantities. Never use floating-point math for price calculations. ## Trade Tape **Is there a public trade tape?** No. Trades are not disseminated via market data APIs. You can only see your own trades via [Reporting APIs](/trader-guide/reporting) and your own executions via [Order subscriptions](/trader-guide/streaming-apis). ## Market Data Scopes **Basic market data** (`read:marketdata`): Best bid/offer, top-of-book snapshots, basic instrument data **Level 2 market data** (`read:l2marketdata`): Full order book depth, multiple price levels, aggregate depth **Reference data** (`read:instruments`): Instrument listings, metadata and specifications, trading schedules ## Subscribing to Market Data Use the Market Data Subscription API to receive updates: ``` POST /v1/marketdata/subscribe { "instruments": ["tec-nfl-sbw-2026-02-08-kc", "aec-nfl-buf-nyj-2025-01-15"], "dataType": "orderbook" } ``` The connection remains open and the server pushes updates as market conditions change. ## Update Frequency Market data updates are sent on every change to the order book, at regular intervals (even if no changes), and as snapshots (full book, not deltas). ## Best Practices **Cache reference data**: Instrument metadata changes infrequently. Cache it locally and refresh periodically (every 5-15 minutes). **Use streaming for real-time data**: Don't poll market data endpoints repeatedly. Use the subscription API for live updates. **Handle reconnections**: Streaming connections can drop. Implement automatic reconnection with exponential backoff. **Process snapshots correctly**: Each market data message is a complete snapshot. Replace your local book state with each update. ## Troubleshooting **Market data seems stale** Check: * Is your subscription still active? * Has the connection dropped? * Is the market actually open? **Missing price levels** Check: * Do you have the `read:l2marketdata` scope? * Are you requesting depth parameter correctly? * Is there actually liquidity at those levels? **Can't see my own orders in the book** Correct behavior. Market data shows aggregated depth only. To see your own orders, use the [Order Management](/trader-guide/order-management) APIs. # Onboarding Source: https://docs.polymarket.us/trader-guide/onboarding Get started with the Polymarket Exchange API **Individual traders:** You do not need to complete this onboarding process. Head to the [Retail Trading](/retail-api/overview) tab to get started. ## Step 1: Register Create your account through the [Polymarket Institutional registration portal](https://institutional.polymarketexchange.com/register). The portal walks you through setting up the credentials you need to access the API across each environment (development, pre-production, and production). ### API-Specific Requirements No additional setup is required beyond registering through the portal. If you intend to use the FIX API, include your **AWS Account ID** during registration so that a VPC PrivateLink connection can be established. You should still complete registration even if FIX is your primary connectivity method, as it is required for full platform functionality. ## Step 2: Submit Your Onboarding Documents Complete your application in the institutional registration portal, then review and sign the Entity Participant and Clearing Member Agreement: [Entity Participant Agreement](https://drive.google.com/uc?export=download\&id=1KTJaIlu_qONjnSlTwsdXnaQ3f7Vjl9xh) ## Step 3: Receive Your Credentials The Polymarket team will review your submission and provide your Client ID credentials for both pre-production and production environments. If you requested FIX connectivity, you will also receive your FIX connection details. See [FIX Connection Setup](/institutional/fix-api/fix-connection-setup) for VPC endpoint configuration and session setup. ## Step 4: Fund Your Account Your pre-production account will be funded with dummy funds for testing purposes. To begin trading on the production environment, fund your account via wire transfer: * [Inbound Wire Form](https://drive.google.com/uc?export=download\&id=1BJrmFYk1_RIZjj1tybNnqgRWJbdCNgr0) - Use this form to wire funds into your Polymarket account * [Outbound Wire Form](https://drive.google.com/uc?export=download\&id=1X0fC4kZzEj9-_ZXcbAIEr4YH0QuQUNos) - Use this form to withdraw funds from your Polymarket account Complete the appropriate form and follow the wire instructions provided. Funds are typically available for trading within 1-2 business days of receipt. # Order Management Source: https://docs.polymarket.us/trader-guide/order-management Best practices and troubleshooting for order operations ## Supported Order Types **Market-to-Limit**: Submits as market order; unfilled quantity converts to limit order; prevents walking the book excessively **Limit**: Standard limit order, executes at specified price or better, rests in book if not immediately filled **Stop**: Triggers market order when stop price reached; used for stop-loss scenarios **Stop-Limit**: Triggers limit order when stop price reached; provides price protection after trigger ## Time-In-Force Values **DAY**: Order expires at end of trading day; canceled automatically at market close **Good-Till-Cancel (GTC)**: Order remains active until filled or explicitly canceled; persists across trading days **Immediate-Or-Cancel (IOC)**: Execute immediately or cancel; partial fills allowed; remaining quantity canceled **Fill-Or-Kill (FOK)**: Execute entire order immediately or cancel; no partial fills; all-or-nothing **Good-Till-Time**: Expires at specified time; custom expiration timestamp ## Order Lifecycle Understanding the order lifecycle is critical for proper order management: 1. **New** - Order submitted but not yet acknowledged 2. **Pending** - Order accepted and waiting for execution 3. **Partially Filled** - Some quantity executed, remaining quantity active 4. **Filled** - Entire order quantity executed 5. **Canceled** - Order canceled before full execution 6. **Rejected** - Order rejected by the exchange ## Executions vs Trades **What is the difference between an execution and a trade?** * **Execution**: A state change on a single order (your order) * **Trade**: A matched event between two orders (aggressor and passive side) A trade contains two executions - one for each side of the match. **Are trades final immediately?** No. Trades progress through states: * **NEW**: Initial matched state * **CLEARED**: Cleared through DCO * **BUSTED**: Voided post-execution by the exchange and the position reversed; terminal, and rare. Busted trades stay in history — treat them as reversed, don't drop them. See [Trade States](/streaming-endpoints/dropcopy-stream#trade-states). **What identifies a trade uniquely?** `tradeId` - Use this for deduplication and reconciliation. ## Verifying Order Status **Important**: A returned order ID with HTTP 200 does **not** guarantee the order was successfully processed. Failed orders don't generate user-visible errors unless a required field is missing. **Always verify order status** via the order stream or by querying the order status endpoint. Do not rely solely on HTTP 200 responses or returned order IDs. ## Common Order Failures Orders can fail for several reasons: ### Invalid Account The account specified in the order doesn't exist or you don't have permission to trade on it. **Solution**: Verify the account ID is correct and you have trading permissions. ### Expired Instrument The instrument you're trying to trade has expired or is no longer active. **Solution**: Check the instrument status before placing orders. Use the reference data API to get current instrument information. ### Insufficient Balance Your account doesn't have enough funds to place the order. **Solution**: Check your account balance before placing orders. Consider the total notional value including fees. Note that risk checks are scoped per instrument - see [Open Orders and Order Collateralization](/market-structure/collateral-and-margin#open-orders-and-order-collateralization) for how open orders and buying power interact. ### Invalid Price The price is outside the allowed range for the instrument. **Common causes:** * Price too far from market price * Price increment doesn't match tick size * Price negative or zero for instruments that require positive prices **Solution**: Check current market prices and instrument specifications. ### Invalid Quantity The quantity doesn't meet instrument requirements. **Common causes:** * Quantity below minimum order size * Quantity above maximum order size * Quantity doesn't match lot size increment **Solution**: Review instrument specifications for min/max quantities and lot size. ## Pre-Trade Validation **Is pre-trade validation available?** Yes. Use the order preview endpoint: ```bash theme={null} POST /v1/trading/orders/preview { "accountId": "your-account-id", "instrument": "tec-nfl-sbw-2026-02-08-kc", "side": "buy", "quantity": 100, "price": "500" } ``` This validates the order without submitting it, checking account permissions, balance requirements, price and quantity validity, and instrument status. ## Order Submission **Is order submission synchronous?** Order submission is synchronous (you get an immediate response), but final acceptance may be asynchronous, rejection can occur after initial acceptance, and final state is delivered via order status updates. Always monitor order status via order status endpoint and order subscription stream. ## Partial Fills **How are partial fills represented?** Through executions: each execution updates cumulative quantity, remaining quantity calculated as original quantity minus cumulative quantity, order remains active until fully filled or canceled. **Monitoring fills**: Subscribe to order updates, track `cumulativeQty` and `remainingQty` fields, process execution reports as they arrive. ## Order Best Practices ### 1. Validate Before Submitting Pre-validate orders before submission: check account balance, verify instrument is active, ensure price is within valid range, confirm quantity meets requirements. ### 2. Use Order Preview Use the order preview endpoint to validate orders without submitting them. ### 3. Monitor Order Status Implement order status monitoring: subscribe to order status updates via streaming API, poll order status for important orders, track partial fills and remaining quantity, handle unexpected cancellations. In particular, the exchange automatically cancels resting orders that are no longer fully funded after a fill elsewhere in your account - see [Open Orders and Order Collateralization](/market-structure/collateral-and-margin#open-orders-and-order-collateralization). ### 4. Handle Rejections Gracefully When orders are rejected: log the rejection reason, don't automatically retry without fixing the issue, alert on repeated rejections, review rejection patterns to improve order logic. ## Modifying Orders ### Cancel and Replace To modify an existing order: 1. Cancel the original order 2. Wait for cancellation confirmation 3. Submit the new order with updated parameters **Don't submit the new order before confirming cancellation** - you may end up with both orders active. ### Bulk Cancellations When canceling multiple orders: use bulk cancel endpoints when available, handle partial success (some orders canceled, others failed), verify cancellation status for critical orders. ## Order Timing ### Market Hours Orders can only be placed during market hours. Check the instrument trading schedule: pre-market, regular trading hours, post-market, and closed periods. ### Order Expiration Set appropriate time-in-force (TIF) values: **Day** (order expires at end of trading day), **GTC** (Good-til-Canceled - order remains active until filled or canceled), **IOC** (Immediate-or-Cancel - execute immediately or cancel), **FOK** (Fill-or-Kill - execute entire order immediately or cancel). ## Rate Limits for Order Operations Order operations have specific rate limits: maximum orders per second, maximum cancellations per second, maximum modifications per second. Exceeding these limits results in 429 errors. See [Rate Limits](/trader-guide/rate-limits) for details. ## Order Fill Notifications Monitor order fills through **Order stream** (real-time updates on order status), **Trade stream** (individual fill notifications), and **Position updates** (reflected in position changes). **Don't rely on polling** - use streaming APIs for real-time order updates. ## Testing Order Logic ### Pre-production Environment Always test order logic in pre-production: same API behavior as production, test with dummy funds (no real money), verify order validation logic, test error handling. ### Order Scenarios to Test 1. **Valid orders** - Confirm successful submission and fills 2. **Invalid orders** - Verify proper error handling 3. **Partial fills** - Handle partial execution correctly 4. **Order modifications** - Test cancel/replace logic 5. **Rate limiting** - Verify backoff behavior 6. **Network failures** - Test retry logic 7. **Market closed** - Handle off-hours submissions ## Reporting Order Issues When reporting order-related issues, include order ID (if available), timestamp, order parameters (instrument, side, quantity, price), account ID, expected behavior vs. actual behavior, and environment (dev, preprod, prod). # Trader Guide Overview Source: https://docs.polymarket.us/trader-guide/overview Connect to the Polymarket Exchange for direct market access trading ## Who Is This For? The Trader Guide is designed for: * **Institutional traders** accessing markets directly * **Proprietary trading firms** executing strategies programmatically * **Market makers** providing liquidity * **Quantitative traders** building automated trading systems ## What You Can Do With trading access, you can: | Action | Description | Access Method | | ------------------------ | ---------------------------------------- | ------------------------ | | **Place Orders** | Submit limit orders, market orders | REST API, gRPC | | **Cancel Orders** | Cancel individual or bulk orders | REST API, gRPC | | **Monitor Positions** | Track your positions and balances | REST API, gRPC Streaming | | **Access Market Data** | Real-time quotes, order book, statistics | REST API, gRPC Streaming | | **Query Reference Data** | Instruments, symbols, metadata | REST API | ## Key Concepts ### Environments Polymarket Exchange provides multiple environments for testing and production: * **Development** - Internal testing environment * **Pre-production** - UAT environment with test funds * **Production** - Live trading environment View API endpoints and configuration ### Rate Limits API requests are subject to rate limits to ensure fair usage: * REST API: Request-based limits per endpoint * gRPC: Connection and message limits Understand rate limiting policies ### Connection Options Choose the right protocol for your use case: * **REST API** - Simple request/response for orders and queries * **gRPC** - High-performance streaming for market data and order updates * **FIX** - Industry-standard protocol for institutional trading Compare connection protocols ### Schema Strongly-typed instrument fields for safely identifying sports markets without parsing symbols or slugs. Identify markets via instrument metadata # Positions & Risk Source: https://docs.polymarket.us/trader-guide/positions-risk Position tracking and risk management ## Available Position Data **Net Position**: Current position quantity, long or short, per instrument **Cost Basis**: Average entry price, total cost, unrealized P\&L calculation **Realized P\&L**: Profit/loss from closed positions, per instrument and aggregate, intraday and cumulative **Position State**: Open positions, position changes from trades, real-time updates ## Intraday Updates **Are positions updated intraday?** Yes. Positions reflect real-time execution state, clearing state, and risk calculations. Positions update as orders fill, trades clear, and risk limits change. ## Position Streaming **Are position updates streamed?** Yes, via the **Positions Subscription API**. Subscribe to receive real-time position updates: ``` POST /v1/positions/subscribe { "accountId": "your-account-id" } ``` Updates are pushed whenever a trade executes, a position changes, or risk metrics update. ## Querying Positions **REST endpoint:** ```bash theme={null} GET /v1/positions?accountId=your-account-id ``` Returns current positions for the specified account: all open positions, position quantities, unrealized P\&L, and cost basis. ## Buying Power **What determines buying power?** Available cash balance, collateral value, margin requirements, and risk model calculations. **Checking buying power:** ```bash theme={null} GET /v1/accounts/accounts?accountId=your-account-id ``` Returns account details including current buying power. Open orders consume buying power per instrument, and resting orders that become unfunded after a fill are automatically canceled - see [Collateral and Margin](/market-structure/collateral-and-margin#open-orders-and-order-collateralization). ## Position Reconciliation **Best practices**: Subscribe to position updates for real-time state, periodically query positions endpoint to reconcile, use `tradeId` and `execId` for audit trail, compare position changes with execution records. ## Mark-to-Market Positions are marked-to-market using current market prices (for liquid instruments), settlement prices (for less liquid instruments), and daily official settlement. Unrealized P\&L is calculated from current mark vs. cost basis. ## Position Lifecycle 1. **Order fills** → Position opens or changes 2. **Trade clears** → Position confirmed 3. **Mark-to-market** → Unrealized P\&L updates 4. **Position closes** → Realized P\&L recorded 5. **Settlement** → Final P\&L determined ## Multiple Accounts If you manage multiple trading accounts: each account has independent positions, risk limits apply per account, query positions per account using `accountId`. ## Position Reports Download position reports for end-of-day positions, historical position snapshots, P\&L statements, and compliance reporting. See [Reporting](/trader-guide/reporting) for report access. ## Ledger Access For full audit-grade history of every position change and every cash event, use the ledger APIs (covered by the `read:positions` scope, with a hard historical floor of `2026-05-01`): * **[Position Ledger](/institutional/positions/overview#position-ledger)** — `GET /v1/positions/ledger` (paginated) and `/download` (CSV) return every position change with both deltas (`quantityChange`, `costChange`, `realizedChange`) and the cumulative state after each change. * **[Balance Ledger](/institutional/funding/overview)** — `GET /v1/funding/balance-ledger` (paginated) and `/download` (CSV) return every cash balance change with `before_balance` / `after_balance` and a typed `entry_type` (deposits, withdrawals, fills, fees, adjustments, …). * **[Balance Ledger Stream](/streaming-endpoints/balance-ledger-stream)** — gRPC `CreateBalanceLedgerSubscription` pushes the same balance ledger entries in real time, with `resume_time` for gap-free reconnection. ## Troubleshooting **Position doesn't match expectations** Check: * Have all trades cleared? * Are you looking at the correct account? * Is there a pending order that will affect the position? * Have you received all position updates? **Unrealized P\&L calculation differs** Verify: * What mark price is being used? * What cost basis is being used? * Are fees included in P\&L? **Can't place order due to risk limits** Check current position size, order size would exceed limits, available buying power, and account status (active, suspended, etc.). # Quickstart Source: https://docs.polymarket.us/trader-guide/quickstart Place your first order in 5 minutes This guide walks you through placing your first order on Polymarket US. By the end, you'll have: 1. Authenticated with the API 2. Listed available instruments 3. Checked your account balance 4. Placed a limit order **Prerequisites:** Complete [Onboarding](/trader-guide/onboarding) first to generate your keys and receive your Client ID. ## Step 1: Get an Access Token Create a signed JWT and exchange it for an access token: ```python theme={null} import jwt import uuid import time import requests from cryptography.hazmat.primitives import serialization # Your credentials (from onboarding) AUTH_DOMAIN = "pmx-preprod.us.auth0.com" CLIENT_ID = "your_client_id" AUDIENCE = "https://api.preprod.polymarketexchange.com" PRIVATE_KEY_PATH = "private_key.pem" def get_access_token(): # Load private key with open(PRIVATE_KEY_PATH, 'rb') as f: private_key = serialization.load_pem_private_key(f.read(), password=None) # Create JWT assertion now = int(time.time()) claims = { "iss": CLIENT_ID, "sub": CLIENT_ID, "aud": f"https://{AUTH_DOMAIN}/oauth/token", "iat": now, "exp": now + 300, "jti": str(uuid.uuid4()), } assertion = jwt.encode(claims, private_key, algorithm="RS256") # Exchange for access token response = requests.post( f"https://{AUTH_DOMAIN}/oauth/token", json={ "client_id": CLIENT_ID, "client_assertion_type": "urn:ietf:params:oauth:client-assertion-type:jwt-bearer", "client_assertion": assertion, "audience": AUDIENCE, "grant_type": "client_credentials" } ) response.raise_for_status() return response.json()["access_token"] # Get token access_token = get_access_token() print("Got access token!") ``` **Required packages:** ```bash theme={null} pip install PyJWT cryptography requests ``` ## Step 2: Verify Authentication Check that your token works: ```python theme={null} BASE_URL = "https://api.preprod.polymarketexchange.com" PARTICIPANT_ID = "firms/YourFirm/users/your-user" # From onboarding, or from KYC approval for an end user headers = { "Authorization": f"Bearer {access_token}", "Content-Type": "application/json", "x-participant-id": PARTICIPANT_ID } # Check who you are response = requests.get(f"{BASE_URL}/v1/whoami", headers=headers) response.raise_for_status() print(f"Authenticated as: {response.json()}") ``` **The `x-participant-id` header is required** for all account-scoped endpoints (trading, positions, reports). You are given this value rather than discovering it: it comes from onboarding for your own users, or as `participantId` on KYC approval for an end user you onboard. Don't construct it by hand — see [Finding Your Participant ID](/trader-guide/accounts-identity#finding-your-participant-id). Expected response: ```json theme={null} { "user": "firms/Acme-Trading/users/trader1", "userDisplayName": "Trader 1", "firm": "Acme-Trading", "firmDisplayName": "Acme Trading", "firmType": "FIRM_TYPE_TRADER" } ``` ## Step 3: List Available Instruments Find instruments to trade: ```python theme={null} # List all tradable instruments response = requests.post( f"{BASE_URL}/v1/refdata/instruments", headers=headers, json={"tradable_filter": "TRADABLE_FILTER_TRADABLE"} ) response.raise_for_status() instruments = response.json().get("instruments", []) print(f"Found {len(instruments)} instruments") # Show first 5 for inst in instruments[:5]: print(f" {inst['symbol']}: {inst.get('description', 'N/A')}") ``` Cache the `price_scale` for each instrument - you'll need it to convert prices. ## Step 4: Check Your Balance Verify you have funds to trade: ```python theme={null} # Get account balance response = requests.post( f"{BASE_URL}/v1/positions/balance", headers=headers, json={} ) response.raise_for_status() balance = response.json() print(f"Available balance: {balance}") ``` ## Step 5: Place Your First Order Place a limit order to buy: ```python theme={null} import uuid # Choose an instrument (use one from Step 4) symbol = "tec-nfl-sbw-2026-02-08-kc" # Kansas City Chiefs to win Super Bowl 2026 price_scale = 100 # Get this from instrument metadata # Order parameters order = { "clord_id": str(uuid.uuid4()), # Your unique order ID "symbol": symbol, "side": "SIDE_BUY", "type": "ORDER_TYPE_LIMIT", "time_in_force": "TIME_IN_FORCE_GOOD_TILL_CANCEL", "order_qty": 10, # Quantity to buy "price": int(0.50 * price_scale), # $0.50 as integer } # Submit order response = requests.post( f"{BASE_URL}/v1/trading/orders", headers=headers, json=order ) if response.status_code == 200: result = response.json() print(f"Order placed successfully!") print(f" Order ID: {result['order']['id']}") print(f" State: {result['order']['state']}") else: print(f"Order failed: {response.text}") ``` Expected response: ```json theme={null} { "order": { "id": "ord_abc123", "clord_id": "your-uuid", "symbol": "tec-nfl-sbw-2026-02-08-kc", "side": "SIDE_BUY", "state": "ORDER_STATE_NEW", "order_qty": 10, "price": 5000, "leaves_qty": 10, "cum_qty": 0 } } ``` ## Step 6: Check Your Order Verify your order is in the book: ```python theme={null} # Search for your orders response = requests.post( f"{BASE_URL}/v1/report/orders/search", headers=headers, json={ "symbols": [symbol], "state_filter": "ORDER_STATE_FILTER_OPEN" } ) response.raise_for_status() orders = response.json().get("orders", []) print(f"You have {len(orders)} open orders") for order in orders: price = order['price'] / price_scale print(f" {order['id']}: {order['side']} {order['order_qty']} @ ${price:.2f}") ``` ## Step 7: Cancel Your Order (Optional) Cancel the order if you don't want it to execute: ```python theme={null} # Cancel by order ID response = requests.post( f"{BASE_URL}/v1/trading/orders/cancel", headers=headers, json={ "order_id": "ord_abc123" # Use the order ID from Step 6 } ) if response.status_code == 200: print("Order cancelled successfully!") else: print(f"Cancel failed: {response.text}") ``` ## Complete Example Here's the full working script: ```python theme={null} #!/usr/bin/env python3 """Polymarket US API Quickstart - Place your first order""" import jwt import uuid import time import requests from cryptography.hazmat.primitives import serialization # Configuration AUTH_DOMAIN = "pmx-preprod.us.auth0.com" CLIENT_ID = "your_client_id" # From onboarding AUDIENCE = "https://api.preprod.polymarketexchange.com" PRIVATE_KEY_PATH = "private_key.pem" BASE_URL = "https://api.preprod.polymarketexchange.com" PARTICIPANT_ID = "firms/YourFirm/users/your-user" # From onboarding def get_access_token(): with open(PRIVATE_KEY_PATH, 'rb') as f: private_key = serialization.load_pem_private_key(f.read(), password=None) now = int(time.time()) claims = { "iss": CLIENT_ID, "sub": CLIENT_ID, "aud": f"https://{AUTH_DOMAIN}/oauth/token", "iat": now, "exp": now + 300, "jti": str(uuid.uuid4()), } assertion = jwt.encode(claims, private_key, algorithm="RS256") response = requests.post( f"https://{AUTH_DOMAIN}/oauth/token", json={ "client_id": CLIENT_ID, "client_assertion_type": "urn:ietf:params:oauth:client-assertion-type:jwt-bearer", "client_assertion": assertion, "audience": AUDIENCE, "grant_type": "client_credentials" } ) response.raise_for_status() return response.json()["access_token"] def main(): # 1. Get access token print("1. Authenticating...") token = get_access_token() headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json", "x-participant-id": PARTICIPANT_ID} print(" Authenticated!") # 2. Verify identity print("\n2. Checking identity...") resp = requests.get(f"{BASE_URL}/v1/whoami", headers=headers) resp.raise_for_status() print(f" Logged in as: {resp.json()}") # 3. List instruments print("\n3. Listing instruments...") resp = requests.post( f"{BASE_URL}/v1/refdata/instruments", headers=headers, json={"tradable_filter": "TRADABLE_FILTER_TRADABLE", "page_size": 5} ) resp.raise_for_status() instruments = resp.json().get("instruments", []) print(f" Found {len(instruments)} instruments (showing first 5)") if not instruments: print(" No instruments available. Exiting.") return # Use first instrument symbol = instruments[0]["symbol"] price_scale = instruments[0].get("price_scale", 100) print(f" Using: {symbol} (price_scale={price_scale})") # 4. Place order print("\n4. Placing order...") order = { "clord_id": str(uuid.uuid4()), "symbol": symbol, "side": "SIDE_BUY", "type": "ORDER_TYPE_LIMIT", "time_in_force": "TIME_IN_FORCE_GOOD_TILL_CANCEL", "order_qty": 10, "price": int(0.50 * price_scale), } resp = requests.post(f"{BASE_URL}/v1/trading/orders", headers=headers, json=order) if resp.status_code == 200: result = resp.json() order_id = result["order"]["id"] print(f" Order placed: {order_id}") print(f" State: {result['order']['state']}") # 5. Cancel order print("\n5. Cancelling order...") resp = requests.post( f"{BASE_URL}/v1/trading/orders/cancel", headers=headers, json={"order_id": order_id} ) if resp.status_code == 200: print(" Order cancelled!") else: print(f" Cancel failed: {resp.text}") else: print(f" Order failed: {resp.text}") print("\nQuickstart complete!") if __name__ == "__main__": main() ``` ## Next Steps Token refresh and key rotation Real-time order and market data Complete endpoint documentation Usage limits and best practices # Rate Limits Source: https://docs.polymarket.us/trader-guide/rate-limits API rate limits and best practices for integration The Polymarket US API enforces rate limits to ensure fair usage and system stability. All limits are **per participant firm** unless stated otherwise. ## REST API ### Trading Endpoints REST API traffic is subject to a **firm-wide cap of 100 requests per second per firm**, averaged over a 1-minute window. This means short bursts above 100 req/sec are permitted as long as the average stays within budget. Some REST endpoints may also enforce additional lower limits, such as the query/report endpoints listed below. RFQ requests use the limits in [RFQ Endpoints](#rfq-endpoints). ### Query / Report Endpoints In addition to the firm-wide REST cap above, these read-heavy endpoints have lower per-firm limits. Cache responses where noted. | Endpoint | Limit | Notes | | ------------------ | ---------- | ----------------------------------- | | `GetTradeStats` | 60 req/min | Heavy aggregation query | | `ListInstruments` | 6 req/min | Static data - cache client-side | | `ListSymbols` | 6 req/min | Static data - cache client-side | | `GetOrderBook` | 12 req/min | Prefer streaming for real-time data | | `GetBBO` | 12 req/min | Prefer streaming for real-time data | | `SearchOrders` | 12 req/min | Use filters to narrow results | | `SearchExecutions` | 12 req/min | Use filters to narrow results | | `SearchTrades` | 12 req/min | Use filters to narrow results | ### Combos Endpoints In addition to the firm-wide REST cap above, Combos endpoints have these per-firm limits: | Endpoint | Limit | Notes | | ------------- | ----------- | ------------------------------------ | | `GetCombos` | 100 req/sec | Exact combo lookup | | `CreateCombo` | 1 req/sec | Create or retrieve a canonical combo | ### RFQ Endpoints The institutional `polymarket.v1.RFQAPI` endpoints have these per-firm limits, shared across REST and unary gRPC requests: | Endpoint | Limit | Notes | | -------------- | ----------------- | ----------------------------------------------- | | `GetRFQUserID` | 1 req/sec | RFQ user ID lookup | | `GetRFQs` | 10 req/sec | Prefer `StreamRFQEvents` for live RFQ changes | | `GetQuotes` | 10 req/sec | Prefer `StreamRFQEvents` for live quote changes | | `CreateRFQ` | 1 req/sec | RFQ creation | | `DeleteRFQ` | 100 req/sec | Close an open RFQ | | `CreateQuote` | 400–2,000 req/sec | Quote creation; determined by RFQ tier | | `DeleteQuote` | 400–2,000 req/sec | Quote deletion; determined by RFQ tier | | `AcceptQuote` | 100 req/sec | Quote acceptance | | `ConfirmQuote` | 100 req/sec | Last-look quote confirmation | Each row above has a separate per-firm endpoint bucket with one second of burst capacity. Traffic to one method does not consume another method's endpoint-specific allowance. Exchange-wide limits also apply. #### RFQ rate-limit tiers Your RFQ tier determines your `CreateQuote` and `DeleteQuote` limits. Each method has its own allowance: | Tier | `CreateQuote` | `DeleteQuote` | | ------ | ------------- | ------------- | | Tier 1 | 400 req/sec | 400 req/sec | | Tier 2 | 600 req/sec | 600 req/sec | | Tier 3 | 800 req/sec | 800 req/sec | | Tier 4 | 2,000 req/sec | 2,000 req/sec | #### RFQ volume share & tier requirements Your RFQ volume share is your RFQ-originated maker contracts over the trailing 30 days divided by the total RFQ-originated maker contracts across all firms, including retail participants, over the same period. A fill counts when its passive (maker) order originated from an RFQ. Each filled contract counts once, regardless of its price or the number of combo legs. Once you meet the Earn volume share for a given tier, you are eligible for that tier's RFQ rate limits. You must keep at least the Maintain volume share to stay eligible for your current tier. Maintain is 80% of Earn. If you drop below the Maintain volume share, your rate limit will not drop immediately. You will have 30 days to get back to at least the Maintain volume share before being moved to a lower tier. | Tier | Earn | Maintain | | ------ | ---- | -------- | | Tier 1 | N/A | N/A | | Tier 2 | 0.5% | 0.4% | | Tier 3 | 1% | 0.8% | | Tier 4 | 5% | 4% | RFQ and FIX tiers use separate volume measures and eligibility thresholds. ### Public (Unauthenticated) Endpoints | Limit | Value | | ----------------------- | --------- | | Max requests per second | 20 per IP | ## gRPC Streaming | Setting | Value | | --------------------------------------------------- | ----------- | | Max concurrent streams per firm | 20 | | Ingress message rate (per firm, across all streams) | 100 msg/sec | | `StreamRFQEvents` new stream opens per firm | 1/sec | | Egress (server to client) | Unlimited | Ingress rate is averaged over a 1-minute window, allowing short bursts. Exceeding the average limit will result in throttled or rejected messages. This limit applies to all participants. The `StreamRFQEvents` limit is checked only when opening a stream. It does not limit server-pushed RFQ or quote events on an established stream. ## FIX Protocol FIX rate limiting is enforced at the FIX gateway level. Rate limits are determined by tier. | Tier | Rate limit | | ------ | ----------------------- | | Tier 1 | 35 msg/sec per session | | Tier 2 | 150 msg/sec per session | | Tier 3 | 300 msg/sec per session | | Tier 4 | 500 msg/sec per session | Tiers are determined by your trading volume share on Polymarket US. ### Volume share & tier requirements Each day, Polymarket US sums your 30-day trailing notional volume (contracts × execution price) and divides it by the same figure across the exchange. This number is your volume share. Once you meet the Earn volume share for a given tier, you are automatically eligible for the tier's rate limit. You must keep at least the Maintain volume share to stay eligible for your current tier. If you drop below the Maintain volume share, your rate limit will not drop immediately. You will have 30 days to get back above the Maintain volume share before being moved to a lower tier. Each tier has its own Earn and Maintain volume share requirement, seen below. | Tier | Earn | Maintain | | ------ | ------ | -------- | | Tier 1 | N/A | N/A | | Tier 2 | 0.125% | 0.10% | | Tier 3 | 0.50% | 0.225% | | Tier 4 | 1.50% | 1.20% | **Accelerated Tier Placement:** A Participant may provide verifiable proof of their trailing-30-day notional trading volume on another prediction market venue and be assigned to the tier corresponding to that volume. ## Summary | Protocol | Scope | Limit | | ------------------------------------- | ---------------------- | ---------------------------------------- | | REST - trading (orders) | Per firm | 100 req/sec (1-min avg) | | REST - query endpoints | Per firm, per endpoint | 0.5–60 req/min (see table above) | | REST - combos and RFQ unary endpoints | Per firm, per endpoint | 1–2,000 req/sec (see tables above) | | REST - public/unauth | Per IP | 20 req/sec | | gRPC - `StreamRFQEvents` opens | Per firm | 1 new stream/sec | | gRPC streaming (ingress) | Per firm (all streams) | 100 msg/sec (1-min avg) | | gRPC streaming (egress) | Per firm | Unlimited | | FIX | Per session | 35–500 msg/sec by tier (see table above) | ## Rate Limit Response When rate limited, the REST API returns: ```json theme={null} { "code": 8, "message": "rate limit exceeded", "details": [] } ``` **HTTP Status:** `429 Too Many Requests` ### Retry Strategy When receiving a 429 response: 1. Stop making requests immediately 2. Wait 1 second before retrying 3. Implement exponential backoff for repeated 429s 4. Consider reducing your request rate ```python theme={null} import time def make_request_with_retry(url, headers, max_retries=3): for attempt in range(max_retries): response = requests.get(url, headers=headers) if response.status_code == 429: wait_time = 2 ** attempt # 1, 2, 4 seconds print(f"Rate limited. Waiting {wait_time}s...") time.sleep(wait_time) continue return response raise Exception("Max retries exceeded") ``` ## Latency Stopgap on Orders During periods of increased latency, Polymarket US applies a **5-second stopgap** to inbound orders. If an order has been received by Polymarket US but has not been processed within 5 seconds, we reject it to protect you from a bad fill at a stale price. These rejects carry the message **`Global Rate Limit Exceeded`**, but they are **not** an actual rate limit. You do **not** need to throttle your traffic in response to them. Treat them as a transient latency reject, not a signal to back off. What it applies to: * **New orders** — rejected if not processed within 5 seconds. * **Order modifications via cancel/replace** — also subject to the stopgap. * **Pure cancels are not affected** — a standalone cancel is never rejected by this stopgap. You can always cancel an order before you have received an acknowledgement, and even before it has been processed. ## Best Practices ### Use Streaming Instead of Polling The API is designed as a **streaming-first** system. Instead of repeatedly polling for updates, subscribe to real-time streams: | Don't Poll | Use Streaming Instead | | ------------------------------------------------- | ------------------------------------------ | | Repeated calls to `/v1/report/orders/search` | `CreateOrderSubscription` gRPC stream | | Repeated calls to `/v1/rfqs` or `/v1/rfqs/quotes` | `StreamRFQEvents` gRPC stream | | Repeated calls to `/v1/positions` | `CreatePositionSubscription` gRPC stream | | Repeated calls to `/v1/orderbook` | `CreateMarketDataSubscription` gRPC stream | Streaming connections don't count against the REST rate limit. One streaming connection can replace hundreds of polling requests. New `StreamRFQEvents` connections are limited to one open attempt per second per firm, so reconnect with backoff. ### Cache Reference Data Reference data (instruments, symbols, metadata) changes infrequently - `ListInstruments` and `ListSymbols` are limited to just 6 req/min. Cache responses locally: ```python theme={null} class InstrumentCache: def __init__(self): self.instruments = {} self.last_refresh = None def get_instrument(self, symbol): # Refresh cache every 5 minutes if self._needs_refresh(): self._refresh_instruments() return self.instruments.get(symbol) def _needs_refresh(self): if not self.last_refresh: return True return (time.time() - self.last_refresh) > 300 def _refresh_instruments(self): response = api.list_instruments() for inst in response.instruments: self.instruments[inst.symbol] = inst self.last_refresh = time.time() ``` ### Batch Operations Where possible, batch your operations instead of making individual requests: * Use `SearchOrders` with filters instead of fetching orders one by one * Use `ListInstruments` with symbol filters instead of individual lookups * Subscribe to multiple symbols in a single streaming connection ## Monitoring Your Usage Track your request patterns to stay within limits: ```python theme={null} import time from collections import deque class RateLimiter: def __init__(self, max_requests=100, window_seconds=1): self.max_requests = max_requests self.window = window_seconds self.requests = deque() def can_make_request(self): now = time.time() # Remove old requests outside the window while self.requests and self.requests[0] < now - self.window: self.requests.popleft() return len(self.requests) < self.max_requests def record_request(self): self.requests.append(time.time()) def wait_if_needed(self): while not self.can_make_request(): time.sleep(0.05) # 50ms self.record_request() ``` ## Abuse Prevention Patterns that may result in temporary or permanent restrictions: * Sustained requests above the rate limit * Polling for data available via streaming * Requesting the same unchanged data repeatedly * Automated retry loops without backoff Abuse of the API may result in temporary or permanent restrictions on your API credentials. Contact [onboarding@polymarket.us](mailto:onboarding@polymarket.us) if you need higher limits for legitimate use cases. ## Troubleshooting Rate Limits ### Consistently Hitting Limits If you're consistently receiving 429 errors: * Reduce request frequency * Batch multiple operations where possible * Cache responses that don't change frequently (reference data, instrument lists) * Use streaming endpoints instead of polling * Contact support to discuss higher rate limits for production use ### Need Higher Limits For production use cases requiring higher limits: 1. Document your use case and expected volume 2. Contact support at [onboarding@polymarket.us](mailto:onboarding@polymarket.us) 3. Provide environment (dev, preprod, prod) 4. Specify which endpoints you need higher limits for ## Next Steps Replace polling with real-time streams Set up API authentication # Reporting Data Source: https://docs.polymarket.us/trader-guide/reporting Historical data, trade searches, and drop copy ## Reporting APIs Reporting APIs provide query-based access to historical data for reconciliation, compliance reporting, historical analysis, and CSV exports. ## Trade Search **What does `/v1/report/trades/search` return?** It returns only trades associated with the authenticated participant's authorized accounts. **Does it return all exchange trades?** No. You only see trades for accounts you have access to. **Query capabilities**: Filter by time range, filter by instrument, filter by account, paginated results. **Example:** ```bash theme={null} POST /v1/report/trades/search { "accountId": "your-account-id", "startTime": "2024-01-01T00:00:00Z", "endTime": "2024-01-31T23:59:59Z", "instrument": "tec-nfl-sbw-2026-02-08-kc" } ``` ## Order Search Search for historical orders: all order states (filled, canceled, rejected), filter by instrument/time/status, include execution details, paginated results. ## Report Downloads Download reports as CSV files: trade reports, execution reports, position reports, and fee reports. CSV exports are useful for importing into spreadsheets, compliance record-keeping, and third-party analysis tools. ### Ledger CSV Exports Two streaming CSV endpoints expose the full position and cash audit trail: | Endpoint | Description | | ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | | `GET /v1/positions/ledger/download` | Every position change (deltas + cumulative state). See [Position Ledger](/institutional/positions/overview#position-ledger). | | `GET /v1/funding/balance-ledger/download` | Every cash balance change (`before_balance` / `after_balance` + typed `entry_type`). See [Balance Ledger](/institutional/funding/overview). | Both require the `read:positions` scope and are subject to a per-firm rate limit of \~5 downloads per minute. The historical floor is `2026-05-01`; pre-floor data is not retrievable. ## Real-Time vs Historical **Are reporting APIs real-time?** No. Reporting APIs are query-based (not streaming), paginated (for large result sets), and designed for historical analysis. For real-time updates, use [Streaming APIs](/trader-guide/streaming-apis) for orders and positions and [Drop Copy](#drop-copy) for authoritative post-trade feed. ## Drop Copy **What is Drop Copy?** A real-time, authoritative post-trade feed delivering executions, trades, position changes, and instrument state changes. **Who should use Drop Copy?** ISVs building trading platforms, IBs managing customer order flow, FCMs requiring authoritative execution records, and any participant needing real-time post-trade data. **Is Drop Copy redundant with reporting APIs?** No. **Drop Copy** is a real-time feed of events as they happen, while **Reporting APIs** are historical queries of past events. Use Drop Copy for real-time operations and reporting APIs for historical analysis. **Are Drop Copy streams resumable?** Yes, where supported. Clients should persist resume state (token or sequence number), resume from last processed event after reconnection, and handle at-least-once delivery (deduplicate using IDs). ## Pagination **Are search APIs paginated?** Yes. All search endpoints use pagination: ```json theme={null} { "results": [...], "nextPageToken": "abc123..." } ``` **How to paginate:** 1. Make initial request 2. Check for `nextPageToken` in response 3. Include token in next request 4. Repeat until `nextPageToken` is null/absent **Example pagination:** ```python theme={null} all_trades = [] page_token = None while True: response = api.search_trades( account_id=account_id, page_token=page_token ) all_trades.extend(response['trades']) page_token = response.get('nextPageToken') if not page_token: break ``` ## Large Time Ranges **Can large time ranges be queried?** Yes, but large ranges may require many pages; consider incremental querying (query each day separately), be mindful of rate limits, and cache results to avoid repeated queries. **Recommendation:** Query incrementally and store results locally rather than querying large ranges repeatedly. ## Data Retention Historical data is retained according to regulatory requirements. Contact support for specific retention policies. ## Trade Identification **What identifies a trade uniquely?** `tradeId` - Use this for deduplication and reconciliation. **Pagination must use** `pageToken` not trade IDs. Don't attempt to paginate by incrementing trade IDs. ## Execution vs Trade **What is the difference?** An **execution** is a state change on a single order, while a **trade** is a matched event between two orders (aggressor and passive side). A trade contains two executions (one for each side). **Are trades final immediately?** No. Trades progress through states: **NEW** (initial matched state), **CLEARED** (cleared through DCO), **BUSTED** (voided post-execution by the exchange and reversed; terminal and rare — busted trades stay in history, so treat them as reversed rather than dropping them). See [Trade States](/streaming-endpoints/dropcopy-stream#trade-states) for the full list. ## Reporting Best Practices **Query incrementally**: Query recent data frequently, archive older data locally. **Use appropriate time ranges**: Don't query months of data repeatedly. Cache and query incrementally. **Monitor pagination**: Always follow `nextPageToken` until exhausted. **Reconcile using IDs**: Use `tradeId` and `execId` for reconciliation, not timestamps. **Export to CSV**: For compliance and record-keeping, use CSV export endpoints. # Sports Schema Source: https://docs.polymarket.us/trader-guide/sports-schema Common instrument fields for safely identifying sports markets This page documents the strongly-typed fields on the instrument that should be used to identify a sports market. **Do not parse the instrument symbol or slug** — symbol formats are not part of the public contract and may change. Always identify markets using the fields below. ## Section 1: Common Identifying Fields The following fields appear on every sports instrument. Together they uniquely describe the sport, league, event, period, market type, and handicap of a contract. ### Field Reference | Field | Location | Type | Description | | --------------------- | ---------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `event_start_time` | `metadata` | Timestamp (UTC) | Scheduled start time of the underlying event. Updated in place when a game is rescheduled. | | `event_subcategory` | `metadata` | Enum | The sport. Values: `BASKETBALL`, `FOOTBALL`, `BASEBALL`, `SOCCER`, etc. | | `outcome_type` | `metadata` | Enum | The market structure. Still present on every instrument, but **do not use it for market mapping** — use `market_sport_type` instead. See the enum list below. | | `market_sport_type` | `metadata` | Enum | The fully-qualified market type, including sport, participant scope, and game period. **Always populated on every sports instrument**, including full-game spreads, totals, and outright winners. See the enum list below. | | `outcome_strike` | `metadata` | String (numeric) | The handicap or threshold for the market. Unsigned magnitude — the side the contract pays out on is encoded by `long_participant_id`. For a Cleveland +3.5 spread, this is `"3.5"`. For an over/under total of 4.5, this is `"4.5"`. Empty/`"0.0"` for moneylines. | | `long_participant_id` | `metadata` | String | Globally unique ID of the side the contract pays out on (e.g. `mlb-cle`). Only populated on individual game markets (moneylines, spreads, totals, props) — do not rely on it for futures. | ### `outcome_type` Enum The market structure of a sports instrument. | Value | Meaning | | ------------------ | ---------------------------------------------------------------------- | | `moneyline` | Win/lose market (no handicap). | | `spreads` | Point/goal/run handicap market. | | `totals` | Over/under combined-score market. | | `props` | Player or team prop. | | `futures` | Season-long or event-long futures market. | | `drawable_outcome` | Exclusive group market that admits a draw (e.g. 3-way soccer outcome). | ### `market_sport_type` Enum The fully-qualified market type, including sport, participant scope, and game period. This field is populated on **every** sports instrument — full-game spreads, totals, and outright winners included — so you can treat it as the single source of truth for any market. **You no longer need to check `outcome_type` to map a market**; `market_sport_type` alone is sufficient. Some values are registered before their first listing; treat every value in this table as one that can appear on an instrument. | Value | Sport | Scope | Period | Type | | --------------------------------------------------------- | ------------ | ------- | --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `baseball_player_home_runs` | Baseball | Player | Full game | Home runs prop | | `baseball_player_strikeouts` | Baseball | Player | Full game | Strikeouts prop | | `baseball_player_hits` | Baseball | Player | Full game | Hits prop | | `baseball_player_total_bases` | Baseball | Player | Full game | Total bases prop | | `baseball_player_hits_runs_rbis` | Baseball | Player | Full game | Hits + runs + RBIs prop | | `baseball_player_rbis` | Baseball | Player | Full game | RBIs prop | | `baseball_player_stolen_bases` | Baseball | Player | Full game | Stolen bases prop | | `baseball_player_outs` | Baseball | Player | Full game | Pitcher outs prop | | `baseball_player_earned_runs_allowed` | Baseball | Player | Full game | Earned runs allowed prop | | `baseball_player_hits_allowed` | Baseball | Player | Full game | Hits allowed prop | | `baseball_player_walks_allowed` | Baseball | Player | Full game | Walks allowed prop | | `baseball_team_first_inning_run` | Baseball | Team | 1st inning | Run scored | | `baseball_team_inning1_winner` | Baseball | Team | 1st inning | Inning winner | | `baseball_team_inning2_winner` | Baseball | Team | 2nd inning | Inning winner | | `baseball_team_inning3_winner` | Baseball | Team | 3rd inning | Inning winner | | `baseball_team_inning4_winner` | Baseball | Team | 4th inning | Inning winner | | `baseball_team_inning5_winner` | Baseball | Team | 5th inning | Inning winner | | `baseball_team_inning6_winner` | Baseball | Team | 6th inning | Inning winner | | `baseball_team_inning7_winner` | Baseball | Team | 7th inning | Inning winner | | `baseball_team_inning8_winner` | Baseball | Team | 8th inning | Inning winner | | `baseball_team_inning9_winner` | Baseball | Team | 9th inning | Inning winner | | `baseball_game_extra_innings` | Baseball | Game | Full game | Extra innings yes/no | | `baseball_team_first_five_winner` | Baseball | Team | First 5 innings | Prop | | `baseball_team_first_five_spread` | Baseball | Team | First 5 innings | Spread | | `baseball_team_first_five_total` | Baseball | Team | First 5 innings | Total | | `baseball_team_full_game_winner` | Baseball | Team | Full game | Moneyline | | `baseball_team_full_game_spread` | Baseball | Team | Full game | Spread | | `baseball_team_full_game_total` | Baseball | Team | Full game | Total | | `baseball_team_total_runs` | Baseball | Team | Full game | Selected-team runs total | | `basketball_player_points` | Basketball | Player | Full game | Points prop | | `basketball_player_assists` | Basketball | Player | Full game | Assists prop | | `basketball_player_rebounds` | Basketball | Player | Full game | Rebounds prop | | `basketball_player_threes` | Basketball | Player | Full game | Three-pointers prop | | `basketball_player_steals` | Basketball | Player | Full game | Steals prop | | `basketball_player_blocks` | Basketball | Player | Full game | Blocks prop | | `basketball_player_double_double` | Basketball | Player | Full game | Double-double yes/no | | `basketball_player_triple_double` | Basketball | Player | Full game | Triple-double yes/no | | `basketball_team_first_half_winner` | Basketball | Team | First half | Prop | | `basketball_team_first_half_spread` | Basketball | Team | First half | Spread | | `basketball_team_first_half_total` | Basketball | Team | First half | Total | | `basketball_team_second_half_winner` | Basketball | Team | Second half | Prop | | `basketball_team_second_half_spread` | Basketball | Team | Second half | Spread | | `basketball_team_second_half_total` | Basketball | Team | Second half | Total | | `basketball_team_first_quarter_spread` | Basketball | Team | 1st quarter | Spread | | `basketball_team_first_quarter_total` | Basketball | Team | 1st quarter | Total | | `basketball_team_second_quarter_spread` | Basketball | Team | 2nd quarter | Spread | | `basketball_team_second_quarter_total` | Basketball | Team | 2nd quarter | Total | | `basketball_team_third_quarter_spread` | Basketball | Team | 3rd quarter | Spread | | `basketball_team_third_quarter_total` | Basketball | Team | 3rd quarter | Total | | `basketball_team_fourth_quarter_spread` | Basketball | Team | 4th quarter | Spread | | `basketball_team_fourth_quarter_total` | Basketball | Team | 4th quarter | Total | | `basketball_game_overtime` | Basketball | Game | Full game | Overtime yes/no | | `basketball_team_full_game_winner` | Basketball | Team | Full game | Moneyline | | `basketball_team_full_game_spread` | Basketball | Team | Full game | Spread | | `basketball_team_full_game_total` | Basketball | Team | Full game | Total | | `football_team_full_game_winner` | Football | Team | Full game | Moneyline | | `football_team_full_game_spread` | Football | Team | Full game | Spread | | `football_team_full_game_total` | Football | Team | Full game | Total | | `football_player_touchdowns` | Football | Player | Full game | Touchdowns from scrimmage prop (passing touchdowns excluded) | | `football_player_rushing_yards` | Football | Player | Full game | Rushing yards prop | | `football_player_passing_yards` | Football | Player | Full game | Passing yards prop | | `football_player_receiving_yards` | Football | Player | Full game | Receiving yards prop | | `football_player_passing_touchdowns` | Football | Player | Full game | Passing touchdowns prop | | `football_player_scrimmage_yards` | Football | Player | Full game | Scrimmage yards prop (rushing plus receiving yards) | | `football_player_receptions` | Football | Player | Full game | Receptions prop | | `football_player_passer_rating` | Football | Player | Full game | Passer rating prop | | `football_player_interceptions_thrown` | Football | Player | Full game | Interceptions thrown prop | | `football_player_sacks` | Football | Player | Full game | Sacks prop | | `football_player_defensive_interceptions` | Football | Player | Full game | Defensive interceptions prop | | `football_player_field_goals_made` | Football | Player | Full game | Field goals made prop | | `football_player_50_plus_yard_field_goals_made` | Football | Player | Full game | 50+ yard field goals made prop | | `football_player_fantasy_points_ppr` | Football | Player | Full game | Fantasy points prop (PPR scoring) | | `football_player_first_touchdown` | Football | Player | Full game | Scores the first touchdown yes/no | | `football_player_most_passing_yards` | Football | Player | Full game | Most passing yards in the game yes/no | | `football_player_most_rushing_yards` | Football | Player | Full game | Most rushing yards in the game yes/no | | `football_player_most_receiving_yards` | Football | Player | Full game | Most receiving yards in the game yes/no | | `football_player_passing_attempts` | Football | Player | Full game | Passing attempts prop | | `football_player_passing_completions` | Football | Player | Full game | Passing completions prop | | `football_player_rushing_attempts` | Football | Player | Full game | Rushing attempts prop | | `football_player_longest_rush` | Football | Player | Full game | Longest rush yards prop | | `football_player_longest_reception` | Football | Player | Full game | Longest reception yards prop | | `football_player_team_first_touchdown` | Football | Player | Full game | Scores the selected team's first touchdown yes/no (passing touchdowns excluded) | | `football_team_first_half_winner` | Football | Team | First half | Winner | | `football_team_second_half_winner` | Football | Team | Second half | Winner | | `football_team_first_quarter_winner` | Football | Team | 1st quarter | Winner | | `football_team_second_quarter_winner` | Football | Team | 2nd quarter | Winner | | `football_team_third_quarter_winner` | Football | Team | 3rd quarter | Winner | | `football_team_fourth_quarter_winner` | Football | Team | 4th quarter | Winner | | `football_team_first_half_spread` | Football | Team | First half | Spread | | `football_team_second_half_spread` | Football | Team | Second half | Spread | | `football_team_first_quarter_spread` | Football | Team | 1st quarter | Spread | | `football_team_second_quarter_spread` | Football | Team | 2nd quarter | Spread | | `football_team_third_quarter_spread` | Football | Team | 3rd quarter | Spread | | `football_team_fourth_quarter_spread` | Football | Team | 4th quarter | Spread | | `football_game_first_half_total` | Football | Game | First half | Combined-points total | | `football_game_second_half_total` | Football | Game | Second half | Combined-points total | | `football_game_first_quarter_total` | Football | Game | 1st quarter | Combined-points total | | `football_game_second_quarter_total` | Football | Game | 2nd quarter | Combined-points total | | `football_game_third_quarter_total` | Football | Game | 3rd quarter | Combined-points total | | `football_game_fourth_quarter_total` | Football | Game | 4th quarter | Combined-points total | | `football_team_points_full_game_total` | Football | Team | Full game | Selected-team points total | | `football_team_first_half_total` | Football | Team | First half | Selected-team points total | | `football_team_second_half_total` | Football | Team | Second half | Selected-team points total | | `football_game_exact_margin` | Football | Game | Full game | Winning team and exact-margin bucket | | `football_game_race_to_points` | Football | Game | Full game | First team to reach the points target (legs: each team plus Neither Team); `outcome_strike` carries the target (7, 14, 21, 28, or 35) | | `football_game_highest_scoring_quarter` | Football | Game | Full game | Highest-scoring quarter | | `football_game_possession_winner` | Football | Team | Full game | Time-of-possession winner | | `football_game_tie` | Football | Game | Full game | Game ends in a tie yes/no | | `football_game_first_score` | Football | Game | Full game | First scoring team/type | | `football_game_first_half_first_score` | Football | Game | First half | First scoring team/type | | `football_game_second_half_first_score` | Football | Game | Second half | First scoring team/type | | `football_game_first_touchdown` | Football | Game | Full game | First touchdown team | | `football_game_first_half_first_touchdown` | Football | Game | First half | First touchdown team | | `football_game_second_half_first_touchdown` | Football | Game | Second half | First touchdown team | | `football_game_last_score` | Football | Game | Full game | Last scoring team | | `football_game_last_touchdown` | Football | Game | Full game | Last touchdown team | | `football_game_first_half_both_teams_score_points` | Football | Game | First half | Both teams score points | | `football_game_second_half_both_teams_score_points` | Football | Game | Second half | Both teams score points | | `football_game_first_quarter_both_teams_score_points` | Football | Game | 1st quarter | Both teams score points | | `football_game_second_quarter_both_teams_score_points` | Football | Game | 2nd quarter | Both teams score points | | `football_game_third_quarter_both_teams_score_points` | Football | Game | 3rd quarter | Both teams score points | | `football_game_fourth_quarter_both_teams_score_points` | Football | Game | 4th quarter | Both teams score points | | `football_game_both_teams_score_touchdown` | Football | Game | Full game | Both teams score a touchdown | | `football_game_first_half_both_teams_score_touchdown` | Football | Game | First half | Both teams score a touchdown | | `football_game_second_half_both_teams_score_touchdown` | Football | Game | Second half | Both teams score a touchdown | | `football_game_first_quarter_both_teams_score_touchdown` | Football | Game | 1st quarter | Both teams score a touchdown | | `football_game_second_quarter_both_teams_score_touchdown` | Football | Game | 2nd quarter | Both teams score a touchdown | | `football_game_third_quarter_both_teams_score_touchdown` | Football | Game | 3rd quarter | Both teams score a touchdown | | `football_game_fourth_quarter_both_teams_score_touchdown` | Football | Game | 4th quarter | Both teams score a touchdown | | `football_game_total_defensive_special_teams_touchdowns` | Football | Game | Full game | Defensive and special-teams touchdowns total | | `football_team_total_touchdowns` | Football | Team | Full game | Selected-team touchdowns total | | `football_game_total_touchdowns` | Football | Game | Full game | Combined touchdowns total | | `football_game_total_pass_touchdowns` | Football | Game | Full game | Combined passing touchdowns total (also reserved for combined receiving touchdowns) | | `football_game_total_rush_touchdowns` | Football | Game | Full game | Combined rushing touchdowns total | | `football_game_total_pass_yards` | Football | Game | Full game | Combined passing yards total (also reserved for combined receiving yards) | | `football_game_total_rush_yards` | Football | Game | Full game | Combined rushing yards total | | `football_game_total_offensive_yards` | Football | Game | Full game | Combined offensive yards total | | `football_game_total_turnovers` | Football | Game | Full game | Combined turnovers total | | `football_game_total_interceptions` | Football | Game | Full game | Combined defensive interceptions total | | `football_game_total_fourth_down_conversions` | Football | Game | Full game | Combined fourth-down conversions total | | `football_game_total_two_point_conversions` | Football | Game | Full game | Successful two-point conversion yes/no (total with a 0.5 line) | | `football_team_total_pass_touchdowns` | Football | Team | Full game | Selected-team passing touchdowns total (also used for receiving touchdowns) | | `football_team_total_rush_touchdowns` | Football | Team | Full game | Selected-team rushing touchdowns total | | `football_team_total_pass_yards` | Football | Team | Full game | Selected-team passing yards total (also used for receiving yards) | | `football_team_total_rush_yards` | Football | Team | Full game | Selected-team rushing yards total | | `football_team_total_scrimmage_yards` | Football | Team | Full game | Selected-team scrimmage yards total | | `football_team_total_receptions` | Football | Team | Full game | Selected-team receptions total | | `football_team_total_takeaways` | Football | Team | Full game | Selected-team takeaways total | | `football_team_total_defensive_interceptions` | Football | Team | Full game | Selected-team defensive interceptions total | | `football_team_total_sacks` | Football | Team | Full game | Selected-team sacks total | | `football_team_total_field_goals_made` | Football | Team | Full game | Selected-team field goals made total | | `football_team_total_40_plus_yard_field_goals_made` | Football | Team | Full game | Selected-team 40+ yard field goals made total | | `football_team_total_offensive_yards` | Football | Team | Full game | Selected-team offensive yards total | | `football_team_total_first_downs` | Football | Team | Full game | Selected-team first downs total | | `football_team_total_fourth_down_conversions` | Football | Team | Full game | Selected-team fourth-down conversions total | | `football_team_total_defensive_special_teams_touchdowns` | Football | Team | Full game | Selected-team defensive and special-teams touchdowns total | | `football_game_pick_six` | Football | Game | Full game | Pick six scored yes/no | | `football_game_kickoff_punt_return_touchdown` | Football | Game | Full game | Kickoff or punt return touchdown yes/no | | `football_game_overtime` | Football | Game | Full game | Overtime yes/no | | `football_game_safety` | Football | Game | Full game | Safety scored yes/no | | `football_game_onside_kick_attempt` | Football | Game | Full game | Onside kick attempted yes/no | | `football_game_double_result` | Football | Game | Full game | Halftime/full-time result combination (9 outcomes) | | `football_next_team_touchdown` | Football | Game | Full game | Live in-game market: team that scores the next touchdown (legs: each team plus No TD); a new three-leg product lists after each touchdown; regulation only | | `football_next_team_field_goal` | Football | Game | Full game | Live in-game market: team that scores the next made field goal (legs: each team plus No FG); a new three-leg product lists after each made field goal; regulation only | | `soccer_team_full_time_winner` | Soccer | Team | Full time | Moneyline (3-way) | | `soccer_team_full_game_spread` | Soccer | Team | Full game | Spread | | `soccer_team_full_game_total` | Soccer | Game | Full game | Total goals across **both** teams (combined) | | `soccer_game_btts` | Soccer | Game | Full game | Both teams to score | | `soccer_game_first_team_to_score` | Soccer | Game | Full game | First team to score | | `soccer_game_exact_score` | Soccer | Game | Full game | Exact score | | `soccer_game_to_advance` | Soccer | Team | Tie/match | To advance | | `soccer_team_first_half_winner` | Soccer | Team | First half | Prop | | `soccer_team_first_half_spread` | Soccer | Team | First half | Spread | | `soccer_team_first_half_total` | Soccer | Game | First half | Total goals across **both** teams (combined) | | `soccer_game_first_half_btts` | Soccer | Game | First half | Both teams to score, first-half goals only | | `soccer_game_first_half_first_team_to_score` | Soccer | Game | First half | First team to score, first-half goals only | | `soccer_game_total_corners` | Soccer | Game | Full game | Total corners across **both** teams (combined) | | `soccer_team_second_half_winner` | Soccer | Team | Second half | Prop | | `soccer_team_second_half_spread` | Soccer | Team | Second half | Spread | | `soccer_team_second_half_total` | Soccer | Game | Second half | Total goals across **both** teams (combined) | | `soccer_game_second_half_btts` | Soccer | Game | Second half | Both teams to score, second-half goals only | | `soccer_game_second_half_first_team_to_score` | Soccer | Game | Second half | First team to score, second-half goals only | | `soccer_team_total_goals` | Soccer | Team | Full game | Total goals for a **single** team | | `soccer_team_total_goals_first_half` | Soccer | Team | First half | Total goals for a **single** team | | `soccer_game_total_goals_odd_even` | Soccer | Game | Full game | Total goals odd/even | | `soccer_game_first_half_exact_score` | Soccer | Game | First half | Exact score | | `soccer_player_goals` | Soccer | Player | Full game | Goals prop | | `soccer_player_assists` | Soccer | Player | Full game | Assists prop | | `soccer_player_shots` | Soccer | Player | Full game | Shots prop | | `soccer_player_shots_on_target` | Soccer | Player | Full game | Shots on target prop | | `soccer_player_goalkeeper_saves` | Soccer | Player | Full game | Goalkeeper saves prop | | `soccer_player_goals_plus_assists` | Soccer | Player | Full game | Goals + assists prop | | `soccer_team_total_corners` | Soccer | Team | Full game | Total corners for a **single** team | | `soccer_game_corners_odd_even` | Soccer | Game | Full game | Corners odd/even | | `soccer_game_goes_to_extra_time` | Soccer | Game | Full time | Will the match go to extra time (knockout) | | `soccer_team_extra_time_spread` | Soccer | Team | Extra time | Spread — extra-time goals only | | `soccer_game_extra_time_total` | Soccer | Game | Extra time | Total goals across **both** teams, extra time only | | `soccer_game_extra_time_btts` | Soccer | Game | Extra time | Both teams to score in extra time | | `soccer_game_extra_time_first_team_to_score` | Soccer | Game | Extra time | First team to score in extra time | | `hockey_team_full_game_winner` | Hockey | Team | Full game | Moneyline | | `hockey_team_full_game_spread` | Hockey | Team | Full game | Spread (puck line) | | `hockey_team_full_game_total` | Hockey | Team | Full game | Total | | `hockey_game_overtime` | Hockey | Game | Full game | Overtime yes/no | | `hockey_game_double_overtime` | Hockey | Game | Full game | Double overtime yes/no | | `hockey_player_goals` | Hockey | Player | Full game | Goals prop | | `hockey_player_assists` | Hockey | Player | Full game | Assists prop | | `hockey_player_points` | Hockey | Player | Full game | Points prop | | `tennis_match_winner` | Tennis | Player | Match | Moneyline | | `tennis_match_games_spread` | Tennis | Player | Match | Games spread | | `tennis_match_sets_spread` | Tennis | Player | Match | Sets spread | | `tennis_match_total_games` | Tennis | Match | Match | Total games | | `tennis_match_total_sets` | Tennis | Match | Match | Total sets | | `tennis_match_exact_score` | Tennis | Match | Match | Exact set score | | `tennis_set_1_winner` | Tennis | Player | Set 1 | Set winner | | `tennis_set_2_winner` | Tennis | Player | Set 2 | Set winner | | `tennis_set_3_winner` | Tennis | Player | Set 3 | Set winner | | `table_tennis_match_winner` | Table tennis | Player | Match | Moneyline | | `table_tennis_set_1_winner` | Table tennis | Player | Set 1 | Set winner | | `table_tennis_set_2_winner` | Table tennis | Player | Set 2 | Set winner | | `table_tennis_set_3_winner` | Table tennis | Player | Set 3 | Set winner | | `table_tennis_set_4_winner` | Table tennis | Player | Set 4 | Set winner | | `cricket_match_winner` | Cricket | Team | Match | Moneyline | | `ufc_fight_winner` | UFC | Fighter | Fight | Moneyline | | `ufc_method_of_victory` | UFC | Fighter | Fight | Method of victory | | `ufc_go_the_distance` | UFC | Game | Fight | Go the distance yes/no | | `ufc_round_of_victory` | UFC | Fighter | Fight | Round of victory | | `ufc_round_of_finish` | UFC | Fight | Fight | Round of finish | | `ufc_method_of_finish` | UFC | Fight | Fight | Method of finish | | `boxing_match_winner` | Boxing | Fighter | Fight | Moneyline | | `darts_match_winner` | Darts | Player | Match | Moneyline | | `pickleball_match_winner` | Pickleball | Team | Match | Moneyline | | `lacrosse_team_full_game_winner` | Lacrosse | Team | Full game | Moneyline | | `esports_match_winner` | Esports | Team | Match | Moneyline | | `esports_map_winner_1` | Esports | Team | Map 1 | Prop | | `esports_map_winner_2` | Esports | Team | Map 2 | Prop | | `esports_map_winner_3` | Esports | Team | Map 3 | Prop | | `esports_map_winner_4` | Esports | Team | Map 4 | Prop | | `esports_game_winner_1` | Esports | Team | Game 1 | Prop | | `esports_game_winner_2` | Esports | Team | Game 2 | Prop | | `esports_game_winner_3` | Esports | Team | Game 3 | Prop | | `esports_game_winner_4` | Esports | Team | Game 4 | Prop | | `esports_series_map_handicap` | Esports | Team | Match | Maps handicap (spread) across the series | | `esports_series_total_maps` | Esports | Match | Match | Total maps in the series | | `esports_series_game_handicap` | Esports | Team | Match | Games handicap (spread) across the series | | `esports_series_total_games` | Esports | Match | Match | Total games in the series | | `esports_map_rounds_handicap_1` | Esports | Team | Map 1 | Rounds handicap (spread) | | `esports_map_rounds_handicap_2` | Esports | Team | Map 2 | Rounds handicap (spread) | | `esports_map_rounds_handicap_3` | Esports | Team | Map 3 | Rounds handicap (spread) | | `esports_map_rounds_handicap_4` | Esports | Team | Map 4 | Rounds handicap (spread) | | `esports_map_total_rounds_1` | Esports | Match | Map 1 | Total rounds | | `esports_map_total_rounds_2` | Esports | Match | Map 2 | Total rounds | | `esports_map_total_rounds_3` | Esports | Match | Map 3 | Total rounds | | `esports_map_total_rounds_4` | Esports | Match | Map 4 | Total rounds | | `esports_game_first_blood_1` | Esports | Team | Game 1 | First blood | | `esports_game_first_blood_2` | Esports | Team | Game 2 | First blood | | `esports_game_first_blood_3` | Esports | Team | Game 3 | First blood | | `esports_game_first_blood_4` | Esports | Team | Game 4 | First blood | | `esports_game_total_kills_1` | Esports | Match | Game 1 | Total kills | | `esports_game_total_kills_2` | Esports | Match | Game 2 | Total kills | | `esports_game_total_kills_3` | Esports | Match | Game 3 | Total kills | | `esports_game_total_kills_4` | Esports | Match | Game 4 | Total kills | | `esports_game_kills_odd_even_1` | Esports | Match | Game 1 | Total kills odd/even | | `esports_game_kills_odd_even_2` | Esports | Match | Game 2 | Total kills odd/even | | `esports_game_kills_odd_even_3` | Esports | Match | Game 3 | Total kills odd/even | | `esports_game_kills_odd_even_4` | Esports | Match | Game 4 | Total kills odd/even | Esports map markets (`esports_map_*`) apply to round-based titles such as Counter-Strike 2 and Valorant. Esports game markets (`esports_game_*`) apply to titles played as a series of games such as League of Legends and Dota 2. Series markets (`esports_series_*`) cover the whole best-of series. The `market_sport_type` enum is the source of truth for sport, period, and prop variants, and is now guaranteed to be present on every instrument. When in doubt about which value applies to a market you're seeing, use this field rather than parsing the symbol. ### Generic Values Four generic values are also registered. Each has the same market structure as the matching `outcome_type` value. They appear on instruments that carry no sport-specific value: instruments created before the sport-specific values existed, hand-listed markets, and sports that do not yet have their own winner, spread, or total type. Treat them as valid `market_sport_type` values and map them by structure: `moneyline` is a 2-way winner, `spreads` is a handicap, `totals` is an over/under, and `drawable_outcome` is a 3-way winner that admits a draw. These values carry no sport or period, so read the sport from `event_subcategory`. On Retail data, `sportsMarketType` shows the `outcome_type` value (for example `futures`) when the instrument carries no `market_sport_type` at all. | Value | Meaning | | ------------------ | ---------------------------------------------------------------------- | | `moneyline` | 2-way winner market without a sport-specific type. | | `spreads` | Handicap market without a sport-specific type. | | `totals` | Over/under market without a sport-specific type. | | `drawable_outcome` | 3-way winner market that admits a draw, without a sport-specific type. | *** ## Football Market Metadata Use structured metadata rather than parsing an NFL instrument ID. Markets for the same game share an `event_id` containing the canonical game slug without a contract prefix. `event_product_id` is the exchange's event-level product grouping and is not a substitute for `event_id`; `product_id` groups the strikes or outcomes belonging to one market product. For NFL game props, use this tuple as the canonical lookup and deduplication key: ```text theme={null} event_external_id_sportradar + market_sport_type + long_participant_id + short_participant_id + outcome_strike ``` Participant IDs can be empty for game-wide totals. For player props, also use `external_participant_id` to resolve and validate the player. | Field | Meaning | | ---------------------------------------------- | ----------------------------------------------------------------------------------------------------- | | `event_id` | Canonical game slug shared by every market for the same NFL game. | | `event_product_id` | Exchange event-level product grouping; do not use it in place of `event_id`. | | `event_external_id_sportradar` | Sportradar game ID used for creation, querying, scores, and settlement. | | `product_id` | Groups the strikes or outcomes belonging to one market product. | | `market_sport_type` | Specific football market classification. | | `outcome_type` | `moneyline`, `spreads`, or `totals` for primary lines; `props` for secondary game and player markets. | | `prop_type` | `team` or `player`; NFL player props use `player`. | | `outcome_strike` | Line, threshold, or binary outcome. Required and included in the deduplication key. | | `long_participant_id` / `short_participant_id` | Canonical team IDs in `nfl-` format. | | `external_participant_id` | Sportradar player ID for player props, or the selected provider team ID where applicable. | | `interval_start` / `interval_end` | Exact-margin bucket boundaries used for display and settlement. | ### Football Metadata Profiles In addition to `event_id`, `event_external_id_sportradar`, `market_sport_type`, and `product_id`, use the fields required by the market's profile: | Profile | Additional required metadata | | -------------------- | -------------------------------------------------------------------------------------------------- | | Game + line | `outcome_strike`; participant IDs are not required. | | Game/team + line | `long_participant_id`, `short_participant_id`, `outcome_strike`. | | Team + line | `long_participant_id`, usually `external_participant_id`, `outcome_strike`. | | Player + line | `external_participant_id`, `long_participant_id`, `prop_type=player`, `outcome_strike`. | | Game + outcome | `outcome_strike`; exact-margin markets also need `interval_start`, `interval_end`, and sort order. | | Game + binary strike | `outcome_strike` plus the `market_sport_type` that encodes the period and statistic. | Race-to-points markets (`football_game_race_to_points`) are mutually exclusive team-outcome products, like the first-score markets. Each points target (7, 14, 21, 28, 35) is one product with three instruments: away team, home team, and Neither Team. `outcome_strike` carries the points target on all three legs. `long_participant_id` names the team on the team legs and is absent on the Neither Team leg; `short_participant_id` is not set. Live next-touchdown and next-field-goal markets (`football_next_team_touchdown`, `football_next_team_field_goal`) re-list during the game. Each touchdown or field-goal number is one product with three instruments: away team, home team, and No TD or No FG. The first instance lists at kickoff, a new instance lists after each confirmed touchdown or made field goal, and none lists in overtime. Successive instances share `market_sport_type`, `event_id`, and participant IDs, so the canonical deduplication key above does not separate them. Use `product_id` (`-next-team-td-`, `-next-team-fg-`) or the `touchdown_number` / `field_goal_number` metadata field instead. On these legs `outcome_strike` holds the team abbreviation or `no-td` / `no-fg`, not a number. *** ## Section 2: Identifying Markets Use this table as a recipe book for identifying a specific market type from instrument metadata. **Match on `market_sport_type` alone** — it fully identifies any market (structure and period), so you no longer need to check `outcome_type`. | To identify... | `market_sport_type` | | ------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Full-game moneyline** | `_team_full_game_winner` (soccer uses `soccer_team_full_time_winner`, a 3-way market) | | **Full-game spread** | `_team_full_game_spread` | | **Full-game total** | `_team_full_game_total` | | **MLB first-5-innings moneyline** | `baseball_team_first_five_winner` | | **MLB first-5-innings spread** | `baseball_team_first_five_spread` | | **MLB first-5-innings total** | `baseball_team_first_five_total` | | **MLB first-inning run** | `baseball_team_first_inning_run` | | **MLB inning winner (innings 1–9)** | `baseball_team_inning1_winner` through `baseball_team_inning9_winner` — derive the inning number from this enum value, not the instrument ID | | **MLB player home runs prop** | `baseball_player_home_runs` | | **MLB player strikeouts prop** | `baseball_player_strikeouts` | | **MLB player RBIs prop** | `baseball_player_rbis` | | **MLB player stolen bases prop** | `baseball_player_stolen_bases` | | **MLB selected-team total runs** | `baseball_team_total_runs` | | **NBA first-half moneyline** | `basketball_team_first_half_winner` | | **NBA first-half spread** | `basketball_team_first_half_spread` | | **NBA first-half total** | `basketball_team_first_half_total` | | **NBA player points prop** | `basketball_player_points` | | **NBA player assists prop** | `basketball_player_assists` | | **NFL player anytime touchdown prop** | `football_player_touchdowns` | | **NFL player rushing yards prop** | `football_player_rushing_yards` | | **NFL player passing yards prop** | `football_player_passing_yards` | | **NFL player receiving yards prop** | `football_player_receiving_yards` | | **NFL player passing touchdowns prop** | `football_player_passing_touchdowns` | | **NFL player fantasy points (PPR) prop** | `football_player_fantasy_points_ppr` | | **NFL race to X points** | `football_game_race_to_points` (`outcome_strike` carries the points target; three legs per target: away team, home team, Neither Team) | | **NFL live next touchdown / next field goal** | `football_next_team_touchdown` / `football_next_team_field_goal` | | **Table tennis set winner (sets 1–4)** | `table_tennis_set_1_winner` through `table_tennis_set_4_winner` | | **Esports series handicap and total** | `esports_series_map_handicap` / `esports_series_total_maps` (map-based titles), `esports_series_game_handicap` / `esports_series_total_games` (game-based titles) | | **Boxing, darts, pickleball, or lacrosse winner** | `boxing_match_winner`, `darts_match_winner`, `pickleball_match_winner`, `lacrosse_team_full_game_winner` | ### Worked Example: Cleveland/Detroit Over 4.5 Total A full-game total on the May 21 Cleveland Guardians vs Detroit Tigers MLB game: ```json theme={null} { "symbol": "tsc-mlb-cle-det-2026-05-21-4pt5", "metadata": { "event_id": "mlb-cle-det-2026-05-21", "event_start_time": "2026-05-21 17:10:00+00", "event_subcategory": "BASEBALL", "outcome_type": "totals", "market_sport_type": "baseball_team_full_game_total", "outcome_strike": "4.5", "long_participant_id": "mlb-cle", "long_participant_name": "Cleveland Guardians", "short_participant_id": "mlb-det" } } ``` To safely interpret this contract: 1. `market_sport_type = "baseball_team_full_game_total"` → full-game total. (`outcome_type` is also present but you don't need it.) 2. `event_subcategory = "BASEBALL"` → MLB game. 3. `outcome_strike = "4.5"` → over/under line is 4.5 combined runs. 4. The contract resolves "Over" if the combined score is 4.5 or more, otherwise "Under". ### Worked Example: Cleveland +3.5 Spread A full-game run-line spread on the May 20 Cleveland Guardians vs Detroit Tigers MLB game: ```json theme={null} { "symbol": "asc-mlb-cle-det-2026-05-20-pos-3pt5", "metadata": { "event_id": "mlb-cle-det-2026-05-20", "event_start_time": "2026-05-20 22:40:00+00", "event_subcategory": "BASEBALL", "outcome_type": "spreads", "market_sport_type": "baseball_team_full_game_spread", "outcome_strike": "3.5", "long_participant_id": "mlb-cle", "long_participant_name": "Cleveland Guardians", "short_participant_id": "mlb-det" } } ``` To safely interpret this contract: 1. `market_sport_type = "baseball_team_full_game_spread"` → full-game spread. (`outcome_type` is also present but you don't need it.) 2. `long_participant_id = "mlb-cle"` → the contract pays out on Cleveland. 3. `outcome_strike = "3.5"` → handicap magnitude is 3.5 runs. 4. The contract resolves to Cleveland if Cleveland wins, or loses by fewer than 3.5 runs. *** ## Best Practices * **Never parse the symbol or slug** to determine market type, period, or handicap. Symbol formats are not part of the public contract. * **Map on `market_sport_type` alone.** It is guaranteed on every instrument — full-game, sub-period, and prop alike — and fully identifies the market. `outcome_type` is still present on the instrument, but **do not rely on it for mapping** (it is subject to change); the sole exception is futures, which carry no `market_sport_type` and are identified by `outcome_type = "futures"`. * **Treat `outcome_strike` as a string** in client code and convert to your numeric type. The value is unsigned — the spread direction is determined by `long_participant_id`. * **Cache and version your enum mappings.** New `market_sport_type` values are announced in the [changelog](/changelog) and rolled out in preprod before production. Subscribe to the RSS feed to be notified before new enums appear in production. An instrument can carry a value that is not yet in this inventory: treat an unknown value as a prop, fall back to `outcome_type` for its structure, and watch the changelog for the new value. * **Subscribe to instrument updates** via the streaming APIs to catch new instruments as they are listed. # Streaming Source: https://docs.polymarket.us/trader-guide/streaming-apis Real-time data streaming semantics and best practices ## Streaming Semantics Streaming APIs provide real-time updates over long-lived HTTP connections. ## Delivery Guarantees **Are streaming APIs exactly-once?** No. Delivery is **at-least-once**. This means messages may be delivered more than once, clients must be idempotent (handle duplicates), and you should use message IDs to deduplicate. **Example deduplication:** ```python theme={null} seen_messages = set() for message in stream: message_id = message.get('id') if message_id in seen_messages: continue # Skip duplicate seen_messages.add(message_id) process_message(message) ``` ## Snapshots and Deltas **Do streams always start with a snapshot?** Yes. When you subscribe to a stream, the first message is a complete snapshot and subsequent messages are updates (deltas or new snapshots). You must apply the initial snapshot to establish state and process subsequent updates against that base state. **Example:** ``` Message 1: Snapshot of all open orders Message 2: Order 123 filled (delta) Message 3: Order 456 canceled (delta) Message 4: New order 789 opened (delta) ``` ## Stream Types **Market Data Streams**: Order book updates, BBO (best bid/offer), instrument state changes **Order Streams**: Order status changes, execution reports, order lifecycle events **Position Streams**: Position changes, realized/unrealized P\&L updates, risk metric changes **Drop Copy Streams**: Authoritative post-trade feed, executions and trades, settlement updates ## Connection Management **Persistent connections**: Streams use long-lived HTTP connections, not WebSockets (standard HTTP streaming), server pushes updates as they occur. **Reconnection**: Connections can drop due to network issues; implement automatic reconnection with exponential backoff. **Example reconnection:** ```python theme={null} def connect_with_retry(max_retries=5): for attempt in range(max_retries): try: return connect_to_stream() except ConnectionError: wait = 2 ** attempt # 1, 2, 4, 8, 16 seconds time.sleep(wait) raise Exception("Max retries exceeded") ``` ## Resume Capability **Some streams are resumable**: Persist resume state (token or sequence number), resume from last processed event after disconnect, avoid missing events during disconnection. **Example resume:** ```bash theme={null} POST /v1/orders/subscribe { "accountId": "your-account-id", "resumeToken": "last_processed_token" } ``` Not all streams support resume. Check API documentation for each endpoint. ## Message Ordering Messages on a single stream are delivered in order. Messages across different streams may not be ordered relative to each other. If you need cross-stream ordering, use timestamps in messages, implement local ordering logic, and use sequence numbers where available. ## Idempotency **Why is idempotency important?** With at-least-once delivery, you may process the same message twice and your logic must handle duplicates gracefully. **How to be idempotent**: Track processed message IDs, use database unique constraints on IDs, design operations to be repeatable (e.g., "set to X" not "add Y"). **Example idempotent order processing:** ```python theme={null} def process_order_update(order_update): order_id = order_update['orderId'] exec_id = order_update['execId'] # Try to insert execution try: db.insert_execution(exec_id, order_update) except UniqueConstraintError: # Already processed this execution return # Update order state db.update_order(order_id, order_update['status']) ``` ## Rate Limiting on Streams **Client-to-server messages**: Limited to 100 messages per second per firm (across all streams), averaged over a 1-minute window. Short bursts above this rate are allowed. Applies to requests you send (subscriptions, commands), does not apply to server-pushed updates. **Connection limits**: Maximum 20 concurrent streams per firm; plan your subscription strategy accordingly. ## Snapshot Refresh Some streams send periodic snapshots even if state hasn't changed to help detect missed messages, allow clients to reconcile state, typically every few minutes. When you receive a snapshot, replace your entire local state with the snapshot; don't try to merge or diff against previous state. ## Troubleshooting **Missing messages**: Possible causes include connection dropped (implement reconnection), resume state lost (persist resume tokens), at-least-once delivery issue (check for duplicates elsewhere). Solution: Use resumable streams and persist state. **Duplicate messages**: Expected behavior. Implement deduplication using message IDs. **Stream stops sending updates**: Check if the connection is still alive, send periodic heartbeats or test messages, implement connection timeout detection, reconnect if no messages received for X seconds. **State inconsistency**: If your local state doesn't match server state, unsubscribe and resubscribe (forces new snapshot), reconcile with REST API query, or check for processing errors in your code. ## Best Practices **Always handle reconnections**: Network issues are inevitable. Auto-reconnect with backoff. **Deduplicate messages**: Track processed message IDs and skip duplicates. **Persist resume state**: Save tokens/sequence numbers to resume after restart. **Monitor stream health**: Detect stale connections and reconnect. **Process snapshots correctly**: Replace state, don't merge. **Use appropriate connection counts**: Stay within the 10-connection limit. **Handle snapshot + delta pattern**: Apply initial snapshot, then process deltas.