Hosted trading errors all descend from HostedTradingError. Each subclass also inherits from a semantic parent — InsufficientEscrowBalance is also an InsufficientFunds, OrderSizeTooSmall is also an InvalidOrder. Catch the parent and you handle both hosted and self-hosted paths with the same code. Catch the leaf and you can branch on the specific recovery action.
In Python, this is true multi-inheritance — isinstance(e, InsufficientFunds) and isinstance(e, HostedTradingError) both work. In TypeScript, the same effect is achieved with a static isHostedError = true flag and the isHostedError() helper, since JS only allows single-extends.
For the full class reference, see API Reference / Errors.
This page covers the five errors you’ll hit most often.
InsufficientEscrowBalance
When it fires: the order would draw more USDC than your escrow free balance. PMXT debits escrow when an order is submitted; if the requested amount exceeds balance.free, the build phase rejects the order.
Detail string: Insufficient escrow balance: requested 50.0 USDC, available 12.34 USDC.
Parent classes: InsufficientFunds, HostedTradingError.
Recovery: deposit more, or shrink the order. See Escrow lifecycle.
OrderSizeTooSmall
When it fires: the resolved order is below one of Polymarket’s two independent venue-side minimums on marketable BUY orders. Both rules are enforced by the venue itself (not PMXT), and the higher of the two binds:
- 5-share minimum. A 2buyat0.78/share is only 2.5 shares — rejected.
- **1notionalminimum(marketableBUY).∗∗A5−sharebuyat0.138/share is 0.69—passesthe5−sharerulebutisrejectedbythevenuewiththeliteralerror‘invalidamountforamarketableBUYorder(0.69), min size: 1`. The effective minimum at that price is
8 shares ($1.10).
Detail string (5-share rule): Order size 2.564 below the minimum 5 shares for venue polymarket.
**Detail string (1rule,passedthroughfromvenue):∗∗‘invalidamountforamarketableBUYorder(X), min size: 1`.
Parent classes: InvalidOrder, HostedTradingError.
Recovery: size up the order or pick a cheaper outcome.
The 5-share minimum is enforced after PMXT resolves your USDC amount into shares using the current price. If the price moves between price-check and submit, a borderline-sized order may flip from accepted to rejected. Add a buffer for marginal sizes.
InvalidApiKey
When it fires: the pmxt_api_key is missing, malformed, revoked, or expired. Surface is HTTP 401 from trade.pmxt.dev.
Detail string: invalid api key or missing api key.
Parent classes: AuthenticationError, HostedTradingError.
Recovery: rotate the key from pmxt.dev/dashboard. Update your deployed config. Do not retry with the same key.
BuiltOrderExpired
When it fires: between build_order and submit_order, the built-order TTL elapsed (typically 30 seconds). Also fires for cancel_id expired in the cancel flow.
Detail string: built_order_id expired or cancel_id expired.
Parent classes: InvalidOrder, HostedTradingError.
Recovery: re-build, then re-sign, then submit. Don’t reuse the old built_order_id.
Hardware-wallet signing is the most common cause — Ledger confirmations can take 10–60 seconds, blowing past the TTL. If you sign with a hardware wallet, expect to retry once on BuiltOrderExpired.
NoLiquidity
When it fires: the side of the book you’re crossing is empty — there are no resting asks for a market buy, or no resting bids for a market sell.
Detail string: book has no resting asks or book has no resting bids.
Parent classes: InvalidOrder, HostedTradingError.
Recovery: wait for liquidity, pick a different outcome, or post a resting limit order via client.create_order(order_type="limit", price=..., amount=...). Limit BUY and SELL are supported, and both use denom="shares" in hosted SDK requests.
Workaround warnings
Use aggressive slippage_pct until the upstream economic validator tightens its worst_price checks. Pragmatic defaults: slippage_pct=30 for buys, slippage_pct=99.9 for sells. Lower values frequently trip a precision check that has nothing to do with actual slippage. This will tighten once the validator ships its fix.
Marketable limit price selection. The SDK’s _validate_worst_price gate applies a slippage buffer on top of the book — it is NOT simply “must cross top-of-book”. For an immediately-executable price:
- Marketable limit BUY: use
price = best_ask (a small +1-tick buffer works too).
- Marketable limit SELL: use
price = best_ask (i.e. at or above the ask), NOT best_bid or best_bid - 0.01. Posting at best_bid looks marketable in book terms, but the validator’s slippage floor (currently worst_price ≥ best_bid × 0.8 + 0.029, see _hosted_typeddata.py:519) will reject it for thin books or low-price outcomes. Pricing at or above best_ask is the reliable rule.
If you see OutcomeNotFound despite having a valid-looking ID, the SDK could not resolve it on either the catalog UUID or (venue, venue_outcome_id) path. Most commonly: the outcome was resolved against a different venue than the client’s exchange_name, or the outcome has since been removed from the catalog. See Catalog UUID vs venue ID.
Catching everything hosted
If you only want to know “did the hosted layer reject this?”, catch HostedTradingError (Python) or use isHostedError(e) (TS):
For the full error class reference, parent classes, and status codes, see API Reference / Errors.