If you've been building a Polymarket trading bot in Python, you've probably hit this:
{"error": "order_version_mismatch"}
Or in py-clob-client:
PolyApiException: order_version_mismatch
This is the debugging guide we wish existed when we hit it ourselves. It covers what order_version_mismatch actually means on Polymarket's CLOB, the five causes we've seen in production, and a working fix for each.
What order_version_mismatch Actually Means
When you submit an order to Polymarket's CLOB API, you don't send the raw order — you send a signed EIP-712 typed-data message. The CLOB server independently recomputes the expected hash from the order's fields (owner address, token ID, price, side, salt, expiration, Exchange contract address, chain ID) and verifies that your signature is valid for that hash.
If the recomputed hash doesn't match the hash you signed against, your signature is necessarily invalid — but the server doesn't return "invalid signature." It returns order_version_mismatch, because the most likely cause is that the version of the order struct or domain separator you signed against is different from the version the server expects.
In other words: the server is telling you that your client is signing orders against the wrong contract, the wrong owner, the wrong chain, or an outdated order schema.
The fix is not retrying. The fix is making your client match what the server expects. Here are the five things that have to line up.
Cause #1: Outdated py-clob-client (the 80% fix)
Polymarket has rolled out multiple Exchange contract upgrades and order-schema revisions over the life of the CLOB. Each one bumps internal constants in py-clob-client. If your installed version predates the current Exchange schema, every order you sign uses an old domain separator and the CLOB rejects all of them with order_version_mismatch.
Fix:
pip install --upgrade py-clob-client
Then verify:
import py_clob_client
print(py_clob_client.__version__)
Compare against the latest release on GitHub. If you're more than one minor version behind, upgrade and retry. In our experience this resolves order_version_mismatch cleanly in roughly 80% of cases — the other four causes below account for the remainder.
If you pin the version in requirements.txt, also delete and recreate your virtualenv to make sure you're not running cached bytecode against a stale package. We've seen this trip people up: the new version is installed system-wide but the venv still resolves to the old one.
Cause #2: Wrong signature_type for Your Wallet Topology
This is the most common cause that isn't solved by upgrading. Polymarket supports three signature types on the CLOB, and you must construct your client with the right one:
signature_type |
Wallet type | When to use |
|---|---|---|
0 |
Plain EOA (externally-owned account) | You sign directly with your private key and trade from that same address |
1 |
POLY_PROXY (legacy Polymarket proxy) |
Older accounts given a Polymarket-managed proxy contract |
2 |
POLY_GNOSIS_SAFE |
Modern accounts where the Polymarket UI provisioned a Gnosis Safe |
If you signed up on polymarket.com any time in the last couple of years, your funds almost certainly live in a Gnosis Safe proxy whose only owner is your EOA. Trading from that Safe means signing with signature_type=2, where the EOA is the signer but the owner of the order — the address the CLOB checks balance against — is the Safe.
If you pass signature_type=0 for a Safe-controlled account, the CLOB recomputes the order hash against the EOA's address while you signed against the Safe (or vice versa). The hashes don't match. You get order_version_mismatch.
Fix — set the signature type at client construction:
import os
from py_clob_client.client import ClobClient
client = ClobClient(
host="https://clob.polymarket.com",
key=os.environ["POLY_PRIVATE_KEY"], # EOA private key (signer)
chain_id=137, # Polygon mainnet
signature_type=2, # POLY_GNOSIS_SAFE
funder=os.environ["POLY_FUNDER"], # the Safe proxy address
)
If you don't know which type you have, log into polymarket.com, open your profile, and compare your deposit address against the address derived from your private key. If they differ, you have a proxy — almost certainly Safe in 2026, so signature_type=2. If they match, you're trading as a plain EOA and signature_type=0 is correct.
Cause #3: Wrong funder Address for Proxy Wallets
Closely related to cause #2: when you trade via a proxy, py-clob-client needs to know which proxy is paying. The funder parameter tells the client the address that actually holds USDC and will be debited on a fill.
If you instantiate the client with the EOA as the funder but signature_type=2, the order's owner address is computed wrong, the CLOB rejects with order_version_mismatch, and you've burned a request quota slug for nothing.
The convention we use across our own bots — and document in our Polymarket API Python tutorial — is to keep POLY_PRIVATE_KEY and POLY_FUNDER as two distinct environment variables:
# .env
POLY_PRIVATE_KEY=0x... # your EOA private key (signer)
POLY_FUNDER=0xYOUR_SAFE_PROXY_ADDRESS # your Safe proxy address (NOT the EOA)
POLY_SIG_TYPE=2
They are different addresses and conflating them will burn a long debugging session. If you don't know your proxy address, the Polymarket UI surfaces it under your profile as the deposit address — copy that into POLY_FUNDER.
Cause #4: Chain ID Mismatch (It Must Be 137)
The EIP-712 domain separator includes chainId. Polymarket's CLOB lives on Polygon mainnet, which is chain ID 137.
If you pass chain_id=80001 (Mumbai), chain_id=1 (Ethereum mainnet), or somehow leave it at a library default that doesn't match, the recomputed hash differs from the server's expected hash → order_version_mismatch.
Fix:
client = ClobClient(
host="https://clob.polymarket.com",
key=os.environ["POLY_PRIVATE_KEY"],
chain_id=137, # Polygon mainnet — non-negotiable
signature_type=2,
funder=os.environ["POLY_FUNDER"],
)
This sounds obvious. We've seen it bite people who copy-pasted a snippet from a multichain bridge tutorial and left the chain ID untouched.
Cause #5: Neg-Risk Markets Use a Different Exchange Contract
Polymarket runs two parallel order books with two different Exchange contracts:
- Standard markets (binary YES/NO on a single outcome) use the standard CTF Exchange.
- Neg-risk markets (mutually exclusive multi-outcome events, e.g. "which team wins the championship?") use the
NegRiskAdapterat0xd91E80cF2E7be2e162c6513ceD06f1dD0dA35296.
Each Exchange contract has its own EIP-712 domain separator. py-clob-client picks the right one based on a neg_risk flag, which on modern versions is set from the market metadata returned by the Gamma API. On older versions, or if you bypass that lookup, you may end up signing a neg-risk order against the standard Exchange address → order_version_mismatch.
Symptom: orders work fine for some markets and fail consistently for others — typically the multi-outcome / "winner of" markets.
If you're constructing orders manually, pass neg_risk=True to create_order:
signed = client.create_order(
order_args,
options={"neg_risk": True},
)
If you're not sure whether a market is neg-risk, query its metadata first:
import requests
market = requests.get(
"https://gamma-api.polymarket.com/markets",
params={"clob_token_ids": token_id},
).json()
neg_risk = market[0].get("negRisk", False) if market else False
For more on how the two market types differ, see our practical Polymarket API guide.
A Minimal Reproducer That Works
Putting it together, here is an end-to-end snippet that places an order without hitting order_version_mismatch. Assume a Gnosis Safe proxy account (the default for users who joined Polymarket in 2024 or later):
import os
import requests
from py_clob_client.client import ClobClient
from py_clob_client.clob_types import OrderArgs
from py_clob_client.order_builder.constants import BUY
# 1. Build the client with the correct signature type, funder, and chain
client = ClobClient(
host="https://clob.polymarket.com",
key=os.environ["POLY_PRIVATE_KEY"], # EOA private key
chain_id=137, # Polygon mainnet
signature_type=2, # POLY_GNOSIS_SAFE
funder=os.environ["POLY_FUNDER"], # Safe proxy address
)
client.set_api_creds(client.create_or_derive_api_creds())
# 2. Look up whether the market is neg-risk
token_id = "YOUR_TOKEN_ID_HERE"
meta = requests.get(
"https://gamma-api.polymarket.com/markets",
params={"clob_token_ids": token_id},
).json()
neg_risk = meta[0].get("negRisk", False) if meta else False
# 3. Build the order with neg_risk routing
order_args = OrderArgs(
price=0.50,
size=10,
side=BUY,
token_id=token_id,
)
signed = client.create_order(
order_args,
options={"neg_risk": neg_risk},
)
# 4. Post it
resp = client.post_order(signed)
print(resp)
If this script returns order_version_mismatch, work down the five causes above — the issue is one of them.
Diagnostic Checklist
Before you start changing code, verify each of these against your client config:
pip show py-clob-client— version is the latest stablesignature_type— matches your wallet type (0 / 1 / 2)funder— your proxy address ifsignature_type > 0, otherwise your EOAchain_id— 137neg_risk— matches what the Gamma API says for the market you're trading
The fastest path to isolating the cause is to test against a known-good liquid market — any active NBA moneyline works — and see if it succeeds. If a liquid standard market works but a specific market doesn't, the difference is almost always neg-risk routing. If nothing works, the issue is upstream: client version, signature type, funder, or chain.
Related CLOB Errors You'll Hit Next
After you fix order_version_mismatch, the next class of CLOB rejections is usually:
INVALID_ORDER_MIN_TICK_SIZE— your price isn't a multiple of the market's tick (1c on most sports markets, 0.1c on some)INVALID_ORDER_MIN_SIZE— your size is below the market's minimum (usually 5 shares)INSUFFICIENT_BALANCE— funder doesn't hold enough USDC, or hasn't approved the Exchange contract to spend itNOT_ENOUGH_BALANCE— same family; check the Exchange contract approval first
The first two are math errors in your order builder. The second two are setup: approvals for the Exchange contract — and, for neg-risk markets, the NegRiskAdapter — need to be done once per funder address. py-clob-client exposes helpers for this, or you can do it directly on-chain through your wallet.
Why This Error Is So Hard to Find Online
order_version_mismatch is a string the CLOB returns, but it isn't a well-documented error in Polymarket's official docs. It also isn't unique to one symptom — it covers a whole family of signature-domain mismatches. Most discussion of it lives in py-clob-client GitHub issues, which Google indexes poorly and which aren't searchable in a structured way.
If you're building a real trading bot on top of the CLOB, you will hit this error and the next ten like it. Our broader guide to the eight components of a Polymarket bot covers what the rest of the stack looks like; this post is the one we wish we'd had when we hit order_version_mismatch for the first time.
If you want to see what's possible once your signing actually works, our Polymarket trading bot P&L breakdown walks through the real results from running these bots across 8 sports.