If a Polymarket Python integration returns:
{"error": "order_version_mismatch"}
start with the version boundary, not a retry loop. CLOB V2 went live on April 28, 2026. The legacy py-clob-client order-signing flow is unsupported in production. The production host is still https://clob.polymarket.com, which makes an old client look deceptively close to correct.
Polymarket's official CLOB V2 migration guide identifies the breaking changes: a new Python package, new Exchange contracts, EIP-712 Exchange domain version 2, a changed order struct, and pUSD collateral. This guide turns that migration list into a Python debugging sequence.
Source check: October 8, 2026. Polymarket recommends the unified polymarket-client SDK for new projects. The token-based CLOB examples below target py-clob-client-v2 1.2.0. That package also distinguishes position_id orders, which use a different signing route; do not substitute identifiers or apply one Exchange version to every market. Public GET response shapes were checked separately; authenticated orders, cancellation and redemption were not executed for this article.
Check the Running Package and Market Route
Do not treat an upgrade of the old py-clob-client distribution as a migration. Check the service interpreter and its dependency lock; in an isolated environment, the retained CLOB-specific examples use:
python -m pip install py-clob-client-v2==1.2.0
python -m pip show py-clob-client-v2
The import path changes too:
# Legacy package — unsupported order-signing example
from py_clob_client.client import ClobClient
# V2
from py_clob_client_v2 import ClobClient
If the traceback still mentions py_clob_client rather than py_clob_client_v2, the running process is loading the old environment. Check the interpreter used by the service, not only the one in your shell:
python -c "import sys, py_clob_client_v2; print(sys.executable); print(py_clob_client_v2.__file__)"
The April 2026 Token-Order Version Boundary
An order is signed as EIP-712 typed data. The April 28 CLOB cutover changed the token-order Exchange domain and struct described in the migration guide. These are that migration’s changes, not a universal signer template for newer position-backed markets:
- Exchange domain version changed from
"1"to"2". - The V2
verifyingContractaddresses differ for standard and neg-risk markets. - Signed fields
nonce,feeRateBps,taker, andexpirationwere removed (expirationstill exists in the POST body for GTD handling). - Signed fields
timestamp,metadata, andbuilderwere added. - Fees are now chosen by the protocol at match time, not embedded in the signed order.
A V1 signature is therefore a signature over the wrong message. Reposting the same payload cannot make it valid. If you sign raw orders, update the domain, contract address, and order type exactly as documented in the API-user section of the migration guide. If you use the V2 SDK, let it build the payload.
Check the Wallet Signature Type and Funder
CLOB V2 supports four wallet paths. The signature type and funder must describe the wallet that actually holds the trading collateral:
| Signature type | Wallet path | Funder |
|---|---|---|
0 |
Standalone EOA | The EOA address |
1 |
Existing POLY_PROXY account |
The existing proxy address |
2 |
Existing Gnosis Safe account | The Safe address |
3 |
New deposit-wallet flow (POLY_1271) |
The deposit-wallet address |
Polymarket's current authentication documentation says new API users should use deposit wallets with signature type 3. Existing proxy and Safe users can keep types 1 and 2; do not migrate the number by guesswork.
For a deposit wallet, the owner key signs, but the order's maker and signer are validated through the deposit wallet using ERC-1271. A type-0, 1, or 2 order pointed at a type-3 deposit wallet is not equivalent.
A Token-Based Example for CLOB Client 1.2.0
This unexecuted example assumes an already configured deposit wallet, owner signer and existing CLOB credentials. It submits an order when run; price, size and options are placeholders to validate against the selected token market.
import os
from py_clob_client_v2 import (
ApiCreds,
ClobClient,
OrderArgs,
OrderType,
PartialCreateOrderOptions,
Side,
SignatureTypeV2,
)
client = ClobClient(
host="https://clob.polymarket.com",
chain_id=137,
key=os.environ["POLYMARKET_PRIVATE_KEY"],
creds=ApiCreds(
api_key=os.environ["POLYMARKET_API_KEY"],
api_secret=os.environ["POLYMARKET_API_SECRET"],
api_passphrase=os.environ["POLYMARKET_API_PASSPHRASE"],
),
signature_type=SignatureTypeV2.POLY_1271,
funder=os.environ["POLYMARKET_DEPOSIT_WALLET"],
)
response = client.create_and_post_order(
order_args=OrderArgs(
token_id=os.environ["POLYMARKET_TOKEN_ID"],
price=0.50,
size=10,
side=Side.BUY,
),
options=PartialCreateOrderOptions(
tick_size="0.01",
neg_risk=False,
),
order_type=OrderType.GTC,
)
print(response)
Use the market's real tick size and neg-risk flag; the literal values above are only appropriate when the selected market has those settings. The V2 client can fetch CLOB market metadata by condition ID:
info = client.get_clob_market_info("CONDITION_ID")
print(info) # tokens, minimum order size, tick size, neg-risk and fee details
Derive API Credentials with the Same Signer
CLOB authentication has two layers. The wallet signature creates or derives API credentials; those credentials then authenticate trading requests. If you need to derive them:
bootstrap = ClobClient(
host="https://clob.polymarket.com",
chain_id=137,
key=os.environ["POLYMARKET_PRIVATE_KEY"],
)
api_creds = bootstrap.create_or_derive_api_key()
Rebuild the fully authenticated client with api_creds, the correct signature_type, and the correct funder. Do not mix credentials derived by one signer with orders signed by another.
Check pUSD, Balance, and Allowance
CLOB V2 uses pUSD, not the V1 USDC.e collateral contract. The Polymarket interface handles wrapping for normal site users. API-only users must follow the pUSD and deposit-wallet flow.
This normally causes balance or allowance errors rather than order_version_mismatch, but it is the next failure after signing is corrected. Verify that:
- pUSD is held by the configured funder.
- Allowances were granted by that funder, not by a different EOA.
- The CLOB balance cache was refreshed with the same signature type.
- Polygon mainnet chain ID is
137.
Standard vs Neg-Risk Markets
Standard and neg-risk markets use different V2 Exchange verifying contracts. Do not copy a V1 adapter address into a V2 signer, and do not infer neg-risk from a title such as “winner.” Resolve the market through Gamma, then use CLOB market metadata or the SDK's market lookup.
If you supply order options yourself, make sure neg_risk matches the selected token:
options = PartialCreateOrderOptions(
tick_size="0.01",
neg_risk=True,
)
A failure confined to one market family is a reason to inspect identifiers, position system, contract routing and options. It does not uniquely diagnose neg-risk; compare the actual response with the official error reference.
Diagnostic Checklist
Work through these in order:
pip show py-clob-client-v2succeeds in the service's interpreter.- No runtime import comes from
py_clob_client. - The production host is
https://clob.polymarket.com. - Chain ID is
137. - Wallet type and funder match:
0EOA,1proxy,2Safe, or3deposit wallet. - The signer used for API credentials matches the signer used by the client.
- Raw signers use the domain, contract and fields required for that market’s position system; the April token-order migration alone does not cover every current market.
- Tick size and neg-risk settings come from the selected market.
- The funder holds pUSD and has current allowances.
The inspected client has version-aware create-and-post routing. That does not repair a payload you manually signed over an incompatible domain or identifier. Record the package version, market identifier type and selected route when debugging.
Related Errors After the Version Fix
Once signing is correct, expect business-rule errors to become visible:
- Invalid tick size: the limit price is not a valid increment for this market.
- Minimum order size: the order is below the market's current
mosvalue. - Insufficient balance or allowance: the configured funder lacks pUSD or approval.
- Maker address not allowed: the wallet path, signature type, or funder is wrong.
- Signer address must match the API key: credentials and the order signer are from different identities.
Fetch current parameters instead of relying on global constants. A production integration needs its own controlled lifecycle verification; this article checked source contracts and public GET shapes, not live orders, account funding or redemption.
For the broader integration, see the Polymarket API Python tutorial and practical Polymarket API guide.