The unified Python SDK gives you a consistent surface across Polymarket discovery, market data, trading, account data, and realtime streams.
The Python SDK is currently in beta. We are keeping it in this beta phase
while we address issues and harden the SDK before transitioning to a more
stable release.
The SDK ships parallel async and sync clients with matching method names and arguments: AsyncPublicClient / PublicClient for public reads, and AsyncSecureClient / SecureClient for authenticated reads and trading. Prefer the async clients for servers, bots, and any code that already runs inside an event loop. Use the sync clients for scripts, notebooks, and one-off tools where an event loop would just add ceremony. Realtime stream subscriptions are async only and require the async clients.Examples below show the body of an async def main() function; wrap them with asyncio.run(main()) to run as a script, as shown in Quickstart. To switch a snippet to sync, swap AsyncPublicClient / AsyncSecureClient for PublicClient / SecureClient, drop await, replace async with with with, replace async for with for, and remove the asyncio.run(...) wrapper.
Identifiers and EVM addresses are exposed as typing.NewType aliases (MarketId, ConditionId, TokenId, EventId, EvmAddress, …) so static type checkers can keep them distinct from plain strings. Precision-sensitive price, size, and amount fields generally use decimal.Decimal; date and time fields use datetime.date or datetime.datetime.
Production is the default environment. Pass an Environment object when your integration needs to target a different deployment or custom endpoint set. The client owns network transports, so use async with (or call await client.close()) to release them when you are done.
from polymarket import AsyncPublicClient, PRODUCTIONasync with AsyncPublicClient(environment=PRODUCTION) as client: ...
With async clients, list methods return an AsyncPaginator across paginated endpoints. Use async for to iterate through pages.
async with AsyncPublicClient() as client: markets = client.list_markets(closed=False, page_size=10) async for page in markets: # page.items: tuple[Market, ...] ...
You can also fetch the first page directly and resume later from a cursor.
first_page = await markets.first_page()# first_page.items: tuple[Market, ...]async for page in markets.from_cursor(first_page.next_cursor): # page.items: tuple[Market, ...] ...
When you only care about the items and not page boundaries, iterate them directly.
async for market in markets.items(): # market: Market ...
Use discovery methods to browse events, markets, teams, tags, comments, sports metadata, and search results. The examples below show a few common entry points.
Events
Markets
Teams
Tags
Comments
Sports
Search
events = client.list_events(page_size=10)async for page in events: # page.items: tuple[Event, ...] ...
markets = client.list_markets(closed=False, page_size=10)async for page in markets: # page.items: tuple[Market, ...] ...
teams = client.list_teams(league="NBA", page_size=10)async for page in teams: # page.items: tuple[Team, ...] ...
Subscribe through one SDK interface even when events come from different stream families. The SDK routes each subscription spec to the right stream and merges the results into one async iterator. Subscriptions are async only and require AsyncPublicClient or AsyncSecureClient.
from polymarket import AsyncPublicClientfrom polymarket.streams import CryptoPricesSpec, MarketSpecyes_token_id = market.outcomes.yes.token_idif yes_token_id is None: raise RuntimeError("Market does not have a YES token id")async with AsyncPublicClient() as client: stream = await client.subscribe( [ MarketSpec(token_ids=[yes_token_id]), CryptoPricesSpec( topic="prices.crypto.binance", symbols=["btcusdt"], ), ], ) async with stream: async for event in stream: # event: # | MarketBookEvent # | MarketPriceChangeEvent # | MarketLastTradePriceEvent # | MarketTickSizeChangeEvent # | MarketBestBidAskEvent # | NewMarketEvent # | MarketResolvedEvent # | CryptoPricesBinanceEvent print(type(event).__name__) break
AsyncSecureClient.subscribe accepts the same public subscription specs and adds UserSpec for user-scoped order and trade events on the authenticated wallet.
import osfrom polymarket import AsyncSecureClientfrom polymarket.streams import UserSpecasync with await AsyncSecureClient.create( private_key=os.environ["POLYMARKET_PRIVATE_KEY"], wallet=os.environ.get("POLYMARKET_WALLET_ADDRESS"),) as secure_client: user_stream = await secure_client.subscribe(UserSpec()) async with user_stream: async for event in user_stream: # event: # | UserOrderEvent # | UserTradeEvent print(type(event).__name__) break
Create a secure client when you need wallet-scoped reads or trading.
Secure clients own multiple network transports. Wrap them in async with, or
call await secure_client.close() when you are done, to release the
underlying connections. The snippets below show client creation and subsequent
calls as a flat sequence for readability — in real code, keep the client
inside an async with block or close it explicitly.
The Python SDK authenticates with a local private key. By default,
AsyncSecureClient.create uses the signer’s deterministic Deposit Wallet as the
account wallet. Pass wallet when you want to authenticate an existing wallet,
such as an existing Deposit Wallet, Poly Safe, Poly Proxy, or the signer address
itself for EOA trading.The examples below pass wallet to make account selection explicit. Omit
wallet to use the default Deposit Wallet flow.
import osfrom polymarket import AsyncSecureClientasync with await AsyncSecureClient.create( private_key=os.environ["POLYMARKET_PRIVATE_KEY"], wallet=os.environ.get("POLYMARKET_WALLET_ADDRESS"),) as secure_client: ...
Keep private keys and API credentials in your secret manager or local environment. Do not commit them to source control.
Builder API keys are supported for backwards compatibility with builders that
still use them for wallet operations. They are not used for order attribution.
Use builder_code on orders for attribution.
Before placing orders, make sure the authenticated wallet is deployed and has
the required trading approvals. AsyncSecureClient.create resolves the signer’s
deterministic Deposit Wallet by default and deploys it if needed.
From this point forward, snippets in Trading Setup, Trading, Position
Lifecycle, and Wallet Operations submit real on-chain transactions or live
orders against the configured environment when executed. Review each call
before running it against a wallet that holds funds.
Set up the approvals required for trading.
await secure_client.setup_trading_approvals()
setup_trading_approvals() waits for the setup transaction internally and is
idempotent. If the wallet already has the required approvals, it returns without
submitting a transaction.
Use a secure client to create, sign, and submit orders. Limit orders specify the price and size you want to trade. Market orders execute against resting liquidity immediately.Order placement returns a discriminated response. Check response.ok before reading order details.
Use position lifecycle methods to split collateral into outcome tokens, merge
complete sets back into collateral, or redeem resolved positions. These examples
assume the secure client is configured with API key authorization as shown in
API Key Authorization, and that you set up trading
approvals as shown above.
Split Position
Merge Positions
Redeem Positions
condition_id = market.condition_idif condition_id is None: raise RuntimeError("Market does not have a condition id")handle = await secure_client.split_position( condition_id=condition_id, amount=1,)outcome = await handle.wait()# outcome.transaction_hash: TransactionHash
condition_id = market.condition_idif condition_id is None: raise RuntimeError("Market does not have a condition id")handle = await secure_client.merge_positions( condition_id=condition_id, amount="max",)outcome = await handle.wait()# outcome.transaction_hash: TransactionHash
Use wallet operation methods for direct token movements from the authenticated
wallet. Amounts are in base units. These examples assume the secure client is
configured with API key authorization as shown in API Key
Authorization.
Manage open orders for the authenticated wallet after placement. These examples assume order_id comes from an accepted order response.
Get Order
List Open Orders
Cancel Order
Cancel Market Orders
order = await secure_client.get_order(order_id=order_id)# order: OpenOrder
condition_id = market.condition_idif condition_id is None: raise RuntimeError("Market does not have a condition id")open_orders = secure_client.list_open_orders(market=condition_id)async for page in open_orders: # page.items: tuple[OpenOrder, ...] ...
yes_token_id = market.outcomes.yes.token_idif yes_token_id is None: raise RuntimeError("Market does not have a YES token id")response = await secure_client.cancel_market_orders(token_id=yes_token_id)# response.canceled: tuple[str, ...]
Use rewards methods to inspect active reward programs and scoring methods to check whether orders are eligible for scoring. list_current_rewards and list_market_rewards are public reads and are also available on AsyncPublicClient / PublicClient; get_order_scoring and get_orders_scoring require a secure client because they read account-scoped order data.
Current Rewards
Market Rewards
Order Scoring
Batch Order Scoring
rewards = secure_client.list_current_rewards()async for page in rewards: # page.items: tuple[CurrentReward, ...] ...
condition_id = market.condition_idif condition_id is None: raise RuntimeError("Market does not have a condition id")rewards = secure_client.list_market_rewards(condition_id=condition_id)async for page in rewards: # page.items: tuple[MarketReward, ...] ...
Secure clients read account-scoped data for the authenticated wallet by default. Methods that take a user= parameter (positions, portfolio value, activity) accept a different wallet address to read its data instead.
Positions
Portfolio Value
Activity
Trades
Notifications
positions = secure_client.list_positions( market=[market.id], page_size=10,)async for page in positions: # page.items: tuple[Position, ...] ...
value = await secure_client.get_portfolio_values(market=[market.id])# value: tuple[PortfolioValue, ...]
activity = secure_client.list_activity( market=[market.id], page_size=10,)async for page in activity: for item in page.items: match item.type: case "TRADE": # item.token_id: TokenId # item.shares: Decimal ... case "REWARD": # item.amount: Decimal ... case _: # SPLIT / MERGE / REDEEM / CONVERSION / MAKER_REBATE # / REFERRAL_REWARD / YIELD ...
yes_token_id = market.outcomes.yes.token_idif yes_token_id is None: raise RuntimeError("Market does not have a YES token id")trades = secure_client.list_account_trades(token_id=yes_token_id)async for page in trades: # page.items: tuple[ClobTrade, ...] ...
Secure clients expose the API credentials created for the authenticated session. Store them securely if you want to reuse the session later without producing a new authentication signature while the credentials remain valid.
Point Combos RFQ endpoints at the production domains: combos-rfq-api.polymarket.com (REST) and combos-rfq-gateway-quoter.polymarket.com (quoter WebSocket).
Added dataframe conversion support for SDK models and response collections.
Secure client setup now defaults to the Deposit Wallet flowAsyncSecureClient.create can now derive and use the signer’s deterministic
Deposit Wallet when you omit wallet. If you already know which Polymarket
wallet you want to use, keep passing wallet.
setup_trading_approvals() now waits internallyYou no longer need to wait on the returned handle. Call the method once before
trading; it is safe to call again if approvals are already set.
Gasless setup helpers are deprecatedYou no longer need to call is_gasless_ready() or setup_gasless_wallet() in
the normal setup path. Create the secure client, then set up trading approvals.