> ## Documentation Index
> Fetch the complete documentation index at: https://docs.limitless.exchange/llms.txt
> Use this file to discover all available pages before exploring further.

# Error Handling & Retry

> Retry logic and error handling with the TypeScript SDK

## APIError Class

All HTTP errors from the SDK are thrown as `APIError` instances. Inspect the `status`, `message`, and `data` properties to determine what went wrong.

```typescript theme={null}
import { APIError } from '@limitless-exchange/sdk';

try {
  await orderClient.createOrder({ /* ... */ });
} catch (error) {
  if (error instanceof APIError) {
    console.error('Status:', error.status);   // HTTP status code
    console.error('Message:', error.message);  // Human-readable error
    console.error('Data:', error.data);        // Raw response body (if any)
  }
}
```

### APIError Properties

| Property  | Type      | Description                                 |
| --------- | --------- | ------------------------------------------- |
| `status`  | `number`  | HTTP status code (400, 401, 429, 500, etc.) |
| `message` | `string`  | Error description from the server           |
| `data`    | `unknown` | Raw response body, useful for debugging     |

## Common Status Codes

| Code  | Meaning                                                       | Action                                                                                                       |
| ----- | ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
| `400` | Bad request (invalid parameters)                              | Fix request parameters                                                                                       |
| `401` | Unauthorized (invalid or missing API key)                     | Check your `LIMITLESS_API_KEY`                                                                               |
| `403` | Forbidden (insufficient permissions)                          | Verify account permissions                                                                                   |
| `404` | Not found (invalid slug or resource)                          | Check market slug or resource ID                                                                             |
| `425` | Too Early — receive-window check failed (`POST /orders` only) | Re-stamp `timestamp` and resubmit. See [Receive Window](/api-reference/trading/create-order#receive-window). |
| `429` | Rate limited                                                  | Back off and retry with delay                                                                                |
| `500` | Internal server error                                         | Retry with exponential backoff                                                                               |
| `502` | Bad gateway                                                   | Retry with exponential backoff                                                                               |
| `503` | Service unavailable                                           | Retry with exponential backoff                                                                               |
| `504` | Gateway timeout                                               | Retry with exponential backoff                                                                               |

## withRetry Wrapper

The SDK provides a `withRetry` utility function that wraps any async operation with configurable retry logic.

```typescript theme={null}
import { withRetry, APIError } from '@limitless-exchange/sdk';

const result = await withRetry(
  () => orderClient.createOrder({
    marketSlug: 'btc-100k-weekly',
    tokenId: market.tokens.yes,
    side: 'BUY',
    price: 0.65,
    size: 100,
    orderType: 'GTC',
  }),
  {
    statusCodes: [429, 500, 502, 503, 504],
    maxRetries: 3,
    delays: [1, 2, 4], // Seconds between retries
    onRetry: (attempt, error, delay) => {
      console.warn(`Retry ${attempt} in ${delay}s: ${error.message}`);
    },
  }
);
```

### withRetry Options

| Option            | Type                                                     | Default                     | Description                                                                                                                                 |
| ----------------- | -------------------------------------------------------- | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `statusCodes`     | `number[]`                                               | `[429, 500, 502, 503, 504]` | HTTP status codes that trigger a retry                                                                                                      |
| `maxRetries`      | `number`                                                 | `3`                         | Maximum number of retry attempts                                                                                                            |
| `delays`          | `number[]`                                               | —                           | Explicit delays in **seconds** for each attempt. If omitted, exponential backoff is used. If fewer delays than retries, the last is reused. |
| `exponentialBase` | `number`                                                 | `2`                         | Base for exponential backoff when `delays` is omitted (`base^attempt` seconds)                                                              |
| `maxDelay`        | `number`                                                 | `60`                        | Maximum backoff delay in **seconds**                                                                                                        |
| `onRetry`         | `(attempt: number, error: Error, delay: number) => void` | —                           | Callback invoked before each retry                                                                                                          |

<Note>
  Only `APIError` instances with a matching `status` code trigger retries. Other errors (network failures, timeouts) are thrown immediately.
</Note>

## @retryOnErrors Decorator

For class-based architectures, use the `@retryOnErrors` decorator to add retry logic to individual methods.

```typescript theme={null}
import { retryOnErrors } from '@limitless-exchange/sdk';

class TradingBot {
  private orderClient: OrderClient;

  constructor(orderClient: OrderClient) {
    this.orderClient = orderClient;
  }

  @retryOnErrors({
    statusCodes: [429, 500, 502, 503, 504],
    maxRetries: 5,
    exponentialBase: 2,
    maxDelay: 60,
  })
  async placeOrder(params: CreateOrderParams) {
    return this.orderClient.createOrder(params);
  }
}
```

### Decorator Options

`@retryOnErrors` takes the same `RetryConfigOptions` as `withRetry`.

| Option            | Type                                                     | Default                     | Description                                                                    |
| ----------------- | -------------------------------------------------------- | --------------------------- | ------------------------------------------------------------------------------ |
| `statusCodes`     | `number[]`                                               | `[429, 500, 502, 503, 504]` | HTTP status codes that trigger a retry                                         |
| `maxRetries`      | `number`                                                 | `3`                         | Maximum number of retry attempts                                               |
| `delays`          | `number[]`                                               | —                           | Explicit per-attempt delays in **seconds** (overrides exponential backoff)     |
| `exponentialBase` | `number`                                                 | `2`                         | Base for exponential backoff when `delays` is omitted (`base^attempt` seconds) |
| `maxDelay`        | `number`                                                 | `60`                        | Maximum backoff delay in **seconds**                                           |
| `onRetry`         | `(attempt: number, error: Error, delay: number) => void` | —                           | Callback invoked before each retry                                             |

When `delays` is omitted, the delay for each retry is calculated (in **seconds**) as:

```
delay = min(exponentialBase ^ attempt, maxDelay)
```

For example, with `exponentialBase: 2` and `maxDelay: 60`:

| Attempt | Delay         |
| ------- | ------------- |
| 0       | 1 s           |
| 1       | 2 s           |
| 2       | 4 s           |
| 3       | 8 s           |
| 4       | 16 s          |
| 5       | 32 s          |
| 6       | 60 s (capped) |

## Rate Limiting with OrderQueue

For high-frequency trading scenarios, implement an `OrderQueue` to throttle outgoing requests and avoid 429 errors:

```typescript theme={null}
import { APIError } from '@limitless-exchange/sdk';

class OrderQueue {
  private queue: Array<() => Promise<void>> = [];
  private processing = false;
  private minDelayMs: number;

  constructor(minDelayMs = 200) {
    this.minDelayMs = minDelayMs;
  }

  async enqueue<T>(fn: () => Promise<T>): Promise<T> {
    return new Promise((resolve, reject) => {
      this.queue.push(async () => {
        try {
          resolve(await fn());
        } catch (error) {
          reject(error);
        }
      });
      this.processQueue();
    });
  }

  private async processQueue() {
    if (this.processing) return;
    this.processing = true;

    while (this.queue.length > 0) {
      const task = this.queue.shift()!;
      await task();
      await new Promise((r) => setTimeout(r, this.minDelayMs));
    }

    this.processing = false;
  }
}

// Usage
const orderQueue = new OrderQueue(250); // 250ms between requests

const result = await orderQueue.enqueue(() =>
  orderClient.createOrder({
    marketSlug: 'btc-100k-weekly',
    tokenId: market.tokens.yes,
    side: 'BUY',
    price: 0.65,
    size: 100,
    orderType: 'GTC',
  })
);
```

## Best Practices

<Accordion title="Always handle APIError specifically">
  Catch `APIError` separately from other errors. Network failures, JSON parse errors, and timeouts are not `APIError` instances and should be handled differently.

  ```typescript theme={null}
  try {
    await orderClient.createOrder({ /* ... */ });
  } catch (error) {
    if (error instanceof APIError) {
      // Server responded with an error status
      handleApiError(error);
    } else if (error instanceof TypeError) {
      // Network error (DNS, connection refused, etc.)
      console.error('Network error:', error.message);
    } else {
      // Unexpected error
      throw error;
    }
  }
  ```
</Accordion>

<Accordion title="Do not retry 400 or 401 errors">
  Client errors (400, 401, 403) indicate a problem with your request or credentials. Retrying them wastes time and API quota. Only retry transient server errors (429, 5xx).
</Accordion>

<Accordion title="Use exponential backoff for 429 responses">
  When rate limited, the server may include a `Retry-After` header. If available, respect it. Otherwise, use exponential backoff starting at 1 second.
</Accordion>

<Accordion title="Combine retry with OrderQueue">
  For the most robust setup, use `OrderQueue` to throttle request rate *and* `withRetry` or `@retryOnErrors` to handle transient failures:

  ```typescript theme={null}
  const result = await orderQueue.enqueue(() =>
    withRetry(
      () => orderClient.createOrder({ /* ... */ }),
      { statusCodes: [429, 500, 502, 503, 504], maxRetries: 3 }
    )
  );
  ```
</Accordion>

## Logging

The SDK provides optional logging through a simple `ILogger` interface. Logging is completely opt-in with zero overhead by default.

### Quick Start

```typescript theme={null}
import { HttpClient, ConsoleLogger } from '@limitless-exchange/sdk';

const logger = new ConsoleLogger('info');

const httpClient = new HttpClient({
  baseURL: 'https://api.limitless.exchange',
  apiKey: process.env.LIMITLESS_API_KEY,
  logger,
});
```

### Log Levels

| Level   | What's Logged                                                                  |
| ------- | ------------------------------------------------------------------------------ |
| `debug` | Request headers (API key redacted), request/response bodies, WebSocket events  |
| `info`  | API requests (method + URL), successful responses, order creation/cancellation |
| `warn`  | Warnings only                                                                  |
| `error` | API errors (with status code), network errors, WebSocket connection errors     |

### Custom Logger for Production

Implement the `ILogger` interface to integrate with your own logging infrastructure:

```typescript theme={null}
import { ILogger } from '@limitless-exchange/sdk';

class MyLogger implements ILogger {
  debug(message: string, meta?: Record<string, any>): void { /* ... */ }
  info(message: string, meta?: Record<string, any>): void { /* ... */ }
  warn(message: string, meta?: Record<string, any>): void { /* ... */ }
  error(message: string, error?: Error, meta?: Record<string, any>): void { /* ... */ }
}

const httpClient = new HttpClient({
  baseURL: 'https://api.limitless.exchange',
  apiKey: process.env.LIMITLESS_API_KEY,
  logger: new MyLogger(),
});
```

### Passing Logger to SDK Components

All SDK components accept an optional logger:

```typescript theme={null}
const logger = new ConsoleLogger('info');

const httpClient = new HttpClient({ baseURL: '...', logger });
const marketFetcher = new MarketFetcher(httpClient, logger);
const orderClient = new OrderClient({ httpClient, wallet, logger });
```

<Note>
  The SDK automatically redacts API keys in logs (shown as `***`). Private keys are never logged.
</Note>
