> For the complete documentation index, see [llms.txt](https://docs.dimes.fi/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.dimes.fi/for-developers/api-and-events/error-handling.md).

# Error Handling

### Error format

```json
{
  "error": {
    "type": "INVALID_REQUEST_ERROR",
    "code": "quote_leverage_below_minimum",
    "message": "Leverage is below the minimum allowed for this market."
  }
}
```

| Field     | Type   | Description                                                                                                                                                |
| --------- | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `type`    | string | Error category (see below)                                                                                                                                 |
| `code`    | string | Machine-readable error code (`snake_case`). Use this for programmatic handling                                                                             |
| `message` | string | Human-readable description of the error. Suitable for displaying to end users. **Don't parse this** — values are duplicated as typed fields under `params` |
| `params`  | object | Optional. Structured hint values for codes that carry actionable data. See [Errors with hints](#errors-with-structured-hints)                              |

***

### Error types

| Type                    | HTTP Statuses | Description                                                                |
| ----------------------- | ------------- | -------------------------------------------------------------------------- |
| `INVALID_REQUEST_ERROR` | 400, 404, 409 | The request was malformed, missing parameters, or violated a business rule |
| `AUTHENTICATION_ERROR`  | 401, 403      | Missing or invalid API key / JWT                                           |
| `RATE_LIMIT_ERROR`      | 429           | Too many requests                                                          |
| `API_ERROR`             | 500+          | Something went wrong on our side                                           |

***

### Error codes by endpoint

#### `POST /tokens`

| Code                                   | Status | When                                                |
| -------------------------------------- | ------ | --------------------------------------------------- |
| `customer_auth_invalid_wallet_address` | 400    | Wallet address is not a valid EVM or Solana address |

#### `GET /markets/{ticker}`

| Code                        | Status | When                       |
| --------------------------- | ------ | -------------------------- |
| `customer_market_not_found` | 404    | No market with that ticker |

#### `GET /positions/{position_id}`

| Code                          | Status | When                                   |
| ----------------------------- | ------ | -------------------------------------- |
| `customer_position_not_found` | 404    | No position with that ID for this user |

#### `GET /positions/{position_id}/transactions`

| Code                                       | Status | When                                   |
| ------------------------------------------ | ------ | -------------------------------------- |
| `customer_position_transactions_not_found` | 404    | No position with that ID for this user |

#### `POST /quotes`

**Market eligibility:**

| Code                                           | Status | When                                                                  |
| ---------------------------------------------- | ------ | --------------------------------------------------------------------- |
| `quote_market_not_eligible`                    | 400    | Market exists but isn't eligible for leverage                         |
| `quote_market_not_ready`                       | 400    | Market is not yet ready for trading                                   |
| `quote_event_not_started`                      | 400    | Event has not started yet — trading opens at the scheduled start time |
| `risk_engine_market_closed`                    | 400    | Market is no longer accepting new positions                           |
| `risk_engine_market_disabled`                  | 400    | Market has been disabled (see `params.reason`)                        |
| `risk_engine_market_settlement_detected`       | 400    | Market settlement has been detected                                   |
| `circuit_breaker_price_divergence_tripped`     | 400    | Price divergence circuit breaker has been triggered                   |
| `quote_polymarket_market_closed`               | 400    | Polymarket market is closed                                           |
| `quote_polymarket_market_inactive`             | 400    | Polymarket market is inactive                                         |
| `quote_polymarket_market_not_accepting_orders` | 400    | Polymarket market is not accepting orders                             |
| `quote_polymarket_missing_token`               | 400    | Polymarket market is missing token configuration                      |
| `quote_market_missing_polymarket_condition_id` | 400    | Market is missing required Polymarket condition ID                    |
| `quote_liquidation_not_viable`                 | 400    | Liquidation price would be too low to maintain a position             |

**Leverage validation:**

| Code                                      | Status | When                                                     |
| ----------------------------------------- | ------ | -------------------------------------------------------- |
| `quote_leverage_below_minimum`            | 400    | Leverage is below the minimum (2x)                       |
| `quote_leverage_exceeds_maximum`          | 400    | Leverage exceeds the absolute maximum                    |
| `quote_leverage_exceeds_model_max`        | 400    | Leverage exceeds the model's maximum for this market     |
| `quote_leverage_exceeds_collateral_floor` | 400    | Implied collateral is below the minimum at this leverage |
| `quote_notional_below_minimum`            | 400    | Notional is below the minimum for this account           |
| `quote_leverage_too_high_for_price`       | 400    | Leverage is too high relative to the current entry price |
| `quote_price_too_low`                     | 400    | Entry price is too low for a leveraged position          |

**Market quality filters:**

| Code                                       | Status | When                                                            |
| ------------------------------------------ | ------ | --------------------------------------------------------------- |
| `quote_entry_depth_too_low`                | 400    | Not enough order book depth                                     |
| `quote_entry_bid_depth_too_low`            | 400    | Minimum bid depth below threshold                               |
| `quote_entry_capacity_exceeded`            | 400    | Notional exceeds market capacity                                |
| `quote_entry_price_out_of_range`           | 400    | Current market price is outside tradeable range                 |
| `quote_entry_spread_too_wide`              | 400    | Market spread exceeds maximum                                   |
| `quote_entry_order_book_stale`             | 400    | Order book data is stale                                        |
| `quote_entry_price_stale`                  | 400    | Price data is stale                                             |
| `quote_entry_crypto_price_stale`           | 400    | Crypto price data is stale                                      |
| `quote_entry_sport_data_stale`             | 400    | Sport event data is stale                                       |
| `quote_entry_excluded_market_type`         | 400    | Market type is excluded from trading (see `params.market_type`) |
| `quote_entry_excluded_sport`               | 400    | Sport type is excluded from trading (see `params.sport`)        |
| `quote_entry_exit_drop_too_high`           | 400    | Recent price drop exceeds safe entry threshold                  |
| `quote_entry_market_too_elapsed`           | 400    | Market is too close to expiry                                   |
| `quote_entry_top_holder_too_high`          | 400    | Top holder concentration exceeds maximum                        |
| `quote_entry_volume_too_low`               | 400    | 24h trading volume is below minimum                             |
| `quote_market_no_prices`                   | 400    | No prices available for this market                             |
| `quote_twap_data_unavailable`              | 400    | TWAP price data is unavailable for this market                  |
| `quote_twap_data_stale`                    | 400    | TWAP price data is stale                                        |
| `notional_selector_insufficient_liquidity` | 400    | Not enough liquidity for the requested size                     |
| `quote_side_capacity_exceeded`             | 400    | Notional exceeds available capacity on this side of the market  |

**Position limits:**

| Code                                    | Status | When                                                    |
| --------------------------------------- | ------ | ------------------------------------------------------- |
| `quote_user_position_limit_exceeded`    | 400    | User has reached their maximum number of open positions |
| `quote_market_position_limit_exceeded`  | 400    | Market-level position limit reached                     |
| `quote_side_position_limit_exceeded`    | 400    | Too many positions on one side of this market           |
| `quote_global_position_limit_exceeded`  | 400    | Platform-wide position limit reached                    |
| `quote_partner_position_limit_exceeded` | 400    | Partner's aggregate exposure cap reached                |

**Borrowing limits:**

These cap the protocol-borrowed capital (notional minus your collateral), separately from the position-count limits above. A quote is rejected when the new position's borrow plus the existing outstanding borrow would exceed a cap.

| Code                            | Status | When                                                                    |
| ------------------------------- | ------ | ----------------------------------------------------------------------- |
| `quote_user_capital_exceeded`   | 400    | Your wallet's total borrowed capital would exceed the per-user limit    |
| `quote_market_capital_exceeded` | 400    | The market's total borrowed capital would exceed the per-market limit   |
| `quote_side_capital_exceeded`   | 400    | Borrowed capital on this side of the market would exceed the side limit |
| `quote_total_capital_exceeded`  | 400    | The protocol-wide borrowed capital limit would be exceeded              |

**Partial-open (FAK) validation:**

| Code                              | Status | When                                                                                                                                                                                                                                                                                                                                                                                                      |
| --------------------------------- | ------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `quote_min_fill_bps_requires_fak` | 400    | `min_fill_bps` sent without `allow_partial_fill: true`. `min_fill_bps` is only valid when partial-open is enabled. See [Partial-Open (FAK)](/for-developers/order-types/partial-open.md)                                                                                                                                                                                                                  |
| `quote_min_fill_bps_out_of_range` | 400    | `min_fill_bps` outside `2000`–`5000`. Params: `min_fill_bps`, `absolute_min_bps`, `max_bps`                                                                                                                                                                                                                                                                                                               |
| `quote_min_fill_bps_step_invalid` | 400    | `min_fill_bps` not divisible by `500`. Params: `min_fill_bps`, `step_bps`                                                                                                                                                                                                                                                                                                                                 |
| `quote_min_fill_bps_below_floor`  | 400    | `min_fill_bps` would shrink the post-partial position below the minimum collateral or minimum notional for this size. Params: `min_fill_bps` (requested), `floor_min_fill_bps` (smallest accepted — raise `min_fill_bps` to at least this, or increase size), `bound_by` (`"collateral"` or `"notional"` — which minimum bound the floor), `requested_collateral_usd_pips`, `requested_notional_usd_pips` |
| `quote_fak_order_type_disabled`   | 403    | Partial-open is temporarily disabled by a server kill switch. Omit `allow_partial_fill` to place an atomic order                                                                                                                                                                                                                                                                                          |

**Other:**

| Code                                      | Status | When                                       |
| ----------------------------------------- | ------ | ------------------------------------------ |
| `quote_hard_exit_too_close`               | 400    | Market is too close to resolution          |
| `quote_slippage_too_high`                 | 400    | Slippage tolerance exceeds maximum allowed |
| `quote_invalid_polymarket_wallet_address` | 400    | Polymarket requires a valid EVM address    |

***

### Errors with structured hints

Some error codes return additional machine-readable fields under `error.params` so you can act on them programmatically — for example, pre-filling an input with the maximum supported size instead of asking the user to retry until something fits. **Don't regex-parse the `message` string** — the values you need are typed under `params`.

`params` is only present on the codes listed below. Dispatch on `code` first, then read the keys you expect. Most codes here are raised by the quoting (`POST /quotes`) path.

**Units & types:**

| Suffix          | Type   | Meaning                                                                   |
| --------------- | ------ | ------------------------------------------------------------------------- |
| `*_usd_pips`    | string | Decimal string, 10,000 pips = $1.00                                       |
| `*_usdc_units`  | string | Decimal string, 1,000,000 units = $1.00 (capital/borrow limits use these) |
| `*_bps`         | number | Basis points (10,000 bps = 1x leverage / 100%)                            |
| `*_pct_elapsed` | number | Fraction in `0`–`1`                                                       |
| `*_age_ms`      | number | Milliseconds                                                              |

Values are strings unless the unit table above marks them as numbers (`side`, `market_type`, `sport`, `market_id`, and `reason` are also strings). Treat capacity, depth, and price values as a snapshot — the order book shifts continuously, so a user who waits before retrying may still hit the same error with different numbers.

| `code`                                     | `params` keys                                                                                                                                                                           |
| ------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `notional_selector_insufficient_liquidity` | `max_supported_collateral_usd_pips`, `min_notional_usd_pips`, `slippage_max_usd_pips` ¹                                                                                                 |
| `quote_entry_bid_depth_too_low`            | `current_bid_depth_usd_pips`, `threshold_usd_pips`                                                                                                                                      |
| `quote_entry_crypto_price_stale`           | `last_update_age_ms`, `market_id`                                                                                                                                                       |
| `quote_entry_depth_too_low`                | `current_depth_usd_pips`, `min_depth_usd_pips`                                                                                                                                          |
| `quote_entry_excluded_market_type`         | `market_type`                                                                                                                                                                           |
| `quote_entry_excluded_sport`               | `sport`                                                                                                                                                                                 |
| `quote_entry_market_too_elapsed`           | `max_pct_elapsed`, `pct_elapsed`                                                                                                                                                        |
| `quote_entry_order_book_stale`             | `last_update_age_ms`, `market_id`                                                                                                                                                       |
| `quote_entry_price_out_of_range`           | `current_price_usd_pips`, `max_price_usd_pips`, `min_price_usd_pips`                                                                                                                    |
| `quote_entry_price_stale`                  | `last_update_age_ms`, `market_id`                                                                                                                                                       |
| `quote_entry_sport_data_stale`             | `last_update_age_ms`, `market_id`                                                                                                                                                       |
| `quote_entry_spread_too_wide`              | `current_spread_usd_pips`, `max_spread_usd_pips`                                                                                                                                        |
| `quote_entry_volume_too_low`               | `current_volume24h_usd_pips`, `min_volume24h_usd_pips`                                                                                                                                  |
| `quote_global_position_limit_exceeded`     | `available_capacity_usd_pips`, `current_notional_usd_pips`, `limit_usd_pips` ²                                                                                                          |
| `quote_leverage_below_minimum`             | `current_book_leverage_bps`, `min_leverage_bps`                                                                                                                                         |
| `quote_leverage_exceeds_collateral_floor`  | `current_book_leverage_bps`, `current_collateral_usd_pips`, `max_leverage_bps`, `min_collateral_usd_pips` ³                                                                             |
| `quote_leverage_exceeds_maximum`           | `current_book_leverage_bps`, `max_leverage_bps`                                                                                                                                         |
| `quote_leverage_exceeds_model_max`         | `current_book_leverage_bps`, `max_leverage_bps`                                                                                                                                         |
| `quote_leverage_too_high_for_price`        | `current_book_leverage_bps`, `entry_price_usd_pips`, `max_acceptable_leverage_bps`, `minimum_buffer_usd_pips`                                                                           |
| `quote_liquidation_not_viable`             | `entry_price_usd_pips`, `liquidation_price_usd_pips`, `min_tolerance_pct_bps`, `side`                                                                                                   |
| `quote_market_capital_exceeded`            | `available_borrowed_usdc_units`, `available_collateral_usdc_units`, `available_notional_usdc_units`, `cap_usdc_units`, `current_borrowed_usdc_units`, `requested_borrowed_usdc_units` ⁴ |
| `quote_market_position_limit_exceeded`     | `available_capacity_usd_pips`, `current_notional_usd_pips`, `limit_usd_pips` ²                                                                                                          |
| `quote_max_leverage_too_low`               | `max_leverage_bps`, `min_leverage_bps`                                                                                                                                                  |
| `quote_notional_below_minimum`             | `min_notional_usd_pips`, `requested_notional_usd_pips`                                                                                                                                  |
| `quote_partner_position_limit_exceeded`    | `active_notional_usd_pips`, `available_capacity_usd_pips`, `current_notional_usd_pips`, `in_flight_notional_usd_pips`, `limit_usd_pips` ² ⁵                                             |
| `quote_side_capacity_exceeded`             | `available_capacity_usd_pips`, `max_supported_collateral_usd_pips`, `min_notional_usd_pips` ⁶                                                                                           |
| `quote_side_capital_exceeded`              | `available_borrowed_usdc_units`, `available_collateral_usdc_units`, `available_notional_usdc_units`, `cap_usdc_units`, `current_borrowed_usdc_units`, `requested_borrowed_usdc_units` ⁴ |
| `quote_side_position_limit_exceeded`       | `available_capacity_usd_pips`, `current_notional_usd_pips`, `limit_usd_pips` ²                                                                                                          |
| `quote_slippage_too_high`                  | `current_slippage_bps`, `max_slippage_bps`                                                                                                                                              |
| `quote_total_capital_exceeded`             | `available_borrowed_usdc_units`, `available_collateral_usdc_units`, `available_notional_usdc_units`, `cap_usdc_units`, `current_borrowed_usdc_units`, `requested_borrowed_usdc_units` ⁴ |
| `quote_user_capital_exceeded`              | `available_borrowed_usdc_units`, `available_collateral_usdc_units`, `available_notional_usdc_units`, `cap_usdc_units`, `current_borrowed_usdc_units`, `requested_borrowed_usdc_units` ⁴ |
| `quote_user_position_limit_exceeded`       | `available_capacity_usd_pips`, `current_notional_usd_pips`, `limit_usd_pips` ²                                                                                                          |
| `risk_engine_market_disabled`              | `reason` ⁷                                                                                                                                                                              |

¹ `params` is only present when the error is raised from the slippage path; other call sites (e.g. an empty order book) omit it entirely.

² The `*_position_limit_exceeded` codes share this shape. `available_capacity_usd_pips` is `limit − current`, clamped at 0.

³ There is no escape via lowering leverage — raise collateral to at least `min_collateral_usd_pips`.

⁴ The four `*_capital_exceeded` codes share this shape and cap protocol-borrowed capital (`notional − collateral`). `requested_borrowed_usdc_units` is the borrow this quote would add; `available_borrowed_usdc_units` is `cap − current`, clamped at 0. `available_collateral_usdc_units` and `available_notional_usdc_units` re-express that same borrow headroom as the collateral and notional you could still deploy **at the leverage this quote requested** — surface these directly in the UI instead of converting `available_borrowed_usdc_units` yourself. Both are present only when the requested leverage is above 1× (a 1× position borrows nothing, so there is no borrow headroom to convert).

⁵ Same base shape as the other position-limit codes, plus `active_notional_usd_pips` and `in_flight_notional_usd_pips` (the cap counts open positions + in-flight quotes).

⁶ `max_supported_collateral_usd_pips` is **the value to surface in the UI** — the maximum collateral the user can submit at their currently selected leverage and still fit. It maps directly to the collateral input.

⁷ `params` is only present when a disable `reason` is set. Current reason values: `force_disabled` (admin killswitch).

Example (`quote_side_capacity_exceeded`):

```json
{
  "error": {
    "type": "INVALID_REQUEST_ERROR",
    "code": "quote_side_capacity_exceeded",
    "message": "Order exceeds available capacity on this side of the market (available 1234567 pips, minimum notional 1000000 pips)",
    "params": {
      "available_capacity_usd_pips": "1234567",
      "max_supported_collateral_usd_pips": "61728",
      "min_notional_usd_pips": "1000000"
    }
  }
}
```

Example (`quote_user_capital_exceeded`, quote requested 2× leverage):

```json
{
  "error": {
    "type": "INVALID_REQUEST_ERROR",
    "code": "quote_user_capital_exceeded",
    "message": "User borrowed capital 9000000 + requested 2000000 exceeds per-user cap 10000000 (USDC units)",
    "params": {
      "available_borrowed_usdc_units": "1000000",
      "available_collateral_usdc_units": "1000000",
      "available_notional_usdc_units": "2000000",
      "cap_usdc_units": "10000000",
      "current_borrowed_usdc_units": "9000000",
      "requested_borrowed_usdc_units": "2000000"
    }
  }
}
```

#### Rate limiting (429)

```json
{
  "error": {
    "type": "RATE_LIMIT_ERROR",
    "code": "too_many_requests",
    "message": "Too Many Requests"
  }
}
```

All endpoints are subject to rate limiting. Throttled responses include a `Retry-After` header indicating how many seconds to wait before retrying. Use this value instead of a fixed backoff when available.

#### Internal server error (500)

```json
{
  "error": {
    "type": "API_ERROR",
    "code": "internal_server_error",
    "message": "Something went wrong on our side. Please try again."
  }
}
```

A 500 response indicates an unexpected error on our side. These are always safe to retry with exponential backoff.

***

### Handling errors in practice

#### Check the error type and code

{% tabs %}
{% tab title="REST API" %}

```javascript
const response = await fetch(
  `${BASE_URL}/prediction-markets/quotes`,
  {
    method: "POST",
    headers: userHeaders,
    body: JSON.stringify(quoteRequest),
  }
);

if (!response.ok) {
  const {error} = await response.json();

  switch (error.code) {
    case "risk_engine_market_closed":
    case "risk_engine_market_disabled":
    case "quote_market_not_eligible":
    case "quote_entry_excluded_market_type":
    case "quote_entry_excluded_sport":
    case "quote_event_not_started":
      // Disable the trade button, show market unavailable
      // For excluded_market_type/excluded_sport, error.params
      // contains the specific market_type or sport
      break;
    case "quote_leverage_below_minimum":
    case "quote_leverage_exceeds_model_max":
      // Adjust leverage input to valid range
      break;
    case "quote_leverage_exceeds_collateral_floor":
      // Implied collateral below the floor. Raise collateral
      // to error.params.min_collateral_usd_pips (NOT lower leverage —
      // there is no escape via leverage adjustment).
      break;
    case "quote_user_position_limit_exceeded":
    case "quote_partner_position_limit_exceeded":
      // Show limit reached message
      break;
    case "notional_selector_insufficient_liquidity":
    case "quote_entry_depth_too_low":
      // Suggest reducing position size
      break;
    case "request_already_in_progress":
      // A previous request is still processing — wait and retry
      break;
    default:
      // Generic error state
      break;
  }
}
```

{% endtab %}

{% tab title="SDK" %}

```typescript
import {DimesApiError, formatErrorMessage} from "@dimes-dot-fi/sdk";
import {quoteErrorHint, hintAdjustment} from "@dimes-dot-fi/sdk";

try {
  const result = await executeQuote(client, params);
} catch (err) {
  if (err instanceof DimesApiError) {
    // Friendly message for any error code
    console.log(formatErrorMessage(err.code, err.params));

    // Auto-correction hints for leverage/collateral/slippage errors
    const hint = quoteErrorHint(err.code, err.params, {
      leverageBps: params.leverageBps,
    });
    const adj = hintAdjustment(hint, {
      collateralUsd: params.collateralUsd,
      leverageBps: params.leverageBps,
      slippageBps: params.slippageBps,
    });
    if (adj) {
      console.log(`Adjust ${adj.field} to ${adj.toLabel}`);
    }
  }
}
```

{% endtab %}
{% endtabs %}

#### Retry logic

Some errors are retryable, others aren't.

| Retryable                     | Not retryable                          |
| ----------------------------- | -------------------------------------- |
| `internal_server_error`       | `quote_market_not_eligible`            |
| `too_many_requests`           | `quote_leverage_below_minimum`         |
| `request_already_in_progress` | `quote_user_position_limit_exceeded`   |
|                               | `risk_engine_market_closed`            |
|                               | `risk_engine_market_disabled`          |
|                               | `quote_entry_excluded_market_type`     |
|                               | `quote_entry_excluded_sport`           |
|                               | `quote_event_not_started`              |
|                               | `customer_auth_invalid_wallet_address` |

For retryable errors, use exponential backoff starting at 1 second.

> **SDK note:** The SDK's `HttpClient` handles 429 retries (with `Retry-After` backoff) and 401 token refresh automatically, so you only need to handle business-logic errors in your application code.
