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

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

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

| Code                              | Status | When                                                                                                                                                                          |
| --------------------------------- | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `quote_min_fill_bps_requires_fak` | 400    | `min_fill_bps` sent with `order_type: "fok"` or `order_type` omitted. `min_fill_bps` is only valid with `order_type: "fak"`                                                   |
| `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 `MIN_COLLATERAL` for this notional. Params: `min_fill_bps`, `floor_min_fill_bps`, `requested_collateral_usd_pips` |

**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 documented below. Dispatch on `code` first, then read the keys you expect.

All pip values are decimal strings (10,000 pips = $1.00). bps values are numbers.

#### `quote_side_capacity_exceeded`

| Key                                 | Type   | Description                                                                                                                                                                             |
| ----------------------------------- | ------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `available_capacity_usd_pips`       | string | Remaining notional headroom on this side of the market                                                                                                                                  |
| `min_notional_usd_pips`             | string | Minimum order size accepted by the protocol                                                                                                                                             |
| `max_supported_collateral_usd_pips` | string | Maximum collateral the user can submit at their currently selected leverage and still fit. **This is the value to surface in the UI** — it maps directly to the user's collateral input |

Example:

```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",
      "min_notional_usd_pips": "1000000",
      "max_supported_collateral_usd_pips": "61728"
    }
  }
}
```

> Treat the values as a snapshot — order book depth shifts continuously, so a user who waits before retrying may still hit the same error with a different max.

#### `notional_selector_insufficient_liquidity`

| Key                                 | Type   | Description                                                                 |
| ----------------------------------- | ------ | --------------------------------------------------------------------------- |
| `slippage_max_usd_pips`             | string | Maximum notional that fits within the configured slippage budget            |
| `min_notional_usd_pips`             | string | Minimum order size accepted by the protocol                                 |
| `max_supported_collateral_usd_pips` | string | Maximum collateral the user can submit at their currently selected leverage |

> Only present when the error is raised from the slippage path. The other call sites (e.g. an empty order book) omit `params`.

#### Position limit errors

The five `*_position_limit_exceeded` codes (`quote_user_position_limit_exceeded`, `quote_market_position_limit_exceeded`, `quote_side_position_limit_exceeded`, `quote_global_position_limit_exceeded`, `quote_partner_position_limit_exceeded`) all share the same shape:

| Key                           | Type   | Description                                              |
| ----------------------------- | ------ | -------------------------------------------------------- |
| `current_notional_usd_pips`   | string | The aggregate notional exposure that triggered the limit |
| `limit_usd_pips`              | string | The configured limit at this scope                       |
| `available_capacity_usd_pips` | string | `limit − current`, clamped at 0                          |

#### Leverage and slippage validation

| Code                                | `params` keys                                                                                            |
| ----------------------------------- | -------------------------------------------------------------------------------------------------------- |
| `quote_leverage_below_minimum`      | `current_leverage_bps`, `min_leverage_bps`                                                               |
| `quote_leverage_exceeds_maximum`    | `current_leverage_bps`, `max_leverage_bps`                                                               |
| `quote_leverage_exceeds_model_max`  | `current_leverage_bps`, `max_leverage_bps`                                                               |
| `quote_leverage_too_high_for_price` | `current_leverage_bps`, `entry_price_usd_pips`, `max_acceptable_leverage_bps`, `minimum_buffer_usd_pips` |
| `quote_slippage_too_high`           | `current_slippage_bps`, `max_slippage_bps`                                                               |
| `quote_liquidation_not_viable`      | `side`, `entry_price_usd_pips`, `liquidation_price_usd_pips`, `min_tolerance_pct_bps`                    |

#### Market disabled

| Code                          | `params` keys |
| ----------------------------- | ------------- |
| `risk_engine_market_disabled` | `reason`      |

Current reason values: `force_disabled` (admin killswitch).

#### Entry filter rejections

| Code                               | `params` keys                                                        |
| ---------------------------------- | -------------------------------------------------------------------- |
| `quote_entry_depth_too_low`        | `current_depth_usd_pips`, `min_depth_usd_pips`                       |
| `quote_entry_price_out_of_range`   | `current_price_usd_pips`, `min_price_usd_pips`, `max_price_usd_pips` |
| `quote_entry_spread_too_wide`      | `current_spread_usd_pips`, `max_spread_usd_pips`                     |
| `quote_entry_bid_depth_too_low`    | `current_bid_depth_usd_pips`, `threshold_usd_pips`                   |
| `quote_entry_exit_drop_too_high`   | `window`, `current_exit_drop`, `max_exit_drop`                       |
| `quote_entry_market_too_elapsed`   | `pct_elapsed`, `max_pct_elapsed`                                     |
| `quote_entry_top_holder_too_high`  | *(none)*                                                             |
| `quote_entry_volume_too_low`       | `current_volume_24h_usd_pips`, `min_volume_24h_usd_pips`             |
| `quote_entry_excluded_market_type` | `market_type`                                                        |
| `quote_entry_excluded_sport`       | `sport`                                                              |

#### `quote_revision_required`

When the requested parameters cannot be honored exactly but a revised quote would fit, this error returns the acceptable parameters so the client can re-quote (or set `allow_revision=true` upfront):

| Key                                | Type   | Description                                                     |
| ---------------------------------- | ------ | --------------------------------------------------------------- |
| `revision`                         | string | What was revised (`leverage_reduced`, `notional_reduced`, etc.) |
| `requested_leverage_bps`           | number | The leverage the client asked for                               |
| `requested_notional_usd_pips`      | string | The notional the client asked for                               |
| `acceptable_leverage_bps`          | number | The leverage the protocol can honor                             |
| `acceptable_notional_usd_pips`     | string | The notional the protocol can honor                             |
| `acceptable_collateral_usdc_units` | string | The collateral required at the acceptable parameters            |

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


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.dimes.fi/for-developers/error-handling.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
