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.
from pmxt.errors import InsufficientFunds
from pmxt._hosted_errors import InsufficientEscrowBalance
try:
client.create_order(...)
except InsufficientEscrowBalance as e:
print(f"Need to deposit more. {e.detail}")
# Build a deposit tx for the shortfall
tx = client.escrow.deposit_tx(amount=20.0)
# ... sign and broadcast, then retry the order
except InsufficientFunds:
# Self-hosted path also lands here
...
OrderSizeTooSmall
When it fires: the resolved share count is below the venue’s minimum. Polymarket’s minimum is 5 shares per order. A 2buyat0.78/share is only 2.5 shares — rejected.
Detail string: Order size 2.564 below the minimum 5 shares for venue polymarket.
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.
from pmxt._hosted_errors import OrderSizeTooSmall
try:
client.create_order(amount=2.0, ...)
except OrderSizeTooSmall as e:
# Resize: at $0.78/share, 5 shares ≈ $3.90. Round up with buffer.
client.create_order(amount=5.0, ...)
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.
from pmxt.errors import AuthenticationError
from pmxt._hosted_errors import InvalidApiKey
try:
client.fetch_balance()
except InvalidApiKey:
# Rotate the key; don't retry with the same one
raise SystemExit("PMXT_API_KEY invalid — rotate from dashboard")
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.
from pmxt._hosted_errors import BuiltOrderExpired
def submit_with_retry(client, *, market_id, outcome_id, **kwargs):
for attempt in range(2):
built = client.build_order(market_id=market_id, outcome_id=outcome_id, **kwargs)
sig = signer.sign_typed_data(built.typed_data)
try:
return client.submit_order(
built_order_id=built.built_order_id,
signature=sig,
)
except BuiltOrderExpired:
if attempt == 1:
raise
continue
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, post a limit order instead of a market order, or pick a different outcome.
from pmxt._hosted_errors import NoLiquidity
try:
client.create_order(order_type="market", ...)
except NoLiquidity:
# Fall back to a limit order at a price you'd accept
client.create_order(order_type="limit", price=0.50, ...)
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.
fetch_markets returns venue-native IDs, but create_order needs catalog UUIDs. If you see OutcomeNotFound despite having a valid-looking ID, you are almost certainly passing a venue-native ID to a hosted endpoint. 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):
from pmxt._hosted_errors import HostedTradingError
try:
client.create_order(...)
except HostedTradingError as e:
log.error("hosted trade failed", status=e.status, detail=e.detail)
For the full error class reference, parent classes, and status codes, see API Reference / Errors.