← Back to blog

Fix Polymarket order_version_mismatch in Python

updated 2026-10-08 polymarket market-microstructure debugging python

By ZenHodl. Dataset documentation, research and model evaluations are linked in the article. A separate filtered ledger of bot-attributed trades, including losses and its admission rules, is public at /results.

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:

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:

  1. pUSD is held by the configured funder.
  2. Allowances were granted by that funder, not by a different EOA.
  3. The CLOB balance cache was refreshed with the same signature type.
  4. 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:

  1. pip show py-clob-client-v2 succeeds in the service's interpreter.
  2. No runtime import comes from py_clob_client.
  3. The production host is https://clob.polymarket.com.
  4. Chain ID is 137.
  5. Wallet type and funder match: 0 EOA, 1 proxy, 2 Safe, or 3 deposit wallet.
  6. The signer used for API credentials matches the signer used by the client.
  7. 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.
  8. Tick size and neg-risk settings come from the selected market.
  9. 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.

Once signing is correct, expect business-rule errors to become visible:

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.

Related reading

Get ZenHodl Weekly

Dataset releases, research notes, and public results, including corrections.

Research and dataset updates from ZenHodl.

Want the data behind this post?

Historical sports prediction-market datasets with measured coverage, documented schemas, and disclosed gaps.