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

# Error handling and troubleshooting

> Complete guide to DexPaprika API error codes, common issues, and troubleshooting steps for REST API and Streaming API.

## REST API errors

All REST API errors return a JSON object with a `message` field (and sometimes an `error` field). Here's every status code you can encounter:

### 200 Success

Normal response. Parse the JSON body.

One important edge case: **batch pricing** (`GET /networks/{network}/multi/prices`) returns HTTP 200 with an empty array `[]` when none of the requested tokens have pricing data. This is not an error. It means the tokens were processed but none had prices.

***

### 400 Bad Request

The request was malformed or contained invalid parameters. The body names the offending parameter and, where there is a fixed set, lists the accepted values:

```json theme={null}
{"message": "invalid query parameters: order_by (must be one of [volume_usd_24h volume_usd_7d volume_usd_30d liquidity_usd txns_24h created_at price_usd price_change_percentage_24h price_change_percentage_6h price_change_percentage_1h price_change_percentage_5m])"}
```

**Common causes:**

* Invalid `order_by` on `/networks/{network}/pools/search`. The accepted values are `volume_usd_24h`, `volume_usd_7d`, `volume_usd_30d`, `liquidity_usd`, `txns_24h`, `created_at`, `price_usd`, `price_change_percentage_24h`, `price_change_percentage_6h`, `price_change_percentage_1h` and `price_change_percentage_5m`. The older `volume_usd` spelling is rejected
* Invalid `order_by` on `/networks/{network}/tokens/search`, which takes a **different** list: `volume_usd_24h`, `volume_usd_7d`, `volume_usd_30d`, `liquidity_usd`, `txns_24h`, `price_change_percentage_24h`, `created_at` and `fdv_usd`
* Sorting tokens by price. Do not read `price_usd` out of the token 400 body and retry with it. That value appears in the message, and `order_by=price_usd` on either token search path answers with a second `400`. Ordering tokens by raw price is not supported. Sort by `volume_usd_24h` or `liquidity_usd` instead and read `price_usd` off the rows you get back. The eight values listed above are the ones that actually return `200`
* Sorting tokens by a short price change window. The 6h, 1h and 5m windows exist on pool search only, so passing one as `order_by` to either token search endpoint returns a `400`
* Using an unknown parameter **name**. Unknown names are silently dropped rather than rejected, so a request with `sort_by=` or `volume_24h_min=` returns 200 with default ordering and no filter applied. Check the echoed `query` object in the response to see what the API actually used
* Invalid `interval` value for OHLCV (must be one of: `1m`, `5m`, `10m`, `15m`, `30m`, `1h`, `6h`, `12h`, `24h`)
* Batch pricing with more than 10 tokens
* Batch pricing with zero tokens (empty `tokens` parameter)
* Invalid UNIX timestamp format in filter parameters
* Missing required parameters (e.g., `start` for OHLCV)

**What to do:** Check the parameter values against the [API reference](/api-reference/introduction) or [common patterns](/knowledge-base/common-patterns).

***

### 404 Not Found

The requested resource doesn't exist.

```json theme={null}
{"message": "not found"}
```

**Common causes:**

* Invalid network ID (e.g., `eth` instead of `ethereum`, `sol` instead of `solana`)
* Token address doesn't exist on that network
* Pool address doesn't exist on that network
* Typo in the URL path

**What to do:**

1. Verify the network ID by checking `GET /networks`
2. Use `GET /search?query={name}` to find the correct network and address
3. Check that the address format matches the chain (e.g., `0x...` for EVM chains, base58 for Solana)

***

### 410 Gone

The endpoint has been permanently removed. The body names its own replacement:

```json theme={null}
{"code": 410, "message": "endpoint removed", "replacement": "/networks/:network/pools/search"}
```

**Currently applies to (all return `410 Gone`):**

* `GET /pools`: use `GET /pools/search` instead.
* `GET /networks/{network}/pools`: use `GET /networks/{network}/pools/search` instead.
* `GET /networks/{network}/pools/filter`: use `GET /networks/{network}/pools/search` instead.
* `GET /networks/{network}/dexes/{dex}/pools`: use `GET /networks/{network}/pools/search?dex_name={dex}` instead.
* `GET /networks/{network}/tokens/{token_address}/pools`: use `GET /networks/{network}/pools/search?token_address={token_address}` instead.
* `GET /networks/{network}/tokens/filter`: use `GET /networks/{network}/tokens/search` instead.
* `GET /networks/{network}/tokens/top`: use `GET /networks/{network}/tokens/search` instead.

