> ## Documentation Index
> Fetch the complete documentation index at: https://pmxt-sync-hosted-docs.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Handling Hosted Errors

> Every common hosted-trading error, when it fires, and how to recover.

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](/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](/guides/escrow-lifecycle).

<CodeGroup>
  ```python Python theme={null}
  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
      ...
  ```

  ```typescript TypeScript theme={null}
  import { InsufficientEscrowBalance, isHostedError } from "pmxtjs";

  try {
    await client.createOrder({ ... });
  } catch (e) {
    if (e instanceof InsufficientEscrowBalance) {
      console.log(`Need to deposit. ${e.detail}`);
      const tx = await client.escrow.depositTx(20);
      // sign and broadcast, retry
    } else if (isHostedError(e)) {
      // any other hosted-error parent
    } else {
      throw e;
    }
  }
  ```
</CodeGroup>

## 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 $2 buy at $0.78/share is only 2.5 shares — rejected.
* \*\*$1 notional minimum (marketable BUY).** A 5-share buy at $0.138/share is $0.69 — passes the 5-share rule but is rejected by the venue with the literal error `invalid amount for a marketable BUY order ($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 ($1 rule, passed through from venue):** `invalid amount for a marketable BUY order ($X), min size: 1\`.

**Parent classes:** `InvalidOrder`, `HostedTradingError`.

**Recovery:** size up the order or pick a cheaper outcome.

<Warning>
  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.
</Warning>

<CodeGroup>
  ```python Python theme={null}
  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, ...)
  ```

  ```typescript TypeScript theme={null}
  import { OrderSizeTooSmall } from "pmxtjs";

  try {
    await client.createOrder({ amount: 2, ... });
  } catch (e) {
    if (e instanceof OrderSizeTooSmall) {
      await client.createOrder({ amount: 5, ... });
    } else {
      throw e;
    }
  }
  ```
</CodeGroup>

## 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](https://pmxt.dev/dashboard). Update your deployed config. **Do not retry** with the same key.

<CodeGroup>
  ```python Python theme={null}
  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")
  ```

  ```typescript TypeScript theme={null}
  import { InvalidApiKey } from "pmxtjs";

  try {
    await client.fetchBalance();
  } catch (e) {
    if (e instanceof InvalidApiKey) {
      throw new Error("PMXT_API_KEY invalid — rotate from dashboard");
    }
    throw e;
  }
  ```
</CodeGroup>

## 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`.

<Warning>
  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`.
</Warning>

<CodeGroup>
  ```python Python theme={null}
  from pmxt._hosted_errors import BuiltOrderExpired

  def submit_with_retry(client, **kwargs):
      # In hosted mode, create_order handles build -> sign -> submit atomically.
      # On BuiltOrderExpired the internal submit raced the 30s TTL; just call again.
      for attempt in range(2):
          try:
              return client.create_order(**kwargs)
          except BuiltOrderExpired:
              if attempt == 1:
                  raise
              continue
  ```

  ```typescript TypeScript theme={null}
  import { BuiltOrderExpired } from "pmxtjs";
  import type { CreateOrderInput, Order } from "pmxtjs";

  async function submitWithRetry(client, params: CreateOrderInput): Promise<Order> {
    // In hosted mode, createOrder handles build -> sign -> submit atomically.
    // On BuiltOrderExpired the internal submit raced the 30s TTL; just call again.
    for (let attempt = 0; attempt < 2; attempt++) {
      try {
        return await client.createOrder(params);
      } catch (e) {
        if (e instanceof BuiltOrderExpired && attempt === 0) continue;
        throw e;
      }
    }
    throw new Error("unreachable");
  }
  ```
</CodeGroup>

## 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.

<CodeGroup>
  ```python Python theme={null}
  from pmxt._hosted_errors import NoLiquidity

  try:
      client.create_order(order_type="market", ...)
  except NoLiquidity:
      # Wait, retry against a different outcome, or post a hosted limit order.
      pass
  ```

  ```typescript TypeScript theme={null}
  import { NoLiquidity } from "pmxtjs";

  try {
    await client.createOrder({ type: "market", ... });
  } catch (e) {
    if (e instanceof NoLiquidity) {
      // Wait, retry against a different outcome, or post a hosted limit order.
    } else {
      throw e;
    }
  }
  ```
</CodeGroup>

## Workaround warnings

<Warning>
  **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.
</Warning>

<Warning>
  **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.
</Warning>

<Warning>
  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](/concepts/catalog-uuid-vs-venue-id).
</Warning>

## Catching everything hosted

If you only want to know "did the hosted layer reject this?", catch `HostedTradingError` (Python) or use `isHostedError(e)` (TS):

<CodeGroup>
  ```python Python theme={null}
  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)
  ```

  ```typescript TypeScript theme={null}
  import { isHostedError } from "pmxtjs";

  try {
    await client.createOrder({ ... });
  } catch (e) {
    if (isHostedError(e)) {
      log.error("hosted trade failed", e);
    } else {
      throw e;
    }
  }
  ```
</CodeGroup>

For the full error class reference, parent classes, and status codes, see [API Reference / Errors](/api-reference/errors).
