place_limit_order() and place_market_order() are the current polymarket-client methods that place trades on Polymarket. On AsyncSecureClient / SecureClient you can also sign without submitting (create_limit_order / create_market_order → post_order) or batch up to 15 signed orders with post_orders. This page covers GTC/GTD limits, FAK/FOK markets, post-only quotes, builder attribution, and response handling.
For agent builders: automate limit ladders, post_only maker quotes, and paced batch posts — those keep price control and stay within documented batch size. What burns keys and accounts is shipping the owner private key into an agent runtime, ignoring rate limits, and firing FOK into thin books that cannot fill.
Official sources: Place Orders, Python SDK, and SDK Migration. The older py-clob-client-v2 names create_order / create_and_post_order are superseded — treat them as historical only. The sibling CLOB V2 migration guide still documents the V1→V2 cutover and is stale relative to the unified SDK.
Update (September 2026): Uninstall
py-clob-client-v2(and related builder packages); installpolymarket-client(0.9.0 on PyPI as of 2026-09-04, Python >=3.11). Method renames:
Old py-clob-client-v2 | Current polymarket-client |
|---|---|
create_and_post_order | place_limit_order |
create_order | create_limit_order |
create_and_post_market_order | place_market_order |
create_market_order | create_market_order (still exists) |
post_order | post_order |
ClobClient + set_api_creds | AsyncSecureClient.create / SecureClient.create |
OrderArgs / Side enum | keyword args; side="BUY" / "SELL" strings in official Python examples |
PartialCreateOrderOptions(tick_size, neg_risk) | resolved automatically by SDK |
Install and create a secure client
pip uninstall -y py-clob-client-v2 py-builder-relayer-client py-builder-signing-sdk
pip install polymarket-client
uv remove py-clob-client-v2 py-builder-relayer-client py-builder-signing-sdk
uv add polymarket-client
import os
from polymarket import AsyncSecureClient
async with await AsyncSecureClient.create(
private_key=os.environ["POLYMARKET_PRIVATE_KEY"],
) as client:
...
Omit wallet to use the default Deposit Wallet flow. Pass wallet=... when reconnecting an existing account wallet. The sync twin is SecureClient.create(...).
Before placing, load a market and read outcome token IDs:
market = await client.get_market(slug="MARKET_SLUG")
if market.outcomes.yes.token_id is None or market.outcomes.no.token_id is None:
raise RuntimeError("Market token IDs not found")
yes_token_id = market.outcomes.yes.token_id
no_token_id = market.outcomes.no.token_id
One-step limit: place_limit_order
Omit expiration for GTC. Pass a Unix timestamp (seconds) for GTD. Set post_only=True so the order is rejected instead of taking if it would match immediately.
import time
# GTC — rests until filled or canceled
response = await client.place_limit_order(
token_id=yes_token_id,
side="BUY",
price="0.52",
size="10",
)
# GTD — effective lifetime ~1 hour (see GTD rules below)
expiration = int(time.time()) + 60 + 60 * 60
response = await client.place_limit_order(
token_id=yes_token_id,
side="BUY",
price="0.52",
size="10",
expiration=expiration,
)
# Post-only maker quote
response = await client.place_limit_order(
token_id=yes_token_id,
side="BUY",
price="0.52",
size="10",
post_only=True,
)
Check acceptance:
if response.ok:
print(response.order_id, response.status)
print(response.trade_ids, response.transactions_hashes)
else:
print(response.code, response.message)
Accepted limit statuses:
| Status | Meaning |
|---|---|
live | Resting on the book |
matched | Matched immediately with resting liquidity |
delayed | Marketable but subject to a matching delay |
Two-step: create_limit_order → post_order
Sign locally when you need to inspect, store, or batch the order later. Your private key stays on the machine that signs; the CLOB receives the signature.
signed_order = await client.create_limit_order(
token_id=yes_token_id,
side="BUY",
price="0.52",
size="10",
)
response = await client.post_order(signed_order)
Use create_market_order the same way when preparing an FAK/FOK market order to post later.
Market: place_market_order
Market orders derive a marketable price from current book depth. FAK is the default (fill available liquidity, cancel remainder). FOK requires a full immediate fill or nothing.
Estimate first, then place with a price cap:
estimated_buy_price = await client.estimate_market_price(
token_id=yes_token_id,
side="BUY",
amount="10",
order_type="FAK",
)
response = await client.place_market_order(
token_id=yes_token_id,
side="BUY",
amount="10",
max_price=estimated_buy_price,
order_type="FAK",
)
Sell by shares (not USD):
estimated_sell_price = await client.estimate_market_price(
token_id=yes_token_id,
side="SELL",
shares="10",
order_type="FAK",
)
response = await client.place_market_order(
token_id=yes_token_id,
side="SELL",
shares="10",
min_price=estimated_sell_price,
order_type="FAK",
)
amount on a BUY is pre-fee USD notional. Set max_spend when the all-in cost (order + applicable fees) must stay inside a fixed budget:
response = await client.place_market_order(
token_id=yes_token_id,
side="BUY",
amount="10",
max_spend="10",
max_price=estimated_buy_price,
order_type="FAK",
)
FOK example:
response = await client.place_market_order(
token_id=yes_token_id,
side="BUY",
amount="10",
order_type="FOK",
)
Market responses use matched or delayed. A delayed acceptance has empty trade_ids / transactions_hashes until matching finishes — treat it as pending. Check market.trading.seconds_delay before you rely on immediate fills.
Optional settlement wait (default 30 seconds via timeout_s):
if response.ok:
hashes = await client.wait_for_order_fill_settlement(response)
Order types: GTC / GTD / FOK / FAK
| Type | Behavior | Typical use |
|---|---|---|
| GTC | Remains active until filled or canceled | Limit with no deadline |
| GTD | Remains until your expiration timestamp | Limit that must die before an event |
| FOK | Entire size fills immediately, or nothing | Market — all-or-nothing |
| FAK | Fills available size immediately, cancels remainder | Market — partial OK (SDK default) |
GTD rules (from Place Orders): orders expire 1 minute before the stated expiration as a security threshold. To get an effective lifetime of N seconds, use now + 60 + N. Expiration must be at least 3 minutes in the future — sooner timestamps are rejected — so the minimum effective lifetime is about two minutes.
Batch: post_orders (1–15)
Create each signed order, then submit together. Useful for quote ladders.
first_order = await client.create_limit_order(
token_id=yes_token_id,
side="BUY",
price="0.51",
size="10",
)
second_order = await client.create_limit_order(
token_id=yes_token_id,
side="BUY",
price="0.52",
size="10",
)
responses = await client.post_orders([first_order, second_order])
for response in responses:
if response.ok:
print(response.order_id, response.status)
else:
print(response.code, response.message)
post_orders accepts 1–15 signed orders per call. Each response entry can succeed or fail independently — check ok on every entry.
Tick size and neg_risk
The unified SDK resolves tick size, negative-risk routing, fees, and signing details when you place. You do not pass PartialCreateOrderOptions anymore.
Still useful to read for sizing and debugging:
tick_size = market.trading.minimum_tick_size
neg_risk = market.state.neg_risk
Price must follow the market’s minimum increment; size must meet minimum order size. The CLOB rejects non-conforming prices.
Builder attribution
Pass your 32-byte hex builder code so matched trades credit your Builder profile. It is part of the signed order — set it before signing / one-step place.
import os
response = await client.place_limit_order(
token_id=yes_token_id,
side="BUY",
price="0.52",
size="10",
builder_code=os.environ["POLYMARKET_BUILDER_CODE"],
)
Copy the code from polymarket.com → Settings → Builders. Verify later with list_builder_trades(builder_code=...).
Complete workflow sketch
import asyncio
import os
from polymarket import AsyncSecureClient
async def main() -> None:
async with await AsyncSecureClient.create(
private_key=os.environ["POLYMARKET_PRIVATE_KEY"],
) as client:
market = await client.get_market(slug="MARKET_SLUG")
yes_token_id = market.outcomes.yes.token_id
if yes_token_id is None:
raise RuntimeError("YES token ID missing")
response = await client.place_limit_order(
token_id=yes_token_id,
side="BUY",
price="0.52",
size="10",
)
if not response.ok:
raise RuntimeError(f"{response.code}: {response.message}")
print(response.order_id, response.status)
if response.trade_ids:
hashes = await client.wait_for_order_fill_settlement(response)
print(hashes)
condition_id = market.condition_id
assert condition_id is not None
page = await client.list_positions(market=[condition_id]).first_page()
for position in page.items:
if position.token_id == yes_token_id:
print(position.size)
asyncio.run(main())
For pre-trade collateral checks, see get_balance_allowance. For depth before market orders, see get_order_book.
Common errors
| Symptom | Cause | What to do |
|---|---|---|
response.ok is False | CLOB rejected the order | Read response.code / response.message |
not enough balance / allowance | Collateral or allowance too low | Fund / approve; SDK also refreshes allowance during place |
| GTD rejected | Expiration less than 3 minutes ahead | Use now + 60 + N with N large enough |
| Post-only rejected | Order would take immediately | Widen from the touch or drop post_only |
| FOK rejected / empty fill | Book cannot fill full size | Check depth; prefer FAK on thin books |
delayed status | Matching delay (seconds_delay) | Treat as pending; do not assume fill yet |
RequestRejectedError status 425 | Matching engine restart window | Retry place_limit_order with exponential backoff |
RateLimitError | Too many requests | Back off; see rate limits |
TimeoutError from settlement wait | Fills still settling after ~30s | Trades still executed; poll positions / wait longer |
Agent compatibility
Safer automation
- Limit ladders and
post_ordersbatches of 1–15 post_only=Truemaker quotes that refuse to take- GTC/GTD with explicit size/price strings aligned to tick size
estimate_market_price+max_price/min_price/max_spendbefore market buys- Scoped session keys for Deposit Wallet trading (time-limited signer) — keep owner keys off the bot host when you can
Unsafe / account-burning patterns
- Shipping the owner private key in an agent container, chat tool, or CI log
- Ignoring rate limits and hammering
/orderor/orders - Blind FOK on thin books
- Treating
delayedas a confirmed fill
Related
- py-clob-client reference — broader method map (legacy V2 names still appear elsewhere on the site)
- get_balance_allowance — collateral before you size an order
- get_order_book — depth before market / FOK
- Polymarket rate limits — pacing for bots
- CLOB V2 migration (sibling, stale vs unified SDK) — older V1→V2 cutover only
- Polymarket trading bot quickstart — bot architecture around order placement
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.
