October 07, 2025
By Rian Dolphin
Hit a confusing error when placing orders on Polymarket:
PolyApiException[status_code=400, error_message={'error': 'not enough balance / allowance'}]
Checked balance and allowances—everything looked fine. USDC balance sufficient, allowances set correctly. Error only appeared on specific markets, not others. Spent time debugging the wrong thing before finding the likely cause.
Short version: Position sizes aren't round numbers. Tried to sell 23 YES, actually held 22.99695 YES. Order rejected.
Not 100% confirmed this is the issue, but timing and behavior match up.
First instinct: check balance and allowances as suggested by folks online.
from py_clob_client.clob_types import AssetType, BalanceAllowanceParams
polymarket_client.clob_client.get_balance_allowance(
params=BalanceAllowanceParams(asset_type=AssetType.COLLATERAL)
)
Output:
{
"balance": "123456", # plenty of USDC
"allowances": {
"0x4bFb41d5B3570DeFd03C39a9A4D8dE6Bd8B8982E": "115792...760179", # Exchange
"0xC5d563A36AE78145C45a50134d48A1215220f80a": "115792...639935", # Operator
"0xd91E80cF2E7be2e162c6513ceD06f1dD0dA35296": "115792...639935", # NegRisk
}
}
Those allowances are uint256.max (unlimited). Balance is fine. Allowances are fine. But order still fails.
Found others online with similar error, but they had zero balance or missing allowances. Different issue. That was a rabbit hole.
Next observation: error only happened when SELLING (I think, didn't do extensive testing). Buying worked fine.
Tried this:
Economically equivalent (buying NO at 0.06 is selling YES at 0.94), mechanically different.
Initial hypothesis: Sell requires holding the token, buy only needs USDC. Maybe I don't have enough YES tokens?
Checked positions. Had YES tokens. Long position in that market. Should work.
Still failed.
Looked closer at actual position sizes. Positions API returns floats:
positions_df = client.positions_df
print(positions_df.filter(pl.col("outcome") == "Yes").select(["title", "size"]))
# Output:
# title: "Will Gemini 3.0 be released by October 15?"
# size: 22.99695
Not 23. 22.99695.
When I tried to SELL 23 YES, the order was rejected. Makes sense if I only held 22.99695—can't sell what you don't have.
Why aren't positions round numbers? Not entirely clear. Could be:
Doesn't really matter why. Point is: positions aren't the round numbers you might expect from your order sizes.
Query actual position sizes before selling:
positions = client.positions_df
yes_position = positions.filter(
(pl.col("conditionId") == condition_id) &
(pl.col("outcome") == "Yes")
)
actual_size = yes_position["size"][0] # 22.99695, not 23
# Sell what you actually have
client.create_order(
token_id=yes_token_id,
price=0.94,
size=actual_size,
side="SELL"
)
After using actual position sizes, the error stopped appearing.
When you BUY YES tokens:
price * size ≤ usdc_balance, order succeedsWhen you SELL YES tokens:
size > token_balance (even by 0.00305), order failsThe asymmetry: buying checks collateral (usually exact), selling checks tokens (fractional).
If you're placing orders programmatically, you might do:
# Buy 100 YES at 0.48
client.create_order(token_id=yes_id, price=0.48, size=100, side="BUY")
# Later, sell 100 YES at 0.52
client.create_order(token_id=yes_id, price=0.52, size=100, side="SELL")
Second order maybe fails. You might not have exactly 100 YES.
Better approach is to track actual positions:
# After buying, check what you actually received
positions = client.positions_df
actual_yes = positions.filter(
(pl.col("asset") == yes_token_id)
)["size"][0]
# Sell your actual position
client.create_order(token_id=yes_id, price=0.52, size=actual_yes, side="SELL")
Or build in a buffer:
import math
actual_yes = 99.97 # what you actually have
safe_size = math.floor(actual_yes) # 99
# Sell 99, leaves 0.97 behind
client.create_order(token_id=yes_id, price=0.52, size=safe_size, side="SELL")
First approach (sell everything) is cleaner. Second approach (floor to integer) leaves dust that accumulates over time.
Few open questions:
Why are positions fractional? Probably fees, but could be other rounding in the settlement process. Haven't traced through the full exchange mechanics to confirm.
Are there other precision issues? If positions can be off by 0.00305, what about orders? Do order sizes get rounded anywhere in the flow? Unknown.
Position sizes aren't round numbers: Even if you bought 23 shares, you might hold 22.99695.
Error message is misleading: "not enough balance" could mean "0.00305 tokens short." Check actual position size, not expected size.
Always query actual balances: Don't assume positions match your order history. Query positions_df before placing sell orders.
Buy vs sell asymmetry: Buying checks collateral (typically exact), selling checks tokens (fractional). Sells are more likely to hit precision issues?
Probably related to fees or settlement rounding: But haven't confirmed the exact mechanism.
Simple fix (use actual position sizes), but took time to figure out. The balance/allowance checks were red herrings—those were fine. The issue was trying to sell slightly more than I held.
If you hit this error and your balances/allowances look correct, check if your sell size matches your actual position size. Might save you some debugging time.