The whole route is removed in each case, not just the specific network, DEX, or token. A nonsense DEX slug on `/networks/{network}/dexes/{dex}/pools` returns the same 410, not a 404, which is how you tell a removed endpoint from a bad identifier.

***

### 401 Unauthorized

The `Authorization` header is missing, or its value is not a valid key.

**The key goes in the header on its own**, with no scheme word in front of it. This
is the single most common cause of a 401 here, because almost every other API
expects the opposite.

```bash theme={null}
# correct
curl -H "Authorization: api_YOUR_KEY" "https://api-pro.dexpaprika.com/networks"

# none of these work
curl -H "Authorization: ApiKey api_YOUR_KEY" ...
curl -H "Authorization: Token api_YOUR_KEY"  ...
curl -H "Authorization: Basic api_YOUR_KEY"  ...
```

Send the key on its own. A request carrying a scheme word in front of it is not the
documented form, and on the Pro host it is indistinguishable from a wrong key.

**What you see can differ by host**, which is why this is harder to diagnose than it
should be. `api-pro.dexpaprika.com` sits behind an edge rule that can reject a request
before it reaches the API, answering **HTTP 403 with an HTML page** rather than the JSON
error above. If you get an HTML body from an API that otherwise always answers JSON,
check the header format and the base URL first, before you start regenerating the key.

**What to do:** send the key as the entire header value, starting with `api_`. If you
are using an SDK or an HTTP client that prepends a scheme word for you, which many do
when you fill in a token field, set a raw header instead.

