Skip to main content
This guide covers best practices for handling errors in the CLOB Client, including common error types, retry strategies, and debugging techniques.

Error Types

The SDK uses several error types:

ApiError

Thrown when API requests fail:
import { ApiError } from "@polymarket/clob-client";

try {
  const order = await clobClient.createAndPostOrder({
    tokenID: "invalid-token",
    price: 0.50,
    side: Side.BUY,
    size: 100,
  });
} catch (error) {
  if (error instanceof ApiError) {
    console.error("API Error:", error.message);
    console.error("Status Code:", error.status);
    console.error("Response Data:", error.data);
  }
}

Authentication Errors

Thrown when required authentication is missing:
import { 
  L1_AUTH_UNAVAILABLE_ERROR,
  L2_AUTH_NOT_AVAILABLE,
  BUILDER_AUTH_NOT_AVAILABLE,
  BUILDER_AUTH_FAILED
} from "@polymarket/clob-client";

try {
  const apiKeys = await clobClient.getApiKeys();
} catch (error) {
  if (error === L1_AUTH_UNAVAILABLE_ERROR) {
    console.error("Signer is needed to interact with this endpoint!");
  } else if (error === L2_AUTH_NOT_AVAILABLE) {
    console.error("API Credentials are needed to interact with this endpoint!");
  }
}

Validation Errors

Thrown when parameters are invalid:
try {
  const order = await clobClient.createOrder({
    tokenID: "71321045...",
    price: 0.523,  // Invalid for tick size 0.01
    side: Side.BUY,
    size: 100,
  });
} catch (error) {
  if (error instanceof Error) {
    // "invalid price (0.523), min: 0.01 - max: 0.99"
    console.error(error.message);
  }
}

Throw on Error Mode

By default, the client returns error objects. Enable throwOnError to throw exceptions instead:
const clobClient = new ClobClient(
  host,
  chainId,
  wallet,
  creds,
  undefined,  // signatureType
  undefined,  // funderAddress
  undefined,  // geoBlockToken
  undefined,  // useServerTime
  undefined,  // builderConfig
  undefined,  // getSigner
  undefined,  // retryOnError
  undefined,  // tickSizeTtlMs
  true        // throwOnError - enable exception throwing
);

try {
  const order = await clobClient.postOrder(signedOrder, OrderType.GTC);
  console.log("Order posted successfully");
} catch (error) {
  if (error instanceof ApiError) {
    console.error("Failed to post order:", error.message);
    console.error("Status:", error.status);
  }
}

Retry Logic

Enable automatic retries for transient errors:
const clobClient = new ClobClient(
  host,
  chainId,
  wallet,
  creds,
  undefined,  // signatureType
  undefined,  // funderAddress
  undefined,  // geoBlockToken
  undefined,  // useServerTime
  undefined,  // builderConfig
  undefined,  // getSigner
  true        // retryOnError - enable automatic retries
);

// POST requests will automatically retry once after 30ms for:
// - Network errors (ECONNABORTED, ENETUNREACH, etc.)
// - 5xx server errors
const order = await clobClient.postOrder(signedOrder, OrderType.GTC);

Manual Retry Logic

Implement custom retry logic for specific operations:
async function postOrderWithRetry(
  client: ClobClient,
  order: SignedOrder,
  maxRetries = 3,
  delayMs = 1000
) {
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await client.postOrder(order, OrderType.GTC);
    } catch (error) {
      if (i === maxRetries - 1) throw error;
      
      if (error instanceof ApiError) {
        // Retry on 5xx errors or network errors
        if (error.status && error.status >= 500) {
          console.log(`Retry ${i + 1}/${maxRetries} after ${delayMs}ms`);
          await new Promise(resolve => setTimeout(resolve, delayMs));
          continue;
        }
      }
      
      // Don't retry other errors
      throw error;
    }
  }
}

// Usage
try {
  const result = await postOrderWithRetry(clobClient, signedOrder);
  console.log("Order posted:", result);
} catch (error) {
  console.error("Failed after retries:", error);
}

Common Errors

Invalid Price

