create_order() and post_order() are the py-clob-client-v2 methods that place trades on Polymarket. Order placement is a two-step process: first sign the order locally with your wallet, then submit it to the CLOB API. This page covers limit orders with OrderArgs, market orders with MarketOrderArgs, order types (GTC/GTD/FOK/FAK), the new options argument, batch orders, and common errors.
⚠️ CLOB V2 (live April 28, 2026). Imports come from
py_clob_client_v2. Key changes for ordering: theBUY/SELLconstants became theSideenum (Side.BUY);create_order()takes a secondoptionsargument (PartialCreateOrderOptions(tick_size=..., neg_risk=...));OrderArgs/MarketOrderArgsno longer carryfee_rate_bps/nonce/taker(fees are protocol-set at match time); and there’s a new one-stepcreate_and_post_market_order(). The V1py-clob-clientpackage no longer works on production — see Migrating to CLOB V2.
For the complete V2 method reference, see the py-clob-client-v2 Reference. For Kalshi’s equivalent order placement, see the Prediction Market API Reference.
The Two-Step Order Flow
Every order on Polymarket follows this pattern:
- Sign locally —
create_order()orcreate_market_order()signs the order with your private key, producing aSignedOrder - Post to API —
post_order()submits the signed order to the CLOB for matching
# Step 1: Sign (options carries tick_size / neg_risk)
signed = client.create_order(order_args, options)
# Step 2: Post
response = client.post_order(signed, OrderType.GTC)
This design means your private key never leaves your machine — the API only receives the cryptographic signature. For straightforward trades, create_and_post_order() (limit) and create_and_post_market_order() (market) combine both steps into one call.
Limit Orders with OrderArgs
Signatures
client.create_order(order_args: OrderArgs, options: PartialCreateOrderOptions = None) -> SignedOrder
client.post_order(signed_order, order_type: OrderType = OrderType.GTC, post_only: bool = False, defer_exec: bool = False) -> dict
OrderArgs Fields
| Field | Type | Required | Description |
|---|---|---|---|
token_id | str | Yes | The outcome token ID (YES or NO token) |
price | float | Yes | Price per share (0.01 to 0.99) |
size | float | Yes | Number of shares to buy or sell |
side | Side | Yes | Side.BUY or Side.SELL (the V2 Side enum, imported from py_clob_client_v2) |
expiration | int | No | Unix timestamp; 0 (default) = no expiration (GTC). Set a future ts and post as GTD |
builder_code | str | No | bytes32 builder code for fee attribution |
metadata | str | No | Optional bytes32 metadata |
V2:
OrderArgsno longer takesfee_rate_bps,nonce, ortaker.tick_size/neg_riskmoved to theoptionsargument ofcreate_order().
post_order() Parameters
| Parameter | Type | Description |
|---|---|---|
signed_order | signed order | The signed order from create_order() |
order_type | OrderType | GTC, GTD, FOK, or FAK (see Order Types) |
post_only | bool | If True, rejects if the order would immediately match |
defer_exec | bool | If True, defers matching-engine execution (advanced) |
Example — Place a Limit Buy
from py_clob_client_v2 import ClobClient, OrderArgs, OrderType, PartialCreateOrderOptions, Side
client = ClobClient(
host="https://clob.polymarket.com",
chain_id=137,
key="<your-private-key>",
)
client.set_api_creds(client.create_or_derive_api_key())
order = OrderArgs(
token_id="<token-id>",
price=0.45,
size=20.0,
side=Side.BUY,
)
signed = client.create_order(order, PartialCreateOrderOptions(tick_size="0.01"))
response = client.post_order(signed, OrderType.GTC)
print(f"Order ID: {response['orderID']}")
print(f"Status: {response.get('status', 'submitted')}")
Example — Place a Limit Sell
from py_clob_client_v2 import OrderArgs, OrderType, PartialCreateOrderOptions, Side
order = OrderArgs(
token_id="<token-id>",
price=0.65,
size=10.0,
side=Side.SELL,
)
signed = client.create_order(order, PartialCreateOrderOptions(tick_size="0.01"))
response = client.post_order(signed, OrderType.GTC)
Example — Order with Expiration (GTD)
import time
from py_clob_client_v2 import OrderArgs, OrderType, PartialCreateOrderOptions, Side
order = OrderArgs(
token_id="<token-id>",
price=0.40,
size=50.0,
side=Side.BUY,
expiration=int(time.time()) + 3600, # Expires in 1 hour
)
signed = client.create_order(order, PartialCreateOrderOptions(tick_size="0.01"))
response = client.post_order(signed, OrderType.GTD) # GTD = good-til-date
Market Orders with MarketOrderArgs
Market orders execute immediately against resting liquidity at the best available prices.
Signatures
client.create_market_order(order_args: MarketOrderArgs, options: PartialCreateOrderOptions = None) -> SignedOrder
client.create_and_post_market_order(order_args: MarketOrderArgs, options: PartialCreateOrderOptions = None, order_type: OrderType = OrderType.FOK) -> dict
MarketOrderArgs Fields
| Field | Type | Required | Description |
|---|---|---|---|
token_id | str | Yes | The outcome token ID |
amount | float | Yes | For BUY: USDC to spend. For SELL: number of shares to sell |
side | Side | Yes | Side.BUY or Side.SELL |
order_type | OrderType | No | OrderType.FOK (default) or OrderType.FAK |
user_usdc_balance | float | No | Your collateral balance; lets the SDK fee-adjust market buys |
builder_code | str | No | bytes32 builder code for fee attribution |
metadata | str | No | Optional bytes32 metadata |
V2:
MarketOrderArgsdroppedfee_rate_bps(andnonce/taker). Fees are protocol-set at match time; the newuser_usdc_balancefield lets the SDK size fee-adjusted market buys.
Example — Buy $25 at Market Price (one-step)
from py_clob_client_v2 import MarketOrderArgs, OrderType, PartialCreateOrderOptions, Side
response = client.create_and_post_market_order(
order_args=MarketOrderArgs(
token_id="<token-id>",
amount=25.0,
side=Side.BUY,
order_type=OrderType.FOK,
),
options=PartialCreateOrderOptions(tick_size="0.01"),
order_type=OrderType.FOK,
)
print(f"Order ID: {response['orderID']}")
Example — Sell 50 Shares at Market Price (two-step)
from py_clob_client_v2 import MarketOrderArgs, OrderType, PartialCreateOrderOptions, Side
sell_order = MarketOrderArgs(
token_id="<token-id>",
amount=50.0,
side=Side.SELL,
order_type=OrderType.FOK,
)
signed = client.create_market_order(sell_order, PartialCreateOrderOptions(tick_size="0.01"))
response = client.post_order(signed, OrderType.FOK)
Key difference: OrderArgs uses price + size (price per share × number of shares). MarketOrderArgs uses amount (total USDC to spend for buys, or shares to sell for sells).
Order Types: GTC, GTD, FOK, FAK
| Type | Constant | Behavior | Use Case |
|---|---|---|---|
| GTC | OrderType.GTC | Rests on the order book until filled or canceled | Limit orders — you want a specific price |
| GTD | OrderType.GTD | Rests until a chosen expiration timestamp | Limit orders with a deadline |
| FOK | OrderType.FOK | Must fill entirely and immediately, or is rejected | Market orders — you want instant execution |
| FAK | OrderType.FAK | Fills what it can immediately, cancels the rest | Partial fills OK — you want whatever is available now |
from py_clob_client_v2 import OrderType
# Limit order that rests on the book
client.post_order(signed, OrderType.GTC)
# Limit order that rests until its expiration timestamp
client.post_order(signed, OrderType.GTD)
# Market order that fills immediately or fails
client.post_order(signed, OrderType.FOK)
# Aggressive order — fill what you can, cancel rest
client.post_order(signed, OrderType.FAK)
Post-Only Orders (Market Makers)
Post-only orders are rejected if they would immediately match against resting orders. This guarantees you’re always the maker (providing liquidity), never the taker.
from py_clob_client_v2 import OrderArgs, OrderType, PartialCreateOrderOptions, Side
order = OrderArgs(
token_id="<token-id>",
price=0.49,
size=100.0,
side=Side.BUY,
)
signed = client.create_order(order, PartialCreateOrderOptions(tick_size="0.01"))
response = client.post_order(signed, OrderType.GTC, post_only=True)
# If the best ask is <= 0.49, this order is rejected
# Otherwise, it rests on the book at 0.49
Batch Orders
Place up to 15 orders in a single API call. Essential for market makers updating quotes on both sides.
Signature
client.post_orders(args: list, post_only: bool = False, defer_exec: bool = False) -> dict
In V2 the order type travels with each signed order, so post_orders() takes post_only/defer_exec flags rather than a single order_type for the whole batch.
Example — Ladder of Buy Orders
from py_clob_client_v2 import OrderArgs, PartialCreateOrderOptions, Side
opts = PartialCreateOrderOptions(tick_size="0.01")
orders = []
for price in [0.40, 0.42, 0.44, 0.46, 0.48]:
order = OrderArgs(
token_id="<token-id>",
price=price,
size=50.0,
side=Side.BUY,
)
orders.append(client.create_order(order, opts))
response = client.post_orders(orders)
print(f"Batch submitted: {len(orders)} orders")
Example — Two-Sided Quotes
from py_clob_client_v2 import OrderArgs, PartialCreateOrderOptions, Side
opts = PartialCreateOrderOptions(tick_size="0.01")
signed_orders = []
# Buy side
for price in [0.48, 0.47, 0.46]:
order = OrderArgs(token_id="<token-id>", price=price, size=100.0, side=Side.BUY)
signed_orders.append(client.create_order(order, opts))
# Sell side
for price in [0.52, 0.53, 0.54]:
order = OrderArgs(token_id="<token-id>", price=price, size=100.0, side=Side.SELL)
signed_orders.append(client.create_order(order, opts))
response = client.post_orders(signed_orders)
print(f"Posted {len(signed_orders)} orders (3 bids + 3 asks)")
Tick Size Validation
Every Polymarket market has a tick_size — the minimum price increment. Orders that don’t align to the tick size are rejected. In CLOB V2 you pass it via the options argument: PartialCreateOrderOptions(tick_size="0.01"). Read a market’s tick size with client.get_tick_size(token_id) or from the tick_size field on the get_order_book() response.
| Tick Size | Valid Prices | Example |
|---|---|---|
0.1 | 0.1, 0.2, …, 0.9 | Coarse markets |
0.01 | 0.01, 0.02, …, 0.99 | Most markets |
0.001 | 0.001, 0.002, …, 0.999 | High-volume markets |
0.0001 | 0.0001, …, 0.9999 | Finest granularity |
def align_price(price, tick_size=0.01):
"""Round a price to the nearest valid tick."""
return round(round(price / tick_size) * tick_size, 4)
# Example
raw_price = 0.4537
aligned = align_price(raw_price, tick_size=0.01)
print(f"{raw_price} → {aligned}") # 0.4537 → 0.45
aligned_fine = align_price(raw_price, tick_size=0.001)
print(f"{raw_price} → {aligned_fine}") # 0.4537 → 0.454
To get the tick size for a market, query the Gamma API:
import requests
slug = "bitcoin-above-100k"
resp = requests.get(f"https://gamma-api.polymarket.com/markets?slug={slug}")
market = resp.json()[0]
tick_size = float(market.get("minimum_tick_size", "0.01"))
Neg Risk Markets
Some Polymarket markets are flagged as neg_risk (negative risk) — multi-outcome markets that share a collateral pool. You must set this flag correctly or the order routes to the wrong Exchange contract and is rejected. In CLOB V2, pass it via the options argument; read a market’s flag with client.get_neg_risk(token_id) or the neg_risk field on the get_order_book() response.
from py_clob_client_v2 import OrderArgs, OrderType, PartialCreateOrderOptions, Side
# Read the flag (or use the neg_risk field from get_order_book)
is_neg_risk = client.get_neg_risk("<token-id>")
# Pass it through options when creating the order
client.create_and_post_order(
order_args=OrderArgs(token_id="<token-id>", price=0.50, size=10.0, side=Side.BUY),
options=PartialCreateOrderOptions(tick_size="0.01", neg_risk=is_neg_risk),
order_type=OrderType.GTC,
)
When using the TypeScript SDK, neg_risk is passed in the options:
const order = await client.createAndPostOrder(
{ tokenID: "TOKEN_ID", price: 0.50, size: 10, side: Side.BUY },
{ tickSize: "0.01", negRisk: true } // Pass neg_risk flag
);
Complete Workflow: Buy, Monitor, Sell
A full trading cycle combining order placement with position and balance checks:
import requests
from py_clob_client_v2 import (
ClobClient, OrderArgs, OrderType,
PartialCreateOrderOptions, Side,
BalanceAllowanceParams, AssetType,
)
client = ClobClient(
host="https://clob.polymarket.com",
chain_id=137,
key="<your-private-key>",
)
client.set_api_creds(client.create_or_derive_api_key())
token_id = "<token-id>"
WALLET = "<your-wallet-address>" # funder address that holds your positions
opts = PartialCreateOrderOptions(tick_size="0.01")
# 1. Check pUSD balance (V2: get_balance_allowance, not get_balance)
balance = client.get_balance_allowance(
BalanceAllowanceParams(asset_type=AssetType.COLLATERAL)
)
pusd = int(balance["balance"]) / 1e6
print(f"Available: ${pusd:.2f} pUSD")
# 2. Check order book for pricing (V2 returns a dict)
book = client.get_order_book(token_id)
best_ask = float(book["asks"][0]["price"]) if book["asks"] else None
print(f"Best ask: ${best_ask}")
# 3. Place a limit buy below the best ask
size = 0.0
if best_ask and pusd > 0:
buy_price = round(best_ask - 0.02, 2) # 2 cents below ask
buy_size = min(100, pusd / buy_price)
client.create_and_post_order(
order_args=OrderArgs(token_id=token_id, price=buy_price, size=buy_size, side=Side.BUY),
options=opts,
order_type=OrderType.GTC,
)
print(f"Buy order placed: {buy_size:.0f} shares @ ${buy_price}")
# 4. Check positions after fill — positions come from the Data API in V2
positions = requests.get(
"https://data-api.polymarket.com/positions",
params={"user": WALLET},
).json()
for pos in positions:
if pos["asset"] == token_id:
size = float(pos["size"])
print(f"Position: {size:.2f} shares")
# 5. Place a sell order at target price
if size > 0:
client.create_and_post_order(
order_args=OrderArgs(token_id=token_id, price=0.70, size=size, side=Side.SELL),
options=opts,
order_type=OrderType.GTC,
)
print(f"Sell order placed: {size:.0f} shares @ $0.70")
Common Errors
| Error | Cause | Fix |
|---|---|---|
maker address not allowed, please use the deposit wallet flow | New-style accounts must trade via a deposit wallet (POLY_1271) | Use signature_type=SignatureTypeV2.POLY_1271 with your deposit-wallet funder. This is the most-reported V2 error — see the known-bugs tracker |
INSUFFICIENT_BALANCE | Not enough pUSD for the order | Check balance with get_balance_allowance() first (pUSD) |
INVALID_TICK_SIZE | Price doesn’t match market’s tick size | Pass the right tick via options=PartialCreateOrderOptions(tick_size=...); read it with get_tick_size() |
| Order rejected (sub-cent amount) | Float-precision drift in V2 amount rounding | Known V2 issue on FOK/limit orders — round size/price yourself before signing |
MARKET_NOT_TRADABLE | Market is closed or resolved | Check market status via Gamma API before ordering |
INVALID_SIGNATURE / 401 | Auth signature/credential mismatch | Regenerate credentials: client.set_api_creds(client.create_or_derive_api_key()) |
| FOK order rejected | Not enough liquidity for full fill | Check order book depth or use FAK for partial fills |
| Post-only rejected | Order would immediately match | Your price crosses the spread — adjust it |
See Also
- py_clob_client Complete Reference — Every method documented
- py_clob_client get_order_book() — Check liquidity before placing orders
- py_clob_client get_balance_allowance() — Verify funds before trading
- py_clob_client get_positions() — Track positions after fills
- Prediction Market API Reference — Polymarket vs Kalshi comparison
- Polymarket Rate Limits Guide — Handle 429 errors when placing orders
- Build a Polymarket Trading Bot — Full bot tutorial
- Agent Betting Glossary — Key terms defined
- Sports Betting Arbitrage Bot — Use order placement across prediction markets and sportsbooks
This reference is maintained by AgentBets.ai. Found an error or SDK change we missed? Let us know on Twitter.
Not financial advice. Built for builders.
