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
{{ partial “marketplace-cta.html” . }}
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.