try {
  const order = await clobClient.createOrder({
    tokenID: "71321045...",
    price: 1.05,  // Price must be between 0.0001 and 0.9999
    side: Side.BUY,
    size: 100,
  });
} catch (error) {
  if (error instanceof Error && error.message.includes("invalid price")) {
    console.error("Price must be between 0.0001 and 0.9999");
    console.error("And must be a multiple of the tick size");
  }
}
Solution: Ensure price is in range and matches tick size:
const tickSize = await clobClient.getTickSize(tokenID);
const tickValue = parseFloat(tickSize);

// Round price to nearest tick
const validPrice = Math.round(desiredPrice / tickValue) * tickValue;

const order = await clobClient.createOrder({
  tokenID: tokenID,
  price: validPrice,
  side: Side.BUY,
  size: 100,
});

Invalid Tick Size

try {
  const order = await clobClient.createOrder(
    {
      tokenID: "71321045...",
      price: 0.501,
      side: Side.BUY,
      size: 100,
    },
    { tickSize: "0.001" }  // Too small for this market
  );
} catch (error) {
  if (error instanceof Error && error.message.includes("invalid tick size")) {
    console.error("Specified tick size is smaller than market minimum");
  }
}
Solution: Omit tick size to use market default, or fetch it first:
const tickSize = await clobClient.getTickSize(tokenID);

const order = await clobClient.createOrder(
  {
    tokenID: tokenID,
    price: 0.50,
    side: Side.BUY,
    size: 100,
  },
  { tickSize }  // Use market's tick size
);

Insufficient Balance

import { AssetType } from "@polymarket/clob-client";

try {
  const order = await clobClient.createAndPostOrder({
    tokenID: "71321045...",
    price: 0.50,
    side: Side.BUY,
    size: 10000,  // Very large order
  });
} catch (error) {
  if (error instanceof ApiError && error.message.includes("insufficient")) {
    // Check balance
    const balance = await clobClient.getBalanceAllowance({
      asset_type: AssetType.COLLATERAL,
    });
    
    console.error(`Insufficient balance`);
    console.error(`Balance: ${balance.balance}`);
    console.error(`Allowance: ${balance.allowance}`);
  }
}
Solution: Check balance before placing orders:
const balance = await clobClient.getBalanceAllowance({
  asset_type: AssetType.COLLATERAL,
});

const availableBalance = parseFloat(balance.balance);
const requiredAmount = size * price;

if (requiredAmount > availableBalance) {
  console.error(`Insufficient balance: need ${requiredAmount}, have ${availableBalance}`);
} else {
  const order = await clobClient.createAndPostOrder({
    tokenID: tokenID,
    price: price,
    side: Side.BUY,
    size: size,
  });
}

Authentication Errors

// L1 Authentication (missing signer)
try {
  const client = new ClobClient(host, chainId);  // No wallet
  await client.createApiKey();  // Requires L1 auth
} catch (error) {
  if (error === L1_AUTH_UNAVAILABLE_ERROR) {
    console.error("Provide a wallet signer:");
    console.error("new ClobClient(host, chainId, wallet)");
  }
}

// L2 Authentication (missing credentials)
try {
  const client = new ClobClient(host, chainId, wallet);  // No creds
  await client.getOpenOrders();  // Requires L2 auth
} catch (error) {
  if (error === L2_AUTH_NOT_AVAILABLE) {
    console.error("Provide API credentials:");
    console.error("new ClobClient(host, chainId, wallet, creds)");
  }
}

Order Not Found

try {
  const order = await clobClient.getOrder("non-existent-order-id");
} catch (error) {
  if (error instanceof ApiError && error.status === 404) {
    console.error("Order not found");
  }
}
Solution: Check if order exists in open orders:
const openOrders = await clobClient.getOpenOrders();
const orderExists = openOrders.some(o => o.id === orderId);

if (!orderExists) {
  console.log("Order may be filled or canceled");
}

Rate Limiting

try {
  // Making too many requests
  for (let i = 0; i < 1000; i++) {
    await clobClient.getOrderBook(tokenID);
  }
} catch (error) {
  if (error instanceof ApiError && error.status === 429) {
    console.error("Rate limit exceeded");
    console.error("Wait before retrying");
  }
}
Solution: Implement rate limiting and exponential backoff:
import pLimit from "p-limit";