A key that is correct but sent to the wrong base URL returns [403](#403-forbidden-wrong-host),
not 401.

If you have just upgraded and this is your first call to `api-pro.dexpaprika.com`, start
from [upgrading to Pro](/api-pro/upgrading): the host needs an `Authorization` header
carrying your key on every request, including the root path.

***

### 402 Payment Required

Your credit allowance is exhausted. This is not a rate limit, and retrying does not help. On free tiers the allowance refills gradually, an hour at a time, as usage ages out of a rolling 30-day window; on Pro it returns at the start of the next billing period, or when you add credits.

The body is structured and names the next step for your tier:

```json theme={null}
{
  "error": "payment_required",
  "tier": "keyless",
  "message": "Credit limit for the last 30 days reached for unauthenticated use. Register for a free API key to get more credits/month.",
  "credits": { "limit": 30000, "used": 30000, "remaining": 0 }
}
```

There is no `resets_at` here. Keyless and free credits age out of a rolling 30-day window continuously, so there is no moment the counter returns to zero; Dev and Pro 402s carry the field, holding the end of the billing period. A 402 deliberately carries no `Retry-After` header either, which is how you tell it apart from a 429 in code.

**What to do:**

* Keyless and over 30,000: [register a free API key](https://console.dexpaprika.com) for 100,000 credits per rolling 30 days, no card
* On a free key and over 100,000: move to [Dev or Pro](https://dexpaprika.com/api/pricing) for 500,000 or 5,000,000 per billing period
* On Dev or Pro: turn on autoscaling in [console.dexpaprika.com](https://console.dexpaprika.com), which adds credits at \$20 per additional 1,000,000 up to the spending limit you set, and track what you have spent there
* Either way, size the workload first with [plan your credit usage](/knowledge-base/credit-usage)

***

### 403 Forbidden (wrong host)

Your key is valid, but it is being sent to the wrong base URL: free keys are served on `api.dexpaprika.com`, paid keys on `api-pro.dexpaprika.com`. Endpoints, paths and parameters are identical on both hosts; only the base URL changes.

```json theme={null}
{
  "error": "wrong_host",
  "tier": "free",
  "message": "This API key is on the free plan, but this host serves paid plans only. Send your requests to https://api.dexpaprika.com instead - endpoints, paths and parameters are identical, and your free monthly credits apply there. Update the base URL in your client and retry, or upgrade to a paid plan to use this host.",
  "links": {
    "api": "https://api.dexpaprika.com",
    "docs": "https://docs.dexpaprika.com/",
    "coupon": "https://dexpaprika.com/api/pricing?coupon=START10"
  }
}
```

**What to do:** update the base URL in your client and retry. Nothing is wrong with the key or the plan. [Upgrading to Pro](/api-pro/upgrading) has the full host and header mapping, and a one-line `/usage` check that confirms a paid key is set up correctly.

***

### 429 Too Many Requests

Per-minute request rate exceeded. This is separate from the credit allowance, which returns 402 instead. A 429 carries a `Retry-After` header giving the seconds to wait.

**Free tier limit:** 15 requests a minute without a key, 30 with a [free key](https://console.dexpaprika.com), against an allowance of 30,000 credits per rolling 30 days without a key (100,000 with a free key). Pro allows 500 requests a minute against 5,000,000 credits.

**What to do:**

* Retry after the number of seconds in `Retry-After`
* Cache responses for data that changes rarely (network lists, DEX lists)
* Use batch pricing instead of individual token requests. It does not reduce credits, but it does reduce HTTP calls, which is what the per-minute limit counts
* Use the streaming API for live prices instead of polling
* Consider the [Pro API](/api-pro/introduction) for 500 requests a minute

***

### 500 Internal Server Error

Something went wrong on our side.

**What to do:** Retry with exponential backoff (wait 1s, then 2s, then 4s, etc.). If the error persists, check our [Discord](https://discord.gg/DhJge5TUGM) for status updates or contact support.

***

## Streaming API errors

The streaming API at `https://streaming.dexpaprika.com` can fail in two ways: HTTP errors before the stream starts, or SSE error events during an active stream.

### HTTP errors (before stream starts)

| Status | Meaning                                                                                                                                                                                                                  |
| ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| 200    | Connected successfully, streaming                                                                                                                                                                                        |
| 400    | Bad parameters, unsupported chain, token not found, or one invalid asset in a batch. Body looks like `{"message": "asset not found: ethereum/0xdead... (not found)"}`                                                    |
| 429    | Stream limit exceeded. Most often the per-IP cap: the 11th concurrent SSE stream from one IP returns `{"message": "ip stream limit exceeded"}`. The per-route rate limit and the global stream slot pool also return 429 |

**The 400 behavior is strict:** In a POST request with multiple tokens, if even one token is invalid, the entire request fails. Validate all tokens via the REST API before streaming them.

### SSE errors (during active stream)

If something goes wrong during an active stream, the error arrives as an SSE event:

```
event: error
data: {"message": "..."}
```

**What to do:** Close the connection and reconnect with exponential backoff.

***

## Pagination gotchas

Two styles are in play, and mixing them up is the most common migration bug.

**Cursor-based**, which covers every search endpoint (`/networks/{network}/pools/search`, `/pools/search`, `/networks/{network}/tokens/search`, `/tokens/search`): read `has_next_page` and pass `next_cursor` back as `cursor`. There is no `page` parameter and no `page_info` object. Passing `page` here does nothing: unknown query parameters are dropped silently and you get a plausible-looking first page back every time.

**Page-based**, which is now only `GET /networks/{network}/dexes` and `GET /networks/{network}/pools/{pool_address}/transactions`: `page=1` returns the first page and `page=0` is silently treated as `page=1`.

Maximum page size is 100 items (via `limit` parameter) in both styles. Transaction pagination is limited to 100 pages; for deeper history use the `cursor` parameter there too.

***

## Common mistakes and fixes

<AccordionGroup>
  <Accordion title="Getting empty results from pool search">
    **Symptom:** `GET /networks/{network}/pools/search` returns `{"results": [], "has_next_page": false}`

    **Cause:** Filters are too strict (for example a high `volume_usd_24h_min` on a quiet network), or a very small `limit`.

    **Fix:** Loosen the filters, or use `limit=10` or higher. Note the response wraps rows in `results` and pages with `next_cursor`, not `page_info`.
  </Accordion>

  <Accordion title="Search returns no results for a known token">
    **Symptom:** `GET /search?query=USDC` returns empty arrays.

    **Cause:** Search is best-effort and may not match very common/generic terms well. It searches across tokens, pools, and DEXes.

    **Fix:** Try more specific queries (e.g., the token address), or use the token endpoint directly if you know the network and address.
  </Accordion>

  <Accordion title="OHLCV returns empty array">
    **Symptom:** OHLCV request returns `[]`.

    **Cause:** The `start` date might be before the pool existed, or the pool may have very low activity in the requested period.

    **Fix:** Check the pool's `created_at` field to ensure your date range is valid. Try a broader interval (e.g., `24h` instead of `1h`).
  </Accordion>

  <Accordion title="Migrating from the removed pool list/filter endpoints">
    **Symptom:** Code that used `/pools`, `/networks/{network}/pools`, or `/networks/{network}/pools/filter` now returns `410 Gone`.

    **Cause:** Those endpoints were removed. The replacement is `GET /networks/{network}/pools/search` (single network) or `GET /pools/search` (across networks).

    **Fix:** Repoint to `*/search` and update params and response handling: `volume_24h_min` becomes `volume_usd_24h_min`, `sort_by` becomes `order_by`, `sort_dir` becomes `sort`, and `page` becomes cursor-based (`has_next_page` + `next_cursor`). The response wraps rows in `results`; each row uses `id` (pool address), `volume_usd_24h`, and `transactions_24h`. See the [pool filtering tutorial](/tutorials/pool-filtering).
  </Accordion>

  <Accordion title="Migrating from the removed DEX pools endpoint">
    **Symptom:** Code that used `/networks/{network}/dexes/{dex}/pools` now returns `410 Gone`.

    **Cause:** The endpoint was removed. The DEX moved out of the path and into the `dex_name` filter on pool search.

    **Fix:** Call `GET /networks/{network}/pools/search?dex_name={dex}`. `dex_name` takes the DEX id, the `dex_id` field from `GET /networks/{network}/dexes`, matched case-insensitively. That is what the old path segment held, so it is the value already in your code. Passing that response's `dex_name` field instead, a display name like `Uniswap V3`, returns HTTP 200 with an empty `results` array rather than an error, so an empty page here usually means a display name went in where an id belonged.

    ```bash theme={null}
    # before
    curl "https://api.dexpaprika.com/networks/ethereum/dexes/curve/pools?order_by=volume_usd&sort=desc&limit=10"

    # after
    curl "https://api.dexpaprika.com/networks/ethereum/pools/search?dex_name=curve&order_by=volume_usd_24h&sort=desc&limit=10"
    ```

    Three response changes bite here. Rows come back under `results`, not `pools`. Pagination is `has_next_page` + `next_cursor` instead of `page` + `page_info`. And the 24h volume field is `volume_usd_24h`, with no bare `volume_usd` on the row; passing the old `order_by=volume_usd` is rejected with a `400` naming the accepted values.
  </Accordion>

  <Accordion title="Streaming connection drops immediately">
    **Symptom:** Streaming connection returns 400 instantly.

    **Cause:** One or more assets in the request are invalid (wrong chain ID, non-existent token address).

    **Fix:** Validate every token via the REST API before adding it to a streaming request. In a POST batch, all assets must be valid. One bad asset cancels the entire stream.
  </Accordion>
</AccordionGroup>

### FAQs

<AccordionGroup>
  <Accordion title="Does DexPaprika return rate limit headers?">
    Not the draft standard `RateLimit-*` family. Every response does carry `X-Api-Plan`, naming the plan the request was evaluated against. For counts, call `GET /usage` or open [console.dexpaprika.com](https://console.dexpaprika.com). See [response headers](/knowledge-base/response-headers).
  </Accordion>

  <Accordion title="What happens after a rate limit reset?">
    The per-minute rate frees up within the next minute; after a 429, wait the `Retry-After` seconds and retry. Credits come back differently per tier: on Dev and Pro the whole allowance returns at `resets_at`, the end of the billing period, and on the free tiers it returns continuously as usage older than 30 days ages out, which is why their 402 carries no such field.
  </Accordion>

  <Accordion title="Are there different limits per endpoint?">
    No. Every endpoint costs one credit per request, with no per-endpoint weights or compute units. The one wrinkle is batch endpoints, which charge one credit per item in the batch.
  </Accordion>
</AccordionGroup>

<script type="application/ld+json">
  {JSON.stringify({
      "@context": "https://schema.org",
      "@type": "FAQPage",
      "mainEntity": [
        {"@type": "Question","name": "Does DexPaprika return rate limit headers?","acceptedAnswer": {"@type": "Answer","text": "Not the draft RateLimit-* family. Every response carries X-Api-Plan. For counts, call GET /usage or open console.dexpaprika.com."}},
        {"@type": "Question","name": "What happens after a rate limit reset?","acceptedAnswer": {"@type": "Answer","text": "The per-minute rate frees up within a minute; retry after the Retry-After seconds. On Dev and Pro the credit allowance returns at the resets_at timestamp on a 402. On the free tiers credits return continuously as usage ages out of the rolling 30-day window, and the 402 carries no resets_at."}},
        {"@type": "Question","name": "Are there different rate limits per endpoint?","acceptedAnswer": {"@type": "Answer","text": "No. Every endpoint costs one credit per request, with no per-endpoint weights. Batch endpoints charge one credit per item."}}
      ]
    })}
</script>