// Limit concurrent requests
const limit = pLimit(5);  // Max 5 concurrent requests

const tokenIDs = ["token1", "token2", "token3", /* ... */];

const orderbooks = await Promise.all(
  tokenIDs.map(tokenID => 
    limit(() => clobClient.getOrderBook(tokenID))
  )
);

Validation Before Posting

Validate orders before posting to catch errors early:
import { priceValid } from "@polymarket/clob-client";

async function validateAndPostOrder(
  client: ClobClient,
  userOrder: UserOrder
) {
  // 1. Validate tick size
  const tickSize = await client.getTickSize(userOrder.tokenID);
  const tickValue = parseFloat(tickSize);
  
  if (!priceValid(userOrder.price, tickSize)) {
    throw new Error(
      `Invalid price ${userOrder.price} for tick size ${tickSize}`
    );
  }
  
  // 2. Check balance
  const balance = await client.getBalanceAllowance({
    asset_type: AssetType.COLLATERAL,
  });
  
  const required = userOrder.size * userOrder.price;
  const available = parseFloat(balance.balance);
  
  if (required > available) {
    throw new Error(
      `Insufficient balance: need ${required}, have ${available}`
    );
  }
  
  // 3. Validate price is in range
  if (userOrder.price < tickValue || userOrder.price >= 1) {
    throw new Error(
      `Price must be between ${tickValue} and ${1 - tickValue}`
    );
  }
  
  // 4. Create and post
  return await client.createAndPostOrder(userOrder);
}

// Usage
try {
  const result = await validateAndPostOrder(clobClient, {
    tokenID: "71321045...",
    price: 0.50,
    side: Side.BUY,
    size: 100,
  });
  console.log("Order posted successfully:", result);
} catch (error) {
  console.error("Validation failed:", error.message);
}

Logging and Debugging

Enable detailed logging for debugging:
class DebugClobClient extends ClobClient {
  async createAndPostOrder(...args: any[]) {
    console.log("[DEBUG] Creating order:", args[0]);
    
    try {
      const result = await super.createAndPostOrder(...args);
      console.log("[DEBUG] Order posted successfully:", result);
      return result;
    } catch (error) {
      console.error("[DEBUG] Order failed:", error);
      if (error instanceof ApiError) {
        console.error("[DEBUG] Status:", error.status);
        console.error("[DEBUG] Data:", error.data);
      }
      throw error;
    }
  }
}

const client = new DebugClobClient(host, chainId, wallet, creds);

Error Handling Patterns

Graceful Degradation

async function getOrderBookSafely(tokenID: string) {
  try {
    return await clobClient.getOrderBook(tokenID);
  } catch (error) {
    console.error(`Failed to fetch orderbook for ${tokenID}:`, error);
    
    // Return empty orderbook structure
    return {
      market: "",
      asset_id: tokenID,
      bids: [],
      asks: [],
      timestamp: new Date().toISOString(),
      min_order_size: "1",
      tick_size: "0.01",
      neg_risk: false,
      last_trade_price: "0",
      hash: "",
    };
  }
}

Circuit Breaker

class CircuitBreaker {
  private failures = 0;
  private lastFailure = 0;
  private readonly threshold = 5;
  private readonly timeout = 60000; // 1 minute
  
  async execute<T>(fn: () => Promise<T>): Promise<T> {
    if (this.isOpen()) {
      throw new Error("Circuit breaker is open");
    }
    
    try {
      const result = await fn();
      this.onSuccess();
      return result;
    } catch (error) {
      this.onFailure();
      throw error;
    }
  }
  
  private isOpen(): boolean {
    return (
      this.failures >= this.threshold &&
      Date.now() - this.lastFailure < this.timeout
    );
  }
  
  private onSuccess() {
    this.failures = 0;
  }
  
  private onFailure() {
    this.failures++;
    this.lastFailure = Date.now();
  }
}

const breaker = new CircuitBreaker();

try {
  const order = await breaker.execute(() =>
    clobClient.postOrder(signedOrder, OrderType.GTC)
  );
} catch (error) {
  console.error("Circuit breaker prevented request:", error);
}

Next Steps

Build docs developers (and LLMs) love